← Startseite

Labor 3: Sortieren & Komplexität — Algorithmen verstehen

Fortgeschrittene Algorithmen und Programmierung • HTW Berlin
Dauer: 180 Minuten (3 Stunden) • deckt Vorlesungen 4 & 5 ab

🧠 Worum geht es?

In Vorlesung 4 habt ihr drei klassische Sortier-Algorithmen kennengelernt: Bubble, Selection und Insertion Sort. In Vorlesung 5 kam dann die Sprache, mit der wir sie vergleichen: die Big-O-Notation.

Heute setzt ihr beides um — mit Fokus auf Verstehen, nicht nur Tippen:

💻 Sprachen-Hinweis — bitte einmal lesen

Im Kurs ist C unsere gemeinsame Sprache, und alle Musterlösungen sind in C. Das ist die Sprache, in der Algorithmen am wenigsten „verzaubert" sind — kein verstecktes Sortieren im Hintergrund, keine Magie.

Aber: wenn ihr sicher in einer anderen Sprache seid — Arduino C++, Python, JavaScript, Rust — dann macht die Aufgaben dort. Algorithmen sind sprachunabhängig.

Wenn ihr in Klausur oder Bachelorarbeit Algorithmen erklären müsst, fragt niemand nach printf-Syntax. Es geht um das Wieso und Wie schnell.

🎯 Lernziele

🃏 Sortieren ausführen

Bubble, Selection, Insertion auf Papier nachvollziehen — Schritt für Schritt

⌨️ Selber implementieren

Alle drei Sortier-Algorithmen aus dem Pseudocode in Code übersetzen

🔢 Zählen statt schätzen

Vergleiche und Vertauschungen mitzählen — der Realitätscheck zur Big-O-Theorie

📈 O(n²) live sehen

Laufzeit für n = 100, 1 000, 5 000 messen — und das Wachstum mit den Augen sehen

🔍 Big-O ableiten

Aus einem Code-Stück die Komplexitätsklasse erkennen

ℹ️ Vorgehen pro Aufgabe

  1. Problem lesen — was ist die Eingabe, was die Ausgabe?
  2. Algorithmus auf Papier — kleines Beispiel von Hand durchspielen
  3. Pseudocode notieren — bevor ihr in eine Sprache wechselt
  4. Implementieren — in C oder einer anderen Sprache, die ihr sicher beherrscht
  5. Testen + Operationen zählen — wie viele Vergleiche, wie viele Swaps?
  6. Mit Musterlösung vergleichen (nur C) — die Passwörter sind ganz unten im Sammel-Block frei zugänglich. Bitte erst selbst probieren!

1 Sortieren von Hand — Bubble, Selection, Insertion auf Papier ⏱️ 25 Min VL 4

Ziel: Den Unterschied zwischen den drei Algorithmen spüren — bevor ihr ihn programmiert.

Aufgabenstellung:

Nehmt diese Karten:

7
3
9
1
5
2

Sortiert sie aufsteigend — drei Mal, einmal mit jedem Algorithmus. Schreibt für jede Methode jeden Schritt auf Papier mit:

  • Der Zustand des Arrays nach jedem Schritt
  • Welche zwei Elemente verglichen wurden
  • Ob getauscht wurde (✓) oder nicht (✗)
Beispiel — Bubble Sort, erste Iteration:
[7, 3, 9, 1, 5, 2] Vergleich 7 vs 3 → tauschen ✓ → [3, 7, 9, 1, 5, 2] Vergleich 7 vs 9 → nicht tauschen ✗ → [3, 7, 9, 1, 5, 2] Vergleich 9 vs 1 → tauschen ✓ → [3, 7, 1, 9, 5, 2] …

Am Ende füllt diese Tabelle aus:

