0 / 150 XP
Pivot-Neuling
🃏
⚙️
🏁
🏆

Lernstrecke · Teil 2 · Sortieren in BlueJ

QuickSort: Sortieren durch Zerlegen

BubbleSort und SelectionSort laufen in deinem BlueJ-Projekt schon. Beide brauchen aber viele Vergleiche. Heute baust du den Algorithmus ein, den auch echte Programmbibliotheken benutzen: QuickSort. Sammle unterwegs XP und Abzeichen in der Leiste oben.

✅ bubbleSort() fertig ✅ selectionSort() fertig 🆕 quickSort() ist deine Mission 🆕 neues Werkzeug: Rekursion
01

🎯 Warum noch ein Sortierverfahren?

Starte dein BlueJ-Projekt und lass bubbleSort() laufen. Schau auf den Zähler oben rechts im Fenster: Bei 8 Zahlen macht BubbleSort 28 Vergleiche. Bei 100 Zahlen wären es schon 4950. Bei einer Million Zahlen: rund 500 Milliarden. Das dauert selbst für einen Computer spürbar lange.

So arbeiten BubbleSort und SelectionSort

Sie vergleichen stur fast jede Zahl mit fast jeder anderen. Doppelt so viele Zahlen bedeutet ungefähr viermal so viel Arbeit.

So arbeitet QuickSort

Er zerlegt die Liste clever in zwei Hälften, die sich gegenseitig nie wieder anschauen müssen. Das spart enorm viele Vergleiche.

💡 Trivia: QuickSort wurde 1960 von Tony Hoare erfunden, als er versuchte, russische Wörterbuch-Einträge für ein Übersetzungsprogramm zu sortieren. Er war damals 26. Varianten von QuickSort stecken bis heute in Java, Python und C.
02

🃏 Das Pivot-Spiel

+30 XP · Abzeichen 🃏

Stell dir eine Schulklasse vor, die sich der Größe nach aufstellen soll. Eine Person wird zur Vergleichsperson erklärt, in der Informatik heißt sie Pivot. Alle anderen stellen sich links von ihr auf, wenn sie kleiner sind, sonst rechts. Danach steht das Pivot garantiert schon an seinem endgültigen Platz!

Deine Aufgabe: Verteile die Zahlen. Die erste Zahl der Liste ist das Pivot. Entscheide für jede weitere Zahl: links oder rechts?

⬅️ kleiner als Pivot
Pivot
?
größer oder gleich ➡️
Drücke auf eine Antwort, sobald die gelb markierte Zahl erscheint.
03

🌳 Zerlegen, zerlegen, zerlegen: Rekursion

+20 XP

So sieht das vollständige Zerlegen für eine Beispielliste aus. Gelb ist jeweils das Pivot. Jede Zeile zeigt eine Zerleg-Runde tiefer:

25 17 32 56 19 8 66 29 Start → 17 19 8 25 32 56 66 29 kleiner als 25 → 8 17 19 29 32 56 66 Pivot 17 → 56 66 8 17 19 25 29 32 56 66 ✅ Ergebnis →

🔁 Das neue Werkzeug: Rekursion

Bei BubbleSort hast du Schleifen benutzt: wiederhole etwas mehrmals. QuickSort braucht zusätzlich Rekursion: eine Methode ruft sich selbst auf, aber jedes Mal mit einer kleineren Aufgabe. Damit das nicht ewig weitergeht, gibt es eine Abbruchregel:

Rekursiver Fall

Der Bereich hat mehr als eine Zahl → zerlege ihn am Pivot und rufe die Methode für beide Teilbereiche erneut auf.

Abbruchfall

Der Bereich hat nur noch eine Zahl (oder gar keine) → nichts zu tun, eine einzelne Zahl ist immer sortiert.

🧪 Schnellcheck: Was passiert, wenn man bei einer Rekursion den Abbruchfall vergisst?
🤯 Trivia: Der Absturz bei endloser Rekursion heißt Stack Overflow, der Aufruf-Stapel läuft über. Die berühmte Programmierer-Webseite stackoverflow.com ist nach genau diesem Fehler benannt.
04

⚙️ Der Zeiger-Simulator

+20 XP · Abzeichen ⚙️

