Das Sortierverfahren Mergesort gehört zu den schnellsten Sortierverfahren. Es ist ein Paradebeispiel für die Anwendung der Divide-and-Conquer-Strategie.
Divide-and-Conquer-Strategie
Oft kannst du ein Problem schneller lösen, indem du es zunächst in zwei kleinere Teilprobleme zerlegst (divide). Dann löst du die Teilprobleme (conquer), und aus diesen Lösungen setzt du die Gesamtlösung zusammen (combine).
Nach diesem Prinzip funktioniert das Sortierverfahren Mergesort.
Sortierverfahren Mergesort
Du willst ein Array von Daten sortieren. Du unterteilst das Array in zwei Hälften (divide). Dann sortierst du jede Hälfte des Arrays für sich (conquer). Diese beiden sortierten Hälften verschmilzt du dann zu einem insgesamt sortierten Array (combine).
Das Verschmelzen (engl. to merge) der beiden sortierten Hälften geht so: Du kopierst die sortierte vordere Hälfte des Arrays in ein Hilfsarray und die sortierte hintere Hälfte in ein Hilfsarray .
Dann entnimmst du aus diesen beiden Hilfsarrays immer das jeweils nächstgrößere Element und kopierst es der Reihe nach zurück in das ursprüngliche Array . Die Indexpositionen , und zeigen dir dabei an, wo du gerade bist. Zum Schluss ist das Array insgesamt sortiert.
Funktion merge
In der Programmiersprache Python sieht die entsprechende Funktion merge folgendermaßen aus:
# Verschmilzt sortiertes vorderes Teilstueck von Array a,
# beginnend beim Index lo bis zum Index m (ausschliesslich),
# mit hinterem sortiertem Teilstueck von Array a,
# beginnend beim Index m bis zum Index hi (ausschliesslich),
# zu einem insgesamt sortierten Teilstueck von Array a,
# beginnend beim Index lo bis zum Index hi (ausschliesslich)
def merge(a, lo, m, hi):
# Haelften in Hilfsarrays b und c kopieren
b=a[lo:m]
c=a[m:hi]
# Eintraege der Hilfsarrays geordnet nach a zurueckkopieren
i=0
j=0
k=lo
while i<len(b) and j<len(c):
if b[i]<=c[j]:
a[k]=b[i]
i+=1
else:
a[k]=c[j]
j+=1
k+=1
# falls vorhanden Rest des Hilfsarrays b zurueckkopieren
while i<len(b):
a[k]=b[i]
i+=1
k+=1
# falls vorhanden Rest des Hilfsarrays c zurueckkopieren
while j<len(c):
a[k]=c[j]
j+=1
k+=1
Anhand der Abbildungen hast du vielleicht eine Vorstellung davon gewonnen, wie die Funktion merge funktioniert. Probiere einmal selbst mit Karopapier und Bleistift aus, wie die Funktion merge das folgende Array , das aus zwei sortierten Hälften besteht, zu einem insgesamt sortierten Array verschmilzt.
Du wirst feststellen, dass du den eventuell verbliebenen Rest des Hilfsarrays gar nicht zurückkopieren musst – warum? Den letzten Absatz der Funktion merge kannst du also weglassen.
Es gibt noch ein paar andere Möglichkeiten, die Funktion merge zu optimieren, hast du Ideen?
Funktion mergesort
Aber bevor du optimierst, schreibe erst noch die Funktion mergesort. Denn du möchtest ja ein komplett unsortiertes Array sortieren. Dies machst du rekursiv:
Du sortierst die vordere Hälfte des Arrays durch einen rekursiven Aufruf der Funktion mergesort.
Du sortierst die hintere Hälfte durch einen weiteren rekursiven Aufruf der Funktion mergesort.
Nun ist alles bereit für den Aufruf der Funktion merge, du rufst sie also auf – fertig!
Das Programm dazu ist folgendes:
# Sortiert ein Teilstueck des Arrays a
# beginnend beim Index lo bis zum Index hi (ausschliesslich)
def mergesort(a, lo, hi):
if hi-lo>1:
m=(lo+hi)//2 # m = Index in der Mitte
mergesort(a, lo, m) # vordere Haelfte sortieren
mergesort(a, m, hi) # hintere Haelfte sortieren
merge(a, lo, m , hi) # Haelften verschmelzen
Die wichtigste Zeile im Funktionskörper ist die erste Zeile: Du musst sicherstellen, dass die Rekursion irgendwann endet. Dies tut sie dann, wenn ein zu sortierendes Teilstück nur noch aus einem Element besteht.
Beachte bitte eine python-spezifische Besonderheit: Indexbereiche beginnen immer bei einem unteren Index (zum Beispiel lo) und enden immer vor dem genannten oberen Index (zum Beispiel hi).
Rekursion verstehen
Vielleicht fragst du dich: Wie kann denn die Funktion mergesort schon aufgerufen werden, wenn sie noch gar nicht fertig definiert ist?
Doch! Die Funktion ist an dieser Stelle schon fertig definiert! Und zwar für Array-Teilstücke der Länge 1. Denn ein Teilstück, das nur aus einem Element besteht, ist bereits sortiert. Dieser Fall wird durch die nicht erfüllte Bedingung, dass hi-lo>1 ist, erkannt. In diesem Fall macht die Funktion mergesort das einzig Richtige: nichts.
Jedes Mal, wenn du mergesort rekursiv aufrufst, springst du ja wieder in die Funktion hinein, aber jedes Mal mit einem kürzeren zu sortierenden Teilstück des Arrays. Und irgendwann besteht das Teilstück nur noch aus einem einzigen Element. Dann endet die Rekursion.
Im Endeffekt verschmilzt du auf diese Weise zunächst Teilstücke der Länge 1 zu sortierten Teilstücken der Länge 2, diese dann zu sortierten Teilstücken der Länge 4 und so weiter. Jedenfalls dann, wenn die Länge des Arrays eine Zweierpotenz ist.
Aber das Programm funktioniert auch, wenn die Länge des Arrays eine beliebige Zahl ist. In diesem Fall werden auch Teilstücke, deren Länge sich um 1 unterscheidet, miteinander verschmolzen.
Programm ausprobieren
Im Hauptprogramm rufst du die Funktion mergesort folgendermaßen auf:
# Ausprobieren
if __name__=="__main__":
a=[7,6,5,4,3,2,1,0]
print(a)
mergesort(a, 0, len(a))
print(a)
Wie schnell ist Mergesort?
Du sortierst ein Array der Länge mit Mergesort, wobei eine Zweierpotenz ist.
Die Funktion merge benötigt Schritte, um zwei Teilstücke zu einem Stück der Länge zu verschmelzen. Insgesamt benötigst du also
Schritte, um Teilstücke der Länge 2 aus Teilstücken der Länge 1 zu verschmelzen,
Schritte, um Teilstücke der Länge 4 aus Teilstücken der Länge 2 zu verschmelzen,
Schritte, um Teilstücke der Länge 8 aus Teilstücken der Länge 4 zu verschmelzen,
und so weiter...
Schritte, um Teilstücke den Länge aus Teilstücken der Länge zu verschmelzen.
Dies sind insgesamt Schritte.
Schneller als in Schritten geht es nicht, jedenfalls wenn keine Annahmen über die Art der zu sortierenden Daten gemacht werden (zum Beispiel dass es nur Zahlen zwischen 1 und 100 sind).
Es lässt sich beweisen, dass es kein Sortierverfahren geben kann, das Daten in weniger als Schritten sortieren kann.
Mergesort ist damit optimal.