Heap Sort

8
Heapsort Esta página examina um algoritmo sofisticado que rearranja um vetor dado em ordem crescente. Para que a descrição do algoritmo fique mais simples, convém que os índices do vetor sejam 1..n e não 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 binária 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 não n de propósito.) Aqui, como no resto desta página, vamos convencionar que as expressões que figuram como índices de um vetor são sempre inteiras. Uma expressão da forma f/2 significa f/2, ou seja, o piso de f/2. Por exemplo, se v[1..17] é um heap então, 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 definição de heap, não? Talvez a coisa fique mais clara se encararmos a sequência de índices 1..m como um árvore binária: o índice 1 é a raiz da árvore; o pai de um índice f é f/2 (é claro que 1 não 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 números nas caixas são os índices i e não os valores v[i].) Observe que cada camada, exceto a última, tem duas vezes mais elementos que a camada anterior. Com isso, o número de camadas de um vetor v[1..m] é exatamente 1 + lg( m) , sendo lg( m) o piso de log m. 1

description

Esta página examina um algoritmo sofisticado que rearranja um vetor dado em ordem crescente. Para que a descrição do algoritmo fique mais simples, convém que os índices do vetor sejam 1..n e não 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.

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.