In deinem Java-Programm steckt die Liste in einem einzigen Feld zahlen. Es gibt also keine echten Teillisten, das Zerlegen passiert durch Tauschen innerhalb des Feldes. Dafür laufen zwei Zeiger aufeinander zu:

🔴 Zeiger links und rechts

links wandert nach rechts, bis er eine Zahl findet, die nicht kleiner als das Pivot ist. rechts wandert nach links, bis er eine Zahl findet, die nicht größer ist. Beide Fundstücke stehen falsch → tauschen!

🟡 Das Pivot

Der gelbe Balken zeigt, wo das Pivot am Anfang des Bereichs stand. Sobald sich die Zeiger gekreuzt haben, ist der Bereich zerlegt und die Rekursion übernimmt die beiden Teile.

Vergleiche: 0 Tausche: 0 Rekursionstiefe: 0
Drücke auf „Ein Schritt“ und beobachte die Zeiger 🔴 und das Pivot 🟡.
🤔 Beobachtungsaufgabe (für Schnelle)

Lass den Simulator mehrmals mit neuen Zahlen laufen und beantworte in deinem Heft:

a) Woran erkennst du im Ablauf, dass ein neuer, kleinerer Bereich begonnen wurde?
b) Wie tief wurde die Rekursion maximal? Wovon hängt das ab?
c) Warum dürfen die Zeiger beim Pivot selbst stehen bleiben, ohne dass etwas kaputtgeht?

05

🏁 Das Wettrennen: BubbleSort gegen QuickSort

+20 XP · Abzeichen 🏁

Beide Verfahren bekommen exakt dieselben 12 Zahlen. Wer zuerst fertig ist, gewinnt. Tipp: Schau am Ende auf die Vergleichszähler, nicht nur auf die Ziellinie.

🫧 BubbleSort

⚡ QuickSort

Bereit für den Start.
06

☕ Von Python zu Java übersetzen

+20 XP · Abzeichen ☕

Auf inf-schule hast du QuickSort mit einer Liste und den Indexgrenzen anfang und ende in Python gesehen. Genau diese Variante baust du jetzt in Java nach, denn sie passt perfekt zu deinem Feld zahlen:

📄 Python-Vorlage (nach inf-schule.de)
def quicksort(L, anfang, ende):
    if L != []:
        pivot = L[anfang]
        links = anfang
        rechts = ende
        while links <= rechts:
            while L[links] < pivot:
                links = links + 1
            while L[rechts] > pivot:
                rechts = rechts - 1
            if links <= rechts:
                if links < rechts:
                    h = L[links]
                    L[links] = L[rechts]
                    L[rechts] = h
                links = links + 1
                rechts = rechts - 1
        if anfang < rechts:
            L = quicksort(L, anfang, rechts)
        if links < ende:
            L = quicksort(L, links, ende)
    return L

🔁 Das Übersetzungs-Wörterbuch

Vieles kennst du schon aus deiner Klasse Main. Die wichtigsten Übersetzungen:

Python (inf-schule)Java (dein BlueJ-Projekt)Bemerkung
def quicksort(L, anfang, ende):private void sortiereBereich(int anfang, int ende)Das Feld zahlen gehört zum Objekt, es muss nicht übergeben oder zurückgegeben werden.
pivot = L[anfang]pivot = zahlen[anfang];Variablen vorher deklarieren: int pivot;
while links <= rechts:while(links <= rechts) { ... }Java: Klammern und geschweifte Klammern statt Einrückung.
h = L[links] usw. (3 Zeilen)tausche(links, rechts);Den Dreieckstausch hast du schon als fertige Methode! 🎉
L = quicksort(L, anfang, rechts)sortiereBereich(anfang, rechts);Selbstaufruf der Methode = Rekursion.
🧪 Übersetzer-Quiz: Wie lautet die Zeile while L[rechts] > pivot: in deinem Java-Programm?
07

🧩 Programmierauftrag: quickSort() einbauen

Du erweiterst deine bestehende Klasse Main. Nichts löschen! BubbleSort und SelectionSort bleiben drin, du brauchst sie später noch für den Vergleich.

