Knuth deixou isso como um exercício (Vol 3, 5.2.5). Existem classificações de mesclagem no local. Eles devem ser implementados com cuidado.
Primeiro, a mesclagem ingênua no local, como descrita aqui, não é a solução certa. Reduz o desempenho para O (N 2 ) .
A idéia é classificar parte da matriz enquanto usa o restante como área de trabalho para mesclagem.
Por exemplo, como a seguinte função de mesclagem.
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++);
}
Leva a matriz xs
, as duas sub-matrizes ordenadas são representadas como intervalos [i, m)
e [j, n)
respectivamente. A área de trabalho começa em w
. Comparado com o algoritmo de mesclagem padrão fornecido na maioria dos livros, este troca o conteúdo entre a sub-matriz classificada e a área de trabalho. Como resultado, a área de trabalho anterior contém os elementos classificados mesclados, enquanto os elementos anteriores armazenados na área de trabalho são movidos para as duas sub-matrizes.
No entanto, existem duas restrições que devem ser atendidas:
- A área de trabalho deve estar dentro dos limites da matriz. Em outras palavras, ele deve ser grande o suficiente para reter os elementos trocados sem causar nenhum erro fora de limite.
- A área de trabalho pode ser sobreposta a uma das duas matrizes classificadas; no entanto, ele deve garantir que nenhum dos elementos não imersos seja substituído.
Com esse algoritmo de fusão definido, é fácil imaginar uma solução, que pode classificar metade da matriz; A próxima pergunta é: como lidar com o restante da peça não classificada armazenada na área de trabalho, como mostrado abaixo:
... unsorted 1/2 array ... | ... sorted 1/2 array ...
Uma idéia intuitiva é classificar recursivamente outra metade da área de trabalho, portanto, existem apenas 1/4 dos elementos ainda não classificados.
... unsorted 1/4 array ... | sorted 1/4 array B | sorted 1/2 array A ...
O ponto chave nesta fase é que devemos mesclar os elementos 1/4 classificados B com os elementos A classificados 1/2, mais cedo ou mais tarde.
A área de trabalho é deixada, que contém apenas 1/4 elementos, grande o suficiente para mesclar A e B? Infelizmente não é.
No entanto, a segunda restrição mencionada acima nos dá uma dica, de que podemos explorá-la organizando a área de trabalho para se sobrepor a qualquer sub-matriz, se pudermos garantir a sequência de mesclagem de que os elementos não imersos não serão substituídos.
Na verdade, em vez de classificar a segunda metade da área de trabalho, podemos classificar a primeira metade e colocar a área de trabalho entre as duas matrizes classificadas da seguinte forma:
... sorted 1/4 array B | unsorted work area | ... sorted 1/2 array A ...
Essa configuração organiza efetivamente a sobreposição da área de trabalho com o subconjunto A. Essa idéia é proposta em [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola. `` Combinação prática no local ''. Nordic Journal of Computing, 1996].
Portanto, a única coisa que resta é repetir o passo acima, o que reduz a área de trabalho de 1/2, 1/4, 1/8,… Quando a área de trabalho se torna pequena o suficiente (por exemplo, apenas dois elementos restantes), podemos alterne para uma classificação de inserção trivial para finalizar esse algoritmo.
Aqui está a implementação no ANSI C com base neste 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);
}
}
Onde wmerge é definido anteriormente.
O código fonte completo pode ser encontrado aqui e a explicação detalhada pode ser encontrada aqui
A propósito, esta versão não é a classificação de mesclagem mais rápida, porque precisa de mais operações de troca. De acordo com o meu teste, é mais rápido que a versão padrão, que aloca espaços extras em cada recursão. Mas é mais lento que a versão otimizada, que dobra a matriz original com antecedência e a utiliza para mesclagem adicional.