Estruturas de Dados
Filas
Prof. Ricardo J. G. B. Campello
Parte deste material é baseado em adaptações e extensões de slides disponíveis em http://ww3.datastructures.net (Goodrich & Tamassia).
O TAD Fila
Inserções e remoções seguem a política “First-in First-out”
Inserções são feitas sempre no FIM da fila, e as remoções sempre do INÍCIO da fila Principais operações:
enqueue(F, x): acrescenta um
novo elemento x no final (cauda) da fila F
dequeue(F): remove e retorna
o elemento que está na frente (início) da fila F
Operações auxiliares:
front(F): retorna o elemento
da frente sem removê-lo
size(F): retorna o no. de
elementos na fila F
fila_vazia(F): indica se a fila
F está vazia ou não
3
Aplicações de Filas
Aplicações Diretas:
Filas de espera
Acesso a recursos compartilhados
Buffers(p. ex. impressoras) Multi-programação (threads)
Aplicações Indiretas:
Componente de outras estruturas mais complexas
Estrutura auxiliar para determinados algoritmos
4
É trivial implementar uma fila utilizando um vetor
uma vez que já sabemos como implementar uma
lista estática seqüencial:
Basta restringir a lista às seguintes operações:
Inserir no Início Remover no final
ou vice-versa...
O problema é que inserir ou remover no início de
um vetor armazenando n elementos é ineficiente...
demanda O(n) deslocamentos...
porque esse problema não ocorre com as pilhas ?
5
Implementação Baseada em Arranjo
Solução mais eficiente:
Use um arranjo (vetor) de tamanho MAX + 1 como estrutura circular Opera em ambos os extremos sem demandar deslocamentos!
Além do arranjo, ao menos duas variáveis devem ser mantidas:
f índice que aponta para o elemento da frente
c índice que aponta para o elemento seguinte ao da cauda
Configuração 1
Q
0 1 2
f
c
MAXQ
0 1 2
c
f
Configuração 2
MAXAlgoritmo
fila_vazia(F)
se
( F.c = F.f )
retorne
true
senão
retorne
false
/* O(1) */Operações:
Garantir ao menos uma célula
livre no arranjo assegura que podemos checar se a fila está vazia verificando se c = fe cheia verificando se c = f−1
Caso contrário, essas
condições coincidiriam
Implementação Baseada em Arranjo
Q
0 1 2
f
c
Q
0 1 2
c
f
OBS. Opcionalmente pode-se manter um campo F.nelem
7
Algoritmo
fila_cheia(F)
se
( F.c = F.f – 1 )
retorne
true
senão
retorne
false
/* O(1) */Operações:
Garantir ao menos uma célula
livre no arranjo assegura que podemos checar se a fila está vazia verificando se c = fe cheia verificando se c = f−1
Caso contrário, essas
condições coincidiriam
Implementação Baseada em Arranjo
Q
0 1 2
f
c
Q
0 1 2
c
f
OBS. Opcionalmente pode-se manter um campo F.nelem
e retornar o valor lógico (F.nelem== MAX)
Algoritmo
enqueue(F, x)
se
fila_cheia(F)
então
retorne
false
senão
F.Q[F.c]
←
x
F.c
←
(F.c + 1) mod (MAX + 1)
retorne
true
/
* O(1) */
Implementação Baseada em Arranjo
Operações:
Operador mod (resto inteiro da divisão inteira): x mod y = x – piso(x/y)*y
Q
0 1 2
f
c
Q
9
Algoritmo
dequeue(F)
se
fila_vazia(F)
então
retorne
null
senão
x
←
F.Q[F.f ]
F.f
←
(F.f + 1) mod (MAX + 1)
retorne
x
/* O(1) */
Implementação Baseada em Arranjo
Operações:
Q
0 1 2
f
c
Q
0 1 2
f
c
Desempenho e Limitações
Desempenho:
Seja n o número de elementos na fila
O espaço utilizado (memória) é O(MAX) Cada operação roda em tempo O(1)
Limitação:
O tamanho máximo da fila deve ser definido
a
priori
e não pode ser alterado a não ser copiando
toda a fila para uma outra, de capacidade maior
11
Implementação Encadeada
Para eliminar a necessidade de prever o tamanho
máx. da fila, utiliza-se uma implementação dinâmica
Encadeamento simples é suficiente:
Inserçõesno final Remoções noinício
O espaço utilizado (memória) é
O(n)
Cada operação roda em tempo
O(1)
A B C D ∅∅∅∅
inicio (head) final (tail)
12
1.
Aloque o novo nodo.
2.
Insira o novo elemento.
3.
Faça o novo nodo
apontar para nil.
4.
Faça o antigo último
nodo apontar para o
novo.
5.
Atualize
tail
para o novo
nodo.
13
1.
Aponte um ponteiro
auxiliar
Pa
para
head
2.
Aponte
head
para o
próximo nodo.
3.
Desaloque o primeiro
nodo usando
Pa
Implementação Encadeada (dequeue)
Por simplicidade, implementaremos a fila usando a
implementação simplesmente encadeada do TAD Lista
Logo, assume-se que foram predefinidos os tipos:
nodo, tipo_elem, Lista.
p. ex. via #include arquivo .h (header) da lista
Implementação Encadeada
typedef struct {
Lista *Lis_din;
} Fila;
15
Implementação Encadeada
Fila *define(void){
Fila *F;
F = malloc(sizeof(Fila));
(F->Lis_din) =
Definir
();
return F;
}
/* O(1), pois Definir é O(1) */16
Implementação Encadeada
int size(Fila *F){
return
Tamanho
(F->Lis_din);
}
/* O(1), pois Tamanho é O(1) */bool fila_vazia(Fila *F){
return (size(F)==0);
17
Implementação Encadeada
nodo *enqueue(tipo_elem x, Fila *F){
return Inserir_final(x, F->Lis_din); } /* O(1), pois Inserir_final é O(1) */
tipo_elem dequeue(Fila *F){ if (!fila_vazia(F))
return Remover_frente(F->Lis_din); else printf("Fila Vazia!!!");
} /* O(1), pois Remover_frente é O(1) */
Implementação Encadeada
tipo_elem front(Fila *F){ if (!fila_vazia(F))
return Elemento(F->Lis_din->head); else printf("Fila Vazia!!!");
19
Implementação Estática vs Dinâmica
Ambas realizam todas as ops. em tempo O(1)
Implementação estática circular:
mais simplesImplementação dinâmica:
mais apropriada para filas cujo tamanho não pode ser antecipado ou
é muito variável
20
Exercícios
1.
A implementação dinâmica de Fila discutida anteriormente em aula utiliza uma técnica de projeto de software denominada deadaptação, pois baseia-se na adaptação de uma Lista para a
implementação da Fila. De fato, observa-se que os tipos de dados e operações de fila foram implementados com base nos tipos e operações já disponíveis para Lista. Assuma agora que não se dispõe de uma Lista pronta e reimplemente totalmente os tipos de dados e operações básicas de Fila dinamicamente.
21
Exercícios
2.
Assuma novamente, como no exercício 1, que não se dispõe de uma implementação pronta de Lista. Porém, assuma que se dispõe de uma implementação de Pilha e que não se deseja reimplementar totalmente a Fila como no exercício 1. Implemente a Fila através de adaptação de Pilha:Dica: Implemente a fila utilizando duas pilhas !
Discuta porque essa estratégia de implementação de fila não é
eficiente analisando o tempo de execução (BIG-O) de cada operação !
Exercícios
3.
Implemente em C uma rotina recursiva esvaziar que esvazie uma fila F, a deixando como se tivesse sido reinicializada. Faça como uma função adicional àquelas funções básicas do TAD fila, que portanto podem ser assumidas disponíveis e serem utilizadas4.
Implemente também uma outra rotina adicional inverterque reposicione os elementos em uma fila F de forma que o início se torne o fim e vice-versaDica: assuma que você tem disponível uma implementação de Pilha
23
Exercícios
5.
Um Deque é uma estrutura de dados linear que permite a inserção e remoção de elementos em ambas as extremidades da estrutura. Ou seja, um Deque permite inserir e remover elementos tanto no início como no final. Nesse sentido, é mais flexível que uma Pilha ou Fila, e menos flexível que uma Lista. Discuta justificadamente o tempo computacional assintótico (BIG-O) das quatro operações básicas de um Deque com n elementos, para as seguintes estratégias de implementação:(a) Arranjo estático não circular (b) Arranjo estático circular
(c) Lista Dinâmica Simplesmente Encadeada (d) Lista Dinâmica Duplamente Encadeada Discuta também em termos de memória
24
Exercícios
6.
Considere as duas possíveis configurações dos elementos em uma fila implementada através de um arranjo circular:Explique porque, mesmo que a fila não disponha de um contador nelempara o no. de elementos, pode-se calcular esse no. em qualquer das duas configurações acima através da seguinte expressão: [ (MAX + 1) + c −f ] mod(MAX + 1)
Q 0 1 2 f c Q 0 1 2 c f MAX MAX
25 25