AlgorithmusVergleiche gesamtVertauschungen gesamtWie hat es sich „angefühlt"?
Bubble Sort__________________
Selection Sort__________________
Insertion Sort__________________
💡 Was ihr feststellen werdet: Selection Sort braucht weniger Swaps als Bubble (max. n-1), aber gleich viele Vergleiche. Insertion Sort ist bei „fast schon sortierten" Daten am schnellsten.
🤔 Diskussion mit Sitznachbar: wenn ein Swap teurer wäre als ein Vergleich (z.B. weil ihr riesige Objekte tauscht) — welcher Algorithmus wäre dann am besten? Und wenn umgekehrt?
🔒 Musterlösung — vollständige Spurführung für [7, 3, 9, 1, 5, 2]
Falsches Passwort!

🫧 Bubble Sort

Jeder Durchgang: vergleiche jedes Nachbar-Paar und tausche, falls links > rechts. Nach jedem Durchgang steht das größte Element ganz hinten.

Start: [7, 3, 9, 1, 5, 2] Durchgang 1 (5 Vergleiche, 4 Swaps): 7,3 → tauschen [3, 7, 9, 1, 5, 2] 7,9 → nicht [3, 7, 9, 1, 5, 2] 9,1 → tauschen [3, 7, 1, 9, 5, 2] 9,5 → tauschen [3, 7, 1, 5, 9, 2] 9,2 → tauschen [3, 7, 1, 5, 2, 9] ← die 9 ist an ihrem Platz Durchgang 2 (4 Vergleiche, 3 Swaps): 3,7 → nicht [3, 7, 1, 5, 2, 9] 7,1 → tauschen [3, 1, 7, 5, 2, 9] 7,5 → tauschen [3, 1, 5, 7, 2, 9] 7,2 → tauschen [3, 1, 5, 2, 7, 9] ← die 7 sitzt Durchgang 3 (3 Vergleiche, 2 Swaps): 3,1 → tauschen [1, 3, 5, 2, 7, 9] 3,5 → nicht [1, 3, 5, 2, 7, 9] 5,2 → tauschen [1, 3, 2, 5, 7, 9] ← die 5 sitzt Durchgang 4 (2 Vergleiche, 1 Swap): 1,3 → nicht [1, 3, 2, 5, 7, 9] 3,2 → tauschen [1, 2, 3, 5, 7, 9] ← die 3 sitzt Durchgang 5 (1 Vergleich, 0 Swaps): 1,2 → nicht [1, 2, 3, 5, 7, 9] ← fertig ✓ GESAMT: 5+4+3+2+1 = 15 Vergleiche · 4+3+2+1+0 = 10 Swaps

🎯 Selection Sort

Jeder Durchgang: suche das Minimum im unsortierten Teil — tausche es ans vordere Ende. Maximal n-1 Swaps insgesamt.

Start: [7, 3, 9, 1, 5, 2] Durchgang i=0 (5 Vergleiche): Minimum im Bereich [0..5] gesucht Kandidat startet bei 7 3 < 7 → neuer Kandidat 3 9 > 3 → nicht 1 < 3 → neuer Kandidat 1 5 > 1 → nicht 2 > 1 → nicht Minimum = 1 (Position 3). Tausche mit Position 0: [1, 3, 9, 7, 5, 2] Swap 1 Durchgang i=1 (4 Vergleiche): Minimum im Bereich [1..5] = 2 (Position 5) Tausche Position 1 mit Position 5: [1, 2, 9, 7, 5, 3] Swap 2 Durchgang i=2 (3 Vergleiche): Minimum im Bereich [2..5] = 3 (Position 5) Tausche Position 2 mit Position 5: [1, 2, 3, 7, 5, 9] Swap 3 Durchgang i=3 (2 Vergleiche): Minimum im Bereich [3..5] = 5 (Position 4) Tausche Position 3 mit Position 4: [1, 2, 3, 5, 7, 9] Swap 4 Durchgang i=4 (1 Vergleich): Minimum bei Position 4 (=i), kein Swap nötig. GESAMT: 5+4+3+2+1 = 15 Vergleiche · 4 Swaps (immer ≤ n-1 = 5)

🃏 Insertion Sort

Wie Karten sortieren auf der Hand: nimm die nächste Karte, vergleiche von rechts nach links mit den schon sortierten und schiebe größere nach rechts.

