In minimum = head . sort
wird das sort
nicht vollständig erledigt, weil es nicht im Voraus erledigt wird . Das sort
wird nur so viel getan, wie nötig ist, um das allererste Element zu produzieren, das von verlangt wird head
.
Bei z. B. Mergesort werden zunächst die n
Nummern der Liste paarweise verglichen, dann werden die Gewinner gepaart und verglichen ( n/2
Zahlen), dann die neuen Gewinner ( n/4
) usw. Insgesamt werden O(n)
Vergleiche durchgeführt, um das minimale Element zu erhalten.
mergesortBy less [] = []
mergesortBy less xs = head $ until (null.tail) pairs [[x] | x <- xs]
where
pairs (x:y:t) = merge x y : pairs t
pairs xs = xs
merge (x:xs) (y:ys) | less y x = y : merge (x:xs) ys
| otherwise = x : merge xs (y:ys)
merge xs [] = xs
merge [] ys = ys
Der obige Code kann erweitert werden, um jede von ihm produzierte Nummer mit einer Reihe von Vergleichen zu kennzeichnen, die in seine Produktion eingeflossen sind:
mgsort xs = go $ map ((,) 0) xs where
go [] = []
go xs = head $ until (null.tail) pairs [[x] | x <- xs] where
....
merge ((a,b):xs) ((c,d):ys)
| (d < b) = (a+c+1,d) : merge ((a+1,b):xs) ys
| otherwise = (a+c+1,b) : merge xs ((c+1,d):ys)
....
g n = concat [[a,b] | (a,b) <- zip [1,3..n] [n,n-2..1]]
Wenn wir es für mehrere Listenlängen ausführen, sehen wir, dass es tatsächlich so ist~ n
:
*Main> map (fst . head . mgsort . g) [10, 20, 40, 80, 160, 1600]
[9,19,39,79,159,1599]
Um zu sehen, ob der Sortiercode selbst ist ~ n log n
, ändern wir ihn so, dass jede produzierte Nummer nur ihre eigenen Kosten trägt. Die Gesamtkosten werden dann durch Summierung über die gesamte sortierte Liste ermittelt:
merge ((a,b):xs) ((c,d):ys)
| (d < b) = (c+1,d) : merge ((a+1,b):xs) ys
| otherwise = (a+1,b) : merge xs ((c+1,d):ys)
Hier sind die Ergebnisse für Listen unterschiedlicher Länge,
*Main> let xs = map (sum . map fst . mgsort . g) [20, 40, 80, 160, 320, 640]
[138,342,810,1866,4218,9402]
*Main> map (logBase 2) $ zipWith (/) (tail xs) xs
[1.309328,1.2439256,1.2039552,1.1766101,1.1564085]
Das Obige zeigt empirische Wachstumsordnungen für zunehmende Listenlängen n
, die schnell abnehmen, wie dies typischerweise durch ~ n log n
Berechnungen gezeigt wird. Siehe auch diesen Blog-Beitrag . Hier ist eine kurze Korrelationsprüfung:
*Main> let xs = [n*log n | n<- [20, 40, 80, 160, 320, 640]] in
map (logBase 2) $ zipWith (/) (tail xs) xs
[1.3002739,1.2484156,1.211859,1.1846942,1.1637106]
edit: Lazy Evaluation kann metaphorisch als eine Art Produzenten- / Konsumentensprache 1 angesehen werden , mit unabhängigem Memoizing-Speicher als Vermittler. Jede produktive Definition, die wir schreiben, definiert einen Produzenten, der seine Produktion Stück für Stück produziert, wie und wann es von seinen Verbrauchern verlangt wird - aber nicht früher. Was auch immer produziert wird, wird gespeichert, sodass ein anderer Verbraucher, wenn er dieselbe Ausgabe in unterschiedlichem Tempo verbraucht, auf denselben zuvor gefüllten Speicher zugreift.
Wenn keine Verbraucher mehr übrig sind, die sich auf ein Lager beziehen, wird Müll gesammelt. Manchmal ist der Compiler mit Optimierungen in der Lage, den Zwischenspeicher vollständig zu beseitigen und den Mittelsmann auszuschalten.
1 siehe auch: Einfache Generatoren gegen Lazy Evaluation von Oleg Kiselyov, Simon Peyton-Jones und Amr Sabry.
sort
Implementierungen" angegeben ist. Es ist einfach, eine zu schreiben,sort
für die diese KompositionO(n log n)
Zeit oder Schlimmeres braucht , aber nicht aus den Gründen, die Sie denken.