【问题标题】:Haskell merge sort implementationHaskell 归并排序实现
【发布时间】:2017-12-01 17:57:31
【问题描述】:

我正在尝试解决这个问题: 定义一个递归函数 msort :: Ord a => [a] -> [a] 实现- ments 归并排序,可以通过以下两条规则指定: 长度为 1 的列表已经排序; 其他列表可以通过将两半排序并合并 结果列表。

但是我很难理解归并排序的代码是如何工作的。

这是我的代码::

merge :: Ord a => [a] -> [a] -> [a]
merge [] [] = []
merge (x:xs) (y:ys) | y > x =  (x:xs) ++  (y:ys)
                    |otherwise =  (y:ys) ++  (x:xs)


msort :: Ord a => [a] -> [a]
--base case , if length less than or equal 1 , then list is already  sorted
msort [] = [] -- how to implement base case?
msort (x:xs)  = merge  (take (length(x:xs) `div` 2 ) (x:xs)) (drop (length(x:xs) `div` 2 ) (x:xs))

【问题讨论】:

  • 您有问题吗?
  • 您可以在长度与msort [x] = .. 或msort (x:[]) = .. 完全相同的列表上进行模式匹配。或者,您可以在(x:xs) 案例之后添加一个包罗万象的案例,例如msort x = x
  • @user2407038 但是,在(x:xs) 之后添加一个包罗万象的大小写是行不通的,因为它已经捕获了所有长度 >= 1 的列表,包括 [x]。
  • @chi 实际上,递归的情况必须改为(x:y:xs)。

标签: sorting haskell mergesort


【解决方案1】:

您的 merge 函数有两个问题。

i) 模式并非详尽无遗:如果恰好其中一个列表为空,则不匹配。你应该有

merge :: Ord a => [a] -> [a] -> [a]
merge [] ys = ys
merge xs [] = xs
merge (x:xs) (y:ys) = something

ii)您的something 实际上并不是归并排序,它正在查看头部并根据头部的比较连接两个列表。例如,如果给定[1,4,5] 和[2,3,4],1 将绑定到x,2 绑定到y,然后由于y > x 成立,那么它将返回[1,4,5,2,3,4],显然未排序,尽管两个给定的列表都已排序。

想象如下合并。给定两个列表,您只看到正面,然后选择较小的那个。代替您删除的那个,下一个元素滚进来,您继续做同样的事情。这是一个递归过程;每次选择后,你都做同样的事情。在代码中:

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

至于实际排序:: Ord a =&gt; [a] -&gt; [a],你还必须使用已经排序的一半,这也需要递归:

msort :: Ord a => [a] -> [a]
msort []  = []
msort [x] = [x]
msort xs  = merge firstHalfSorted secondHalfSorted
     where firstHalfSorted  = msort . fst $ halves
           secondHalfSorted = msort . snd $ halves
           halves           = splitAt halfPoint xs
           halfPoint        = length xs `div` 2

【讨论】:

  • 虽然这可行,但存在一些效率问题。首先,您应该只splitAt halfPoint xs 一次,而不是两次;实际上,您可能会将拆分工作加倍。其次,所有这些length 的计算都有些昂贵:额外的n * log n。您可以在开头获取长度并递归使用quotRem,使用龟兔算法拆分列表,或切换到自下而上的归并排序。
  • 一个懒惰的龟兔分离器看起来像这样:half :: [a] -&gt; ([a], [a]); half xs0 = go xs0 xs0 where go t [] = ([], t); go t [_] = ([], t); go (t : ts) (_:_:hs) = case go ts hs of ~(front,rear) -&gt; (t : front, rear)
  • @dfeuer 我修复了重复的splitAt。
  • 我刚刚意识到 non-lazy 龟兔分离器可能更合适。
【解决方案2】:

因为我无法抗拒挑战,这里有一个高效、增量、稳定的自上而下的归并排序。相同的设计和一些小的调整应该为严格的语言产生一个有效的(但非增量的)合并排序;惰性只在merge 函数中使用,因此如果需要,应该可以做一些杂耍来避免它。

{-# language BangPatterns #-}

import Control.Monad

-- Divide a list into the first n `quot` 2 elements and
-- the rest. The front of the list is reversed.
half :: [a] -> ([a], [a])
half = join (go []) where
  go front ts [] = (front, ts)
  go front ts [_] = (front, ts)
  go front (t:ts) (_:_:hs) = go (t:front) ts hs

-- Some care is required to make the sort stable.
merge :: Ord a => Bool -> [a] -> [a] -> [a]
merge _ [] ys = ys
merge _ xs [] = xs
merge up xxs@(x : xs) yys@(y : ys)
  | (if up then (<=) else (<)) x y = x : merge up xs yys
  | otherwise = y : merge up xxs ys

msort :: Ord a => [a] -> [a]
msort = go True where
  go _ [] = []
  go _ xs@[_] = xs
  go !up xs = merge up frontSorted rearSorted where
    frontSorted = go (not up) front
    rearSorted = go up rear
    (front, rear) = half xs

更多地使用惰性的版本更容易编写和理解,但如果不注意控制某些编译器优化,可能会受到subtle space leak 的影响:

-- Split a list in half, with both halves in order
half :: [a] -> ([a], [a])
half = join go
  where
    go t [] = ([], t)
    go t [_] = ([], t)
    go (t : ts) (_:_:hs) = (t : front, rear)
      where (front, rear) = go ts hs -- Interesting laziness

merge :: Ord a => [a] -> [a] -> [a]
merge [] ys = ys
merge xs [] = xs
merge xxs@(x:xs) yys@(y:ys)
  | x <= y    = x : merge xs yys
  | otherwise = y : merge xxs ys

msort :: Ord a => [a] -> [a]
msort []  = []
msort [x] = [x]
msort xs  = merge (msort front) (msort rear)
  where (front, rear) = half xs

为了完整起见,我应该提到 自下而上合并排序在 Haskell 中似乎更自然。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-21
    • 2012-08-15
    • 2019-07-29
    • 2016-09-11
    相关资源
    最近更新 更多