Hier ist ein nicht-rekursiver In-Place-Algorithmus in linearer Zeit, mit dem zwei Hälften eines Arrays ohne zusätzlichen Speicher verschachtelt werden können.
Die allgemeine Idee ist einfach: Gehen Sie von links nach rechts durch die erste Hälfte des Arrays und tauschen Sie die richtigen Werte aus. Wenn Sie fortfahren, werden die noch zu verwendenden linken Werte in den von den rechten Werten freigegebenen Bereich getauscht. Der einzige Trick ist herauszufinden, wie man sie wieder herausholt.
Wir beginnen mit einem Array der Größe N, das in zwei nahezu gleiche Hälften aufgeteilt ist.
[ left_items | right_items ]
Während wir es verarbeiten, wird es
[ placed_items | remaining_left_items| swapped_left_items | remaining_right_items]
Der Auslagerungsbereich wächst mit dem folgenden Muster: A) Vergrößern Sie den Bereich, indem Sie das benachbarte rechte Element entfernen und ein neues Element von links einlagern. B) Tauschen Sie den ältesten Artikel mit einem neuen Artikel von links aus. Wenn die linken Elemente mit 1..N nummeriert sind, sieht dieses Muster wie folgt aus
step swapspace index changed
1 A: 1 0
2 B: 2 0
3 A: 2 3 1
4 B: 4 3 0
5 A: 4 3 5 2
6 B: 4 6 5 1
7 A: 4 6 5 7 3
...
Die Reihenfolge, in der sich der Index ändert, ist genau OEIS A025480 , was mit einem einfachen Verfahren berechnet werden kann. Auf diese Weise kann der Auslagerungsort nur anhand der Anzahl der bisher hinzugefügten Elemente gefunden werden. Dies ist auch der Index des aktuell platzierten Elements.
Das ist alles, was wir brauchen, um die erste Hälfte der Sequenz in linearer Zeit zu füllen.
Wenn wir zum Mittelpunkt gelangen, besteht das Array aus drei Teilen:
[ placed_items | swapped_left_items | remaining_right_items]
Wenn wir die ausgetauschten Elemente entschlüsseln können, haben wir das Problem auf die Hälfte der Größe reduziert und können es wiederholen.
Um den Swap-Bereich zu entschlüsseln, verwenden wir die folgende Eigenschaft: Eine Sequenz, die durch N
abwechselnde Operationen append und swap_oldest erstellt wurde, enthält N/2
Elemente, deren Alter durch angegeben ist A025480(N/2)..A025480(N-1)
. (Ganzzahlige Division, kleinere Werte sind älter).
Wenn beispielsweise die linke Hälfte ursprünglich die Werte 1..19 enthielte, würde der Swap Space enthalten [16, 12, 10, 14, 18, 11, 13, 15, 17, 19]
. A025480 (9..18) ist [2, 5, 1, 6, 3, 7, 0, 8, 4, 9]
genau die Liste der Indizes der Elemente vom ältesten zum neuesten.
So können wir unseren Swap-Bereich entschlüsseln, indem wir ihn durchlaufen und S[i]
mit ihm tauschen S[ A(N/2 + i)]
. Dies ist auch eine lineare Zeit.
Die verbleibende Komplikation ist, dass Sie irgendwann eine Position erreichen, an der der richtige Wert bei einem niedrigeren Index liegen sollte, der jedoch bereits ausgetauscht wurde. Der neue Speicherort ist leicht zu finden: Führen Sie die Indexberechnung erneut durch, um festzustellen, wohin der Artikel ausgetauscht wurde. Es kann erforderlich sein, der Kette einige Schritte zu folgen, bis Sie einen nicht vertauschten Ort finden.
Zu diesem Zeitpunkt haben wir die Hälfte des Arrays zusammengeführt und die Reihenfolge der nicht zusammengeführten Teile in der anderen Hälfte mit genau vertauschten Elementen beibehalten N/2 + N/4
. Wir können den Rest des Arrays für insgesamt N + N/4 + N/8 + ....
Swaps durchlaufen, was streng genommen weniger als ist 3N/2
.
Berechnung von A025480:
Dies ist in OEIS definiert als a(2n) = n, a(2n+1) = a(n).
Eine alternative Formulierung ist a(n) = isEven(n)? n/2 : a((n-1)/2)
. Dies führt zu einem einfachen Algorithmus mit bitweisen Operationen:
index_t a025480(index_t n){
while (n&1) n=n>>1;
return n>>1;
}
Dies ist eine amortisierte O (1) -Operation über alle möglichen Werte für N. (1/2 braucht 1 Schicht, 1/4 braucht 2, 1/8 braucht 3, ...) . Es gibt eine noch schnellere Methode, die eine kleine Nachschlagetabelle verwendet, um die Position des niedrigstwertigen Nullbits zu finden.
In Anbetracht dessen ist hier eine Implementierung in C:
static inline index_t larger_half(index_t sz) {return sz - (sz / 2); }
static inline bool is_even(index_t i) { return ((i & 1) ^ 1); }
index_t unshuffle_item(index_t j, index_t sz)
{
index_t i = j;
do {
i = a025480(sz / 2 + i);
}
while (i < j);
return i;
}
void interleave(value_t a[], index_t n_items)
{
index_t i = 0;
index_t midpt = larger_half(n_items);
while (i < n_items - 1) {
//for out-shuffle, the left item is at an even index
if (is_even(i)) { i++; }
index_t base = i;
//emplace left half.
for (; i < midpt; i++) {
index_t j = a025480(i - base);
SWAP(a + i, a + midpt + j);
}
//unscramble swapped items
index_t swap_ct = larger_half(i - base);
for (index_t j = 0; j + 1 < swap_ct ; j++) {
index_t k = unshuffle_item(j, i - base);
if (j != k) {
SWAP(a + midpt + j, a + midpt + k);
}
}
midpt += swap_ct;
}
}
Dies sollte ein ziemlich cachefreundlicher Algorithmus sein, da auf 2 der 3 Datenpositionen nacheinander zugegriffen wird und die verarbeitete Datenmenge stark abnimmt. Diese Methode kann von einem Out-Shuffle in ein In-Shuffle umgewandelt werden, indem der is_even
Test am Anfang der Schleife negiert wird.