Knuth dejó esto como un ejercicio (Vol. 3, 5.2.5). Existen tipos de fusión en el lugar. Deben implementarse con cuidado.
Primero, la fusión ingenua en el lugar como se describe aquí no es la solución correcta. Reduce el rendimiento a O (N 2 ) .
La idea es ordenar parte de la matriz mientras se usa el resto como área de trabajo para la fusión.
Por ejemplo, como la siguiente función de fusión.
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++);
}
Toma la matriz xs
, las dos sub-matrices ordenadas se representan como rangos [i, m)
y [j, n)
respectivamente. El área de trabajo comienza desde w
. Compare con el algoritmo de fusión estándar dado en la mayoría de los libros de texto, este intercambia el contenido entre el subarreglo ordenado y el área de trabajo. Como resultado, el área de trabajo anterior contiene los elementos ordenados combinados, mientras que los elementos anteriores almacenados en el área de trabajo se mueven a las dos sub-matrices.
Sin embargo, hay dos restricciones que deben cumplirse:
- El área de trabajo debe estar dentro de los límites de la matriz. En otras palabras, debería ser lo suficientemente grande como para contener elementos intercambiados sin causar ningún error fuera de límite.
- El área de trabajo puede superponerse con cualquiera de las dos matrices ordenadas; sin embargo, debe asegurarse de que ninguno de los elementos no fusionados se sobrescriba.
Con este algoritmo de fusión definido, es fácil imaginar una solución, que puede clasificar la mitad de la matriz; La siguiente pregunta es, cómo lidiar con el resto de la parte sin clasificar almacenada en el área de trabajo como se muestra a continuación:
... unsorted 1/2 array ... | ... sorted 1/2 array ...
Una idea intuitiva es ordenar recursivamente otra mitad del área de trabajo, por lo tanto, solo hay 1/4 elementos que aún no se han ordenado.
... unsorted 1/4 array ... | sorted 1/4 array B | sorted 1/2 array A ...
El punto clave en esta etapa es que debemos fusionar los 1/4 elementos B ordenados con los 1/2 elementos A ordenados, tarde o temprano.
¿Queda el área de trabajo, que solo contiene 1/4 elementos, lo suficientemente grande como para fusionar A y B? Lamentablemente, no lo es.
Sin embargo, la segunda restricción mencionada anteriormente nos da una pista, de que podemos explotarla organizando el área de trabajo para que se superponga con cualquiera de los subconjuntos si podemos asegurar la secuencia de fusión de que los elementos no fusionados no se sobrescribirán.
En realidad, en lugar de ordenar la segunda mitad del área de trabajo, podemos ordenar la primera mitad y colocar el área de trabajo entre las dos matrices ordenadas de esta manera:
... sorted 1/4 array B | unsorted work area | ... sorted 1/2 array A ...
Esta configuración organiza efectivamente la superposición del área de trabajo con la sub-matriz A. Esta idea se propone en [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola. `` Práctico mergesort en el lugar ''. Revista nórdica de informática, 1996].
Entonces, lo único que queda es repetir el paso anterior, que reduce el área de trabajo de 1/2, 1/4, 1/8, ... Cuando el área de trabajo se vuelve lo suficientemente pequeña (por ejemplo, solo quedan dos elementos), podemos cambie a un tipo de inserción trivial para finalizar este algoritmo.
Aquí está la implementación en ANSI C basada en este documento.
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);
}
}
Donde wmerge se define previamente.
El código fuente completo se puede encontrar aquí y la explicación detallada se puede encontrar aquí
Por cierto, esta versión no es el tipo de fusión más rápido porque necesita más operaciones de intercambio. Según mi prueba, es más rápido que la versión estándar, que asigna espacios adicionales en cada recursión. Pero es más lento que la versión optimizada, que duplica la matriz original por adelantado y la usa para una mayor fusión.