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

use*_*376 2 sorting algorithm mergesort haskell functional-programming

我无法在网上的任何地方找到我的代码,所以你能告诉我为什么或为什么函数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))
Run Code Online (Sandbox Code Playgroud)

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

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

官方解决方案 - 实施:

mergeSortOfficial [] = []
mergeSortOfficial (x : []) = [x]
mergeSortOfficial xs = merge
    (mergeSortOfficial (take ((length xs) ‘div‘ 2) xs))
    (mergeSortOfficial (drop ((length xs) ‘div‘ 2) xs))
Run Code Online (Sandbox Code Playgroud)

pig*_*ker 10

不,那不是mergeSort.这是插入排序,这在本质上是相同的算法冒泡,这取决于你如何盯着它.在每个步骤中,单个列表merged与累积的有序列表 - 到目前为止,因此,有效地,插入该单例的元素.

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

foldr (\ x (ys, zs) -> (x : zs, ys)) ([], [])
Run Code Online (Sandbox Code Playgroud)

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

如果像我一样,你喜欢在前面有结构,你可以看到它,你可以制作有序列表a 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
Run Code Online (Sandbox Code Playgroud)

现在你只需要插入排序

ala' Merge foldMap (:[]) :: [x] -> [x]
Run Code Online (Sandbox Code Playgroud)

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

data Tree x = None | One x | Node (Tree x) (Tree x) deriving Foldable
Run Code Online (Sandbox Code Playgroud)

我没有强制执行平衡不变量,但我可以.关键是与之前相同的操作有另一种类型

ala' Merge foldMap (:[]) :: Tree x -> [x]
Run Code Online (Sandbox Code Playgroud)

它合并了从树状排列的元素中收集的列表.要获得所述安排,请考虑"有什么用处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
Run Code Online (Sandbox Code Playgroud)

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

mergeSort :: Ord x => [x] -> [x]
mergeSort = ala' Merge foldMap (:[]) . foldr twistin None
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

树已经成为程序的递归结构.

  • *那是insertSort,它与bubbleSort的算法基本相同,具体取决于你如何盯着它.*你如何*盯着它?冒泡排序和插入排序可能都具有最坏情况的二次时间复杂度,但它们不是相同的算法. (3认同)

And*_*ács 7

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

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]
Run Code Online (Sandbox Code Playgroud)

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

mergeSortOfficial在技​​术上是正确的,但它是低效的.length采用线性时间,并在每个递归级别调用列表的总长度.take并且drop也是线性的.总体复杂性仍然是最佳的n * log n,但我们运行了几个不必要的圈子.

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

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)
Run Code Online (Sandbox Code Playgroud)

正如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)
Run Code Online (Sandbox Code Playgroud)

最好的方法可能是进行自下而上的合并.这是该方法sortData.List需要.在这里,我们合并连续的列表对,直到只剩下一个列表:

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)
Run Code Online (Sandbox Code Playgroud)

Data.List.sort 除了从输入中查找降序和升序运行开始,而不是仅从元素创建单例列表时,它的工作方式与上述大致相同.