这是Haskell中正确实现的mergesort吗?

时间:2015-03-11 16:43:21

标签: algorithm sorting haskell functional-programming mergesort

我无法在网上的任何地方找到我的代码,所以你能告诉我为什么或为什么函数 myMergeSort 是一个mergesort?我知道我的函数 myMergeSort 排序,但我不确定它是否真的使用mergesort算法进行排序,或者它是否是一个不同的算法。我几天前刚开始使用Haskell。

merge xs [] = xs
merge [] ys = ys
merge (x : xs) (y : ys)
    | x <= y = x : merge xs (y : ys)
    | otherwise = y : merge (x : xs) ys

myMergeSort :: [Int] -> [Int]
myMergeSort [] = []
myMergeSort (x:[]) = [x]
myMergeSort (x:xs) = foldl merge [] (map (\x -> [x]) (x:xs))

我对合并功能没有任何疑问。

以下函数 mergeSortOfficial 是我们提供的解决方案,我理解但不确定我是否正确地在我的函数 myMergeSort 中实现mergesort算法。

官方解决方案 - 实施:

mergeSortOfficial [] = []
mergeSortOfficial (x : []) = [x]
mergeSortOfficial xs = merge
    (mergeSortOfficial (take ((length xs) ‘div‘ 2) xs))
    (mergeSortOfficial (drop ((length xs) ‘div‘ 2) xs))

2 个答案:

答案 0 :(得分:10)

不,那不是 mergeSort 。这是 insertionSort ,它与 bubbleSort 的算法基本相同,具体取决于您如何盯着它。在每个步骤中,单个列表是merge d,其中包含累积的有序列表 - 所以,实际上,插入了该单例的元素。

正如其他评论者已经观察到的那样,为了获得 mergeSort (特别是其效率),有必要将问题重复分成大致相等的部分(而不是&#34) ;一个元素&#34;和#34;其余的&#34;)。 &#34;官方&#34;解决方案提供了一种相当笨重的方法来做到这一点。我非常喜欢

foldr (\ x (ys, zs) -> (x : zs, ys)) ([], [])

作为一种将列表分成两部分而不是在中间,而是分成偶数和奇数位置的元素的方法。

如果像我一样,您希望在可以看到的地方预先安排结构,那么您可以将有序列表设为Monoid

import Data.Monoid
import Data.Foldable
import Control.Newtype

newtype Merge x = Merge {merged :: [x]}
instance Newtype (Merge x) [x] where
  pack = Merge
  unpack = merged

instance Ord x => Monoid (Merge x) where
  mempty = Merge []
  mappend (Merge xs) (Merge ys) = Merge (merge xs ys) where
    -- merge is as you defined it

现在你只需按

进行插入排序
ala' Merge foldMap (:[]) :: [x] -> [x]

获得mergeSort的分而治之结构的一种方法是使其成为一个数据结构:二叉树。

data Tree x = None | One x | Node (Tree x) (Tree x) deriving Foldable

我没有在这里强制平衡不变,但我可以。关键是与之前相同的操作有另一种类型

ala' Merge foldMap (:[]) :: Tree x -> [x]

合并从树状排列的元素中收集的列表。为了获得所述安排,请考虑Tree对于twistin :: x -> Tree x -> Tree x -- a very cons-like type twistin x None = One x twistin x (One y) = Node (One x) (One y) twistin x (Node l r) = Node (twistin x r) l 有什么缺点?&#34;并确保你保持平衡,与我在上面使用的同样的扭曲&#34;划分&#34;操作

mergeSort :: Ord x => [x] -> [x]
mergeSort = ala' Merge foldMap (:[]) . foldr twistin None

现在通过构建二叉树然后合并它来进行mergeSort。

mergeSort :: Ord x => [x] -> [x]
mergeSort []   = []
mergeSort [x]  = [x]
mergeSort xs   = merge (mergeSort ys) (mergeSort zs) where
  (ys, zs) = foldr (\ x (ys, zs) -> (x : zs, ys)) ([], []) xs

当然,引入中间数据结构具有好奇心的价值,但你可以轻松地将其删除并获得类似的内容

{{1}}

其中树已成为程序的递归结构。

答案 1 :(得分:7)

myMergeSort不是正确的合并排序。这是正确的insertion sort。我们从一个空列表开始,然后将这些元素逐个插入到正确的位置:

myMergeSort [2, 1, 4, 3] == 
foldl merge [] [[2], [1], [4], [3]] ==
((([] `merge` [2]) `merge` [1]) `merge` [4]) `merge` [3] == 
(([2] `merge` [1]) `merge` [4]) `merge` [3]
([1, 2] `merge` [4]) `merge` [3] == 
[1, 2, 4] `merge` [3] == 
[1, 2, 3, 4]

由于每次插入都需要线性时间,因此整个排序是二次的。

mergeSortOfficial技术上是正确的,但效率低下。 length占用线性时间,并且在每个递归级别调用列表的总长度。 takedrop也是线性的。整体复杂性仍然是最佳n * log n,但我们会运行几个不必要的圈子。

如果我们坚持自上而下的合并,我们可以做得更好,将列表拆分为具有偶数索引的元素列表和另一个具有奇数索引的元素。拆分仍然是线性的,但在length排序中只有一次遍历而不是两次(take然后drop / official

split :: [a] -> ([a], [a])
split = go [] [] where
  go as bs []     = (as, bs)
  go as bs (x:xs) = go (x:bs) as xs

mergeSortOfficial :: [Int] -> [Int]
mergeSortOfficial [] = []
mergeSortOfficial (x : []) = [x]
mergeSortOfficial xs = 
  let (as, bs) = split xs in
    merge (mergeSortOfficial as) (mergeSortOfficial bs)

正如WillNess在评论中指出的那样,上面的split会产生不稳定的排序。我们可以使用稳定的替代方案:

import Control.Arrow

stableSplit :: [a] -> ([a], [a])
stableSplit xs = go xs xs where
    go (x:xs) (_:_:ys) = first (x:) (go xs ys)
    go xs     ys       = ([], xs)

最好的方法可能是进行自下而上的合并。这是Data.Listsort所采用的方法。在这里,我们合并连续的列表对,直到只剩下一个列表:

mergeSort :: Ord a => [a] -> [a]
mergeSort [] = []
mergeSort xs = mergeAll (map (:[]) xs) where
    mergePairs (x:y:ys) = merge x y : mergePairs ys
    mergePairs xs       = xs

    mergeAll [xs] = xs
    mergeAll xs   = mergeAll (mergePairs xs)

Data.List.sort与上面的工作方式大致相同,只是它首先在输入中查找降序和升序运行,而不是仅从元素创建单例列表。