1. Criado por Julio Cesar de Andrade Vieira Lopes – jcalop@terra.com.br
1 - Heap
A estrutura de dados heap é um objeto arranjo que pode ser visto como uma
árvore binária completa. A árvore está completamente preenchida completamente
em todos os níveis, exceto talvez no nível mais baixo. Isso nos indica que dado um
arranjo A de elementos, o heap formado por este arranjo, é construído por nível, o
que garante que o heap será uma árvore completa; e que para realizarmos
operações sobre o heap, a complexidade será igual a log n(que é a altura da árvore
representada pelo heap).
Um arranjo A que representa um heap é um objeto com dois atributos:
comprimento[A], que é o numero de elementos no arranjo, e tamanho[A], que é o
número de elementos no heap armazenado dentro do arranjo A.
Representação de Árvore Binária
16
1
14 10
2 3
8 7 9 3
4 5 6 7
2 4 1
8 9 10
Estrutura de dados
16 14 10 8 7 9 3 2 4 1
1 2 3 4 5 6 7 8 9 10
A raiz da arvore representada pelo heap é A[1] e, dado o índice i de um nó,
os índices de seu pai PAI(i), do filho da esquerda ESQUERDA(i) e do filho da direita
DIREITA(i) podem ser calculados de modo simples:
PAI(i)
Return piso(i/2);
ESQUERDA(i)
Return 2i;
DIREITA(i)
Return 2i + 1;
2. Criado por Julio Cesar de Andrade Vieira Lopes – jcalop@terra.com.br
1.1 – Heap Máximo e Heap Mínimo
Existem dois tipos de heaps binários: heaps máximos e heaps mínimos. Em
ambos os tipos, os valores nos nós satisfazem a uma propriedade de heap, cujos
detalhes específicos dependem do tipo de heap. Em um heap máximo, a
propriedade de heap máximo é que, para todo nó i diferente da raiz,
A[PAI(i)] >= A[i]
isto é, o valor de um nó é no máximo o valor de seu pai. Desse modo, o maior
elemento em um heap máximo é armazenado na raiz, e a sub-árvore que tem raiz
em um nó contém valores menores que o próprio nó.
Um heap mínimo é organizado de modo oposto; a propriedade de heap
mínimo é que, para todo nó i diferente da raiz,
A[PAI(i)] <= A[i]
Ou seja, o menor elemento em um heap mínimo está na raiz.
3. Criado por Julio Cesar de Andrade Vieira Lopes – jcalop@terra.com.br
1.2 – Manutenção da propriedade de heap
HEAP MÁXIMO é uma sub-rotina importante para manipulação de heaps
máximos. Suas entradas são um arranjo A e um índice i para o arranjo. Quando
HEAP MÁXIMO é chamado, supomos que as arvores binárias com raízes em
ESQUERDA(i) e DIREITA(i) são heaps máximos, mas que A[i] pode ser menor que
seus filhos, violando assim a propriedade de heap máximo. A função de HEAP
MÁXIMO é deixar que o valor em A[i] “flutue para baixo” no heap máximo, de tal
forma que a sub-árvore com raiz no índice i se torne um heap máximo.
HEAP MÁXIMO(A, i)
Esquerdo <- Filho Esquerdo(i)
Direito <- Filho Direito(i)
Se esquerdo estiver no tamanho de A e seu valor for > que o valor de seu o pai i
então
Maior <- Esquerdo
Senão
então
Maior <- i
Se direito estiver no tamanho de A e seu valor for > que o valor do Maior
então
Maior <- Direito
Se o maior for diferente de i(pai)
então
troca(A,i,Maior)
Heap Maximo(A, Maior)
Heap Máximo
16 16
1 1
4 10 14 10
2 3 2 3
14 7 9 3 4 7 9 3
4 5 6 7 4 5 6 7
2 8 1 2 8 1
8 9 10 8 9 10
16
1
14 10
2 3
8 7 9 3
4 5 6 7
2 4 1
8 9 10
4. Criado por Julio Cesar de Andrade Vieira Lopes – jcalop@terra.com.br
1.3 – Construção do Heap
Podemos utilizar o procedimento HEAP MÁXIMO de baixo para cima, a fim de
converter um arranjo A[1..n], onde n = comprimento[A], em um heap máximo. Os
elementos no sub-arranjo A[(piso(n/2)+1)..n] são todos folhas da árvore, e então
cada um deles é um heap de 1 elemento com o qual podemos começar. Como cada
folha não possui nenhum filho, um nó folha pode ser considerado como sendo uma
sub-árvore que é um heap máximo. Portanto, para construir o heap, devemos
começar sua construção no primeiro nó interno da árvore.
HEAP CONSTRÓI(A)
tamanho[A] = comprimento[A]
Para todo i <- comprimento[A]/2 até 1(inclusive o 1)
Faça HEAP MÁXIMO(A, i);
Heap antes da chamada de HEAP MÁXIMO:
4
1
4 1 3 2 16 9 10 14 8 7 1 3
1 2 3 4 5 6 7 8 9 10 2 3
2 16 9 10
4 5 6 7
14 8 7
8 9 10
Heap depois da chamada de HEAP MÁXIMO:
16
1
16 14 10 8 7 9 3 2 4 1 14 10
1 2 3 4 5 6 7 8 9 10 2 3
8 7 9 3
4 5 6 7
2 4 1
8 9 10
6. Criado por Julio Cesar de Andrade Vieira Lopes – jcalop@terra.com.br
1.4 – Filas de Prioridades
A estrutura de dados heap propriamente dita tem uma utilidade enorme. Uma
das aplicações mais populares de um heap é o seu uso como fila de prioridades
eficiente. Como ocorre no caso dos heaps, existem dois tipos de filas de prioridades:
as filas de prioridade máxima e as filas de prioridade mínima.
Uma aplicação de filas de prioridade máxima é escalonar processos em um
sistema multi-programável. A fila de prioridade máxima mantém o controle dos
processos a serem executados e de suas prioridades relativas. Quando um processo
temina ou é interrompido, o processo de prioridade mais alta é selecionado dentre os
processos pendentes. Um novo processo pode ser adicionado a qualquer instante,
assim como um processo existente pode ter sua prioridade alterada. Podemos definir
então as seguintes operações sobre a fila de prioridades estruturada por um heap
máximo:
HEAP REMOVE(A)
Se tamanho[A]<1:
Erro;
max <- A[1];
A[1] <- A[tamanho[A]];
A[tamanho[A]] <- max;
tamanho[A] <- tamanho[A]-1;
HEAP MÁXIMO(A, 1);
return max;
Remoção de um nó do heap:
16 1
1 1
14 10 14 10
2 3 2 3
8 7 9 3 8 7 9 3
4 5 6 7 4 5 6 7
2 4 1 2 4 16
8 9 10 8 9 10
Retira-se sempre o 1º nó Troca o 1º nó com o último, e
passa a se desconsiderar o último
nó(tamanho do heap é
decrementado).
14
1
8 10 Realiza-se então o
procedimento HEAP MÁXIMO,
2 3
4 7 9 3
para reestabelecer a
propriedade de heap máximo,
4 5 6 7 resultando no heap ao lado.
2 1 16
8 9 10
7. Criado por Julio Cesar de Andrade Vieira Lopes – jcalop@terra.com.br
HEAP INSERE(A, chave)
tamanho[A] <- tamanho[A]+1;
A[tamanho[A]] <- chave;
HEAP SUBIR(A, tamanho[A]);
HEAP SUBIR(A, i)
j <- PAI(i);
Se i >1:
{
Se A[j] < A[i]:
troca A[i] <-> A[j];
HEAP SUBIR(A, j);
}
Inserção de um nó no heap: Inserindo: 50
16 16
1 1
14 10 14 10
2 3 2 3
8 7 9 3 8 50 9 3
4 5 6 7 4 5 6 7
2 4 1 50 2 4 1 7
8 9 10 11 8 9 10 11
Inserimos o nó sempre no Realiza-se então o
último nível(final do heap). procedimento HEAP SUBIR,
para re-estabelecer a
propriedade de heap máximo.
16 50
1 1
50 10 16 10
2 3 2 3
8 14 9 3 8 14 9 3
4 5 6 7 4 5 6 7
2 4 1 7 2 4 1 7
8 9 10 11 8 9 10 11
HEAP ALTERA PRIORIDADE(A, chave, nova chave)
Para todo i=1 até tamanho[A](inclusive) && A[i] != chave
Faça i++;
Se i > tamanho[A]:
Retorna erro; /* Não achou */
A[i] <- nova chave;
HEAP SUBIR(A, i);
8. Criado por Julio Cesar de Andrade Vieira Lopes – jcalop@terra.com.br
Alteração de um nó do heap: Alterando a chave 7 para 100
50 50
1 1
16 10 16 10
2 3 2 3
8 14 9 3 8 100 9 3
4 5 6 7 4 5 6 7
2 4 1 100 2 4 1 14
8 9 10 11 8 9 10 11
Achamos o nó e alteramos seu Realiza-se então o
valor. procedimento HEAP SUBIR,
para re-estabelecer a
propriedade de heap máximo.
50
100
1
100 10 1
50 10
2 3
8 16 9 3 2 3
8 16 9 3
4 5 6 7
4 5 6 7
2 4 1 14
2 4 1 14
8 9 10 11
8 9 10 11
1.4.1 – Comparação Heap x Listas
Operação Lista Não Ordenada Lista Ordenada Heap
Seleção O(n) O(1) O(1)
Inserção O(1) O(n) O(log n)
Remoção O(n) O(1) O(log n)
Alteração O(n) O(n) O(n)
Construção O(n) O(n log n) O(n)
9. Criado por Julio Cesar de Andrade Vieira Lopes – jcalop@terra.com.br
1.5 – Heapsort
O algoritmo heapsort começa usando HEAP CONSTRÓI para construir um
heap no arranjo de entrada A[1..n], onde n = comprimento[A]. Tendo em vista que
o elemento máximo do arranjo está na raiz do heap A[1], este pode ser colocado em
sua posição final correta, trocando-se esse elemento com A[tamanho[A]]; Isto é
realizado pelo procedimento HEAP REMOVE, que após realizar esta tarefa,
decrementa o tamanho[A], e verifica se a nova raiz do heap, A[1], quebra a
propriedade de heap máximo, transformando novamente, o arranjo em um heap
máximo. Então o algoritmo heapsort repete esse processo para um heap de
tamanho[A] decrenscendo até um heap de tamanho 1.
Após a execução do algoritmo de heapsort, o arranjo de entrada A[1..n] terá
sido ordenado crescentemente, onde o tamanho[A] será igual a 1, e o
comprimento[A] = n;
HEAPSORT(A)
HEAP CONSTRÓI(A);
Enquanto tamanho[A] > 1
HEAP REMOVE(A);
1.5.1 – Complexidade
Como a complexidade de manter HEAP MÁXIMO é O(log n), o algoritmo de
construção HEAP CONTRÓI tem sua complexidade em torno de O(n log n). Já a
complexidade do algoritmo de remoção fica em torno de log n, pois a complexidade
de remover um elemento do heap é O(1), mas para manter a propriedade de heap
máximo, usamos o procedimento HEAP MÁXIMO que é O(log n).
Portanto, o algoritmo HEAPSORT, tem sua complexidade em torno de O(2n
log n), pois a construção do heap é O(n log n) e a remoção dos n elementos do heap
é O(n log n).