Heap Sort
-
Upload
flavia-gomes -
Category
Documents
-
view
14 -
download
1
description
Transcript of Heap Sort
-
Heapsort
Esta pgina examina um algoritmo sofisticado que rearranja um vetor dado em ordem
crescente. Para que a descrio do algoritmo fique mais simples, convm que os ndices
do vetor sejam 1..n e no 0..n-1, como usual em C. Resumindo, nosso algoritmo
rearranja os elementos de um vetor v[1..n] de tal modo que ele fique em ordem
crescente,
ou seja, de tal modo que tenhamos v[1] v[2] . . . v[n]. O algoritmo, conhecido como Heapsort, foi inventado por J.W.J. Williams em 1964.
Heap (rvore binria quase completa)
O segredo do funcionamento do algoritmo uma estrutura de dados conhecida como
heap (= monte). (H dois sabores da estrutura: max-heap e min-heap. Trataremos
apenas do primeiro, omitindo o prefixo max.) Um heap um vetor v[1..m] tal que
v[f/2] v[f]
para f = 2, . . . , m. (Estou escrevendo m e no n de propsito.) Aqui, como no resto
desta pgina, vamos convencionar que as expresses que figuram como ndices de um
vetor so sempre inteiras. Uma expresso da forma f/2 significa f/2 , ou seja, o piso de f/2. Por exemplo, se v[1..17] um heap ento, em particular, v[5] v[10] e
v[5] v[11]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
999 999 999 999 999 999 999 999 999 999 999 999 999 999 999 999 999
Curiosa essa definio de heap, no? Talvez a coisa fique mais clara se encararmos a
sequncia de ndices 1..m como um rvore binria:
o ndice 1 a raiz da rvore; o pai de um ndice f f/2 ( claro que 1 no tem pai); o filho esquerdo de um ndice p 2p e o filho direito 2p+1 ( claro que o filho
esquerdo s existe se 2p m e o filho direito s existe se 2p+1 m).
A figura abaixo representa um vetor v[1..55] de modo que cada filho fique na camada
imediatamente inferior do pai. (Os nmeros nas caixas so os ndices i e no os
valores v[i].) Observe que cada camada, exceto a ltima, tem duas vezes mais
elementos que a camada anterior. Com isso, o nmero de camadas de um vetor v[1..m]
exatamente 1 + lg( m), sendo lg( m) o piso de log m.
1
-
2 3
4 5 6 7
8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
Uma vez entendido o conceito de pai e filho, podemos dizer que um vetor um heap se
o valor de todo pai maior ou igual que o valor de qualquer de seus dois filhos (onde o
valor de um ndice i v[i]).
Exerccios 1
1. Mostre que todo vetor decrescente indexado por 1,2, . . . um heap. Mostre que a recproca no verdadeira.
2. O vetor 161 41 101 141 71 91 31 21 81 17 16 um heap? 3. Escreva uma funo que decida se um vetor v[1..m] ou no um heap. 4. Mostre que v[1..m] um heap se e somente se para cada ndice p tem-se
1. se 2p m ento v[p] v[2p]; 2. se 2p+1 m ento v[p] v[2p+1].
5. Suponha que v[1..m] um heap com m = 2k-1. Mostre que mais da metade dos elementos do vetor est na ltima camada do heap, ou seja, em v[2k-1.. 2k-1].
A funo peneira
O corao de qualquer algoritmo que manipule um heap uma funo que recebe um
vetor arbitrrio v[1..m] e um ndice p
e faz v[p] descer para sua posio correta.
Como se faz isso? A ideia bvia. Se v[p] v[2p] e v[p] v[2p+1] ento no
preciso fazer nada. Se v[p] < v[2p] e v[2p] v[2p+1] ento basta trocar v[p] com
v[2p] e depois fazer v[2p] descer para sua posio correta. No difcil imaginar o
que se deve fazer no terceiro caso.
Exemplo com p = 1: Cada linha da tabela uma foto do vetor no incio de uma iterao.
85 99 98 97 96 95 94 93 92 91 90 89 97 86
99 85 98 97 96 95 94 93 92 91 90 89 97 86
-
99 97 98 85 96 95 94 93 92 91 90 89 97 86
99 97 98 93 96 95 94 85 92 91 90 89 97 86
Eis uma funo iterativa que faz o servio. A varivel f sempre um filho de p; no
incio de cada iterao, f ajustado de modo a ser o filho de maior valor de p.
// Recebe p em 1..m e rearranja o vetor v[1..m] de
// modo que o "subvetor" cuja raiz p seja um heap.
// Supe que os "subvetores" cujas razes so filhos
// de p j so heaps.
void
peneira( int p, int m, int v[])
{
int f = 2*p, x;
while (f = v[f]) break;
x = v[f/2], v[f/2] = v[f], v[f] = x;
f *= 2;
}
}
A seguinte implementao um pouco melhor, porque faz menos trocas:
void
peneira( int p, int m, int v[])
{
int f = 2*p, x = v[p];
while (f = v[f]) break;
v[p] = v[f];
p = f, f = 2*p;
}
v[p] = x;
}
Desempenho da funo peneira
A funo peneira muito rpida. O consumo de tempo proporcional ao nmero de
iteraes, e esse numero no passa de
log m
pois o valor de f pelo menos dobra a cada iterao.
Exerccios 2
1. A seguinte alternativa para a funo peneira funciona corretamente?
-
2. void peneira( int p, int m, int v[]) { 3. int x, f; 4. for (f = 2*p; f = v[f]) break; 8. x = v[p], v[p] = v[f], v[f] = x; 9. } 10. }
11. Escreva uma verso recursiva da funo peneira. 12. Por que a seguinte implementao de peneira no funciona? 13. void peneira( int p, int m, int v[]) { 14. int x; 15. int f = 2*p; 16. while (f = 1; --p) peneira( p, m, v);
faz o servio em tempo proporcional a m.
Exerccios 3
1. fcil ver que a instruo abaixo faz no mximo (m log m)/2 comparaes entre elementos do vetor, pois o nmero de comparaes em cada uma das m/2 iteraes limitado por log m.
2. for (p = m/2; p >= 1; --p) peneira( p, m, v);
Mostre que a instruo no faz mais que m comparaes. (Portanto, o consumo
de tempo proporcional a m no mximo.)
-
3. O fragmento de programa abaixo transforma um vetor arbitrrio v[1..m] em heap? 4. for (p = 1; p = 1; --p)
peneira( p, n, v);
for (m = n; m >= 2; --m) {
x = v[1], v[1] = v[m], v[m] = x;
peneira( 1, m-1, v);
}
}
A instruo for transforma o vetor em um heap recorrendo cerca de n/2 vezes funo
peneira. Feito isso, temos um processo iterativo controlado pelo segundo for. No
incio de cada iterao valem os seguinte invariantes:
o vetor v[1..n] uma permutao do vetor original, o vetor v[1..m] um heap, v[1..m] v[m+1..n] e o vetor v[m+1..n] est em ordem crescente.
claro que v[1..n] estar em ordem crescente quando m for igual a 1.
1 heap m crescente n
888 777 666 555 444 333 222 111 000 999 999 999 999 999
elementos pequenos elementos grandes
Animaes do Heapsort
Sorting Algorithms Animation by David R. Martin Animao de algoritmos de ordenao de Nicholas Andr Pinho de Oliveira
-
Desempenho do Heapsort
Quanto tempo heapsort leva para fazer o servio? O tempo proporcional ao nmero
de comparaes entre elementos do vetor, e esse nmero no passa de
3 n log n ,
mesmo no pior caso. De fato, o primeiro for faz no mximo n log n comparaes
entre elementos do vetor para construir o heap inicial. (Uma anlise mais cuidadosa
revela que o nmero de comparaes no passa de n). O segundo for executa cerca de
n chamadas a peneira e cada uma dessas chamadas faz no mximo 2 log n
comparaes entre elementos do vetor.
Exerccios 4
1. Use o heapsort para ordenar o vetor 16 15 14 13 12 11 10 9 8 7 6 5 4. 2. Suponha que o vetor v[1..n] um heap. O seguinte fragmento de cdigo rearranja o
vetor em ordem crescente? 3. for (m = n; m >= 2; --m) { 4. int x = v[1]; 5. for (j = 1; j < m; ++j) v[j] = v[j+1]; 6. v[m] = x; 7. }
8. Implemente um heap com ponteiros. Cada clula ter um valor e trs ponteiros: um aponta o pai da clula, outro aponta o filho direito e outro aponta o filho esquerdo. Escreva uma verso da funo peneira para um heap implementado com ponteiros. (Veja a pgina rvores binrias.)
9. Suponha que v[1..m] um heap. Suponha que i < j e v[i] < v[j]. Se os valores de v[i] e v[j] forem trocados, v[1..m] continuar sendo um heap? Repita o exerccio sob a hiptese v[i] > v[j].
10. Escreva uma funo HS que receba um heap v[1..n] e rearranje o vetor de modo que ele fique em ordem crescente. Tire proveito de que o vetor dado no arbitrrio. Sugesto: Digamos que um vetor v[1..m] um quase heap se
v[f/2] v[f] para f = 4, . . . , m.
Escreva uma funo H que receba um quase heap v[1..m] e transforme-o em
um heap. (Basta fazer uma verso ligeiramente especializada de peneira.) Use
H para escrever HS.
11. Escreva uma funo que rearranje um vetor v[1..n] de modo que ele fique em ordem decrescente. Sugesto: Adapte a definio de heap para o problema em questo. Reescreva a funo peneira.
Uma verso mais rpida do Heapsort
A seguinte verso do Heapsort, um pouco diferente da examinada acima, conhecida
com Bottom-Up-Heapsort:
// Rearranja os elementos do vetor v[1..n]
-
// de modo que fiquem em ordem crescente.
void
bottom_up_heapsort( int n, int v[])
{
int m, f, max, t;
constroi_heap( n, v);
for (m = n; m > 1; --m) {
max = v[1];
f = 2;
while (f 1 && v[f/2] < v[f]) {
t = v[f], v[f] = v[f/2], v[f/2] = t;
f = f/2;
}
}
v[m] = max;
}
}
No comeo de cada iterao do for, o vetor v[1..m] um heap que contm os
elementos pequenos e o vetor v[m+1..n] crescente e contm os elementos grandes.
// Recebe um vetor v[1..n] e transforma o vetor em
// um heap.
void
constroi_heap( int n, int v[])
{
int m, f, t;
for (m = 1; m < n; ++m) {
f = m+1;
while (f > 1 && v[f/2] < v[f]) {
t = v[f/2], v[f/2] = v[f], v[f] = t;
f = f/2;
}
}
}
No incio de cada iterao do for, o vetor v[1..m] um heap. No comeo de cada
iterao do while, todos os ndices em 1..m+1 satisfazem a propriedade do heap exceto
talvez v[f], que pode ser grande demais para v[f/2].
I. Wegener e J. Stolfi observaram que esta verso do Heapsort faz, em mdia, duas
vezes menos comparaes entre elementos do vetor que a verso discutida acima. (Mas
isso no significa, necessariamente, que ela seja duas vezes mais rpida se levarmos em
conta as outras operaes, como as trocas, por exemplo.)
Exerccio 5
-
1. Escreva uma verso de bottom_up_heapsort que faa a f/2 uma s vez e reduz o nmero de trocas. Inspire-se na segunda verso de peneira.