Fila (Queue): Estrutura FIFO em Rust

Guia completo sobre a estrutura de dados Fila (Queue) em Rust — conceito FIFO, VecDeque como fila padrão, implementação com buffer circular, fila de prioridade e aplicações práticas como agendador de tarefas e BFS.

Introdução

A fila (queue) é uma estrutura de dados que segue o princípio FIFOFirst In, First Out (primeiro a entrar, primeiro a sair). Assim como uma fila de banco ou supermercado: quem chega primeiro é atendido primeiro.

Filas são fundamentais em computação e aparecem em praticamente todo sistema: filas de mensagens em microsserviços, buffers de I/O em sistemas operacionais, agendadores de processos, algoritmos de busca em largura (BFS) em grafos, e sistemas de impressão. Qualquer cenário onde a ordem de chegada deve ser preservada se beneficia de uma fila.

Em Rust, a estrutura padrão para implementar filas é o VecDeque<T> (vetor de duas pontas), que utiliza internamente um buffer circular para oferecer operações O(1) em ambas as extremidades. Neste artigo, entenderemos o conceito, implementaremos uma fila do zero com buffer circular e construiremos um agendador de tarefas realista com BFS.


Conceito e Teoria

O Princípio FIFO

  Operações em uma Fila:

  enfileirar(40)                desenfileirar()
  ════════════                  ═══════════════

  ANTES:                        ANTES:
  frente                 trás   frente                 trás
    │                      │      │                      │
    ▼                      ▼      ▼                      ▼
  ┌────┐ ┌────┐ ┌────┐          ┌────┐ ┌────┐ ┌────┐ ┌────┐
  │ 10 │→│ 20 │→│ 30 │          │ 10 │→│ 20 │→│ 30 │→│ 40 │
  └────┘ └────┘ └────┘          └────┘ └────┘ └────┘ └────┘

  DEPOIS:                       DEPOIS:
  frente                 trás   frente            trás
    │                      │      │                 │
    ▼                      ▼      ▼                 ▼    retorna: 10
  ┌────┐ ┌────┐ ┌────┐ ┌────┐  ┌────┐ ┌────┐ ┌────┐
  │ 10 │→│ 20 │→│ 30 │→│ 40 │  │ 20 │→│ 30 │→│ 40 │
  └────┘ └────┘ └────┘ └────┘  └────┘ └────┘ └────┘

  → Entra pela TRÁS, sai pela FRENTE

Por Que Não Usar Vec Como Fila?

Usar Vec<T> como fila é ineficiente porque remover do início (remove(0)) custa O(n):

  Vec<T> como fila — desenfileirar é CARO:

  Estado inicial:
  ┌────┬────┬────┬────┬────┐
  │ 10 │ 20 │ 30 │ 40 │ 50 │
  └────┴────┴────┴────┴────┘
    ▲                    ▲
  frente               trás

  Após remove(0) — todos os elementos se movem!
  ┌────┬────┬────┬────┬────┐
  │ 20 │ 30 │ 40 │ 50 │    │  ← 4 cópias para remover 1 elemento
  └────┴────┴────┴────┴────┘

  Custo: O(n) por operação de desenfileirar!

Buffer Circular — A Solução Eficiente

O VecDeque usa um buffer circular: um array onde o início e o fim “envolvem” ao redor:

  Buffer circular com capacidade 8:

  Estado 1: 3 elementos [10, 20, 30]
  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
  │     │     │ 10  │ 20  │ 30  │     │     │     │
  └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
    [0]   [1]   [2]   [3]   [4]   [5]   [6]   [7]
                  ▲                 ▲
                frente            trás

  Estado 2: após enfileirar 40, 50, 60, 70
  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
  │     │     │ 10  │ 20  │ 30  │ 40  │ 50  │ 60  │
  └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
    [0]   [1]   [2]   [3]   [4]   [5]   [6]   [7]
                  ▲     ▲
                frente trás (o próximo enfileirar vai para [0]!)

  Estado 3: após enfileirar 70 — o buffer ENVOLVE (wrap-around)
  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
  │ 70  │     │ 10  │ 20  │ 30  │ 40  │ 50  │ 60  │
  └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
    [0]   [1]   [2]   [3]   [4]   [5]   [6]   [7]
            ▲     ▲
          trás  frente

  A posição é calculada com: índice % capacidade
  Não precisa mover elementos!

Complexidade

