Você está na página 1de 25

Filas e Listas Ordenadas

Conteudista: Prof. Me. Amilton Souza Martha


Revisão Textual: Prof.ª M.ª Rosemary Toffolli

Objetivo da Unidade:

Apresentar os conceitos, a implementação em Java para exemplificação, suas


aplicações na computação e, consequentemente, em jogos digitais.

📄 Contextualização
📄 Material Teórico
📄 Material Complementar
📄 Referências
📄 Contextualização
Página 1 de 4

Tendo em mente as Filas e as Listas Ordenadas, pense e descreva uma situação, inserida em um
jogo, que utilize as estruturas mencionadas, não necessariamente ambas na mesma parte do
jogo, ok?
📄 Material Teórico
Página 2 de 4

Filas
Uma FILA é um tipo especial de lista linear em que as inserções são realizadas num extremo
enquanto as remoções são feitas no outro. O extremo onde os elementos são inseridos é
denominado final e aquele onde são inseridos é denominado começo da fila.

A ordem de saída dos elementos corresponde diretamente à ordem de entrada dos mesmos na
fila, de modo que os primeiros elementos que entram serão os primeiros a sair, caracterizando
uma estrutura FIFO (First-In/First-Out).

A palavra queue, da língua inglesa, significa fila. As duas operações básicas que uma fila suporta
são:

Enqueue: insere um elemento no final da fila;

Dequeue: remove um elemento do começo da fila.


Sendo F uma Fila e x um elemento qualquer, a operação F.enqueue(x) aumenta o tamanho da
Fila F, acrescentando o elemento x no seu final.

Exemplo:

Tabela 1

Operação Estado da Fila

– F:[ ]

F.enqueue(a) F:[ a ]

F.enqueue(b) F:[ a, b ]

F.enqueue(c) F:[ a, b, c ]

F.enqueue(d) F:[ a, b, c, d ]

dequeue() F:[ b, c, d ]

F.enqueue(F.dequeue()) F:[ c, d, b ]

enqueue(e) F:[ c, d, b, e ]

F.enqueue(F.dequeue()) F:[ d, b, e, c ]
Fila Estática Sequencial
Assim com as pilhas, como manipulamos apenas as extremidades da estrutura, não possuindo
inserção e remoção no meio dela, podemos implementar as Filas de maneira estática-sequencial
ou dinâmica-encadeada.

Ao tentarmos implementar uma Fila Estática Sequencial, deparamos com 2 problemas:

As inserções são feitas sempre no fim da fila e as remoções no começo. Como é


inviável o deslocamento de todos os elementos para a esquerda na remoção,
existirão espaços em branco no início da fila que não podem ser reaproveitados,
pois a inserção é sempre à direita;

Quando inserimos elementos até ela ficar cheia (fim=MAX), temos que não há mais
possibilidades de inserção. Porém, mesmo que removamos todos os elementos,
ainda assim ela vai achar que está cheia por não ter possibilidade de inserção e vazia
por não ter mais elementos.

Para resolver o primeiro problema, podemos criar uma Fila Circular onde, após o último
elemento volta para a primeira posição e se estiver disponível para inserção, podemos colocar
mais elementos.

O segundo problema, para descobrir se a Fila está cheia ou vazia, basta cria um contador de
elementos, que se estiver zerado está vazia e se estiver com MAX estará cheia.

//Implementação de uma Fila Estática Sequencial