Start (linke Karte zählt schon als sortiert): [7, 3, 9, 1, 5, 2] i=1: nimm 3 in die Hand [7, _, 9, 1, 5, 2] Vergleich 7 > 3 ✓ → 7 nach rechts schieben [_, 7, 9, 1, 5, 2] Anfang erreicht → 3 vorn einfügen [3, 7, 9, 1, 5, 2] (1 Vergleich, 1 Shift) i=2: nimm 9 [3, 7, _, 1, 5, 2] Vergleich 7 > 9 ✗ → an Ort einfügen [3, 7, 9, 1, 5, 2] (1 Vergleich, 0 Shifts) i=3: nimm 1 [3, 7, 9, _, 5, 2] Vergleich 9 > 1 ✓ → schieben [3, 7, _, 9, 5, 2] Vergleich 7 > 1 ✓ → schieben [3, _, 7, 9, 5, 2] Vergleich 3 > 1 ✓ → schieben [_, 3, 7, 9, 5, 2] Anfang erreicht → 1 einfügen [1, 3, 7, 9, 5, 2] (3 Vergleiche, 3 Shifts) i=4: nimm 5 [1, 3, 7, 9, _, 2] Vergleich 9 > 5 ✓ → schieben [1, 3, 7, _, 9, 2] Vergleich 7 > 5 ✓ → schieben [1, 3, _, 7, 9, 2] Vergleich 3 > 5 ✗ → 5 hier einfügen [1, 3, 5, 7, 9, 2] (3 Vergleiche, 2 Shifts) i=5: nimm 2 [1, 3, 5, 7, 9, _] Vergleich 9 > 2 ✓ → schieben [1, 3, 5, 7, _, 9] Vergleich 7 > 2 ✓ → schieben [1, 3, 5, _, 7, 9] Vergleich 5 > 2 ✓ → schieben [1, 3, _, 5, 7, 9] Vergleich 3 > 2 ✓ → schieben [1, _, 3, 5, 7, 9] Vergleich 1 > 2 ✗ → 2 hier einfügen [1, 2, 3, 5, 7, 9] (5 Vergleiche, 4 Shifts) GESAMT: 1+1+3+3+5 = 13 Vergleiche · 1+0+3+2+4 = 10 Shifts

📊 Auswertungs-Tabelle

AlgorithmusVergleicheVertauschungen / ShiftsBeobachtung
Bubble Sort1510 Swapsviele Swaps — jede Nachbar-Verletzung kostet einen
Selection Sort154 Swapsgleiche Vergleiche, aber deutlich weniger Swaps (max. n-1)
Insertion Sort1310 Shiftsetwas weniger Vergleiche; Shifts sind „halbe" Swaps (nur Lesen+Schreiben in eine Richtung)

Lehre daraus: die Anzahl der Vergleiche ist für alle drei ähnlich (alle O(n²) — 15 ≈ 6²/2). Was sie unterscheidet, ist was sonst noch passiert: viele Swaps (Bubble), wenige große Swaps (Selection), viele kleine Shifts (Insertion). Im Computer kostet das unterschiedlich — daher messen wir das in Aufgabe 5.

Hinweis: Kleine Abweichungen in eurer Zählung sind OK — bei Insertion zählt manche Lehrbücher den letzten „nicht-tauschen"-Vergleich nicht mit. Das Wichtigste ist das Verhältnis zwischen den drei Algorithmen.

2 Bubble Sort implementieren — und mitzählen ⏱️ 30 Min VL 4 + VL 5

Ziel: den ersten Sortier-Algorithmus selbst schreiben — und live zählen, wie viele Vergleiche und Vertauschungen er macht.

Pseudocode (aus VL 4):

function bubbleSort(a, n): for i = 0 to n-2: for j = 0 to n-2-i: if a[j] > a[j+1]: tausche(a[j], a[j+1])

