1.6 Filas de Prioridade Implementadas por Heaps Binários Motivação: Vimos que as soluções anteriores para implementar filas de prioridade têm limitações que podem levar a uma degradação de performance em alguns casos (algoritmos lineares O(n)). Então devemos procurar por soluções mais eficientes!!! Heap é um tipo especial de árvore binária utilizado para implementar eficientemente filas de prioridade. Características do Heap Binário: - Simplicidade: implementado num array - Eficiente: inserção e remoção no pior caso é O(logN) inserção: tempo médio constante findMin: O(1) no pior caso Heap Binário apresenta duas propriedades: 1. Propriedade de Estrutura 2. Propriedade de Ordem 1.6.1 Propriedade de Estrutura Um heap é uma árvore binária completa cujos valores estão armazenados em seus nós de tal forma que nenhum filho possui valor maior do que seu pai. Revisando: Uma árvore binária completa é uma árvore que é completamente preenchida, com exceção dos nós folhas, que são preenchidos da esquerda para direita. Ex.1: Árvore Binária Completa 1 B 2 4 H 8 A D 5 E I J 9 1 10 6 3 C F 7 G Ex.2: Uma árvore binária incompleta 1 2 4 H 8 D A B 5 3 E 6 C F J 10 Exercício: Crie uma árvore binária completa de pesquisa para os seguinte conjunto { 10, 30, 40, 20, 55, 24, 76, 63, 98, 87, 25, 12, 9, 3} Propriedades de uma árvore binária completa: A altura de uma árvore de N nós é no máximo log N, pois uma árvore completa de altura H tem entre 2H e 2H + 1 – 1 nós. As referências esquerda e direita não são necessárias, pois podemos implementar a árvore com caminhamento por níveis (esquerda-direita) num array. Ex.3. A árvore do Ex.1. pode ser representada pelo seguinte array: A B C D E F 0 1 2 3 4 5 6 G H I J 7 8 9 10 11 12 13 Algoritmo para representação de uma árvore binária completa num array: 1. Coloca-se a raiz na posição 1 (para simplificar a operação de achar o pai de um elemento) 2. Manter uma variável que contém quantos nós existem na árvore 3. Para cada elemento i no array - o filho a esquerda está na posição 2i - o filho à direita está na posição 2i + 1 Obs.: (se esta posição (2i ou 2i+1) ultrapassa o número de nós => este filho não existe) - o pai está na posição i/2 Esta representação de árvore binária completa num array é chamada de representação implícita e otimiza a performance de caminhamento na árvore uma vez que apenas uma operação matemática é necessária para acessar um filho ou pai (facilita o caminhamento). 1.6.2 Propriedade de Ordem de um Heap Propriedade que permite a execução rápida de operações sobre heaps Uma vez que a operação de remoção é efetuada sempre sobre o elemento de maior chave, é interessante que ele se encontre sempre na raiz Definição da Propriedade de Ordem: Em um heap, para todo nó X, a chave do pai de X é menor ou igual à chave de X, com a exceção óbvia da raiz (não tem pai). Ex. 4. Uma árvore binária completa que é um heap 1 4 2 30 36 5 42 10 3 65 78 7 6 45 55 80 8 9 1 10 97 2 Ex. 5. Uma árvore binária completa que não é um heap 1 4 2 30 16 5 42 10 3 65 76 7 90 6 45 55 80 8 9 10 1 Exercício: Desenhe um heap para o seguinte conjunto de inteiros {10,25,30,35, 40, 45, 50, 55, 60, 65} 1.6.3 Operações básicas sobre Heaps No nosso caso, queremos usar heap para implementar filas de prioridade, portanto, o heap deve implementar as operções do TAD FilaPrioridade que estudamos anteriormente: inserir(x) removerMin() encontrarMin() ehVazio() tamanho() Vamos enfatizar os métodos insert(x) e removeMin() que são os mais importantes, deixamos os outros métodos como exercicio. Inserção O algoritmo de inserção contém os seguintes passos: 1. Criar um espaço na próxima posição disponivel 2. Se x puder ser colocado nesse espaço novo sem violar as propriedades do heap, inserimos x naquela posição 3. Senão, deslocamos o pai desse novo nó criado para ocupá-lo e o espaço que era do pai fica disponível para a inserção de x 4. Repetir o processo de deslocamento até que o elemento x possa ser inserido no espaço disponível sem burlar as propriedades de heaps. Ex.6. Inserir o elemento de chave 15 no heap abaixo 1 4 2 30 35 5 42 10 3 65 76 7 90 6 45 55 80 9 8 10 1 i. Criar um novo espaço no heap 1 4 2 30 35 5 42 10 3 65 76 7 6 45 55 80 8 9 1 10 11 90 ii. Tenta inserir “15” no novo espaço criado, caso não possa baixa o pai para a nova posição 1 4 10 2 30 3 65 35 5 76 7 90 6 45 55 80 42 9 8 10 11 1 1 2 4 35 5 30 10 3 65 76 7 6 45 55 80 42 8 9 1 10 11 90 1 4 2 15 35 5 30 10 3 65 76 7 90 6 45 55 80 42 8 9 10 11 1 Algoritmo de Inserção: Remoção em Heaps Remoção é tratada da mesma forma que inserção Achar a menor chave é simples devido à propriedade de ordem A parte mais difícil é tratar o espaço deixado na raiz após remoção do elemento, de modo a não violar as propriedades do heap Algoritmo de remoção: 1. Remover o elemento da raiz criando um espaco na raiz 2. Mover o último elemento x do heap para algum lugar 3. Se x puder ser colocado no espaço criado, fim 4. Senão, mover o menor dos filhos do nodo com espaço disponível para o espaço disponível. O espaço disponível desce um nível 5. Repetir o passo 4 até que x ocupe o espaço disponível Ex7. Remoção de um heap binário 1 4 2 30 35 5 10 42 3 65 76 7 90 6 45 55 80 9 8 10 1 i- Remove a raiz 1 4 2 30 35 5 42 3 65 76 7 6 45 55 80 8 9 1 10 90 ii- Elege o menor dos filhos para ser raiz 30 1 2 35 4 5 42 3 65 76 7 90 6 45 55 80 9 8 10 1 iii – Tenta inserir o ultimo elemento (80) no espaço criado, como não pode, escolhe o menor dos filhos para subir para o espaço livre (35) 1 35 2 4 5 42 30 3 65 76 7 6 45 55 80 8 9 1 10 90 iv- Tenta inserir o último elemento (80) no novo espaço criado, como não consegue, escolhe o menor dos filhos do espaço livre (45). 1 4 2 35 45 5 42 30 3 65 76 7 6 55 80 8 9 1 10 90 v – Finalmente, consegue inserir o último elemento (80) no espaço livre e o algoritmo termina. 1 4 2 35 45 5 42 30 3 65 76 7 6 80 55 8 9 1 90 Exemplo de implementação da interface de Fila de Prioridade usando Heap Binário representado em Array. public class HeapBinario implements FilaPrioridade { private int tamanho; private boolean ordemOK; // True se ordem do heap esta OK private Comparador [] heap; private static final int CAPACIDADE_DEFAULT = 11; public HeapBinario (Comparador negInf ) { tamanho = 0; ordemOK = true; heap = new Comparador [CAPACIDADE_DEFAULT + 1]; heap[0] = negInf; // sentinela que contem um valor menor que todos do heap } public void inserir (Comparador x) { checkTamanho(); int lugar = ++ tamanho; for (; x.ehMenorQue( heap[lugar/2] ); lugar /= 2) heap [lugar] = heap[lugar/2]; heap[lugar] = x; } public Object encontrarMin() throws Exception { if ( ehVazio() ) throw new Exception("ERRO: Heap Vazio"); return heap[1]; } public Object removerMin() throws Exception { Comparador minItem = (Comparador) encontrarMin(); int filho; int lugar = 1; heap[1] = heap[tamanho--]; Comparador tmp = heap[1]; for (; lugar * 2 <= tamanho; lugar = filho) { filho = lugar * 2; if (filho != tamanho && 1].ehMenorQue(heap[filho])) filho++; if (heap[filho].ehMenorQue(tmp)) heap[lugar] = heap[filho]; else break; } heap[lugar] = tmp; return minItem; } public boolean ehVazio() { return tamanho == 0; } heap[filho + private void checkTamanho() { if ( tamanho == heap.length - 1 ) { Comparador [] heapAnterior = heap; heap = new Comparador[tamanho*2]; for (int i=0; i < heapAnterior.length; i++) heap[i] = heapAnterior[i]; } } public int tamanho () { return tamanho; } } public class TesteHB { public static void main (String[] args) { HeapBinario fp = new HeapBinario(new MeuInteger(0)); fp.inserir(new MeuInteger(10)); fp.inserir(new MeuInteger(20)); fp.inserir(new MeuInteger(5)); fp.inserir(new MeuInteger(40)); fp.inserir(new MeuInteger(50)); fp.inserir(new MeuInteger(15)); try { MeuInteger v = (MeuInteger) fp.removerMin(); System.out.println(" v= " + v.getX()); v = (MeuInteger) fp.encontrarMin(); System.out.println(" EncontrarMin v= " + v.getX()); v = (MeuInteger) fp.removerMin(); System.out.println(" v= " + v.getX()); v = (MeuInteger) fp.encontrarMin(); System.out.println("Encontrar Min = " + v.getX()); v = (MeuInteger) fp.removerMin(); System.out.println(" v= " + v.getX()); v = (MeuInteger)fp.removerMin(); System.out.println(" v= " + v.getX()); v = (MeuInteger)fp.removerMin(); System.out.println(" v= " + v.getX()); v = (MeuInteger)fp.removerMin(); System.out.println(" v= " + v.getX()); v = (MeuInteger)fp.removerMin(); System.out.println(" v= " + v.getX()); v = (MeuInteger)fp.removerMin(); System.out.println(" v= " + v.getX()); } catch (Exception e) {System.err.println(e.toString());} } } Resultado da execução anterior: C:\EstruturaDados\Programas>java TesteHB v= 5 EncontrarMin v= 10 v= 10 EncontarMin = 15 v= 15 v= 20 v= 40 v= 50 java.lang.Exception: ERRO: Heap Vazio