public class Fila {
private int total; //Total de Elementos
private int comeco; //Comeco da Fila
private int fim; //Final da Fila
private Object memo[]; //Vetor para armazenar os elementos
private int MAX; //Tamanho máximo da Fila Estática

//Inicializa a Fila em estado vazia


public Fila() {
MAX = 1000;
memo = new Object[MAX];
total = 0;
comeco = 0;
fim = 0;
}

//Verifica se Fila está vazia


public boolean qIsEmpty() {
return(total==0);
}

//Verifica se a Fila está cheia


private boolean qIsFull() {
return(total==MAX);
}

//Insere um elemento na Fila


public void enqueue(Object x) {
if(!qIsFull()) {
memo[fim++] = x;
fim %= MAX;
total++;
}
else
System.out.println("Fila Cheia!!");
}

//Remove um elemento do início da Fila


public Object dequeue() {
if(!qIsEmpty()) {
Object resp;
resp = memo[comeco++];
comeco %= MAX;
total--;
return resp;
}
else {
return null;
}
}

//Imprime o conteúdo da Fila


public void qPrint() {
int i;
if(!qIsEmpty()) {
String saida = "";
int cont = comeco;
for(i=0;i<total;i++) {
saida += memo[cont].toString() + ", ";
cont++;
cont %= MAX;
}

System.out.println("F:[ "+saida+" ]");


}
else
System.out.println("Fila Vazia");
}

Fila Dinâmica Encadeada


As filas dinâmicas possuem a vantagem de alocar somente a memória necessária para rodar a
aplicação e solicitar mais memória à heap em tempo de execução assim que for necessário
inserir mais um elemento da fila (como qualquer estrutura dinâmica). Além desta vantagem,
não ocorre a desvantagem que vimos na Fila Estática Sequencial, onde quando um elemento era
retirado da Fila, a posição de memória do vetor não ficava disponível para imediata inserção.

Para tanto, nós tivemos que implementar a Fila Circular que eliminava o problema, o que não
ocorre aqui, pois a alocação não é sequencial e sim encadeada.

Para criarmos uma Fila, ainda vamos usar a notação onde cada elemento da Fila será um nó
contendo um dado e o endereço do próximo nó da Fila através de Listas Simplesmente

Encadeadas. Porém, a Fila por ser uma estrutura FIFO, deve manipular as duas extremidades
(começo e final), pois as inserções são feitas no final da Fila e as remoções no começo. Além
disso, usaremos um contador para armazenar a quantidade de elementos que a Fila possui.

// Implementação de uma Fila Dinâmica com uma lista simplesmente encadeada

public class FilaDin {


Node comeco; //Comeco da Fila
Node fim; //Fim da Fila
int total; //Contador de elementos

public FilaDin() {
comeco = null;
fim = null;
total = 0;
}

public boolean qIsEmpty() {


return(total==0);
}

public void enqueue(Object x) {


Node novo = new Node();
//Cria um novo nó
novo.setDado(x);
//Coloca o dado dentro do nó
novo.setProx(null);
//Como esse será o último, não tem próximo
if(qIsEmpty()) {
comeco = novo;
//Como estava vazia, esse nó será começo e fim
fim= comeco;
}
else {
fim.setProx(novo);
//Depois do fim da fila atual, o novo nó
fim = novo;
//O novo fim é o novo nó
}
total++;
//Incrementa o número de elementos
}

public Object dequeue() {


Object resp = null;
if(!qIsEmpty()) {
resp = comeco.getDado();
//Captura o dado do começo da Fila
comeco = comeco.getProx();
//O começo anda para o próximo
total--;
//Decrementa número de elementos
return resp;
}
return resp;
}

public void qPrint() {


if(qIsEmpty())
System.out.println("Fila Vazia!");
else {
Node aux;
String saida = "";
aux = comeco;
while(aux!=null) {
saida += aux.getDado().toString() + ", ";
aux = aux.getProx();
}
System.out.println("F:[ "+saida+"]");
}
}
}

Aplicação de Filas
Apesar da simplicidade de conceito de Filas, ele tem se mostrado essencial para o
desenvolvimento de muitas aplicações, sobretudo, quando se trata de simular no computador
situações reais, onde algum tipo de atendimento é modelado.

Em resumo, qualquer aplicação onde a ordem de entrada é a mesma ordem de saída dos
elementos é um candidato ao uso de filas.

Colorindo Regiões Gráficas


Uma interessante aplicação de Fila em computação gráfica são os algoritmos para colorir
regiões de desenhos, representados sob a forma de matrizes de pontos. Uma região de um

desenho é um conjunto de pontos conectados entre si e que têm a mesma cor. Dizemos que dois
pontos Pi e Pj estão conectados entre si se, e somente se, podemos partir de Pi, incrementar (ou
decrementar) sua abscissa (ou ordenada e não ambas) e chegar ao ponto Pj.
Para colorir uma região R, podemos utilizar o seguinte algoritmo básico:

Obter um ponto inicial P0 de cor C0, seguramente pertencente á região R;

Obter uma nova cor C1 para a região R;

Colocar P0 numa Fila V, inicialmente vazia;

Enquanto a Fila V não esvaziar:

• Remover um ponto da fila V;

• Inserir em V todos os pontos conectados a P, cuja cor seja C0;

• Inserir em V todos os pontos conectados a P, cuja cor seja C0;

Para poder simular o efeito, vamos representar uma imagem qualquer através de uma matriz,
onde cada elemento da matriz representa um ponto do desenho(pixel) e cada pixel é
discriminado pelas coordenadas da sua posição na matriz. Cada elemento da matriz armazena
um valor correspondente a cor do ponto representado (0=branco, 1=cinza, 2=preto...).
Figura 1

//Aplicação de Filas - Colorir Regiões Gráficas

import javax.swing.*;
public class RegGrafica {
private static Fila F; //Fila para a aplicação
private static int x,y; //Coordenadas do ponto
private static String imagem[][]; //Matriz da imagem
private static int MAX; //Tamanho da matriz

//Inicia o vetor imagem com uma configuração padrão


private static void inicio() {
String A,B,C;
//Solicita as 3 cores da imagem da cadeira
A = JOptionPane.showInputDialog("Qual a cor 0?");
B = JOptionPane.showInputDialog("Qual a cor 1?");
C = JOptionPane.showInputDialog("Qual a cor 2?");
//Atribui as cores às posições
imagem[0][0]=imagem[1][3]=imagem[2][1]=imagem[2][2]=imagem[2][3]
=imagem[3][1]=imagem[3][3]=imagem[4][2]=B;
imagem[0][1]=imagem[1][1]=imagem[1][0]=imagem[0][2]=A;
imagem[0][3]=imagem[0][4]=imagem[1][1]=imagem[1][2]=imagem[1][4]
=imagem[2][0]=imagem[2][4]=imagem[3][0]=C;
imagem[3][2]=imagem[3][4]=imagem[4][0]=imagem[4][1]=imagem[4][3]
=imagem[4][4]=C;
}

//Insere um ponto na Fila (x e y)


private static void qIns(int novo_x, int novo_y) {
F.enqueue(novo_x);
F.enqueue(novo_y);
}

//Remove um ponto da Fila (x e y)


private static void qRem() {
x = Integer.parseInt(F.dequeue().toString());
y = Integer.parseInt(F.dequeue().toString());
}

//Imprime o conteúdo da Matriz


private static void imprime() {
String saida = "..:: Estado da Matriz ::..\n\n";
for(int lin=0; lin<MAX;lin++) {
for(int col=0; col<MAX; col++)
saida += imagem[lin][col] + " ";
saida += "\n";
}
JOptionPane.showMessageDialog(null,saida);
}

public static void main(String args[]) {


String cor_original, nova_cor;
MAX = 5;
F = new Fila();
//Cria a matriz da imagem
imagem = new String[MAX][MAX];

//Atribui cores iniciais


inicio();

//Imprime estado inicial


imprime();

//Solicita o ponto inicial P0


x = Integer.parseInt(JOptionPane.showInputDialog
("Entre com a coordenada X"));
y = Integer.parseInt(JOptionPane.showInputDialog
("Entre com a coordenada Y"));

//Captura a cor original da imagem C0


cor_original = imagem[x][y];

//Solicita nova cor


nova_cor = JOptionPane.showInputDialog
("Entre com a Nova Cor (exceto "+cor_original+")");

//Insere o ponto na Fila


qIns(x,y);

//Enquanto a Fila não estiver vazia


while(!F.qIsEmpty()) {
//Remove um ponto da Fila
qRem();

//Insere os pontos conectados com a mesma cor


if(x<MAX-1 && imagem[x+1][y]==cor_original)
qIns(x+1,y);
if(y>0 && imagem[x][y-1]==cor_original)
qIns(x,y-1);
if(x>0 && imagem[x-1][y]==cor_original)
qIns(x-1,y);
if(y<MAX-1 && imagem[x][y+1]==cor_original)
qIns(x,y+1);

//Troca a cor do ponto para a nova cor


imagem[x][y] = nova_cor;
}

//Imprime a matriz de imagem após a mudança


imprime();

//Fim
System.exit(0);
}

Listas Ordenadas
Lista Ordenada é uma estrutura de dados auto-ajustável, no sentido de sempre se manter
ordenada após cada inserção ou remoção. Devido a este comportamento altamente dinâmico,
esta estrutura será melhor implementada como alocação dinâmica encadeada pois a inserção e
remoção de nós no meio das Listas será inevitável.

Note que é possível a implementação de Listas ordenadas com alocação estática-sequencial,


porém, é inviável já que a inserção e remoção no meio da estrutura fazem com que seja
necessário deslocar todos os elementos à direita.

Uma Lista Ordenada L:[a1, a2, a3, ..., an] é uma lista linear tal que, sendo n>1, temos:

a1 <= ak , para qualquer 1<k<=n;

ak <= an, para qualquer 1<=k<n;

ak-1 <= ak <=ak+1, para qualquer 1<k<n.

Se L é uma lista ordenada, podemos garantir que nenhum elemento em L é inferior ao primeiro
elemento(a1) ou superior ao último elemento (an). Além disso, tomando um elemento qualquer
no meio da lista, nenhum elemento à sua esquerda o supera e nenhum elemento à direita é
inferior a ele. Entre as diversas operações que podem ser realizadas com Listas Ordenadas,
vamos considerar:

Ins: insere um novo elemento na lista ordenada;

Rem: remove um elemento específico da Lista Ordenada;

Find: procura um elemento na Lista e retorna sua posição.

Observe o funcionamento de uma lista L inicialmente vazia após as operações:


Tabela 2

Operação Estado da Lista Resultado

– L:[ ] –

L.ins(e) L:[ e ] –

L.ins(x) L:[ e, x ] –

L.ins(e) L:[ e, e, x ] –

L.ins(m) L:[ e, e, m, x ] –

L.ins(p) L:[ e, e, m, p, x ] –

L.ins(l) L:[ e, e, l, m, p, x ] –

L.ins(o) L:[ e, e, l, m, o, p, x ] –

L.rem(x) L:[ e, e, l, m, o, p ] True

L.rem(m) L:[ e, e, l, o, p ] True

L.find(o) L:[ e, e, l, o, p ] 4

L.find(e) L:[ e, e, l, o, p ] 1

L.rem(q) L:[ e, e, l, o, p ] False


Operação Estado da Lista Resultado

L.rem(e) L:[ e, l, o, p ] True

Assim como Pilhas Dinâmicas e Filas Dinâmicas, a lista ordenada dinâmica encadeada usa a
estrutura de listas simplesmente encadeadas.

//Implementação de uma Lista Ordenada Dinâmica Encadeada

import javax.swing.*;

public class ListaOrd {


private Node comeco; //Nó do começo da Lista

//Inicializa uma Lista no estado vazia (construtor)


public ListaOrd() {
comeco = null;
}

//Verifica se a lista está vazia


public boolean isNull() {
return(comeco==null);
}

//Método que converte um objeto em float para poder ser comparado


private float emFloat(Object x) {
float resp = Float.parseFloat(x.toString());
return resp;
}

//Insere um elemento na Lista Ordenada


public void ins(Object x) {
Node novo = new Node();
Node aux;
novo.setDado(x);

//Se a lista estiver vazia ou o elemento for menor que o primeiro


if(isNull() || emFloat(x) < emFloat(comeco.getDado()) ) {
novo.setProx(comeco);
comeco = novo;
}
else {
aux = comeco;
while(aux.getProx()!=null && emFloat(x) >
emFloat(aux.getProx().getDado()))
aux = aux.getProx();
novo.setProx(aux.getProx());
aux.setProx(novo);
}
}

//Imprime o conteúdo da Lista


public void print() {
if(!isNull()) {
Node aux;
aux = comeco;
String saida = "";
while(aux!=null){
saida += aux.getDado().toString() + ", ";
aux = aux.getProx();
}
JOptionPane.showMessageDialog(null,"L:["+saida+"]");
}
else
System.out.println("Lista Vazia!");
}

//Remove um elemento da lista e retorna true se conseguiu


remover e false caso não
public boolean rem(Object x) {
Node aux;
//Se a lista estiver vazia ou o elemento for menor que o primeiro, não exis
if(isNull() || emFloat(x) < emFloat(comeco.getDado()) )
return false;
else {
//Se for o primeiro elemento
if(emFloat(x)==emFloat(comeco.getDado())) {
comeco = comeco.getProx();
return true;
}
//Se for maior que o primeiro, achar a posição
else {
aux = comeco;
//Navega até aposição onde deveria encontrar
while(aux.getProx() != null && emFloat(x) >
emFloat(aux.getProx().getDado()))
aux = aux.getProx();

//Se encontrou
if(aux.getProx() != null && emFloat(x) ==
emFloat(aux.getProx().getDado()))
{
aux.setProx(aux.getProx().getProx());
return true;
}
else //Não encontrou o elemento
return false;
}
}
}

//Busca por um elemento na lista e retorna o nó onde encontrou


public Node find(Object x) {
Node aux = comeco;

//Navega até o nó de interesse


while(aux !=null && emFloat(x) > emFloat(aux.getDado()) )
aux = aux.getProx();

//Se achou o elemento


if(aux !=null && emFloat(x) == emFloat(aux.getDado()))
return aux;
else
return null;
}
}

Aplicação de Listas Ordenadas


Um mapeamento m: A->B é uma relação que faz corresponder a cada elemento de A um único
elemento em B ou nenhum. Funções geralmente são representadas por fórmulas através das
quais podemos calcular o valor y associado a um dado valor x como f(x) = y.
Mapeamentos, entretanto, na maioria das vezes não podem ser representados por expressões
aritméticas, por exemplo, um mapeamento que associa números de RGM a alunos não pode ser
calculado. Os mapeamentos são representados por uma enumeração explícita de pares cuja
formação não se baseia em nenhum princípio matemática. Assim, uma lista ordenada aparece
como uma boa alternativa para implementar mapeamentos. Exemplo:

L:[(Ana,32),(Beth,17),(Caio,32),(Denis,15),(Elis,28),(Zélia,51)]

Dado um mapeamento m:A->B, a operação set(M,x,y) insere no mapeamento os pares (x,y).


Como o par não pode se repetir, uma inserção com valor de x repetido simplesmente altera o
valor de y. Por exemplo, para o mapeamento M:[(Gal,41),(Gil,52)], a operação set(M,Gal,45)
resulta em M:[(Gal,45),(Gil,52)]. Outra operação importante é get(L,x) que retorna o valor
correspondente de y referente a x dado.

No caso acima, uma instrução set(Caio,28) apenas alteraria o conteúdo do valor de y do par,
ficando assim:

L:[(Ana,32),(Beth,17),(Caio,28),(Denis,15),(Elis,28),(Zélia,51)]

Outra função usada em Mapeamento é a get(M,x) que retorna o correspondente Y do valor x no


mapeamento M. Esta função é muito semelhante à função Find da Lista Ordenada.

Variáveis Polinômios
Desejamos aplicar os conceitos de listas ordenadas para implementar um tipo de dados que nos
possibilitará a criação de variáveis capazes de armazenar polinômios na forma.

P(x) = anxn + an-1xn-1 + ... + ak xk + a3x3 + a2x2 + a1x1 + a0

Analisando a forma de P(x), concluímos facilmente que o polinômio pode ser representado pó
um conjunto de pares (ak,k), onde cada par associa o coeficiente e a potência correspondente de
x.
Para representar cada monômio do polinômio, vamos criar um TAD que representará esse nó do
polinômio.

Figura 2

public class NoPoli {


private float coef;
private int expo;
private NoPoli prox;

public float getCoef() {


return coef;
}

public int getExpo() {


return expo;
}

public NoPoli getProx() {


return prox;
}

public void setCoef(float novo_coef) {


coef = novo_coef;
}

public void setExpo(int novo_expo) {


expo = novo_expo;
}

public void setProx(NoPoli novo_prox) {


prox = novo_prox;
}

A implementação do Polinômio é muito parecida com a lista ordenada, porém, ao se criar um


polinômio, não vai existir uma lista vazia, o menor valor vai ser 0x0

//Implementação de um Polinômio

import javax.swing.*;

public class Polinomio {


private NoPoli comeco;

//Inicializa o polinômio com o termo 0x^0


public Polinomio() {
comeco = new NoPoli();
comeco.setExpo(0);
comeco.setCoef(0);
comeco.setProx(null);
}

//Insere um elemento no Polinômio


public void insTermo(float coef1, int expo1) {

//Se o expoente for o primeiro, juntar os coeficientes


if(expo1 == comeco.getExpo())
comeco.setCoef(comeco.getCoef()+coef1);
//Se não for, procurar para ver se existe como na lista ordenada
else
{
NoPoli aux;
aux = comeco;

//Achar a posição onde o monômio pode estar


while(aux.getProx()!=null && expo1 > aux.getProx().getExpo
aux = aux.getProx();
//Se o próximo for ele, somar coeficientes, senão, inserir novo ite
if(aux.getProx()!=null && expo1 == aux.getProx().getExpo())
aux.getProx().setCoef(aux.getProx().getCoef()+coef1);
else
{
NoPoli novo = new NoPoli();
novo.setCoef(coef1);
novo.setExpo(expo1);
novo.setProx(aux.getProx());
aux.setProx(novo);
}
}
}
📄 Material Complementar
Página 3 de 4

Indicações para saber mais sobre os assuntos abordados nesta Unidade:

Livros

Estruturas de Dados Fundamentais: Conceitos e Aplicações


Vamos continuar usando esse livro para entender a teoria das Filas e Listas Ordenadas.
PEREIRA, S. L. Estruturas de Dados Fundamentais: Conceitos e Aplicações. 9. ed. São Paulo:
Erica, 2001.

Estruturas de Dados: Conceitos e Técnicas de Implementação


VILLAS, M. V. Estruturas de Dados: Conceitos e Técnicas de Implementação. Rio de Janeiro:
Campus, 1993.

Vídeo
Listas, Pilhas e Filas em Estruturas de Dados - Qual a
Diferença?

Listas, Pilhas e Filas em Estruturas de Dados - Qual a diferença?


📄 Referências
Página 4 de 4

PENTON, R. Data Structures for Game Programmers, Ohio, USA, Focal Press, 2003

PEREIRA, S. L. Estruturas de Dados Fundamentais: Conceitos e Aplicações. 9. ed. São Paulo:


Erica, 2001.

VILLAS, M. V. Estruturas de Dados: Conceitos e Técnicas de Implementação. Rio de Janeiro:


Campus, 1993.

DROZDEK, A. Estrutura de Dados e Algoritmos em C++. Sao Paulo: Pioneira Thomson Learning,
2005.

SHERROD, A. Data Structures And Algorithms For Game Developers. New York City: Charles River
Media, 2007.

Você também pode gostar