Aufgabe:

  1. Implementiere Bubble Sort in deiner Sprache.
  2. Füge zwei Zähler hinzu: vergleiche und swaps.
  3. Sortiere das Array [7, 3, 9, 1, 5, 2] und gib die Zähler am Ende aus.
  4. Sortiere das Array [1, 2, 3, 4, 5, 6] (schon sortiert) und gib die Zähler aus.
⚠️ Wichtig: ihr braucht hier keine Optimierung — kein early-exit beim ersten swap-freien Durchgang. Wir wollen den „rohen" Big-O-Worst-Case sehen.

Schritt — Vergleich mit der Theorie:

Aus VL 5: Bubble Sort macht im Worst Case ca. n²/2 Vergleiche. Bei n = 6: ~ 18 Vergleiche.

Passt das zu eurer Messung? Wenn nicht — wo ist der Unterschied?

🤔 Denkfrage: warum heißt es eigentlich „Bubble"? — Tipp: stellt euch ein großes Element vor. Es blubbert in jeder Iteration eine Position nach hinten.
🔒 Musterlösung in C
Falsches Passwort!
#include <stdio.h>

// Globale Zähler — einfacher als Pointer-Parameter
int vergleiche = 0;
int swaps = 0;

void bubble_sort(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            vergleiche = vergleiche + 1;
            if (a[j] > a[j + 1]) {
                int t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
                swaps = swaps + 1;
            }
        }
    }
}

int main(void) {
    int a[] = {7, 3, 9, 1, 5, 2};
    int n = 6;

    bubble_sort(a, n);

    printf("Sortiert: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");
    printf("Vergleiche: %d, Swaps: %d\n", vergleiche, swaps);
    return 0;
}

Erwartete Zähler für [7,3,9,1,5,2]: 15 Vergleiche, 6 Swaps.
Für sortiertes [1,2,3,4,5,6]: 15 Vergleiche, 0 Swaps — Bubble Sort schaut immer alles an.

Tipp: Globale Variablen sind hier OK für Zähler. Bei größeren Programmen würde man sie kapseln (kommt später).

3 Selection Sort — die Strategie „immer das Minimum" ⏱️ 30 Min VL 4 + VL 5

Ziel: Selection Sort selbst schreiben — und beobachten, warum er weniger Swaps braucht als Bubble.

Pseudocode (aus VL 4):

function selectionSort(a, n): for i = 0 to n-2: min_index = i for j = i+1 to n-1: if a[j] < a[min_index]: min_index = j tausche(a[i], a[min_index])

Aufgabe:

  1. Implementiere Selection Sort in deiner Sprache.
  2. Zähle wieder vergleiche und swaps.
  3. Sortiere wieder [7, 3, 9, 1, 5, 2] und gib die Zähler aus.
  4. Vergleiche direkt mit Bubble Sort: welcher Zähler ist niedriger? Warum?
💡 Beobachtung: Selection Sort macht maximal n-1 Swaps, egal wie unsortiert die Daten sind. Bubble kann bei umgekehrt sortierten Daten bis zu n(n-1)/2 Swaps brauchen.

Daher: wenn ein Swap teuer ist (z.B. weil ihr riesige Datensätze tauscht statt nur kleiner Integers), ist Selection in der Praxis oft schneller — obwohl beide formal O(n²) sind.
🤔 Denkfrage: Selection Sort macht immer die gleiche Anzahl Vergleiche — egal ob die Eingabe schon sortiert ist oder nicht. Warum? Was bedeutet das für „Best Case"?
🔒 Musterlösung in C
Falsches Passwort!
#include <stdio.h>

int vergleiche = 0;
int swaps = 0;

void selection_sort(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int min_idx = i;
        for (int j = i + 1; j < n; j++) {
            vergleiche = vergleiche + 1;
            if (a[j] < a[min_idx]) {
                min_idx = j;
            }
        }
        // Nur tauschen, wenn ein kleineres Element gefunden wurde
        if (min_idx != i) {
            int t = a[i];
            a[i] = a[min_idx];
            a[min_idx] = t;
            swaps = swaps + 1;
        }
    }
}

