Wir tauchen weiterhin in eine Vielzahl von Haufen ein.Heute analysieren wir eine elegante Bestellmethode, bei der spezielle Haufen basierend auf den Zahlen von Leonardo verwendet werden.Viele haben von dieser Sortierung gehört, aber nur wenige wissen genau, wie sie funktioniert. Heute werden wir sehen, dass nichts kompliziert ist. Die Methode wurde von der legendĂ€ren Edsger Dijkstra erfunden. Neben den vielen hellsten Errungenschaften in der Theorie der Algorithmen ist er auch der Autor einer solch witzigen Aussage: âStudenten, die zuvor Basic studiert haben, ist es fast unmöglich, gute Programmierung zu unterrichten. Als potenzielle Programmierer haben sie eine irreversible geistige Verschlechterung erfahren. â
Ich hoffe, es ist keine GotteslÀsterung, dass die Animation im Artikel mit VBA erstellt wurde :-)


EDISON.
, Android iOS.
! ;-)
Die Heap-Sortierung an sich ist sehr gut, da ihre zeitliche KomplexitĂ€t unabhĂ€ngig von den Daten O ( n log n ) betrĂ€gt . Um kein Array darzustellen, verschlechtert sich die KomplexitĂ€t von Heapsort niemals auf O ( n 2 ) , was beispielsweise bei einer schnellen Sortierung passieren kann. Die Kehrseite der Medaille ist, dass das Sortieren nach einem binĂ€ren Haufen nicht beschleunigt werden kann, O ( n ) -KomplexitĂ€t auch nicht erwartet werden kann (aber das gleiche schnelle Sortieren kann unter bestimmten Bedingungen solche Indikatoren erreichen).Im Allgemeinen stand eine Frage auf der Tagesordnung: Ist es möglich, so zu erfinden, dass die zeitliche KomplexitĂ€t des Sortierens nach einem Haufen einerseits nicht geringer ist alsO ( n log n ) , aber in einem gĂŒnstigen Szenario (insbesondere wenn ein fast sortiertes Array verarbeitet wird) auf O ( n ) erhöht ?Dieses Problem wurde von Edsger Dijkstra persönlich angesprochen, der herausfand, dass dies möglich ist.Es wird davon ausgegangen, dass diejenigen, die diesen Artikel lesen, verstehen, wie das Sortieren nach Heap im Allgemeinen funktioniert. Sie wissen, was Sortierbaum ist und warum ein Sieben erforderlich ist. Wenn jemand LĂŒcken in diesem Wissen hat, empfehle ich Ihnen, den vorherigen Artikel zu lesen , bevor Sie mit dem Lesen fortfahren .Was ist los mit einem binĂ€ren Heap?
Lassen Sie uns einen Blick darauf werfen, wie Heapsort ein fast geordnetes Array sortiert und warum dieser Algorithmus solche eingehenden Daten nicht schneller verarbeitet.Klicken Sie auf die Animation, um zum Artikel âSortieren nach der n-Pyramideâ zu gelangen.Das erste, was Ihnen auffĂ€llt, ist, dass beim Sieben die Maxima stĂ€ndig an die Wurzel des Heaps verschoben werden, die dem ersten Element des Arrays entspricht. Wenn das Eingabearray fast geordnet ist, bedeutet dies fĂŒr den Algorithmus nur wenig Arbeit. Kleinere Elemente werden immer noch zuerst den Baum hinuntergehen, d. H. Gehen Sie nĂ€her an das Ende des Arrays heran, nicht an den Anfang.Der zweite Verlangsamungsfaktor, der nicht so offensichtlich ist, ist, dass der Standard-BinĂ€rheap selbst immer ein ausgeglichener Baum ist. Bei ursprĂŒnglich bestellten Daten spielt dies eine negative Rolle. Wenn das ursprĂŒngliche Array zufĂ€llige Daten enthĂ€lt, werden diese gleichmĂ€Ăig in einem ausgeglichenen Baum verteilt, und das mehrfache Sieben durchlĂ€uft alle Zweige ungefĂ€hr gleich oft. Bei fast geordneten Daten ist ein unausgeglichener Baum vorzuziehen. In diesem Fall werden die Daten in dem Teil des Arrays, der lĂ€ngeren Zweigen des Baums entspricht, seltener verarbeitet als in anderen.Leonardo-Nummern
Um beide Probleme zu lösen, schlug Dijkstra vor, spezielle binĂ€re Haufen zu verwenden, die auf Leonardo-Zahlen basieren.Leonardo-Zahlen sind fast wie Fibonacci-Zahlen, aber nur besser.Eine Reihe von Leonardo-Zahlen wird rekursiv angegeben:L 0 = 1L 1 = 1L n = L n - 1 + L n - 2 + 1Die ersten 20 Leonardo-Zahlen:1, 1, 3, 5, 9, 15, 25, 41, 67 109, 177, 287, 465, 753, 1219, 1973, 3193, 5167, 8361, 13529Absolut jede ganze Zahl kann als die Summe von Leonardo-Zahlen mit unterschiedlichen Seriennummern dargestellt werden.Dies ist in unserem Fall sehr nĂŒtzlich. Array von nElemente können nicht immer als ein einzelner Haufen von Leonardo dargestellt werden (wenn n keine Leonardo-Zahl ist). Aber dann kann jedes Array immer in mehrere Subarrays unterteilt werden, die einer unterschiedlichen Anzahl von Leonardo entsprechen, d. H. Haufen unterschiedlicher Ordnung sein.Hier ist ein Beispiel eines Arrays des 21. Elements, das aus drei Leonard-Haufen besteht. In jedem der Haufen entspricht die Anzahl der Knoten einer beliebigen Anzahl von Leonardo.Wichtige Punkte zu wissen:- Jeder Leonardov-Stapel ist ein unausgeglichener BinĂ€rbaum.
- Die Wurzel jedes Heaps ist das letzte (und nicht das erste, wie in einem regulÀren binÀren Heap) Element des entsprechenden Subarrays.
- Jeder Knoten mit all seinen Nachkommen ist auch ein Leonard-Haufen kleinerer Ordnung.
Haufen bauen und abbauen
In der Wiederholungsformel fĂŒr Leonardo-Zahlen istL n = L n - 1 + L n - 2 + 1sehr zufrieden mit der Einheit am Ende.Und deshalb. Angenommen, wir haben zwei benachbarte Subarrays im Array, die Heaps entsprechen, die auf zwei benachbarten Leonardo-Zahlen aufgebaut sind. Mit dem Element unmittelbar nach diesen Subarrays können diese Subarrays zu einem gemeinsamen Heap kombiniert werden, der der nĂ€chsten Leonard-Nummer entspricht.Wir gehen die Elemente im Array durch und bauen eine Reihe von Leonard-Haufen. Wenn Sie das Element verwenden, können Sie die beiden vorherigen Heaps kombinieren (dies ist nur dann möglich, wenn die beiden vorherigen Heaps zwei aufeinander folgenden Leonardo-Zahlen entsprechen), und dann kombinieren. Wenn eine Kombination nicht möglich ist (die beiden vorherigen Heaps entsprechen nicht zwei aufeinanderfolgenden Leonardo-Nummern), bildet das aktuelle Element einfach einen neuen Heap eines Elements, das der ersten (oder zweiten, wenn die erste zuvor verwendet wird) Leonardo-Nummer entspricht.In der zweiten Stufe des Algorithmus erfolgt der umgekehrte Prozess - wir analysieren die Haufen. Wenn wir die Wurzel im Heap entfernen, erhalten wir zwei kleinere Heaps, die den beiden vorherigen Leonardo-Zahlen entsprechen. Dies kann geschehen, weil:L n - 1 = L n - 1+ L n - 2In Fibonacci-Zahlen gibt es keine solche nĂŒtzliche Einheit, daher verwenden wir den Fibonacci-Heap nicht.Smooth Sort :: Smoothsort
Der endgĂŒltige Algorithmus:- I. Erstellen Sie eine Reihe von Leonard-Haufen aus dem Array, von denen jeder ein Sortierbaum ist.
- I.1. Durchlaufen Sie die Elemente des Arrays von links nach rechts.
- II.1. ĂberprĂŒfen Sie, ob das aktuelle Element die beiden am weitesten links liegenden Heaps im vorhandenen Heap von Leonard-Heaps kombinieren kann:
- II.1.a. Wenn ja, dann kombinieren wir die beiden am weitesten links liegenden Heaps zu einem. Das aktuelle Element wird zur Wurzel dieses Heaps. Wir durchsuchen den kombinierten Heap.
- II.1.b. Wenn nicht, fĂŒgen Sie das aktuelle Element als neuen Heap (der bisher aus einem Knoten besteht) zum vorhandenen Heap von Leonard-Heaps hinzu.
- II. , :
- II.1. . , .
- II.2. ( ) ( ).
- II.3. , . .
- II.4. ( ), .
- II.5. Nachdem das maximale Element an das Ende verschoben wurde, nahm der sortierte Teil des Arrays zu und der unsortierte Teil ab. Wiederholen Sie die Schritte II.1-II.4 fĂŒr den verbleibenden unsortierten Teil des Arrays.
Beispiel fĂŒr eine Python-Implementierung
import random
def smoothsort(lst):
leo_nums = leonardo_numbers(len(lst))
heap = []
for i in range(len(lst)):
if len(heap) >= 2 and heap[-2] == heap[-1] + 1:
heap.pop()
heap[-1] += 1
else:
if len(heap) >= 1 and heap[-1] == 1:
heap.append(0)
else:
heap.append(1)
restore_heap(lst, i, heap, leo_nums)
for i in reversed(range(len(lst))):
if heap[-1] < 2:
heap.pop()
else:
k = heap.pop()
t_r, k_r, t_l, k_l = get_child_trees(i, k, leo_nums)
heap.append(k_l)
restore_heap(lst, t_l, heap, leo_nums)
heap.append(k_r)
restore_heap(lst, t_r, heap, leo_nums)
def leonardo_numbers(hi):
a, b = 1, 1
numbers = []
while a <= hi:
numbers.append(a)
a, b = b, a + b + 1
return numbers
def restore_heap(lst, i, heap, leo_nums):
current = len(heap) - 1
k = heap[current]
while current > 0:
j = i - leo_nums[k]
if (lst[j] > lst[i] and
(k < 2 or lst[j] > lst[i-1] and lst[j] > lst[i-2])):
lst[i], lst[j] = lst[j], lst[i]
i = j
current -= 1
k = heap[current]
else:
break
while k >= 2:
t_r, k_r, t_l, k_l = get_child_trees(i, k, leo_nums)
if lst[i] < lst[t_r] or lst[i] < lst[t_l]:
if lst[t_r] > lst[t_l]:
lst[i], lst[t_r] = lst[t_r], lst[i]
i, k = t_r, k_r
else:
lst[i], lst[t_l] = lst[t_l], lst[i]
i, k = t_l, k_l
else:
break
def get_child_trees(i, k, leo_nums):
t_r, k_r = i - 1, k - 2
t_l, k_l = t_r - leo_nums[k_r], k - 1
return t_r, k_r, t_l, k_l
def main(n):
lst = list(range(n))
random.shuffle(lst)
print(lst)
smoothsort(lst)
print(lst)
Zeitliche KomplexitÀt
Wenn wir ein fast geordnetes Array als Eingabe nehmen, zeigt die Visualisierung, warum ein solches Array viel schneller verarbeitet wird.Einsparungen entstehen nur durch Sieben. In fast geordneten Daten sinkt das Sieben flach in den Baum, auch nachdem sich die Haufen in der zweiten Stufe allmĂ€hlich aufgelöst haben. In den anfĂ€nglich zufĂ€lligen Daten ist das Sieben teurer, da es oft in seinem Haufen auf die allerletzte Ebene fĂ€llt.Lassen Sie uns die GesamtzeitkomplexitĂ€t schĂ€tzen.In der ersten Phase iterieren wir ĂŒber n Elemente und fĂŒgen sie den bereits links vorhandenen Heaps hinzu. Das HinzufĂŒgen zum Heap selbst kostet ungefĂ€hr in O (1), aber fĂŒr den Heap mĂŒssen Sie einen Siebvorgang durchfĂŒhren. In geordneten Daten kostet ein flaches Sieben hĂ€ufig O (1) fĂŒr ein Element, das dem Heap hinzugefĂŒgt wird. Bei ungeordneten Daten wird das Sieben fĂŒr jede Addition in O (log n ) berechnet., da das Sieben aufgrund von ZufĂ€lligkeiten oft bis zum Grund durch die Ebenen des Baumes gehen muss.Daher ist in der ersten Stufe die beste ZeitkomplexitĂ€t:fĂŒr fast geordnete Daten - O ( n ),fĂŒr zufĂ€llige Daten - O ( n log n ).FĂŒr die zweite Stufe ist die Situation Ă€hnlich. Wenn Sie das nĂ€chste Maximum austauschen, mĂŒssen Sie den Heap, an dessen Wurzel er sich befand, erneut sieben. Und die Siebmetriken fĂŒr geordnete und ungeordnete Daten sind unterschiedlich.In der zweiten Stufe ist die beste ZeitkomplexitĂ€t dieselbe wie in der ersten:fĂŒr fast geordnete Daten - O ( n ),fĂŒr zufĂ€llige Daten - O ( n log n ).HinzufĂŒgen von ZeitkomplexitĂ€t fĂŒr die erste und zweite Stufe:fĂŒr fast geordnete Daten - O (2 n ) = O ( n ),fĂŒr zufĂ€llige Daten - O (2 n log n ) = O ( n log n ).Im Allgemeinen ist die schlechteste und durchschnittliche ZeitkomplexitĂ€t fĂŒr eine reibungslose Sortierung O ( n log n ).Dijkstra hat in seinen Berechnungen (mit denen ich Sie nicht langweilen werde) bewiesen, dass die beste KomplexitĂ€t reibungslos zu O ( n ) tendiert, je geordneter die eingehenden Daten sind. Daher der Name - reibungslose Sortierung.ZusĂ€tzliche SpeicherkomplexitĂ€t
Um die Daten in eine Reihe von Leonard-Haufen zu zerlegen, mĂŒssen Sie sich nur genau merken, welche Leonardo-Nummern bei jedem Schritt beteiligt sind. Wenn man diese Zahlen kennt, werden die Haufen selbst algorithmisch ausgerichtet. Diese Zahlenreihe wĂ€chst sehr schnell, sodass Sie selbst fĂŒr groĂe Arrays einen sehr kleinen Satz von Leonard-Zahlen benötigen.Binomial-Heap-Sortierung :: Binomial-Heap-Sortierung
Es gibt eine Baumstruktur, die der von uns aussortierten sehr Ă€hnlich ist - einen Binomialhaufen . Dies ist auch eine Reihe von Haufen unterschiedlicher GröĂe, bei denen die Anzahl der Knoten jeweils eine Zweierpotenz ist. Jedes Array mit einer beliebigen Anzahl von Elementen kann in diesen Heap erweitert werden, da jede natĂŒrliche Anzahl in die Summe von zwei verschiedenen Graden zerlegt wird.Im Prinzip können Sie eine reibungslose Sortierung basierend auf Binomen durchfĂŒhren:Wird es schneller funktionieren? Kaum. Der Binomialheap ist nicht binĂ€r, und im letzten Artikel haben wir herausgefunden, dass das Erhöhen der Anzahl der Nachkommen nicht beschleunigt, sondern den Bildschirm verlangsamt . AuĂerdem können Sie feststellen, dass der Binomialheap lĂ€ngere Verzweigungen aufweist, weshalb benachbarte geordnete Bereiche des Arrays etwas langsamer miteinander verbunden sind.Es ist nicht bekannt, ob der Dijkstra-Binomialhaufen allgemein als mögliche Grundlage fĂŒr seinen Algorithmus angesehen wurde. Wie dem auch sei, der Leonardov-Haufen ist wahrscheinlich optimaler.Trailer der nĂ€chsten Serie
Selbst wenn ein Binomialstapel nicht die beste Option fĂŒr eine reibungslose Sortierung ist, sollten Sie ihn nicht vollstĂ€ndig verwerfen.Wenn der Binomialbaum leicht modifiziert ist und völlig andere (sehr kĂŒhne) Ideen verwendet werden, um ihn zu umgehen, erhalten wir einen originellen und effektiven Algorithmus, der seine eigenen Vorteile hat. WorĂŒber werden wir das nĂ€chste Mal sprechen?Klicken Sie auf die Animation, um zum Artikel mit der nĂ€chsten Sortierung nach Heap zu gelangen.Verweise
Die glatte / glatte
Leonardo-Zahl , Binomialhaufen / BinomialhaufenSerienartikel:
Die heutige reibungslose Sortierung wurde der AlgoLab-App hinzugefĂŒgt. Sowie einen Bonus - und das Sortieren mit einem Binomialstapel. Wer also die Daten auf den Heap-Heaps persönlich steuern möchte, aktualisiert die Excel-Datei mit Makros.