OperaçãoVec (como fila)VecDequeFila (buffer circular)
Enfileirar (push_back)O(1) amortizadoO(1) amortizadoO(1) amortizado
Desenfileirar (pop_front)O(n)O(1)O(1)
Espiar frente (front)O(1)O(1)O(1)
Espiar trás (back)O(1)O(1)O(1)
BuscaO(n)O(n)O(n)
Acesso por índiceO(1)O(1)O(1)
EspaçoO(n)O(n)O(n)

Implementação em Rust

Fila com Buffer Circular (Do Zero)

use std::fmt;

/// Fila genérica implementada com buffer circular
///
/// O buffer circular permite enfileirar e desenfileirar em O(1)
/// sem necessidade de mover elementos.
#[derive(Debug)]
pub struct FilaCircular<T> {
    buffer: Vec<Option<T>>,
    frente: usize,     // índice do primeiro elemento
    tamanho: usize,    // número de elementos presentes
    capacidade: usize, // tamanho total do buffer
}

impl<T> FilaCircular<T> {
    /// Cria uma fila com a capacidade especificada
    pub fn nova(capacidade: usize) -> Self {
        assert!(capacidade > 0, "Capacidade deve ser maior que zero");

        let mut buffer = Vec::with_capacity(capacidade);
        for _ in 0..capacidade {
            buffer.push(None);
        }

        FilaCircular {
            buffer,
            frente: 0,
            tamanho: 0,
            capacidade,
        }
    }

    /// Enfileira um elemento na trás da fila — O(1) amortizado
    pub fn enfileirar(&mut self, valor: T) -> Result<(), String> {
        if self.tamanho == self.capacidade {
            // Dobra a capacidade quando cheia
            self.redimensionar();
        }

        let indice_tras = (self.frente + self.tamanho) % self.capacidade;
        self.buffer[indice_tras] = Some(valor);
        self.tamanho += 1;
        Ok(())
    }

    /// Desenfileira e retorna o elemento da frente — O(1)
    pub fn desenfileirar(&mut self) -> Option<T> {
        if self.tamanho == 0 {
            return None;
        }

        let valor = self.buffer[self.frente].take();
        self.frente = (self.frente + 1) % self.capacidade;
        self.tamanho -= 1;
        valor
    }

    /// Retorna referência ao elemento da frente sem remover — O(1)
    pub fn frente(&self) -> Option<&T> {
        if self.tamanho == 0 {
            None
        } else {
            self.buffer[self.frente].as_ref()
        }
    }

    /// Retorna referência ao elemento de trás sem remover — O(1)
    pub fn tras(&self) -> Option<&T> {
        if self.tamanho == 0 {
            None
        } else {
            let indice = (self.frente + self.tamanho - 1) % self.capacidade;
            self.buffer[indice].as_ref()
        }
    }

    /// Verifica se a fila está vazia
    pub fn esta_vazia(&self) -> bool {
        self.tamanho == 0
    }

    /// Retorna o número de elementos
    pub fn tamanho(&self) -> usize {
        self.tamanho
    }

    /// Retorna a capacidade atual do buffer
    pub fn capacidade(&self) -> usize {
        self.capacidade
    }

    /// Redimensiona o buffer dobrando a capacidade
    fn redimensionar(&mut self) {
        let nova_capacidade = self.capacidade * 2;
        let mut novo_buffer: Vec<Option<T>> = Vec::with_capacity(nova_capacidade);

        // Copia os elementos na ordem correta
        for i in 0..self.tamanho {
            let indice_antigo = (self.frente + i) % self.capacidade;
            // Usa replace para mover o valor sem clonar
            let valor = self.buffer[indice_antigo].take();
            novo_buffer.push(valor);
        }

        // Preenche o resto com None
        for _ in self.tamanho..nova_capacidade {
            novo_buffer.push(None);
        }

        self.buffer = novo_buffer;
        self.frente = 0;
        self.capacidade = nova_capacidade;
    }
}

/// Exibição formatada
impl<T: fmt::Display> fmt::Display for FilaCircular<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Fila [")?;
        for i in 0..self.tamanho {
            if i > 0 {
                write!(f, " ← ")?;
            }
            let indice = (self.frente + i) % self.capacidade;
            if let Some(ref val) = self.buffer[indice] {
                write!(f, "{}", val)?;
            }
        }
        write!(f, "]")?;
        write!(f, " (tam: {}, cap: {})", self.tamanho, self.capacidade)
    }
}