Öffne dein BlueJ-Projekt aus der letzten Stunde und die Klasse Main.
Füge die beiden Methoden aus der Vorlage unten unterhalb von selectionSort() ein (vor der Methode tausche).
Vervollständige sortiereBereich(...) mit Hilfe der TODOs, des Python-Codes und des Übersetzungs-Wörterbuchs.
Übersetzen mit Compile. Fehler? Klammern zählen, Semikolons prüfen, Variablen deklariert?
Rechtsklick auf dein Objekt → neueZahlen(), dann quickSort(). Beobachte Pivot 🟡 und Zeiger 🔴.
Funktioniert es? Dann rufe neueZahlen() und danach bubbleSort() auf und vergleiche die Zählerstände. 📊
📄 Vorlage: in die Klasse Main einfügen
    /**
     * QuickSort: Startdienst.
     * Setzt die Zaehler zurueck und sortiert das ganze Feld
     * von Index 0 bis Index anzahl - 1.
     */
    public void quickSort()
    {
        vergleiche = 0;
        tausche = 0;
        zeichne("QuickSort", -1, -1, -1, -1,
                "Zerlegen mit Pivot. Los geht es!");
        uhr.warte(900);

        sortiereBereich(0, anzahl - 1);

        zeichne("QuickSort fertig", -1, -1, -1, 0,
                "Fertig! Rufe neueZahlen() und bubbleSort() auf und vergleiche.");
    }

    /**
     * AUFGABE: Sortiert den Bereich von anfang bis ende mit QuickSort.
     * Diese Methode ruft sich selbst auf (Rekursion).
     */
    private void sortiereBereich(int anfang, int ende)
    {
        int pivot;
        int links;
        int rechts;

        // TODO 1: Pivot und Zeiger setzen
        //   pivot  = die Zahl an der Stelle anfang
        //   links  = anfang
        //   rechts = ende

        // TODO 2: Zerlegen, solange links <= rechts ist:
        //   a) links wandert nach rechts (links = links + 1),
        //      solange zahlen[links] kleiner als pivot ist
        //   b) rechts wandert nach links (rechts = rechts - 1),
        //      solange zahlen[rechts] groesser als pivot ist
        //   c) wenn links <= rechts:
        //        wenn links < rechts: tausche(links, rechts)
        //        danach: links um 1 erhoehen, rechts um 1 verringern

        // TODO 3: Rekursion fuer die beiden Teilbereiche:
        //   wenn anfang < rechts: sortiereBereich(anfang, rechts)
        //   wenn links  < ende:   sortiereBereich(links, ende)

        // Tipp fuer die Anzeige zwischendurch:
        //   zeichne("QuickSort", links, rechts, anfang, -1, "...");
        //   uhr.warte(700);
        // Der dritte Wert (anfang) faerbt das Pivot gelb,
        // links und rechts werden rot markiert.
    }
⚠️ Beliebte Stolperfallen: Die inneren while-Schleifen brauchen ein < bzw. >, nicht <= oder >=, sonst rennen die Zeiger aus dem Feld (Fehlermeldung ArrayIndexOutOfBoundsException). Und: Bei der Rekursion unbedingt sortiereBereich aufrufen, nicht quickSort, sonst beginnt alles von vorn.
08

🛟 Gestufte Hilfen

Erst nachdenken, dann Simulator anschauen, dann Hilfe 1, dann Hilfe 2 ... Öffne immer nur so viel, wie du wirklich brauchst.

🥉 Hilfe 1: TODO 1, Pivot und Zeiger
pivot  = zahlen[anfang];
links  = anfang;
rechts = ende;

zeichne("QuickSort: neuer Bereich", -1, -1, anfang, -1,
        "Pivot ist " + pivot + " (gelb).");
uhr.warte(800);

Das Pivot ist eine Zahl (ein Wert), links und rechts sind Stellen (Indizes). Nicht verwechseln!

🥈 Hilfe 2: TODO 2, die Zerlege-Schleife
while(links <= rechts)
{
    while(zahlen[links] < pivot)
    {
        links = links + 1;
    }
    while(zahlen[rechts] > pivot)
    {
        rechts = rechts - 1;
    }

    if(links <= rechts)
    {
        if(links < rechts)
        {
            tausche(links, rechts);
        }
        links = links + 1;
        rechts = rechts - 1;
    }
}

Baue zwischen den Schritten zeichne(...) und uhr.warte(700) ein, damit du den Ablauf siehst. Vergiss die Zähler nicht: vergleiche = vergleiche + 1; in den inneren Schleifen, tausche = tausche + 1; direkt nach jedem Tausch.

