Knuth ließ dies als Übung (Band 3, 5.2.5). Es gibt In-Place-Zusammenführungssorten. Sie müssen sorgfältig umgesetzt werden.
Erstens ist eine naive In-Place-Zusammenführung wie hier beschrieben nicht die richtige Lösung. Die Leistung wird auf O (N 2 ) herabgestuft .
Die Idee ist, einen Teil des Arrays zu sortieren, während der Rest als Arbeitsbereich zum Zusammenführen verwendet wird.
Zum Beispiel wie die folgende Zusammenführungsfunktion.
void wmerge(Key* xs, int i, int m, int j, int n, int w) {
while (i < m && j < n)
swap(xs, w++, xs[i] < xs[j] ? i++ : j++);
while (i < m)
swap(xs, w++, i++);
while (j < n)
swap(xs, w++, j++);
}
Es nimmt die Anordnung xs
, die zwei sortierten Subarrays werden als Bereiche dargestellt [i, m)
und [j, n)
jeweils. Der Arbeitsbereich beginnt ab w
. Vergleichen Sie mit dem Standard-Zusammenführungsalgorithmus, der in den meisten Lehrbüchern angegeben ist. Dieser tauscht den Inhalt zwischen dem sortierten Unterarray und dem Arbeitsbereich aus. Infolgedessen enthält der vorherige Arbeitsbereich die zusammengeführten sortierten Elemente, während die im Arbeitsbereich gespeicherten vorherigen Elemente in die beiden Unterarrays verschoben werden.
Es gibt jedoch zwei Einschränkungen, die erfüllt sein müssen:
- Der Arbeitsbereich sollte innerhalb der Grenzen des Arrays liegen. Mit anderen Worten, es sollte groß genug sein, um ausgetauschte Elemente aufzunehmen, ohne einen Fehler außerhalb der Grenze zu verursachen.
- Der Arbeitsbereich kann mit einem der beiden sortierten Arrays überlappt werden. Es muss jedoch sichergestellt werden, dass keines der nicht zusammengeführten Elemente überschrieben wird.
Wenn dieser Zusammenführungsalgorithmus definiert ist, kann man sich leicht eine Lösung vorstellen, mit der die Hälfte des Arrays sortiert werden kann. Die nächste Frage ist, wie mit dem Rest des unsortierten Teils umgegangen werden soll, der im Arbeitsbereich gespeichert ist, wie unten gezeigt:
... unsorted 1/2 array ... | ... sorted 1/2 array ...
Eine intuitive Idee besteht darin, eine weitere Hälfte des Arbeitsbereichs rekursiv zu sortieren, sodass nur 1/4 Elemente noch nicht sortiert wurden.
... unsorted 1/4 array ... | sorted 1/4 array B | sorted 1/2 array A ...
Der entscheidende Punkt in dieser Phase ist, dass wir die sortierten 1/4 Elemente B früher oder später mit den sortierten 1/2 Elementen A zusammenführen müssen.
Ist der Arbeitsbereich, der nur 1/4 Elemente enthält, groß genug, um A und B zusammenzuführen? Leider ist es nicht.
Die oben erwähnte zweite Einschränkung gibt uns jedoch einen Hinweis, dass wir sie ausnutzen können, indem wir den Arbeitsbereich so anordnen, dass er sich mit einem der beiden Unterarrays überlappt, wenn wir sicherstellen können, dass die Zusammenführungssequenz nicht überschrieben wird.
Anstatt die zweite Hälfte des Arbeitsbereichs zu sortieren, können wir auch die erste Hälfte sortieren und den Arbeitsbereich wie folgt zwischen die beiden sortierten Arrays einfügen:
... sorted 1/4 array B | unsorted work area | ... sorted 1/2 array A ...
Dieser Aufbau ordnet effektiv die Überlappung des Arbeitsbereichs mit dem Subarray A an. Diese Idee wird in [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola. "Praktisches In-Place-Mergesort". Nordic Journal of Computing, 1996].
Das einzige, was noch übrig ist, ist, den obigen Schritt zu wiederholen, wodurch der Arbeitsbereich von 1/2, 1/4, 1/8, ... reduziert wird. Wenn der Arbeitsbereich klein genug wird (zum Beispiel nur noch zwei Elemente übrig), können wir Wechseln Sie zu einer einfachen Einfügungssortierung, um diesen Algorithmus zu beenden.
Hier ist die Implementierung in ANSI C basierend auf diesem Dokument.
void imsort(Key* xs, int l, int u);
void swap(Key* xs, int i, int j) {
Key tmp = xs[i]; xs[i] = xs[j]; xs[j] = tmp;
}
/*
* sort xs[l, u), and put result to working area w.
* constraint, len(w) == u - l
*/
void wsort(Key* xs, int l, int u, int w) {
int m;
if (u - l > 1) {
m = l + (u - l) / 2;
imsort(xs, l, m);
imsort(xs, m, u);
wmerge(xs, l, m, m, u, w);
}
else
while (l < u)
swap(xs, l++, w++);
}
void imsort(Key* xs, int l, int u) {
int m, n, w;
if (u - l > 1) {
m = l + (u - l) / 2;
w = l + u - m;
wsort(xs, l, m, w); /* the last half contains sorted elements */
while (w - l > 2) {
n = w;
w = l + (n - l + 1) / 2;
wsort(xs, w, n, l); /* the first half of the previous working area contains sorted elements */
wmerge(xs, l, l + n - w, n, u, w);
}
for (n = w; n > l; --n) /*switch to insertion sort*/
for (m = n; m < u && xs[m] < xs[m-1]; ++m)
swap(xs, m, m - 1);
}
}
Wo wmerge zuvor definiert wurde.
Den vollständigen Quellcode finden Sie hier und die ausführliche Erklärung finden Sie hier
Diese Version ist übrigens nicht die schnellste Zusammenführungssortierung, da mehr Auslagerungsvorgänge erforderlich sind. Meinem Test zufolge ist es schneller als die Standardversion, die bei jeder Rekursion zusätzliche Leerzeichen zuweist. Es ist jedoch langsamer als die optimierte Version, bei der das ursprüngliche Array im Voraus verdoppelt und für die weitere Zusammenführung verwendet wird.