int main(void) {
    int a[] = {7, 3, 9, 1, 5, 2};
    int n = 6;

    selection_sort(a, n);

    printf("Sortiert: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");
    printf("Vergleiche: %d, Swaps: %d\n", vergleiche, swaps);
    return 0;
}

Erwartete Zähler für [7,3,9,1,5,2]: 15 Vergleiche, ≤ 5 Swaps (immer max. n-1).
Bubble hatte 6 Swaps — bei größeren Arrays wird der Unterschied dramatisch.

4 Insertion Sort — Best vs. Worst Case erleben ⏱️ 30 Min VL 4 + VL 5

Ziel: Insertion Sort schreiben — und sehen, warum er bei fast-sortierten Daten unschlagbar ist.

Pseudocode (aus VL 4):

function insertionSort(a, n): for i = 1 to n-1: wert = a[i] j = i - 1 while j >= 0 and a[j] > wert: a[j+1] = a[j] j = j - 1 a[j+1] = wert

Aufgabe:

  1. Implementiere Insertion Sort in deiner Sprache.
  2. Zähle wieder Vergleiche und Verschiebungen (statt Swaps).
  3. Lass den Algorithmus über drei Eingaben laufen und notiere die Zähler:
    • Zufällig: [7, 3, 9, 1, 5, 2]
    • Schon sortiert: [1, 2, 3, 4, 5, 6]Best Case
    • Umgekehrt sortiert: [6, 5, 4, 3, 2, 1]Worst Case
💡 Was ihr sehen werdet: beim schon sortierten Array braucht Insertion Sort nur n-1 Vergleiche und 0 Verschiebungen — das ist die O(n)-Best-Case-Leistung. Bei umgekehrter Eingabe explodiert es auf O(n²).

Daher: TimSort (Python, Java) baut auf Insertion Sort auf — viele reale Datenmengen sind „fast sortiert", und genau dort glänzt Insertion.
🤔 Denkfrage: stellt euch vor, ihr bekommt jede Minute ein neues Element zu eurem sortierten Array dazu (z.B. ein Live-Score-Board). Welcher der drei Sortier-Algorithmen wäre dafür ideal?
🔒 Musterlösung in C
Falsches Passwort!
#include <stdio.h>

int vergleiche = 0;
int shifts = 0;

void insertion_sort(int a[], int n) {
    for (int i = 1; i < n; i++) {
        int wert = a[i];
        int j = i - 1;
        // Schiebe alle größeren Elemente nach rechts
        while (j >= 0 && a[j] > wert) {
            vergleiche = vergleiche + 1;
            a[j + 1] = a[j];
            shifts = shifts + 1;
            j = j - 1;
        }
        // Der letzte Vergleich, der zum Abbruch geführt hat, zählt auch
        if (j >= 0) {
            vergleiche = vergleiche + 1;
        }
        a[j + 1] = wert;
    }
}

void test(int a[], int n, const char *name) {
    vergleiche = 0;
    shifts = 0;
    insertion_sort(a, n);
    printf("[%s] Vergleiche: %d, Shifts: %d\n", name, vergleiche, shifts);
}

int main(void) {
    int zufall[6]    = {7, 3, 9, 1, 5, 2};
    int sortiert[6]  = {1, 2, 3, 4, 5, 6};
    int umgekehrt[6] = {6, 5, 4, 3, 2, 1};

    test(zufall,    6, "zufaellig");
    test(sortiert,  6, "sortiert");
    test(umgekehrt, 6, "umgekehrt");

    return 0;
}

Erwartete Ausgabe (etwa):

[zufaellig]  Vergleiche: 10, Shifts: 9
[sortiert]   Vergleiche: 5,  Shifts: 0    ← O(n) Best Case!
[umgekehrt]  Vergleiche: 15, Shifts: 15   ← O(n²) Worst Case

Kleine Abweichungen sind OK — der entscheidende Punkt ist das Verhältnis zwischen den drei Fällen.

5 Benchmark — O(n²) mit eigenen Augen sehen ⏱️ 35 Min VL 5

