Estruturas de dados: fila
1. Conceitos Fundamentais da Fila
A fila é uma estrutura de dados linear que segue o princípio FIFO (First In, First Out), onde o primeiro elemento inserido é o primeiro a ser removido. Essa característica a torna ideal para situações onde a ordem de chegada determina a ordem de atendimento.
Uma analogia clássica é a fila de banco: o primeiro cliente a chegar é o primeiro a ser atendido. Outro exemplo é o buffer de impressão, onde documentos são enviados para impressão na ordem em que foram submetidos.
As operações básicas de uma fila são:
- enqueue (inserir): adiciona um elemento ao final da fila
- dequeue (remover): remove o elemento do início da fila
- front (consultar): retorna o primeiro elemento sem removê-lo
2. Implementação com Array (Fila Estática)
A implementação mais simples utiliza um array de tamanho fixo. Definimos uma estrutura com o array, índices front e rear, e um contador opcional.
#include <stdio.h>
#include <stdbool.h>
#define MAX 5
typedef struct {
int dados[MAX];
int front;
int rear;
int tamanho;
} Fila;
void criar_fila(Fila *f) {
f->front = 0;
f->rear = -1;
f->tamanho = 0;
}
bool fila_vazia(Fila *f) {
return f->tamanho == 0;
}
bool fila_cheia(Fila *f) {
return f->tamanho == MAX;
}
bool enfileirar(Fila *f, int valor) {
if (fila_cheia(f)) return false;
f->rear = (f->rear + 1) % MAX;
f->dados[f->rear] = valor;
f->tamanho++;
return true;
}
bool desenfileirar(Fila *f, int *valor) {
if (fila_vazia(f)) return false;
*valor = f->dados[f->front];
f->front = (f->front + 1) % MAX;
f->tamanho--;
return true;
}
O principal problema dessa abordagem é o desperdício de espaço quando rear atinge o final do array, mesmo havendo posições livres no início. A solução é a fila circular.
3. Fila Circular com Array
Na fila circular, rear e front "dão a volta" no array usando a operação módulo. Para distinguir fila cheia de vazia, podemos usar uma posição reservada ou um contador de elementos.
#include <stdio.h>
#include <stdbool.h>
#define TAMANHO 5
typedef struct {
int dados[TAMANHO];
int front;
int rear;
} FilaCircular;
void criar_fila_circular(FilaCircular *f) {
f->front = 0;
f->rear = 0;
}
bool fila_vazia_circular(FilaCircular *f) {
return f->front == f->rear;
}
bool fila_cheia_circular(FilaCircular *f) {
return (f->rear + 1) % TAMANHO == f->front;
}
bool enfileirar_circular(FilaCircular *f, int valor) {
if (fila_cheia_circular(f)) return false;
f->dados[f->rear] = valor;
f->rear = (f->rear + 1) % TAMANHO;
return true;
}
bool desenfileirar_circular(FilaCircular *f, int *valor) {
if (fila_vazia_circular(f)) return false;
*valor = f->dados[f->front];
f->front = (f->front + 1) % TAMANHO;
return true;
}
void imprimir_fila_circular(FilaCircular *f) {
int i = f->front;
while (i != f->rear) {
printf("%d ", f->dados[i]);
i = (i + 1) % TAMANHO;
}
printf("\n");
}
A fila circular resolve o desperdício de espaço, mas ainda tem tamanho fixo. Quando rear alcança front, a fila está cheia — uma posição fica sempre vazia para diferenciar cheia de vazia.
4. Implementação com Lista Encadeada (Fila Dinâmica)
A fila dinâmica usa nós alocados em tempo de execução, permitindo crescimento ilimitado (limitado apenas pela memória disponível).
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct No {
int dado;
struct No *prox;
} No;
typedef struct {
No *front;
No *rear;
} FilaDinamica;
void criar_fila_dinamica(FilaDinamica *f) {
f->front = NULL;
f->rear = NULL;
}
bool fila_vazia_dinamica(FilaDinamica *f) {
return f->front == NULL;
}
void enqueue(FilaDinamica *f, int valor) {
No *novo = (No*) malloc(sizeof(No));
if (novo == NULL) return;
novo->dado = valor;
novo->prox = NULL;
if (fila_vazia_dinamica(f)) {
f->front = novo;
} else {
f->rear->prox = novo;
}
f->rear = novo;
}
bool dequeue(FilaDinamica *f, int *valor) {
if (fila_vazia_dinamica(f)) return false;
No *temp = f->front;
*valor = temp->dado;
f->front = f->front->prox;
if (f->front == NULL) {
f->rear = NULL;
}
free(temp);
return true;
}
A inserção (enqueue) ocorre no final (rear), e a remoção (dequeue) no início (front). A memória é liberada após a remoção, evitando vazamentos.
5. Operações Auxiliares e Casos Especiais
Consultar primeiro elemento (peek):
bool peek(FilaDinamica *f, int *valor) {
if (fila_vazia_dinamica(f)) return false;
*valor = f->front->dado;
return true;
}
Tamanho atual da fila (versão dinâmica):
int tamanho_fila(FilaDinamica *f) {
int count = 0;
No *atual = f->front;
while (atual != NULL) {
count++;
atual = atual->prox;
}
return count;
}
Esvaziar fila dinâmica e liberar memória:
void esvaziar_fila(FilaDinamica *f) {
No *atual = f->front;
while (atual != NULL) {
No *temp = atual;
atual = atual->prox;
free(temp);
}
f->front = NULL;
f->rear = NULL;
}
6. Comparação entre Implementações
| Característica | Fila Estática (Array) | Fila Dinâmica (Lista) |
|---|---|---|
| Tamanho máximo | Fixo | Limitado pela memória |
| Uso de memória | Contínuo, sem overhead | Overhead de ponteiros |
| Operações básicas | O(1) | O(1) |
| Redimensionamento | Não suportado | Automático |
| Fragmentação | Não | Pode ocorrer |
Ambas as implementações oferecem complexidade O(1) para enqueue, dequeue e peek. A fila estática é mais rápida em acesso direto e não tem overhead de alocação, mas sofre com limitação de tamanho. A fila dinâmica é mais flexível, mas consome mais memória por nó (armazenamento do ponteiro).
7. Aplicações Práticas em C
Simulação de escalonamento Round-Robin:
Em sistemas operacionais, a fila é usada para gerenciar processos prontos para execução. Cada processo recebe um quantum de tempo e, ao final, retorna ao final da fila.
typedef struct {
int id;
int tempo_restante;
} Processo;
void round_robin(FilaDinamica *fila, int quantum) {
while (!fila_vazia_dinamica(fila)) {
Processo p;
dequeue(fila, &p);
if (p.tempo_restante > quantum) {
p.tempo_restante -= quantum;
enqueue(fila, p);
} else {
printf("Processo %d concluído\n", p.id);
}
}
}
Buffer de comunicação serial:
Em sistemas embarcados, filas armazenam caracteres recebidos via UART para processamento posterior, evitando perda de dados.
Gerenciamento de tarefas:
Em microcontroladores, filas organizam tarefas assíncronas como leitura de sensores, acionamento de atuadores e comunicação com periféricos.
A escolha entre implementação estática ou dinâmica depende do contexto: sistemas com memória limitada e tamanho conhecido favorecem arrays; sistemas que precisam de flexibilidade e crescimento dinâmico favorecem listas encadeadas.
Referências
- cppreference.com - Fila em C — Documentação oficial sobre algoritmos e estruturas em C, incluindo implementações de fila
- GeeksforGeeks - Queue Data Structure in C — Tutorial completo com exemplos de fila estática e dinâmica em C
- Programiz - Queue Implementation in C — Guia prático com código comentado para implementação de fila
- TutorialsPoint - Data Structures Queue — Explicação teórica e implementação passo a passo em C
- Wikipedia - Queue (abstract data type) — Definição formal, variações e aplicações de filas na ciência da computação
- IBM - Queue data structure in C — Documentação técnica sobre implementação de filas em sistemas mainframe