8

編集:

遅いバージョンは、パフォーマンスの問題を説明するマージ ソート O(n log n) ではなく、実際には挿入ソート O(n^2) であることがわかります。私は、将来の読者がこの答えを見つけるためにコードを探し回る苦痛を軽減できると考えました。


オリジナルはここから始まります -------------------------------------------

Haskell でマージソートの 2 つのバージョンを作成しましたが、一方が他方よりも 1000 倍高速である理由がわかりません。どちらの場合も、リストのリストを作成するリストをソートするリスト内の項目を作成することから始めます。次に、リストをペアにして、リストが 1 つになるまでマージします。問題は、低速バージョンでは "doMerge (x1:x2:xs) = doMerge $ merge x1 x2 : doMerge xs" を呼び出しているが、高速バージョンでは doMerge (mergePairs xs) を呼び出していることです。1000倍の速度差にビックリ!

-- Better version: takes 0.34 seconds to sort a 100,000 integer list.
betMergeSort :: [Int] -> [Int]
betMergeSort list = doMerge $ map (\x -> [x]) list
  where
    doMerge :: [[Int]] -> [Int]
    doMerge [] = []
    doMerge [xs] = xs
    doMerge xs = doMerge (mergePairs xs)

    mergePairs :: [[Int]] -> [[Int]]
    mergePairs (x1:x2:xs) = merge x1 x2 : mergePairs xs
    mergePairs xs = xs

    -- expects two sorted lists and returns one sorted list.
    merge :: [Int] -> [Int] -> [Int]
    merge [] ys = ys
    merge xs [] = xs
    merge (x:xs) (y:ys) = if x <= y
                            then x : merge xs (y:ys)
                            else y : merge (x:xs) ys


-- Slow version: takes 350 seconds to sort a 100,000 integer list.
slowMergeSort :: [Int] -> [Int]
slowMergeSort list = head $ doMerge $ map (\x -> [x]) list
  where
    doMerge :: [[Int]] -> [[Int]]
    doMerge [] = []
    doMerge (oneList:[]) = [oneList]
    doMerge (x1:x2:xs) = doMerge $ merge x1 x2 : doMerge xs

    -- expects two sorted list and returns one sorted list.
    merge :: [Int] -> [Int] -> [Int]
    merge [] ys = ys
    merge xs [] = xs
    merge (x:xs) (y:ys) = if x <= y then x : merge xs (y:ys) else y : merge (x:xs) ys

プロファイラーの出力を見ると、遅いバージョンがより多くのメモリを割り当ていることが明らかです。理由はわかりませんが。私の頭の中では、両方のバージョンが似ているように見えます。割り当てが非常に異なる理由を誰かが説明できますか?

slowMergeSort プロファイリング結果:

    Wed Aug 21 12:24 2013 Time and Allocation Profiling Report  (Final)

       mergeSort +RTS -sstderr -p -RTS s

    total time  =       12.02 secs   (12017 ticks @ 1000 us, 1 processor)
    total alloc = 17,222,571,672 bytes  (excludes profiling overheads)

COST CENTRE         MODULE  %time %alloc

slowMergeSort.merge Main     99.2   99.4


                                                                               individual     inherited
COST CENTRE               MODULE                             no.     entries  %time %alloc   %time %alloc

MAIN                      MAIN                                74           0    0.0    0.0   100.0  100.0
 main                     Main                               149           0    0.0    0.0   100.0  100.0
  main.sortedL            Main                               165           1    0.0    0.0    99.3   99.5
   slowMergeSort          Main                               167           1    0.0    0.0    99.3   99.5
    slowMergeSort.\       Main                               170       40000    0.0    0.0     0.0    0.0
    slowMergeSort.doMerge Main                               168       79999    0.0    0.0    99.2   99.5
     slowMergeSort.merge  Main                               169   267588870   99.2   99.4    99.2   99.4
  main.sortVersion        Main                               161           1    0.0    0.0     0.0    0.0
  randomInts              Main                               151           1    0.0    0.0     0.7    0.5
   force                  Main                               155           1    0.0    0.0     0.0    0.0
    force.go              Main                               156       40001    0.0    0.0     0.0    0.0
   randomInts.result      Main                               152           1    0.7    0.5     0.7    0.5

libMergeSort プロファイリング

    Wed Aug 21 12:23 2013 Time and Allocation Profiling Report  (Final)

       mergeSort +RTS -sstderr -p -RTS l

    total time  =        0.12 secs   (124 ticks @ 1000 us, 1 processor)
    total alloc = 139,965,768 bytes  (excludes profiling overheads)

COST CENTRE              MODULE  %time %alloc

randomInts.result        Main     66.9   64.0
libMergeSort.merge       Main     24.2   30.4
main                     Main      4.0    0.0
libMergeSort             Main      2.4    3.2
libMergeSort.merge_pairs Main      1.6    2.3


                                                                                    individual     inherited
COST CENTRE                    MODULE                             no.     entries  %time %alloc   %time %alloc

MAIN                           MAIN                                74           0    0.0    0.0   100.0  100.0
 main                          Main                               149           0    4.0    0.0   100.0  100.0
  main.sortedL                 Main                               165           1    0.0    0.0    28.2   35.9
   libMergeSort                Main                               167           1    2.4    3.2    28.2   35.9
    libMergeSort.\             Main                               171       40000    0.0    0.0     0.0    0.0
    libMergeSort.libMergeSort' Main                               168          17    0.0    0.0    25.8   32.7
     libMergeSort.merge_pairs  Main                               169       40015    1.6    2.3    25.8   32.7
      libMergeSort.merge       Main                               170      614711   24.2   30.4    24.2   30.4
  main.sortVersion             Main                               161           1    0.0    0.0     0.0    0.0
  randomInts                   Main                               151           1    0.0    0.0    67.7   64.0
   force                       Main                               155           1    0.0    0.0     0.8    0.0
    force.go                   Main                               156       40001    0.8    0.0     0.8    0.0
   randomInts.result           Main                               152           1   66.9   64.0    66.9   64.0
4

1 に答える 1

16

これらの 2 番目O(n^2)は、間違ったアルゴリズムであるため、使用すべきではありません (mergesort と呼ぶべきではありません)。

doMerge (x1:x2:xs) = doMerge $ merge x1 x2 : doMerge xs

xs元のリストよりも定数だけ短い完全にソートします。非常に短いリストを非常に長いリストとマージすることは挿入と同等であるため、これは実際には単なる挿入ソートであることがわかります。マージソートの全体的なポイントは分割統治です。これは分割統治ではありません。リストの長さが長くなるにつれて、速度比は悪化し続けます。

1 つ目は、ほぼ同じ長さのリストをマージする適切なマージ ソートです。

于 2013-08-21T20:44:58.630 に答える