Ziel: Big-O ist Theorie. Heute macht ihr daraus eine Beobachtung: messt die Laufzeit, seht wie sie wächst.

Aufgabe:

  1. Generiert zufällige Arrays in drei Größen: n = 100, 1 000, 5 000.
  2. Messt für jede Größe und jeden Algorithmus die Laufzeit:
    • Bubble Sort
    • Selection Sort
    • Insertion Sort
  3. Tragt eure Werte in diese Tabelle ein:
nBubbleSelectionInsertionFaktor (n×10)
100______ ms______ ms______ ms
1 000______ ms______ ms______ mserwartet ~ 100×
5 000______ ms______ ms______ mserwartet ~ 25×

Hinweis zur Zeitmessung in C:

#include <time.h>

clock_t start = clock();
// ... Algorithmus ausführen ...
clock_t ende = clock();
double ms = ((double)(ende - start)) / CLOCKS_PER_SEC * 1000.0;
printf("Dauer: %.2f ms\n", ms);

In Python: time.perf_counter() · JavaScript: performance.now() · Arduino: millis() oder micros().

💡 Was ihr sehen werdet: wenn ihr n verzehnfacht, wird die Laufzeit etwa 100× länger — nicht 10×. Das ist O(n²) in Aktion. Bei n = 50 000 würden alle drei Algorithmen mehrere Sekunden brauchen. Bei n = 1 Mio. — Stunden.
⚠️ Mess-Fallen:
  • Erste Messung ist oft langsamer (Cache leer) — macht jede Messung 3× und nehmt den Median.
  • Benutzt für alle Algorithmen die gleichen zufälligen Eingaben — sonst vergleicht ihr Äpfel mit Birnen.
  • Bei sehr kleinen n (z.B. 100) sind die Messungen ungenau — kann auch 0 ms anzeigen. Daher mindestens 1 000 als Untergrenze.
🔒 Musterlösung in C — komplette Benchmark-Skelett
Falsches Passwort!
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// Maximale Größe — feste Arrays statt malloc
#define MAX_N 5000

// Drei Sortier-Funktionen, jede mit eigenem Array
void bubble_sort(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (a[j] > a[j + 1]) {
                int t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
            }
        }
    }
}

void selection_sort(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int min_idx = i;
        for (int j = i + 1; j < n; j++) {
            if (a[j] < a[min_idx]) min_idx = j;
        }
        int t = a[i];
        a[i] = a[min_idx];
        a[min_idx] = t;
    }
}

void insertion_sort(int a[], int n) {
    for (int i = 1; i < n; i++) {
        int wert = a[i];
        int j = i - 1;
        while (j >= 0 && a[j] > wert) {
            a[j + 1] = a[j];
            j = j - 1;
        }
        a[j + 1] = wert;
    }
}

// Füllt Array mit den gleichen Pseudo-Zufallszahlen
// (jeder Aufruf gibt die selbe Reihenfolge - für fairen Vergleich)
void fuelle_array(int a[], int n) {
    srand(42);  // gleiche Startzahl = gleiche Reihenfolge
    for (int i = 0; i < n; i++) {
        a[i] = rand() % 10000;
    }
}

int main(void) {
    int groessen[3] = {100, 1000, 5000};
    int daten[MAX_N];

    for (int g = 0; g < 3; g++) {
        int n = groessen[g];
        printf("n = %d:\n", n);

        // Bubble Sort
        fuelle_array(daten, n);
        clock_t start = clock();
        bubble_sort(daten, n);
        clock_t ende = clock();
        double ms = (double)(ende - start) / CLOCKS_PER_SEC * 1000.0;
        printf("  Bubble:    %.2f ms\n", ms);

        // Selection Sort
        fuelle_array(daten, n);
        start = clock();
        selection_sort(daten, n);
        ende = clock();
        ms = (double)(ende - start) / CLOCKS_PER_SEC * 1000.0;
        printf("  Selection: %.2f ms\n", ms);

        // Insertion Sort
        fuelle_array(daten, n);
        start = clock();
        insertion_sort(daten, n);
        ende = clock();
        ms = (double)(ende - start) / CLOCKS_PER_SEC * 1000.0;
        printf("  Insertion: %.2f ms\n", ms);
    }
    return 0;
}

