830 likes | 1.01k Views
Heaps e Filas de Prioridade. Fundamentos. Filas de Prioridade. Uma fila de prioridade é uma lista de itens na qual cada item possui uma prioridade associada. Pode-se determinar o item que tem a maior e o que tem a menor prioridade, por exemplo.
E N D
Filas de Prioridade • Uma fila de prioridade é uma lista de itens na qual cada item possui uma prioridade associada. • Pode-se determinar o item que tem a maior e o que tem a menor prioridade, por exemplo. • Itens são inseridos em filas de prioridade em qualquer ordem. A exclusão de itens é feita em ordem decrescente de prioridade.
Filas de Prioridade • Filas de prioridade são containers que possuem as seguintes operações: • enqueue • colocar objetos no container; • findMin (ou findMax) • retornar o objeto de menor prioridade no container • dequeueMin ( ou dequeueMax) • remover o menor objeto do container. • Uma fila de prioridades é usada para armazenar um conjunto de chaves de um conjunto ordenado de chaves K.
Árvore Binária Perfeita • Definição (Árvore Binária Perfeita) Uma árvore binária perfeita de altura h 0, é uma árvore binária {R, TL, TR} com as seguintes propriedades. • Se h=0, TL = 0 e TR = 0. • Para h>0 tanto TL quanto TR são árvores binárias perfeitas de altura h-1.
Árvore Binária Completa • Definição (Árvore Binária Completa) Uma árvore binária completa de altura h 0, é uma árvore binária {R, TL, TR} com as seguintes propriedades. • Se h=0, TL = 0 e TR = 0. • Para h>0 existem duas possibilidades • TL é uma árvore binária perfeita de altura h-1 e TR é uma árvore binária completa de altura h-1; ou • TL é uma árvore binária completa de altura h-1 e TR é uma árvore binária perfeita de altura h-2.
Árvore N-aria Completa • Definição (Árvore N-aria Completa) Uma árvore N-aria completa de altura h 0, é uma árvore N-aria {R, T0, T1, T2, ... TN-1} com as seguintes propriedades. • Se h=0, T1 = para todo i, 0 i < N. • Para h>0 existe um j, 0 j < N tal que • Ti é uma árvore N-aria perfeita de altura h-1 para todo i: 0 i < j; • Tj é uma árvore N-aria completa de altura h-1; • Ti é uma árvore N-aria perfeita de altura h-2 para todo i:j<i<N.
Árvore N-aria completa • Uma árvore completa é uma árvore na qual todos os níveis estão cheios exceto o último e este nível é cheio da esquerda para a direita.
Heap • As implementações de filas de prioridade são baseadas em um tipo especial de árvore chamado de heap • Há dois tipos de heap (min e max) • Definição ((Min) Heap) Um (Min) Heap é uma árvore, {R, T0, T1, T2, ... TN-1} com as seguintes propriedades: • Cada sub árvore de T é um heap, • A raiz de T é menor ou igual do que a raiz de cada sub árvore de T. Ou seja, R Ri para todo i, 0 i n , aonde Ri é a raiz de Ti.
Heap Binário • Um heap binário é uma árvore binária ordenada que tem a forma de árvore completa. • Um heap binário pode ser implementado sobre um “array”, o que dispensa o armazenamento de ponteiros para a montagem da árvore.
Incluir Itens em um Heap Binário • Como o heap resultante da inclusão deve ser uma árvore completa só existe um local para a inclusão. • Para incluir o item de chave 2 no heap da figura verifica-se a posição da inclusão (figura (a) ). A seguir reajusta-se o heap até chegar à posição adequada (figuras (b) e (c)). Finalmente faz-se a inclusão (figura (d)).
Remover Itens de um Heap Binário O menor dos itens está sempre na raiz de um heap mínimo. • A última linha do heap deve ser esvaziada da direita para a esquerda na remoção de itens. • O dado a ser removido está na raiz mas o nó a ser removido está em folha.
Exemplo de Heap • No heap da figura a operação dequeueMin remove a chave 2 do heap mas o nó contendo 6 é que deve ser removido. • O buraco deixado pela remoção da raiz deve ser reposicionado no heap, descendo até permitir a reinserção na árvore da chave 6 que ocupava a posição do heap a ser eliminada • Para afastar da raiz um buraco este troca de lugar com o menor de seus dois filhos. • O processo continua até o buraco atingir uma folha da árvore ou uma posição adequada à chave que necessita ser reposicionada na árvore.
Interface PriorityQueue // pgm11_01.txt public interface PriorityQueue extends Container { void enqueue (Comparable object); Comparable findMin (); Comparable dequeueMin (); }
Interface MergeablePriorityQueue // pgm11_02.txt public interface MergeablePriorityQueue extends PriorityQueue { void merge (MergeablePriorityQueue queue); }
Campos de BinaryHeap // pgm11_03.txt public class BinaryHeap extends AbstractContainer implements PriorityQueue { protected Comparable[] array; // ... }
Métodos Construtor e purge da classe BinaryHeap // pgm11_04.txt public class BinaryHeap extends AbstractContainer implements PriorityQueue { protected Comparable[] array; public BinaryHeap (int length) { array = new Comparable[length + 1]; } public void purge () { while (count > 0) array [count--] = null; } // ... }
Método enqueue da classe BinaryHeap • Como o heap resultante da inclusão deve ser uma árvore completa só existe um local para a inclusão • Este local (vazio ou buraco) vai “subir” no heap até encontrar seu ligar, deixando para baixo os valores menores do que o objeto o incluir no heap • A “subida” no heap é feita buscando-se a posição do array obtida da divisão por dois da posição corrente • Quando o buraco parar de “subir” nele inclui-se o objeto
Método enqueue da classe BinaryHeap // pgm11_05.txt public void enqueue (Comparable object) { if (count == array.length - 1) throw new ContainerFullException (); ++count; int i = count; while (i > 1 && array [i/2].isGT (object)) { array [i] = array [i / 2]; i /= 2; } array [i] = object; }
Método findMin da classe BinaryHeap // pgm11_06.txt public class BinaryHeap extends AbstractContainer implements PriorityQueue { protected Comparable[] array; public Comparable findMin () { if (count == 0) throw new ContainerEmptyException (); return array [1]; } // ... }
Método dequeue da classe BinaryHeap • O elemento a ser removido é o da raiz do heap • O objeto que estava no último nó do heap deve procurar seu lugar no heap pois esse nó vai desaparecer • Inicia-se pela raiz, agora vazia, testando-se seus dois filhos para verificar o menor • Se o objeto que estava no último nó for menor que os filhos da raiz termina a repetição • Caso contrário a raiz (objeto que estava no último nó) troca de lugar com o menor de seus dois filhos e passa a ser a raiz do heap corrente a pesquisar • Quando a repetição termina a raiz no heap corrente recebe o objeto que estava no último nó
Método dequeue da classe BinaryHeap public Comparable dequeueMin () { if (count == 0) throw new ContainerEmptyException (); Comparable result = array [1]; Comparable last = array [count]; --count; int i = 1; while (2 * i < count + 1) { int child = 2 * i; if (child + 1 < count + 1 && array [child + 1].isLT (array [child])) child += 1; if (last.isLE (array [child])) break; array [i] = array [child]; i = child; } array [i] = last; return result; }
Exemplos • Serão apresentados dois exemplos de uso de Filas de Prioridade • O primeiro exemplo é absolutamente trivial com inclusão e exclusão de objetos manualmente, via teclado • O segundo exemplo consiste em uma simulação de eventos discretos
Exemplo de uso de Fila de Prioridade em simples entrada e saída de dados • Exemplo usando o teclado para inserir dados inteiros em uma fila de prioridade • O usuário recebe um “prompt” para escolher entre inserir, remover dados, listas a fila de prioridade ou encerrar o programa • Caso escolha a inserção o usuário deve entrar com o dado
Composição do código • A hierarquia de classes já foi apresentada no estudo da classe BinaryHeap • Serão apresentadas as classes específicas desta aplicação • Classe de Dados • Classe da Aplicação Fila de Prioridade
Classe de Dados (1) import cap5.*; import java.io.*; public class DataArea extends AbstractObject implements Serializable { private String numero; private String transacao; final private int TAMANHO_INT = 2; public DataArea() { this.numero = ""; } public DataArea( String numero) { if ( numero.length() < TAMANHO_INT ) { numero = "0"+numero; } this.numero = numero; }
Classe de Dados (2) public DataArea( BufferedReader br ) throws IOException { int i = 0; String s, line = br.readLine(); if ( line == null ) { transacao = "err"; return; } transacao = line.substring(i,1); i = 2; if ( transacao.compareToIgnoreCase( "i" ) == 0 ) { numero = line.substring(i,i+TAMANHO_INT); i += TAMANHO_INT+1; nome = line.substring(i,i+TAMANHO_NOME); i += TAMANHO_NOME+2; s = line.substring(i,line.length()); salario = Double.parseDouble( s ); } }
Classe de Dados (3) public String getNumero() { return numero; } public String getTransacao() { return transacao; } public void setNumero( String numero ) { this.numero = numero; } } public void imprime() { System.out.println ("Numero = " + numero ); } protected int compareTo (cap5.Comparable object) { DataArea arg = (DataArea) object; return numero.compareToIgnoreCase( arg.numero ); } }
FilaPrioridade01 (1) import cap5.*; import dados.*; import java.io.*; import corejava.*; public class FilaPrioridade01 { private static void lerDados( PriorityQueue listaP ) { DataArea data = null; String transacao = "ok"; try { BufferedReader br = new BufferedReader( new FileReader("xxxx.zzz") ); while ( transacao.compareToIgnoreCase( "err" ) != 0 ) { data = new DataArea( br ); transacao = data.getTransacao(); if ( transacao.compareToIgnoreCase( "i" ) == 0 ) stack.push( data ); } br.close(); } catch(Exception e) { } }
FilaPrioridade01 (2) public static void main (String[] args) { String numero; DataArea data; Enumeration todos; PriorityQueue filaP = new LeftistHeap(); lerDados( listaP ); boolean continua = true; while (continua) { System.out.println('\n' + “=================="); System.out.println('\n' + "O que deseja fazer?"); System.out.println('\n' + "1. Inserir"); System.out.println("2. Remover"); System.out.println("3. Listar"); System.out.println("4. Sair");
FilaPrioridade01 (3) int opcao = Console.readInt('\n' + "Digite um número entre 1 e 4:"); switch (opcao) { case 1: { numero = Console.readLine('\n' + "Digite o numero: "); data = new DataArea (numero); filaP.enqueue(data); System.out.println('\n' + "Inserido com sucesso!"); break; } case 2: // Remover { data = (DataArea) filaP.dequeue(); System.out.println('\n' + data.getNumero()+ " removido com sucesso!"); break; }
FilaPrioridade01 (4) case 3: // Listar tudo { todos = filaP.getEnumeration(); while (todos.hasMoreElements ()) { System.out.println(""); data = (DataArea)todos.nextElement(); data.imprime(); } break; } case 4: // Sair break; default: System.out.println('\n' + "Opção inválida!"); } } } }
Exemplo de uso de Fila de Prioridade em Simulação de Eventos Discretos • Uma simulação de eventos discretos servirá para ilustrar um emprego das filas de prioridade • Etapas da simulação • Geração do modelo matemático • Programa para avaliação do modelo • Sistemas possuem: • Estados • Eventos (mudanças de estado)
Ambiente escolhido para a simulação • Atendimento a clientes em um caixa de banco • O estado do sistema pode ser caracterizado por: • Estado do atendente de caixa (ocupado, desocupado) • Número de clientes na fila
Características do Modelo • Eventos • Tipo • Chegada (arrival) • Partida (departure) • Tempo • Association (utilizada para fazer comparações de prioridade com um atributo chave em vez de com todo o objeto) • Tempo (chave) • Tipo (valor)
Implementação do exemplo (1) • Simulação de sistema composto de: • Fila • Atendente • A classe Event representa os eventos em simulação • Uma fila de prioridade simulará o atendimento e a prioridade será por tempo
Implementação do exemplo (2) • A classe Simulation contém uma fila de prioridade eventList que armazena os eventos • O estado do sistema é retratado pelas variáveis: • serverBusy (atendente ocupado sim/não ?) • numberInQueue (número de clientes aguardando atendimento)
Implementação do exemplo (3) • O método run implementa a simulação e recebe um argumento timeLimit (tempo total da simulação) • A chegada de clientes será modelada por instâncias da classe ExponentialRV (geradora de pseudo aleatórios): • serviceTime (tempo de um atendimento) • interArrivalTime (intervalo entre chegaadas) • A interface RandomVariable tem o método nextDouble que busca um novo valor que neste exemplo teve distribuição com média 100 para ambas as variáveis
Implementação do exemplo (4) • Fila inicialmente vazia • Um cliente chega no tempo zero • Repetição até que a lista de eventos fique vazia • Cada iteração retira um elemento da fila de prioridade • Quando o tempo do evento a ser atendido ultrapasse timeLimit termina a simulação • Os eventos podem ser de chegada ou de partida
Implementação do exemplo (5) • Atendente desocupado torna-se ocupado na chegada de um cliente disparando o gerador de tempo de atendimento e calculando o tempo de saída correspondente • Atendente ocupado faz aumentar numberInQueue • Depois de cada chegada dispara o gerador de tempo para nova chegada
Implementação do exemplo (6) • Evento de saída e fila vazia tornam atendente desocupado • Evento de saída e fila não vazia chamam o próximo na fila disparando o gerador de tempo de atendimento e calculando o tempo de saída correspondente
Lembrete: Classe Association public class Association extends AbstractObject { protected Comparable key; protected Object value; public Association (Comparable key, Object value) { this.key = key; this.value = value; } public Association (Comparable key) { this (key, null); } public Comparable getKey () { return key; } public Object getValue () { return value; } protected int compareTo (Comparable object) { Association association = (Association) object; return key.compare (association.getKey ()); } }
Classe Event // pgm11_22.txt public class Simulation { static class Event extends Association { public static final int arrival = 0; public static final int departure = 1; Event (int type, double time) { super (new Dbl (time), new Int (type)); } double getTime () { return ((Dbl) getKey ()).doubleValue (); } int getType () { return ((Int) getValue ()).intValue (); } } }
Classe Simulation: Inicialização // pgm11_23.txt public class Simulation { PriorityQueue eventList = new LeftistHeap (); public void run (double timeLimit) { boolean serverBusy = false; int numberInQueue = 0; RandomVariable serviceTime = new ExponentialRV (100.); RandomVariable interArrivalTime = new ExponentialRV (100.); eventList.enqueue (new Event (Event.arrival, 0));
Classe Simulation: Tratamento do evento de chegada while (!eventList.isEmpty ()) { Event event = (Event) eventList.dequeueMin (); double t = event.getTime (); if (t > timeLimit) { eventList.purge (); break; } switch (event.getType ()) { case Event.arrival: if (!serverBusy) { serverBusy = true; eventList.enqueue (new Event(Event.departure, t + serviceTime.nextDouble ())); } else ++numberInQueue; eventList.enqueue (new Event (Event.arrival, t + interArrivalTime.nextDouble ())); break;