我试着为mergesort写一个尾递归代码。代码编译并运行。但是,输出是错误的。它只输出一个整数。我想知道如何修复此代码,以便对整数列表进行排序和输出。
let rec merge L L2 P =
match L, L2 with
| [], [] -> P
| [], _ -> L2
| _, [] -> L
| hd::t1, hd2::t2 ->
if hd <= hd2 then
merge t1 L2 (P @ [hd])
else
merge L t2 (P @ [hd2])
//
// mergesort:
//
let rec ms L =
match L with
| [] -> []
| e::[] -> L
| _ ->
let mid = List.length L / 2
let (L, L2) = List.splitAt mid L
merge (ms L) (ms L2) []
答案 0 :(得分:5)
您的问题在功能merge
中:假设您对列表[2;1]
进行排序。它变为merge [2] [1] []
,然后变为merge [] [2] [1]
,最后变为match
的第二种情况[2]。 match
的第二和第三个案例必须以某种方式解释P
。
事实上,你绝对不需要操纵merge
中的3个列表,如果我们将它重构为两个就足够了:
let rec merge l1 l2 =
match (l1,l2) with
| (x,[]) -> x
| ([],y) -> y
| (x::tx,y::ty) ->
if x <= y then x::merge tx l2
else y::merge l1 ty
并将ms
的最后一行更改为merge (ms L) (ms L2)
- 此变体确实按预期工作:
ms List<int>.Empty
返回[]
ms [2;1]
返回[1;2]
e.t.c
更新:正如@kvb所指出的,上面的merge
函数不是尾递归的。这是正确的,并且通过引入通过acc
函数填充的累加器continuation
,将其重构为尾递归版本需要更多参与:
let merge l1 l2 =
let rec mergeAux continuation l1 l2 =
match l1, l2 with
| l1, [] -> continuation l1
| [], l2 -> continuation l2
| x::tx, y::ty ->
if x <= y then mergeAux (fun acc -> continuation(x::acc)) tx l2
else mergeAux (fun acc -> continuation(y::acc)) l1 ty
mergeAux id l1 l2
现在实现是尾递归的,很容易检查:
let rand = System.Random() in
List.init 1000000 (fun _ -> rand.Next(-10000,10000)) |> ms
>
val it : int list =
[-10000; -10000; -10000; -10000; -10000; -10000; -10000; ...
答案 1 :(得分:1)
即使对已接受的答案进行了更改,您仍然没有尾递归合并排序。合并排序merge (ms L) (ms L2)
的最后一行调用ms
两次,然后调用merge
。为了使函数成为尾递归,您的函数必须以最多一次递归调用结束。这种情况是需要延续的地方。通过传递一个延续,你可以调用ms
并传递一个延续,使第二次调用ms
并传递第二个调用另一个调用merge
的延续。我实际上会删除merge
函数的延续,因为它不需要它会使merge
函数比使用累加器参数实现它更难阅读。最后,为了便于从外部调用,我会将merge
函数和ms
函数嵌套在mergeSort
函数中,该函数只需要一个列表参数,不需要公开剩下的细节给来电者。我在F#中实现完全尾递归合并排序的方法如下:
let mergeSort ls =
let rec merge l1 l2 res =
match l1, l2 with
| [], [] -> res |> List.rev
| x::xs, [] -> merge xs [] (x::res)
| [], y::ys -> merge [] ys (y::res)
| x::xs, y::ys when x < y -> merge xs (y::ys) (x::res)
| xs, y::ys -> merge xs ys (y::res)
let rec ms ls cont =
match ls with
| [] -> cont []
| [x] -> cont [x]
| xs ->
let ys, zs = List.splitAt ((List.length xs) / 2) xs
ms ys (fun ys' -> ms zs (fun zs' -> cont (merge ys' zs' [])))
ms ls id
请注意,有一些方法可以更有效地在内存使用方面做到这一点,由于内存分配较少,这可能也有助于提高速度,但由于这超出了本问题的范围,我不打算进入在答案中。