Beobachtung: Insertion ist typisch ~ 2× schneller als Bubble (weniger Schreibzugriffe). Selection liegt dazwischen. Aber alle drei skalieren mit n² — Faktor 100, wenn n verzehnfacht.

Hinweis: wir nutzen srand(42) mit fester Startzahl, damit alle drei Algorithmen die gleichen Daten sortieren — sonst wäre der Vergleich unfair.

6 Big-O bestimmen — kleine Theorie-Übung ⏱️ 20 Min VL 5

Ziel: Code lesen und ohne Ausführen die Komplexitätsklasse erkennen.

Aufgabe:

Notiert für jeden Code-Schnipsel die Big-O-Komplexität als Funktion von n. Begründet kurz (1 Satz).

Schnipsel A:

int summe = 0;
for (int i = 0; i < n; i++) {
    summe += a[i];
}

O( ______ ) — Begründung: _______________________________

Schnipsel B:

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        printf("%d ", i + j);
    }
}

O( ______ ) — Begründung: _______________________________

Schnipsel C:

int x = 1;
while (x < n) {
    x = x * 2;
}

O( ______ ) — Begründung: _______________________________

Schnipsel D:

for (int i = 0; i < n; i++) {
    int x = 1;
    while (x < n) {
        x = x * 2;
    }
}

O( ______ ) — Begründung: _______________________________

Schnipsel E:

int gefunden = 0;
for (int i = 0; i < n && !gefunden; i++) {
    if (a[i] == zielwert) gefunden = 1;
}

O( ______ ) im Worst Case — Begründung: _______________________________

💡 Spickzettel aus VL 5:
  • Einfache Schleife über n → O(n)
  • Schleife in Schleife über n → O(n²)
  • Variable, die sich verdoppelt oder halbiert → log n
  • Schleife mit log-Verhalten + äußere n-Schleife → O(n log n)
🔒 Musterlösung (Antworten + Begründungen)
Falsches Passwort!
SchnipselBig-OBegründung
AO(n)eine einfache Schleife über n Elemente
BO(n²)verschachtelte Schleifen, beide bis n
CO(log n)x verdoppelt sich — braucht nur log₂(n) Schritte, bis x ≥ n
DO(n · log n)äußere Schleife O(n), innere Schleife O(log n) → multipliziert
EO(n)im Worst Case (Zielwert nicht im Array oder ganz am Ende) wird die ganze Schleife durchlaufen — linear

Hinweis zu E: der Best Case wäre O(1), wenn das Zielelement am Anfang steht. Big-O beschreibt aber per Konvention den Worst Case.

7 Bonus: Bubble Sort optimieren — Best Case O(n) ⏱️ 25 Min · BONUS VL 4 + VL 5 vertieft

Ziel: Bubble Sort hat im Worst Case O(n²) — aber mit einem kleinen Trick kann er bei sortierten Daten in O(n) fertig sein. Das wird gerne in der Klausur gefragt.

Die Idee:

Wenn in einem ganzen Durchgang kein einziger Swap passiert, ist das Array schon sortiert — wir können sofort aufhören. Dazu brauchen wir nur ein Flag (eine 0/1-Variable):

function bubbleSortOptimiert(a, n): for i = 0 to n-2: getauscht = 0 for j = 0 to n-2-i: if a[j] > a[j+1]: tausche(a[j], a[j+1]) getauscht = 1 if getauscht == 0: break // Array ist sortiert!

Aufgabe:

  1. Implementiere die optimierte Version. Zähle die Vergleiche mit (wie in Aufgabe 2).
  2. Teste mit drei Fällen:
    • [7, 3, 9, 1, 5, 2] — zufällig
    • [1, 2, 3, 4, 5, 6] — schon sortiert
    • [1, 2, 3, 4, 6, 5] — fast sortiert (nur ein Swap nötig)
  3. Vergleicht die Vergleichs-Zähler mit dem normalen Bubble Sort aus Aufgabe 2.
