Der Bubble-Sort-Algorithmus
Eine Liste zu sortieren ist eines der ersten Probleme, die jeder Programmierer löst, und Bubble Sort ist meist der erste Algorithmus dafĂŒr. Er ist nicht schnell und auĂerhalb des Klassenzimmers selten die richtige Wahl â aber er ist kurz, braucht keinen zusĂ€tzlichen Speicher, und sein Verhalten lĂ€sst sich Schritt fĂŒr Schritt gut beobachten, was ihn zu einem guten Einstieg macht, bevor man sich schnellere Algorithmen wie Quicksort oder Merge Sort ansieht.
Die Idee
Bubble Sort durchlĂ€uft die Liste wiederholt und vergleicht jedes Paar benachbarter Elemente. Ist ein Paar falsch angeordnet, werden die beiden vertauscht. Ein vollstĂ€ndiger Durchlauf befördert den gröĂten verbleibenden Wert bis ganz ans Ende der Liste â wie eine Blase, die aufsteigt â denn jeder Tausch, der ihn betrifft, trĂ€gt ihn eine Position weiter nach rechts.
Nach dem ersten Durchlauf steht fest, dass das gröĂte Element an seiner
finalen Position ist, sodass der nÀchste Durchlauf nur noch alles davor
betrachten muss. Das fĂŒr n - 1 DurchlĂ€ufe ĂŒber eine Liste mit n
Elementen zu wiederholen reicht aus, um sie vollstÀndig zu sortieren, da
jeder Durchlauf mindestens ein weiteres Element endgĂŒltig platziert.
Implementierung
#include "plot.hpp"
#include <stdlib.h>
const int N = 20;
int values[N];
int i = 0;
int j = 0;
bool done = false;
int tick = 0;
void setup() {
PlotCanvas(640, 480);
for (int k = 0; k < N; k++) {
values[k] = k + 1;
}
srand(1);
for (int k = N - 1; k > 0; k--) {
int r = rand() % (k + 1);
int tmp = values[k];
values[k] = values[r];
values[r] = tmp;
}
}
void frame() {
// Slow the animation down: one comparison every few frames instead of
// one per frame, so individual steps stay visible.
if (!done && ++tick >= 4) {
tick = 0;
if (values[j] > values[j + 1]) {
int tmp = values[j];
values[j] = values[j + 1];
values[j + 1] = tmp;
}
j++;
if (j >= N - 1 - i) {
j = 0;
i++;
if (i >= N - 1) {
done = true;
}
}
}
PlotBackground(250, 250, 250);
int barWidth = plot_width / N;
for (int k = 0; k < N; k++) {
if (!done && (k == j || k == j + 1)) {
PlotColor(220, 60, 60); // pair being compared
} else if (k >= N - i) {
PlotColor(70, 170, 90); // already in final position
} else {
PlotColor(30, 100, 200); // untouched
}
int height = values[k] * (plot_height - 20) / N;
int x0 = k * barWidth + 1;
int x1 = x0 + barWidth - 2;
int y0 = plot_height - height;
int y1 = plot_height;
PlotFilledRectangle(x0, y0, x1, y1);
}
}
Die beiden verschachtelten Schleifen spiegeln die beiden Ideen von oben:
der Ă€uĂere Index i zĂ€hlt, wie viele Elemente am Ende der Liste bereits
endgĂŒltig platziert sind, und der innere Index j lĂ€uft durch den noch
unsortierten Teil und vergleicht Nachbarn. Statt etwas auszugeben, zeichnet
frame() das Array als Balken â die Höhe ist der Wert, die Farbe zeigt,
was der Algorithmus gerade tut:
- blau â unberĂŒhrt, noch unsortiert
- rot â das Paar, das gerade verglichen wird
- grĂŒn â bereits an seiner finalen Position
Damit einzelne Vergleiche sichtbar bleiben statt sofort durchzulaufen,
fĂŒhrt das Sample nur alle paar Aufrufe von frame() einen Vergleich aus
(siehe den tick-ZĂ€hler) â alles andere, einschlieĂlich des Mischens des
Arrays in setup(), lÀuft genau einmal.
Schritt fĂŒr Schritt durchgerechnet
FĂŒr die Liste [5, 1, 4, 2, 8] vergleicht der erste Durchlauf jedes
benachbarte Paar von links nach rechts:
| Vergleich | vorher | tauschen? | nachher |
|---|---|---|---|
| 5, 1 | [5, 1, 4, 2, 8] | ja | [1, 5, 4, 2, 8] |
| 5, 4 | [1, 5, 4, 2, 8] | ja | [1, 4, 5, 2, 8] |
| 5, 2 | [1, 4, 5, 2, 8] | ja | [1, 4, 2, 5, 8] |
| 5, 8 | [1, 4, 2, 5, 8] | nein | [1, 4, 2, 5, 8] |
8 â der gröĂte Wert â ist nach nur einem Durchlauf an seine finale
Position geblubbert. Die restlichen DurchlÀufe wiederholen denselben
Prozess ĂŒber das schrumpfende unsortierte PrĂ€fix [1, 4, 2, 5],
[1, 2, 4] und so weiter, bis nichts mehr zu vergleichen bleibt.
Warum er selten verwendet wird
Jeder Durchlauf kostet O(n), und eine vollstÀndige Sortierung braucht bis
zu n - 1 DurchlÀufe, also ist Bubble Sort im schlechtesten und im
durchschnittlichen Fall O(nÂČ) â quadratisch schlechter als das
O(n log n) von Merge Sort oder Quicksort. Eine gÀngige Optimierung merkt
sich, ob wĂ€hrend eines Durchlaufs ĂŒberhaupt getauscht wurde, und bricht
frĂŒhzeitig ab, falls nicht â das drĂŒckt den besten Fall (eine bereits
sortierte Liste) auf O(n). Die Version hier verzichtet auf diese PrĂŒfung,
um Code und Visualisierung einfach zu halten; jeder Lauf macht unabhÀngig
von der Eingabe alle n - 1 DurchlÀufe.
Sein heutiger Wert ist fast ausschlieĂlich pĂ€dagogisch: Er ist die einfachste mögliche EinfĂŒhrung in vergleichsbasiertes Sortieren und eine nĂŒtzliche Grundlinie, um schnellere Algorithmen zu vergleichen, die dasselbe Problem mit klĂŒgeren Strategien lösen â die Liste zu teilen statt sie wiederholt zu durchlaufen, wie bei Merge Sort und Quicksort.
Fazit
Bubble Sort verwandelt Sortieren in eine Folge lokaler Entscheidungen: zwei Nachbarn ansehen, sie vertauschen, falls sie falsch herum stehen, und das wiederholen, bis nichts mehr zu korrigieren ist. Diese Einfachheit macht ihn zu einer schlechten Wahl fĂŒr groĂe Listen, aber zu einer guten, um ein GefĂŒhl dafĂŒr zu entwickeln, was ein Sortieralgorithmus tatsĂ€chlich tut â einen Vergleich nach dem anderen.