i

Sortieren durch Aufteilen

Sortieren von Zahlen

Es gibt verschiedene Strategien zum Sortieren von Dingen wie zum Beispiel Zahlen. Die folgenden verschiedenen Strategien kennst du bestimmt vielleicht schon aus diesem Kapitel (2.3.2.3). Falls nicht, kannst du sie dir bei Bedarf dort nochmal genauer anschauen.

  • Sortieren durch Einfügen: Wir gehen eine Liste an Zahlen nacheinander durch und fügen jede Zahl an die richtige Stelle in einer zweiten, zu Beginn leeren Liste ein.
  • Sortieren durch Auswählen: Wie das Sortieren durch Einfügen, mit dem Unterschied, dass wir hier immer die kleinste (oder größte) Zahl suchen und die dann an den Anfang (bzw. das Ende) der zweiten Liste anfügen.
  • Sortieren durch Aufsteigen (Bubblesort): Wir gehen die Zahlen in der Liste paarweise durch und vergleichen je zwei Zahlen miteinander. Wenn sie in der falschen Reihenfolge sind, tauschen wir sie. Das machen wir so oft, bis die ganze Liste sortiert ist.
  • Sortieren durch Zerlegen: Wir teilen die Liste in kleinere Listen auf und sortieren die kleineren Listen einzeln für sich. Zum Schluss fügen wir die sortierten Listen zusammen.

Die letzte Strategie schauen wir uns jetzt nochmal genauer an. Also: anstatt eine Liste von Zahlen auf einmal zu sortieren, teilen wir die Liste in mehrere kleinere Listen auf und sortieren diese für sich. Dann müssen wir die Zahlen der beiden kleinen Listen nur noch in der richtigen Reihenfolge zusammenfügen.

Als Pseudocode könnte das Ganze so aussehen:


# Teile die Liste in zwei kleinere Listen auf 
# Sortiere die beiden Listen 
sortiere_erste_liste()
sortiere_zweite_liste()

# Zusammenfügen der sortierten kleineren Listen
fuege_sortierte_listen_zusammen()
return zusammengefuegte_liste

Diesem Sortieralgorithmus können wir jetzt noch einen Namen geben. Da wir die sortierten Teillisten zusammenfügen, auf englisch mergen, nennen wir den Algorithmus mergesort. Die Funktion, die uns die beiden kleinen Listen zusammenfügt, nennen wir dann noch merge. Wie wir die Liste aufteilen, ist im Grunde egal. Deshalb machen wir es uns möglichst einfach und teilen die Liste in der Hälfte auf.

Außerdem müssen wir uns noch überlegen, was wir im Basisfall machen, also wenn die Liste nur eine einzige Zahl enthält. Eine Liste mit nur einer Zahl ist automatisch sortiert, deshalb können wir in dem Fall die Liste einfach unverändert zurückgeben.

Hinweis: Wir könnten uns genauso dafür entscheiden, die leere Liste als den Basisfall zu wählen. Das macht für die Implementierung keinen Unterschied und ist lediglich eine Design-Entscheidung.

def mergesort(l):
    if (len(l) == 1): # Basisfall: Liste mit einem Element ist sortiert
        return l
    else:
        # Rekursionsschritt: Teile die Liste in zwei kleinere Listen auf
        erste_haelfte_sortiert = sortiere(erste_haelfte) 
        zweite_haelfte_sortiert = sortiere(zweite_haelfte) 
        
        # Zusammenfügen der sortierten kleineren Listen
        zusammengefuegte_liste = merge(erste_haelfte_sortiert, zweite_haelfte_sortiert)
        return zusammengefuegte_liste

Nun haben wir nur noch ein Problem: wie sortieren wir die beiden kleinen Teillisten? Im Endeffekt haben wir zwar nun die Größe des Problems halbiert, in dem wir die Liste halbiert haben, aber dadurch haben wir das Problem ja noch nicht gelöst. Dafür brauchen wir doch jetzt einen weiteren Sortieralgorithmus, oder?

Hier kommt die Rekursion ins Spiel: wir erinnern uns an das Prinzip der Magie der Rekursion: wir nehmen an, dass die Funktion für das kleinere Problem bereits funktioniert und rufen die Funktion mergesort rekursiv auf. Die Ergebnisse der rekursiven Aufrufe sind dann also zwei sortiere Listen, die dann von der merge Funktion vereinigt werden zum Endergebnis.


def mergesort(l):
    if (len(l) == 1): # Basisfall
        return l
    else:
        # Rekursionsschritt: Teile die Liste in zwei kleinere Listen auf
        # erste_haelfte = ...
        # zweite_haelfte = ...
        erste_haelfte_sortiert = mergesort(erste_haelfte) 
        zweite_haelfte_sortiert = mergesort(zweite_haelfte) 
        
        # Zusammenfügen der sortierten kleineren Listen
        zusammengefuegte_liste = merge(erste_haelfte_sortiert, zweite_haelfte_sortiert) 
        return zusammengefuegte_liste

Zum Schluss können wir uns noch ein paar Zeilen Code sparen, wenn wir nicht alles in Variablen zwischenspeichern und die Listen Namen etwas abkürzen:


def mergesort(l):
    if (len(l) == 1): 
        return l
    else:
        # l1 = ...
        # l2 = ...
        return merge(mergesort(l1), mergesort(l2)) 

Aufgabe

Noch ist unsere mergesort Funktion so nicht ausführbar, da die Variablen erste_haelfte und zweite_haelfte noch nicht definiert sind und die merge-Funktion fehlt. Überlege dir, wie du die Mitte der Liste findest, um sie in zwei Hälften zu teilen. Ergänze dann die Funktion im folgenden Editor so, dass die Aufteilung korrekt funktioniert. Für eine bessere Übersichtlichkeit kürzen wir den Funktionsnamen mit ms ab.

Eine Implementierung der merge-Funktion findest du im folgenden Fenster, das du aufklappen kannst, wenn du dich auf die Aufteilung der Liste konzentrieren möchtest. Kopiere die Funktion dann einfach in den Editor.

Merge-Funktion

def merge (l1, l2):
    if (len(l1) == 0):
        return l2
    else:
        if (len(l2) == 0):
            return l1
        else:
            if (l1[0] < l2[0]):
                return [l1[0]] + merge(l1[1:], l2)
            else:
                return [l2[0]] + merge(l1, l2[1:])

Wenn wir mehr als eine Funktion im Recursion Tutor stehen haben, müssen wir dem Tool sagen, welche es visualisieren soll. Dafür ersetzen wir das def bei der Funktionsdefinition durch ein dev-t (t für engl. trace, also "überwachen"). Es ist auch möglich, mehrere Funktion gleichzeitig zu visualisieren, allerdings kann dann das Diagramm unübersichtlich werden, weil es sehr verschachtelt wird.

Suche

v
100.141.3.2 Sortieren durch Aufteilen
Kopieren durch Anklicken

Rückmeldung geben