🥇 Hilfe 3: TODO 3, die Rekursion
if(anfang < rechts)
{
    sortiereBereich(anfang, rechts);
}
if(links < ende)
{
    sortiereBereich(links, ende);
}

Die beiden if-Abfragen sind der Abbruchfall: Ist ein Teilbereich leer oder hat nur eine Zahl, wird gar nicht mehr aufgerufen. Die Rekursion endet von selbst.

🚨 Notfall-Hilfe: komplette Musterlösung (wirklich nur im Notfall!)
📄 sortiereBereich, vollständig mit Anzeige und Zählern
    private void sortiereBereich(int anfang, int ende)
    {
        int pivot;
        int links;
        int rechts;

        pivot  = zahlen[anfang];
        links  = anfang;
        rechts = ende;

        zeichne("QuickSort: neuer Bereich", -1, -1, anfang, -1,
                "Bereich " + anfang + " bis " + ende + ". Pivot ist " + pivot + ".");
        uhr.warte(800);

        while(links <= rechts)
        {
            while(zahlen[links] < pivot)
            {
                vergleiche = vergleiche + 1;
                links = links + 1;
            }
            vergleiche = vergleiche + 1;

            while(zahlen[rechts] > pivot)
            {
                vergleiche = vergleiche + 1;
                rechts = rechts - 1;
            }
            vergleiche = vergleiche + 1;

            zeichne("QuickSort: Zeiger gestoppt", links, rechts, anfang, -1,
                    "links und rechts haben passende Zahlen gefunden.");
            uhr.warte(700);

            if(links <= rechts)
            {
                if(links < rechts)
                {
                    tausche(links, rechts);
                    tausche = tausche + 1;
                    zeichne("QuickSort: Tausch", links, rechts, anfang, -1,
                            "Das falsche Paar wurde getauscht.");
                    uhr.warte(700);
                }
                links = links + 1;
                rechts = rechts - 1;
            }
        }

        if(anfang < rechts)
        {
            sortiereBereich(anfang, rechts);
        }
        if(links < ende)
        {
            sortiereBereich(links, ende);
        }
    }

Wenn du die Lösung kopierst: Gehe sie Zeile für Zeile durch und schreibe an jeden Block einen eigenen Kommentar, was er tut. In der nächsten Stunde erklärst du den Ablauf ohne Spickzettel. 😉

09

🚀 Bonus-Missionen

Fertig und alles läuft? Such dir eine Mission aus. Reihenfolge egal, Schwierigkeit steigt mit den Sternen.

⭐ Mission A: Der große Zahlenvergleich

Erstelle eine Tabelle im Heft: Lass bubbleSort(), selectionSort() und quickSort() je dreimal mit neuen Zahlen laufen und notiere Vergleiche und Tausche. Welches Verfahren gewinnt in welcher Kategorie?

⭐ Mission B: Mehr Balken!

Setze im Konstruktor anzahl = 12;. Damit alles ins Fenster passt, musst du auch balkenBreite und luecke anpassen. Probiere passende Werte aus. Wird der Vorsprung von QuickSort größer?

⭐⭐ Mission C: Der fiese Spezialfall

Schreibe eine Methode sortierteZahlen(), die das Feld mit bereits aufsteigend sortierten Werten füllt (z. B. 10, 20, 30, ...). Lass dann quickSort() laufen. Beobachte die Zerlegung: Warum ist das erste Element als Pivot hier eine schlechte Wahl?

⭐⭐⭐ Mission D: Das Zufalls-Pivot

Profis wählen das Pivot zufällig: Ziehe mit rechner.ganzeZufallsZahl(anfang, ende) eine Stelle, tausche sie zuerst an die Position anfang und mache dann normal weiter. Hilft das beim Spezialfall aus Mission C? Teste es!

10

🏆 Abschluss-Check

+40 XP · Abzeichen 🏆

Hake ehrlich ab. Erst wenn alles sitzt, gibt es den Meistertitel.

🎓 Meisterfrage: Eine Liste mit 1000 Zahlen. Ungefähr wie oft wird sie bei QuickSort halbiert, bis nur noch einzelne Zahlen übrig sind?

Tipp: 1000 → 500 → 250 → 125 → ... Zähle die Halbierungen.