Einer der bekanntesten Algorithmen zur Sortierung ist QuickSort. Es handelt sich um einen Divide-and-Conquer-Ansatz, bei dem in jedem Schritt ein Pivot-Element aus dem Array ausgewählt wird. Anschließend wird das Array in drei Teile aufgeteilt: Alle Elemente, die kleiner als das Pivot sind, kommen nach links, die dem Pivot entsprechenden Elemente in die Mitte, und die größeren Elemente nach rechts. Dieses Vorgehen wird wiederholt, bis das gesamte Array sortiert ist.
Anders ausgedrückt, wird in jeder Iteration Folgendes getan:
Ein Pivot-Element aus dem Array wählen
Alle kleineren Elemente in den linken Teil verschieben
Alle größeren Elemente in den rechten Teil verschieben
Alle dem Pivot entsprechenden Elemente in der Mitte zusammenfassen
Dasselbe Verfahren rekursiv auf den linken und den rechten Teil anwenden
def quicksort(a):
if len(a) <= 1:
return a
pivot = a[len(a) // 2]
left = [x for x in a if x < pivot]
middle = [x for x in a if x == pivot]
right = [x for x in a if x > pivot]
return quicksort(left) + middle + quicksort(right)
Diese QuickSort-Implementierung verwendet zusätzliche Arrays für die Teile left, middle und right. Man kann sie jedoch auch in-place gestalten, indem man die Elemente mithilfe von Indizes direkt im Array umordnet:
def quicksort(a, start, end):
if start >= end: # Keine Elemente zum Sortieren
return
pivot = a[start] # Wähle ein Pivot-Element
l, r = start + 1, end # Sortiere a[start+1 ... end]
while l <= r: # Solange es noch Elemente im Bereich gibt
if a[l] < pivot: # wenn a[l] < pivot => belasse a[l] wie es ist
l += 1 # Linken Zeiger erhöhen
else: # ansonsten verschiebe a[l] ans Ende
a[l], a[r] = a[r], a[l] # Vertausche a[l] und a[r] -> verschiebe ans Ende
r -= 1 # Rechten Zeiger verringern
a[start], a[r] = a[r], a[start] # Vertausche pivot mit a[r]
quicksort(a, start, r - 1) # Sortiere a[start ... r-1]
quicksort(a, r + 1, end) # Sortiere a[r+1 ... end]
Vielleicht ist dir aufgefallen, dass das Pivot-Element in den beiden Implementierungen unterschiedlich gewählt wird. Der QuickSort-Algorithmus legt sich nicht auf eine bestimmte Wahl des Pivots fest. Es gibt verschiedene Strategien dafür, unter anderem:
Erstes Element: a[start] (wie im zweiten Beispiel gezeigt)
Letztes Element: a[end]
Mittleres Element: a[(start + end + 1) // 2]
Zufälliges Element: Beliebiges Element in a[start ... end] wählen
Median-Element: Das Median-Element des aktuellen Segments wählen (liefert stets ausgewogene Teillisten, erfordert jedoch einen aufwändigeren Implementierungsansatz)
Lass uns den Algorithmus an einigen Beispielen durchspielen:
Die durchschnittliche Laufzeit des QuickSort-Algorithmus beträgt . Je nach Wahl des Pivot-Elements kann der Algorithmus jedoch auch im schlechtesten Fall in enden.
🤔 Hast du eine Idee, bei welchem Beispiel der Algorithmus Operationen ausführen würde, wenn man immer start als Pivot nimmt?
Herausforderung
Gegeben eine Liste von n ganzen Zahlen und q Pivots, sollst du für jeden Pivot alle Elemente, die kleiner als das Pivot sind, nach links verschieben und alle, die größer als das Pivot sind, nach rechts. Die Werte, die dem Pivot entsprechen, sollen zusammenhängend bleiben (sie gehören rechts von den kleineren und links von den größeren Zahlen).
Eingabe
Die erste Zeile der Eingabe enthält eine einzelne ganze Zahl n (1 ≤ n ≤ 100 000).
Die nächste Zeile enthält eine Liste von n durch Leerzeichen getrennten ganzen Zahlen ( ≤ ≤ ).
Die dritte Zeile enthält eine einzelne ganze Zahl q (1 ≤ q ≤ 10).
Die nächste Zeile enthält eine Liste von n durch Leerzeichen getrennten ganzen Zahlen (1 ≤ ≤ n) für die Indices der Pivot-Elemente.
Ausgabe
Für jedes der q Pivots soll das Programm das Ergebnisarray nach der entsprechenden Umordnung ausgeben. Wenn es mehrere mögliche richtige Antworten gibt, darf das Programm eine davon ausgeben.