Der Insertion-Sort-Algorithmus
Bubble Sort sortiert, indem es wiederholt die gesamte Liste durchlĂ€uft und Nachbarn tauscht. Insertion Sort verfolgt einen anderen Ansatz: Er hĂ€lt vorne in der Liste einen sortierten Abschnitt und lĂ€sst ihn Element fĂŒr Element wachsen, wobei jeder neue Wert genau dort eingefĂŒgt wird, wo er hingehört â so, wie die meisten Menschen ein Blatt Spielkarten sortieren.
Die Idee
Insertion Sort betrachtet die Liste als zweigeteilt: einen sortierten PrĂ€fix am Anfang und den Rest. Zu Beginn besteht der sortierte PrĂ€fix nur aus dem ersten Element â ein einzelnes Element ist trivial "sortiert".
FĂŒr jedes folgende Element, den SchlĂŒssel, schiebt Insertion Sort es in den sortierten PrĂ€fix hinein: Es vergleicht den SchlĂŒssel von rechts nach links mit dem PrĂ€fix und schiebt jedes gröĂere Element eine Position nach rechts, bis es die Stelle findet, an die der SchlĂŒssel gehört. Genau in die dabei entstehende LĂŒcke wird der SchlĂŒssel platziert. Danach ist der sortierte PrĂ€fix um eins gewachsen, und der Vorgang wiederholt sich mit dem nĂ€chsten Element.
Implementierung
#include "plot.hpp"
#include <stdlib.h>
const int N = 20;
int values[N];
int i = 1; // index of the element currently being inserted
int k = -2; // -2 = pick a new key, -1..i-1 = shifting comparison pointer
int key = 0;
bool done = false;
int tick = 0;
void setup() {
PlotCanvas(640, 480);
for (int n = 0; n < N; n++) {
values[n] = n + 1;
}
srand(1);
for (int n = N - 1; n > 0; n--) {
int r = rand() % (n + 1);
int tmp = values[n];
values[n] = values[r];
values[r] = tmp;
}
}
void frame() {
// Slow the animation down: one step every few calls to frame() instead
// of one per call, so individual shifts stay visible.
if (!done && ++tick >= 4) {
tick = 0;
if (k == -2) {
if (i >= N) {
done = true;
} else {
key = values[i];
k = i - 1;
}
} else if (k >= 0 && values[k] > key) {
values[k + 1] = values[k];
k--;
} else {
values[k + 1] = key;
i++;
k = -2;
}
}
PlotBackground(250, 250, 250);
int activeIndex = (k == -2) ? i : (k + 1);
int barWidth = plot_width / N;
for (int idx = 0; idx < N; idx++) {
if (!done && idx == activeIndex) {
PlotColor(220, 60, 60); // the key currently being inserted
} else if (idx < i) {
PlotColor(70, 170, 90); // sorted prefix
} else {
PlotColor(30, 100, 200); // not yet reached
}
int height = values[idx] * (plot_height - 20) / N;
int x0 = idx * barWidth + 1;
int x1 = x0 + barWidth - 2;
int y0 = plot_height - height;
int y1 = plot_height;
PlotFilledRectangle(x0, y0, x1, y1);
}
}
i markiert, wie weit der sortierte PrĂ€fix reicht; k lĂ€uft rĂŒckwĂ€rts
durch ihn und vergleicht mit key, wobei Elemente nach rechts geschoben
werden (values[k + 1] = values[k]), solange sie gröĂer sind. Sobald k
keine gröĂeren Elemente mehr zum Schieben findet, wird key in die LĂŒcke
geschrieben und i rĂŒckt vor. Wie beim Bubble-Sort-Sample fĂŒhrt frame()
nur alle paar Aufrufe einen solchen Schritt aus, damit die einzelnen
Verschiebungen sichtbar bleiben statt sofort durchzulaufen. Die Balkenfarben
markieren dieselben drei ZustÀnde wie zuvor, nur mit anderer Bedeutung:
- grĂŒn â der sortierte PrĂ€fix
- rot â der SchlĂŒssel, der gerade eingefĂŒgt wird
- blau â noch nicht erreicht
Schritt fĂŒr Schritt durchgerechnet
FĂŒr [5, 1, 4, 2, 8] beginnt der sortierte PrĂ€fix als nur [5]. Beim
EinfĂŒgen von 1:
| Schritt | Vergleich | schieben? | Array |
|---|---|---|---|
| Start | key = 1 | â | [5, 1, 4, 2, 8] |
| k=0 | 5 > 1 | 5 nach rechts schieben | [5, 5, 4, 2, 8] |
| platzieren | k = -1, nichts mehr zu vergleichen | 1 einfĂŒgen | [1, 5, 4, 2, 8] |
Der PrĂ€fix ist jetzt [1, 5]. Beim nĂ€chsten EinfĂŒgen von 4 reicht eine
Verschiebung (5 rĂŒckt nach rechts, 4 landet zwischen 1 und 5); das
EinfĂŒgen von 2 braucht zwei Verschiebungen vorbei an 5 und 4; 8
braucht keine, da es bereits gröĂer ist als alles im PrĂ€fix. Nach allen
vier EinfĂŒgungen ist die Liste sortiert.
Warum er manchmal noch verwendet wird
Insertion Sort ist im schlechtesten Fall O(nÂČ), genau wie Bubble Sort â
asymptotisch nicht besser. Aber zwei Eigenschaften halten ihn im
praktischen Einsatz:
- Bester Fall
O(n): Ist die Liste bereits sortiert oder fast sortiert, braucht jeder SchlĂŒssel nur einen Vergleich, um festzustellen, dass keine Verschiebung nötig ist. Die naive Version von Bubble Sort hat diese Eigenschaft nicht; Insertion Sort bekommt sie geschenkt, weil es in diese Richtung vergleicht. - Gut bei kleinen Eingaben: Bei kurzen Arrays (grob unter 10â20
Elementen, je nach System) schlÀgt der geringe Overhead pro Vergleich
von Insertion Sort die Setup-Kosten rekursiver Divide-and-Conquer-Sorts.
Deshalb wechseln Produktions-Implementierungen â Timsort, Introsort und
die meisten
std::sort-Implementierungen â zu Insertion Sort, sobald eine rekursive Partition unter eine kleine GröĂenschwelle schrumpft, statt bis ganz nach unten zu rekursieren.
Er ist auĂerdem ein stabiles Sortierverfahren: Elemente, die als gleich verglichen werden, behalten ihre ursprĂŒngliche relative Reihenfolge, da ein SchlĂŒssel immer nur an strikt kleineren Elementen vorbeigeschoben wird. Bubble Sort teilt diese Eigenschaft; nicht jeder Sortieralgorithmus tut das.
Fazit
WĂ€hrend Bubble Sort die gesamte Liste immer wieder neu durchsucht, setzt
Insertion Sort auf einen wachsenden sortierten Bereich und verrichtet genau
die Arbeit, die nötig ist, um jedes neue Element dort einzufĂŒgen. Dieser
Unterschied â asymptotisch irrelevant, da beide O(nÂČ) sind â macht in der
Praxis viel aus, weshalb Insertion Sort, anders als Bubble Sort, auch heute
noch in den Standard-Sortieralgorithmen auftaucht.