尾递归方式排序和合并两个列表

时间:2019-09-27 04:54:32

标签: list haskell

我正在尝试编写一种尾部递归方法,以将两个已排序列表合并为一个已排序列表。

这就是我所拥有的。首先我有非尾递归的方式

merge2 :: Ord a => [a] -> [a] -> [a]
merge2 l1 [] = l1
merge2 [] l2 = l2
merge2 (x:xs) (y:ys) | x > y = y : merge2 (x:xs) ys
                     | x < y = x : merge2 xs (y:ys)
                     | otherwise = x : merge2 (x:xs) ys

mergeTail :: Ord a => [a] -> [a] -> [a]
mergeTail accum [] = accum
mergeTail accum (x:xs) = mergeTail (x:accum) xs

当我输入诸如merge2Tail [1,2] [2,3,4]之类的东西时,我希望获得[1,2,2,3,4]作为输出,但我会以某种随机顺序获得它。我不确定在哪里或如何实现检查订单的案例,同时保持订单尾部递归。

2 个答案:

答案 0 :(得分:3)

正如其他人所指出的那样,这是一个高度人工的练习:合并两个排序列表以形成一个排序列表,应该在Haskell中进行尾递归。但是,如果您是在调用堆栈大小非常有限且不支持尾递归模态缺点的语言环境中构建 spin-spind 列表,那么这样做可能是合理的。在这种环境下,通常最好将这样的问题分为两部分:

  1. 遍历列表,以累加方式反向构建列表。

  2. 使用累加器生成最终结果。

让我们开始吧。

merge :: Ord a => [a] -> [a] -> [a]
merge = \xs ys -> go [] xs ys
  where
    go acc [] ys = reverse acc ++ ys
    go acc xs [] = reverse acc ++ xs
    go acc xss@(x : xs) yss@(y : ys)
      | x <= y = go (x : acc) xs yss
      | otherwise = go (y : acc) xss ys

存在效率问题(即使在上述条件下):reverse完全重建其参数,而++完全重建其 first 参数。因此reverse acc ++ ys重建了acc两次(如果++也被尾写的话,它将被重建 3次)。让我们解决这个问题。

-- reverseOnto xs ys = reverse xs ++ ys
reverseOnto :: [a] -> [a] -> [a]
reverseOnto [] ys = ys
reverseOnto (x : xs) ys = reverseOnto xs (x : ys)

最后,

merge :: Ord a => [a] -> [a] -> [a]
merge = \xs ys -> go [] xs ys
  where
    go acc [] ys = acc `reverseOnto` ys
    go acc xs [] = acc `reverseOnto` xs
    go acc xss@(x : xs) yss@(y : ys)
      | x <= y = go (x : acc) xs yss
      | otherwise = go (y : acc) xss ys

我相信这是您在自己的限制下可以做的最好的事情。

答案 1 :(得分:2)

首先,让我们看看非尾递归的样子:

merge :: Ord a => [a] -> [a] -> [a]
merge xs []                                 = xs
merge [] ys                                 = ys
merge fullXs@(x:xs) fullYs@(y:ys)  | x <= y = x : merge xs fullYs
                                   | x > y  = y : merge fullXs ys

要使其尾部递归,必须在某处放置accum,然后调用子函数助手。  由于它是尾递归,因此必须在最后添加元素以保持顺序:

看看foldl会发生什么:

foldl (flip (:)) [] [1,2,3,4,5]
=> [5,4,3,2,1]

因此函数将是:

mergeTail :: Ord a => [a] -> [a] -> [a]
mergeTail xs [] = xs
mergeTail [] ys = ys
mergeTail xs ys = mergeAccum [] xs ys


mergeAccum :: Ord a => [a] -> [a] -> [a] -> [a]
mergeAccum acc [] []                       = acc
mergeAccum acc [] (y:ys)                   = mergeAccum (acc ++ [y]) [] ys
mergeAccum acc (x:xs) []                   = mergeAccum (acc ++ [x]) xs []
mergeAccum acc (x:xs) (y:ys)  | x <= y     = mergeAccum (acc ++ [x]) xs (y:ys)
                              | x > y      = mergeAccum (acc ++ [y]) (x:xs) ys

ejemplo:

$> mergeTail [1,2,6] [1,2,3,5]
=> [1,1,2,2,3,5,6]
  

注意:

在这种情况下,此函数效率低下,因为每个递归调用都必须转到accum列表的末尾。