💡 Was ihr sehen werdet:
  • Bei sortierter Eingabe: nur 5 Vergleiche (n-1) → das ist O(n) Best Case.
  • Bei fast-sortierter Eingabe: deutlich weniger Vergleiche als beim normalen Bubble Sort.
  • Bei umgekehrt sortierter Eingabe: kein Gewinn — der Worst Case bleibt O(n²).
🤔 Denkfrage: warum hilft die Optimierung bei Selection Sort nicht? Selection schaut sich auch dann alle Paare an, wenn die Daten sortiert sind — anders als Bubble „weiß" Selection nicht, dass es fertig ist.
🔒 Musterlösung in C
Falsches Passwort!
#include <stdio.h>

int vergleiche = 0;

void bubble_sort_optimiert(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int getauscht = 0;        // Flag: gab es Swaps?
        for (int j = 0; j < n - 1 - i; j++) {
            vergleiche = vergleiche + 1;
            if (a[j] > a[j + 1]) {
                int t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
                getauscht = 1;
            }
        }
        if (getauscht == 0) {
            // Kein Swap im ganzen Durchgang → sortiert
            break;
        }
    }
}

void test(int a[], int n, const char *name) {
    vergleiche = 0;
    bubble_sort_optimiert(a, n);
    printf("[%s] Vergleiche: %d\n", name, vergleiche);
}

int main(void) {
    int zufall[6]     = {7, 3, 9, 1, 5, 2};
    int sortiert[6]   = {1, 2, 3, 4, 5, 6};
    int fastsortiert[6] = {1, 2, 3, 4, 6, 5};

    test(zufall,       6, "zufaellig");
    test(sortiert,     6, "sortiert");
    test(fastsortiert, 6, "fast sortiert");

    return 0;
}

Erwartete Ausgabe:

[zufaellig]      Vergleiche: 14
[sortiert]       Vergleiche: 5     ← O(n) Best Case ✓
[fast sortiert]  Vergleiche: 9     ← spart viele Vergleiche

Zum Vergleich: der normale Bubble Sort aus Aufgabe 2 macht immer 15 Vergleiche — egal wie sortiert die Daten sind.

✅ Was du nach diesem Lab können solltest

🎯 Reflexion am Ende

Bevor du gehst, beantworte für dich:

  1. Welcher der drei Sortier-Algorithmen ist dir am natürlichsten erschienen — beim Sortieren von Hand?
  2. Bei welcher Aufgabe hast du die Komplexität wirklich gespürt (nicht nur als Formel gesehen)?
  3. Wenn du nicht in C codiert hast — welche Sprache hast du gewählt, und was hat dir die Mehrarbeit eingebracht?
  4. Falls du den Bonus geschafft hast: warum nutzt Bubble Sort die Flag-Optimierung, Selection aber nicht?

→ Diese Reflexion ist freiwillig — aber sie zementiert das Gelernte.

🔑 Passwörter für die Musterlösungen

Die Passwörter stehen unten — frei zugänglich. Bitte fair spielen: erst selbst implementieren, dann mit der Musterlösung vergleichen. Eine Lösung, die ihr nur abgeschrieben habt, hilft euch in der Klausur nicht.

AufgabeThemaPasswort
Aufgabe 1Sortieren von Handpapier
Aufgabe 2Bubble Sortnachbar
Aufgabe 3Selection Sortminimum
Aufgabe 4Insertion Sortkarten
Aufgabe 5Benchmarkbenchmark
Aufgabe 6Big-O bestimmenbigo
Aufgabe 7Bubble Sort optimiert (Bonus)earlyexit

Hinweis für Arduino/Python/JavaScript-Lösungen: die Logik der C-Lösung gilt 1:1 — übersetzt nur die Syntax. Eine eigene Musterlösung für jede Sprache gibt es nicht.

← Startseite

© 2026 HTW Berlin · Prof. Dr. Alexandra Mikityuk