fn main() {
    println!("═══ Fila com Buffer Circular ═══\n");

    let mut fila = FilaCircular::nova(4);

    // Enfileirando elementos
    for i in 1..=6 {
        fila.enfileirar(i * 10).unwrap();
        println!("Enfileirou {}: {}", i * 10, fila);
    }

    println!();

    // Desenfileirando
    while let Some(valor) = fila.desenfileirar() {
        println!("Desenfileirou: {} | {}", valor, fila);
    }

    // Tentando desenfileirar de fila vazia
    match fila.desenfileirar() {
        Some(v) => println!("Desenfileirou: {}", v),
        None => println!("\nFila vazia — nada para desenfileirar"),
    }
}

Usando VecDeque como Fila

Na prática, o VecDeque da biblioteca padrão é a forma idiomática:

use std::collections::VecDeque;

fn demonstrar_vecdeque_como_fila() {
    let mut fila: VecDeque<String> = VecDeque::new();

    // Enfileirar (push_back)
    fila.push_back("Primeiro da fila".to_string());
    fila.push_back("Segundo da fila".to_string());
    fila.push_back("Terceiro da fila".to_string());

    println!("Fila: {:?}", fila);

    // Espiar a frente
    if let Some(primeiro) = fila.front() {
        println!("Próximo a ser atendido: {}", primeiro);
    }

    // Desenfileirar (pop_front)
    while let Some(pessoa) = fila.pop_front() {
        println!("Atendendo: {}", pessoa);
    }

    // Fila com capacidade pré-alocada
    let mut fila_grande: VecDeque<i32> = VecDeque::with_capacity(1000);
    for i in 0..100 {
        fila_grande.push_back(i);
    }

    println!("\nFila grande: {} elementos, capacidade: {}",
        fila_grande.len(), fila_grande.capacity());

    // Iterar sem consumir
    let soma: i32 = fila_grande.iter().sum();
    println!("Soma dos elementos: {}", soma);

    // Drenar elementos (remove e retorna iterador)
    let primeiros_cinco: Vec<i32> = fila_grande.drain(..5).collect();
    println!("Drenados: {:?}", primeiros_cinco);
    println!("Restantes: {}", fila_grande.len());
}

fn main() {
    demonstrar_vecdeque_como_fila();
}

Métodos Principais

VecDeque (como Fila)

MétodoComplexidadeDescrição
VecDeque::new()O(1)Cria uma fila vazia
VecDeque::with_capacity(n)O(1)Cria com capacidade pré-alocada
push_back(val)O(1) amortizadoEnfileira no final
pop_front()O(1)Desenfileira do início
front()O(1)Referência ao primeiro (sem remover)
back()O(1)Referência ao último (sem remover)
len()O(1)Número de elementos
is_empty()O(1)Verifica se está vazia
contains(&val)O(n)Busca linear
iter()O(1)Iterador por referência
drain(range)O(n)Remove e retorna elementos do intervalo
retain(f)O(n)Mantém apenas elementos que satisfazem f
make_contiguous()O(n)Reorganiza buffer para ser contíguo

Fila de Prioridade (Menção)

Rust oferece BinaryHeap<T> para filas de prioridade, onde o elemento de maior valor sai primeiro:

use std::collections::BinaryHeap;

fn demonstrar_fila_prioridade() {
    let mut fila = BinaryHeap::new();

    // Insere com prioridade implícita (maior valor = maior prioridade)
    fila.push(5);
    fila.push(1);
    fila.push(10);
    fila.push(3);

    // Remove sempre o maior
    while let Some(valor) = fila.pop() {
        println!("Processando: {}", valor);
        // Saída: 10, 5, 3, 1
    }
}

fn main() {
    demonstrar_fila_prioridade();
}

Para uma fila de prioridade mínima (menor valor sai primeiro), use std::cmp::Reverse ou implemente Ord invertido. Veja o artigo sobre Heap para mais detalhes.


Exemplo Prático: Agendador de Tarefas com BFS

Vamos implementar um sistema que combina dois usos clássicos de filas: um agendador de tarefas round-robin e uma busca em largura (BFS) para resolver dependências entre tarefas.

use std::collections::{HashMap, VecDeque};
use std::fmt;

