Ata da aula: 12/02/2003
Redator: André Santanchè
Para representar o heap de modo linear em um vetor, utilizam-se as seguintes relações:
Dado um nó na posição i no vetor (posições começam em 1):
Ela é responsável por manter a propriedade do heap, ou seja, dado qualquer nó, raiz de um heap, seu valor deve ser maior que o valor de seus filhos.
Para isto o MAX-HEAPIFY executa uma arrumação:
Inicialmente analisamos apenas o nível dos filhos imediatamente abaixo da raiz do heap, conforme o seguinte exemplo:
Note que a disposição dos valores nos nós não está atendendo a propriedade do heap, ou seja, o valor do pai (10) é inferior ao valor de ambos os filhos.Neste caso o nó que possui maior valor (30) troca com o pai.
O procedimento pára neste nível?É importante destacar algumas características do comportamento de MAX-HEAPIFY:Não. Para se garantir a propriedade do heap é necessário que ela seja válida para todos os níveis abaixo.
Quando ocorre uma troca, para ajuste do heap, apenas um dos filhos é afetado. No exemplo apresentado, apenas o lado do filho direito foi afetado. Deste modo, a sub-rotina MAX-HEAPIFY executa uma chamada recursiva a si mesma, para ordenar a sub-árvore (sub-heap) cuja raiz foi modificada. No exemplo apresentado será para a sub-árvore da direita.
Inicialmente se supõe que seja O(n lg n), dado que o MAX-HEAPIFY executa em tempo O(lg n) e o BUILD-MAX-HEAP executa aproximadamente n/2 iterações.
Uma análise mais detalhada demonstra que na árvore do heap existem no máximo teto(n/2h+1) nós raízes de sub-árvores de altura h. Como a alturas das sub-árvores na árvore principal variam entre 0 e piso(lg n) e o tempo que MAX-HEAPIFY processa um heap de altura h é O(h), o custo total para processar o heap completo será de:
Intuitivamente podemos entender este resultado percebendo que, na medida que nos deslocamos da raiz para as folhas, o número de nós aumenta, mas inversamente, o tempo demandado para o processamento de cada nó diminui.
O artifício de transformar o somatório de [0 a lg n] para [0 a infinito] foi utilizado pois, deste modo, pode-se facilmente calcular o resultado que será um valor constante (2). Como o segundo somatório é necessariamente maior que o primeiro a propriedade O se mantém.
O cálculo do número de sub-árvores de altura h pode ser entendido intuitivamente da seguinte maneira: na medida que nos deslocamos da raiz da árvore principal para as folhas, o número de sub-árvores dobra (exceto no último nível) e a altura diminui de um, ou seja, precisamos de uma equação que aumente o número de sub-árvores em uma proporção inversa a redução da altura destas sub-árvores em uma potência de dois.
Inicialmente é acionada a sub-rotina BUILD-MAX-HEAP para construir um heap do vetor inteiro. Em seguida inicia-se um processo de ordenação baseada no heap, que repete as seguintes etapas para todos os elementos do vetor, iniciando-se do último e indo em direção ao segundo:
Duas coisas interessantes podem ser observadas neste processo de ordenação:
HEAPSORT(A)Intuitivamente pode-se compreender este invariante percebendo-se que para cada valor de i do loop for, ele determina que todos os elementos do início do vetor até a posição i ainda fazem parte de um heap (com os menores elementos) e todos os elementos de posição acima de i já fazem parte de um vetor ordenado com os maiores elementos.
1 BUILD-MAX-HEAP(A)
2 for i <- comprimento[A] downto 2
3 do trocar A[1] <-> A[i]
4 tamanho-do-heap[A] <- tamanho-do-heap[A]-1
5 MAX-HEAPIFY(A, 1)No início de cada iteração do loop for das linhas 2 a 5, o subarranjo A[1..i] é um heap máximo contendo os i menores elementos A[1..n], e o subarranjo A[i+1..n] contém os n-i maiores elementos de A[1..n], ordenados.
Este invariante é verdadeiro antes da primeira iteração do loop (inicialização) pois i é igual ao comprimento do vetor, deste modo, não há elementos com posição acima de i e todos os elementos até i fazem parte do heap recém construído.
O invariante é verdadeiro a cada iteração (manutenção), pois dentro do loop ele executa inicialmente a modificação de dois componentes o da posição 1 e o da posição i, trocando-os. O elemento da posição 1 é reordenado dentro do heap pelo MAX-HEAPIFY, garantindo que o heap se refaça até a posição i-1. O elemento da posição i, por sua vez, era anteriormente o maior do heap (raiz) que foi agora inserido no início da lista ordenada. Essa lista continuará ordenada dado que ela possui os maiores elementos de todo o vetor e agora recebe um valor que é por um lado o maior do heap e por outro menor que todos os elementos do vetor ordenado, assumindo sua posição exata no vetor ordenado final. Quando o loop fecha o laço, o i assume a próxima posição e a propriedade se verifica, dado que o heap (de um lado) e o vetor ordenado (de outro) se mantém corretamente.
Finalmente o invariante é verdadeiro no final (término) dado que o i estará na posição 1. Deste modo, o heap será de apenas um elemento e portanto está necessariamente correto, do outro lado todos os elementos acima da posição i estão ordenados e são os maiores.
O Heapsort realiza uma ordenação local, ao contrário do Mergesort que, para realizar a operação de Merge, necessita de espaço adicional fora do vetor, cujo tamanho não é constante.
O Heapsort é uma solução boa para registros de tamanho grande?
Nestes casos deve-se observar duas coisas:
Pode-se observar que a estrutura do heap é eficiente para algumas operações, tais como as apresentadas, mas não será para outras. Deste modo, é importante notar que cada estrutura é mais apta para um certo conjunto de operações.
O heap não é uma estrutura interessante para auxiliar
em uma única operação de busca. Seu uso exigiria,
para uma busca, a construção de um vetor ordenado, para posteriormente
se realizar a busca. O processo total demandaria um tempo O(n lg
n) que é pior do que o algoritmo de busca seqüencial,
que possui tempo no pior caso O(n).