/// Representa o estado de uma tarefa
#[derive(Debug, Clone, PartialEq)]
enum EstadoTarefa {
    Pendente,
    EmExecucao,
    Concluida,
    Bloqueada,
}

/// Representa uma tarefa no agendador
#[derive(Debug, Clone)]
struct Tarefa {
    id: usize,
    nome: String,
    duracao_restante: u32,  // unidades de tempo restantes
    prioridade: u8,         // 1-5 (5 = mais urgente)
    estado: EstadoTarefa,
    dependencias: Vec<usize>, // IDs das tarefas que precisam terminar antes
}

impl Tarefa {
    fn nova(id: usize, nome: &str, duracao: u32, prioridade: u8, deps: Vec<usize>) -> Self {
        Tarefa {
            id,
            nome: nome.to_string(),
            duracao_restante: duracao,
            prioridade,
            estado: EstadoTarefa::Pendente,
            dependencias: deps,
        }
    }
}

impl fmt::Display for Tarefa {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "[T{}] {} (restante: {}u, prio: {}, estado: {:?})",
            self.id, self.nome, self.duracao_restante, self.prioridade, self.estado
        )
    }
}

/// Agendador de tarefas round-robin com resolução de dependências
struct Agendador {
    tarefas: HashMap<usize, Tarefa>,
    fila_execucao: VecDeque<usize>,   // IDs das tarefas prontas
    fila_espera: VecDeque<usize>,     // IDs das tarefas bloqueadas
    concluidas: Vec<usize>,           // IDs das tarefas concluídas
    quantum: u32,                      // tempo por fatia
    tempo_atual: u32,
}

impl Agendador {
    fn novo(quantum: u32) -> Self {
        Agendador {
            tarefas: HashMap::new(),
            fila_execucao: VecDeque::new(),
            fila_espera: VecDeque::new(),
            concluidas: Vec::new(),
            quantum,
            tempo_atual: 0,
        }
    }

    /// Adiciona uma tarefa ao agendador
    fn adicionar_tarefa(&mut self, tarefa: Tarefa) {
        let id = tarefa.id;
        self.tarefas.insert(id, tarefa);
        self.fila_espera.push_back(id);
    }

    /// Verifica se as dependências de uma tarefa foram satisfeitas
    fn dependencias_satisfeitas(&self, id_tarefa: usize) -> bool {
        if let Some(tarefa) = self.tarefas.get(&id_tarefa) {
            tarefa.dependencias.iter().all(|dep_id| {
                self.concluidas.contains(dep_id)
            })
        } else {
            false
        }
    }

    /// Move tarefas da fila de espera para a fila de execução
    /// quando suas dependências são satisfeitas (similar a BFS)
    fn atualizar_filas(&mut self) {
        let mut prontas = Vec::new();
        let mut ainda_bloqueadas = VecDeque::new();

        while let Some(id) = self.fila_espera.pop_front() {
            if self.dependencias_satisfeitas(id) {
                prontas.push(id);
            } else {
                ainda_bloqueadas.push_back(id);
            }
        }

        self.fila_espera = ainda_bloqueadas;

        // Ordena as prontas por prioridade (maior primeiro)
        prontas.sort_by(|a, b| {
            let prio_a = self.tarefas.get(a).map(|t| t.prioridade).unwrap_or(0);
            let prio_b = self.tarefas.get(b).map(|t| t.prioridade).unwrap_or(0);
            prio_b.cmp(&prio_a)
        });

        for id in prontas {
            if let Some(tarefa) = self.tarefas.get_mut(&id) {
                tarefa.estado = EstadoTarefa::Pendente;
            }
            self.fila_execucao.push_back(id);
        }
    }

    /// Executa um ciclo do agendador round-robin
    fn executar_ciclo(&mut self) -> bool {
        self.atualizar_filas();

        if self.fila_execucao.is_empty() {
            if self.fila_espera.is_empty() {
                return false; // Tudo concluído
            }
            println!("  [t={}] Deadlock ou dependências pendentes!", self.tempo_atual);
            return false;
        }

        // Pega a próxima tarefa da frente da fila
        if let Some(id) = self.fila_execucao.pop_front() {
            if let Some(tarefa) = self.tarefas.get_mut(&id) {
                tarefa.estado = EstadoTarefa::EmExecucao;

                // Executa por no máximo 'quantum' unidades
                let execucao = tarefa.duracao_restante.min(self.quantum);
                tarefa.duracao_restante -= execucao;
                self.tempo_atual += execucao;

                println!(
                    "  [t={:>3}] Executando {} por {}u (restante: {}u)",
                    self.tempo_atual, tarefa.nome, execucao, tarefa.duracao_restante
                );

                if tarefa.duracao_restante == 0 {
                    // Tarefa concluída
                    tarefa.estado = EstadoTarefa::Concluida;
                    self.concluidas.push(id);
                    println!(
                        "         >>> {} CONCLUÍDA!",
                        tarefa.nome
                    );
                } else {
                    // Tarefa não terminou — volta para o final da fila
                    tarefa.estado = EstadoTarefa::Pendente;
                    self.fila_execucao.push_back(id);
                }
            }
        }

        true
    }

    /// Executa todas as tarefas até completar
    fn executar_tudo(&mut self) {
        println!("═══ Agendador Round-Robin (quantum: {}u) ═══\n", self.quantum);

        let mut ciclos = 0;
        let max_ciclos = 100; // prevenção contra loops infinitos

        while self.executar_ciclo() && ciclos < max_ciclos {
            ciclos += 1;
        }

        println!("\n─── Resumo ───");
        println!("Tempo total: {}u", self.tempo_atual);
        println!("Ciclos executados: {}", ciclos);
        println!("Tarefas concluídas: {}", self.concluidas.len());

        // Ordem de conclusão
        println!("\nOrdem de conclusão:");
        for (i, id) in self.concluidas.iter().enumerate() {
            if let Some(tarefa) = self.tarefas.get(id) {
                println!("  {}. [T{}] {}", i + 1, tarefa.id, tarefa.nome);
            }
        }
    }
}

/// BFS para encontrar a ordem topológica das dependências
fn ordem_bfs(tarefas: &[Tarefa]) -> Vec<usize> {
    let mut grau_entrada: HashMap<usize, usize> = HashMap::new();
    let mut adjacencias: HashMap<usize, Vec<usize>> = HashMap::new();

    // Inicializa
    for tarefa in tarefas {
        grau_entrada.entry(tarefa.id).or_insert(0);
        adjacencias.entry(tarefa.id).or_default();
    }

    // Constrói o grafo de dependências
    for tarefa in tarefas {
        for &dep in &tarefa.dependencias {
            adjacencias.entry(dep).or_default().push(tarefa.id);
            *grau_entrada.entry(tarefa.id).or_insert(0) += 1;
        }
    }

    // BFS com fila — algoritmo de Kahn
    let mut fila: VecDeque<usize> = VecDeque::new();
    let mut ordem: Vec<usize> = Vec::new();

    // Adiciona nós sem dependências
    for (&id, &grau) in &grau_entrada {
        if grau == 0 {
            fila.push_back(id);
        }
    }

    while let Some(id) = fila.pop_front() {
        ordem.push(id);

        if let Some(vizinhos) = adjacencias.get(&id) {
            for &vizinho in vizinhos {
                if let Some(grau) = grau_entrada.get_mut(&vizinho) {
                    *grau -= 1;
                    if *grau == 0 {
                        fila.push_back(vizinho);
                    }
                }
            }
        }
    }

    ordem
}

fn main() {
    // Define as tarefas de um projeto de software
    let tarefas = vec![
        Tarefa::nova(1, "Análise de Requisitos", 3, 5, vec![]),
        Tarefa::nova(2, "Design do Banco", 4, 4, vec![1]),
        Tarefa::nova(3, "Design da API", 3, 4, vec![1]),
        Tarefa::nova(4, "Implementar Backend", 8, 3, vec![2, 3]),
        Tarefa::nova(5, "Implementar Frontend", 6, 3, vec![3]),
        Tarefa::nova(6, "Testes de Integração", 4, 2, vec![4, 5]),
        Tarefa::nova(7, "Deploy", 2, 5, vec![6]),
    ];

    // Mostra a ordem de dependências via BFS
    println!("═══ Resolução de Dependências (BFS / Kahn) ═══\n");
    let ordem = ordem_bfs(&tarefas);
    println!("Ordem topológica: {:?}\n", ordem);

    for id in &ordem {
        if let Some(tarefa) = tarefas.iter().find(|t| t.id == *id) {
            let deps_str = if tarefa.dependencias.is_empty() {
                "nenhuma".to_string()
            } else {
                tarefa.dependencias.iter()
                    .map(|d| format!("T{}", d))
                    .collect::<Vec<_>>()
                    .join(", ")
            };
            println!("  T{}: {} (deps: {})", tarefa.id, tarefa.nome, deps_str);
        }
    }

    // Executa o agendador round-robin
    println!("\n");
    let mut agendador = Agendador::novo(3); // quantum de 3 unidades

    for tarefa in tarefas {
        agendador.adicionar_tarefa(tarefa);
    }

    agendador.executar_tudo();
}

Comparação com Outras Estruturas

CritérioFila (VecDeque)Pilha (Vec)Lista LigadaBinaryHeap
SemânticaFIFOLIFOVariávelPrioridade
Enfileirar/EmpilharO(1)O(1)O(1)O(log n)
Desenfileirar/DesempilharO(1) frenteO(1) topoO(1) inícioO(log n)
AcessoO(1) ambasO(1) topoO(1) inícioO(1) máximo
Preserva ordem de chegadaSimInversaSimNão
Cache-friendlySimSimNãoSim
Uso idealBFS, buffersDFS, parsingInserção no meioEventos, Dijkstra

Quando Usar Fila?

  • Processamento em ordem de chegada: atendimento, fila de mensagens, buffers de I/O.
  • BFS (Busca em Largura): explorar grafos nível por nível.
  • Agendamento round-robin: processos, threads, tarefas.
  • Buffer produtor-consumidor: desacoplar velocidades de produção e consumo.
  • Simulações: filas de espera, modelos de tráfego, redes.

Quando NÃO Usar Fila?

  • Se a ordem não importa — use Vec<T> ou HashSet<T>.
  • Se precisa de acesso por prioridade — use BinaryHeap<T>.
  • Se precisa de acesso aleatório frequente — use Vec<T>.
  • Se precisa de LIFO — use pilha (Vec::push/pop).

Exercícios

Exercício 1: Fila Circular de Tamanho Fixo

Implemente uma fila circular que não cresce automaticamente. Quando cheia, enfileirar deve retornar Err. Adicione um método esta_cheia() -> bool. Este padrão é comum em sistemas embarcados e buffers de hardware com memória limitada.

Dica: Remova a lógica de redimensionamento da implementação apresentada e adicione a verificação de capacidade.

Exercício 2: BFS em Grade

Implemente uma busca em largura em uma grade 2D (matrix) para encontrar o menor caminho entre dois pontos. A grade contém células livres (.) e paredes (#). O movimento é permitido nas 4 direções (cima, baixo, esquerda, direita).

  Entrada:           Saída: caminho mínimo (comprimento 7)
  S . . # .          S → . → . # .
  . # . . .          .   # . . .
  . # # # .          .   # # # .
  . . . # E          . → . → . # E
                                 ↑

Dica: Use VecDeque<(usize, usize)> como fila. Mantenha uma matriz de visitados e uma de predecessores para reconstruir o caminho.

Exercício 3: Intercalar Duas Filas

Implemente uma função intercalar(f1: VecDeque<T>, f2: VecDeque<T>) -> VecDeque<T> que intercala os elementos de duas filas. Se uma fila tiver mais elementos, os extras vão para o final.

Exemplo: [1, 3, 5] + [2, 4, 6, 8] = [1, 2, 3, 4, 5, 6, 8]

Dica: Desenfileire alternadamente de cada fila enquanto ambas tiverem elementos.


Conclusão

A fila é uma estrutura indispensável em computação, e o VecDeque<T> do Rust oferece uma implementação extremamente eficiente baseada em buffer circular.

Pontos-chave a lembrar:

  1. FIFO — primeiro a entrar, primeiro a sair. A fila preserva a ordem de chegada.

  2. Use VecDeque<T> como fila no Rust — push_back para enfileirar, pop_front para desenfileirar, ambos em O(1).

  3. Nunca use Vec::remove(0) como fila — é O(n) e destrói a performance.

  4. O buffer circular é a técnica que permite operações O(1) em ambas as extremidades, usando aritmética modular para “envolver” os índices.

  5. BFS é o algoritmo canônico que usa filas — encontra o caminho mais curto em grafos não ponderados.

  6. Para filas de prioridade, use BinaryHeap<T> ao invés de VecDeque<T>.

Entender filas é essencial para programação concorrente, sistemas distribuídos e algoritmos de grafos — três pilares fundamentais da engenharia de software moderna.