Trilha de aprendizado · Nível 14 · Tutorial 6

Implementar filas eficientes com deque

Escolha deque para cargas com inserções e remoções nas extremidades, evitando deslocamentos repetidos de elementos em listas.

  • Nível: Intermediário
  • Duração: 15 min
  • 7 passos
Implementar filas eficientes com deque

O que você vai percorrer

  1. Implementar o fluxo FIFO Construa uma fila sequencial com deque, inserindo à direita e retirando à esquerda para preservar a ordem de chegada. 2 min
  2. Operar nas duas extremidades Use os quatro métodos principais da deque para controlar por qual lado cada item entra e sai. 1 min
  3. Comparar o custo de retirar do início Entenda como os deslocamentos acumulados em uma lista tornam o esvaziamento mais caro do que em uma deque. 2 min
  4. Usar maxlen sem perder itens indevidamente Use uma deque limitada para históricos descartáveis e diferencie esse caso de uma fila de trabalho que precisa preservar todos os itens. 2 min
  5. Reconhecer quando a lista é mais adequada Escolha lista ou deque observando quais operações dominam a carga de trabalho. 2 min
  6. Medir uma carga completa de entrada e saída Compare localmente uma fila implementada com lista e outra com deque, incluindo criação, entradas e retiradas FIFO em cada medição. 3 min
  7. Aplicação final: processar pendências e manter um histórico Aplique deque e lista em um fluxo completo: atenda pendências em ordem, retenha somente o histórico permitido e guarde resultados para consulta por posição. 3 min

O que você vai aprender

  • Implementar uma fila FIFO com append e popleft.
  • Comparar o custo de remoções no início de uma lista com o de uma deque.
  • Usar maxlen quando o descarte automático de elementos antigos fizer parte do contrato.
  • Reconhecer situações em que uma lista continua sendo a estrutura mais adequada.

Antes de começar

  • Estimar tempo e memória com notação O grande
  • Medir trechos de código com timeit
  • Organizar e atualizar dados em listas

Passo 1 de 7

Implementar o fluxo FIFO

Construa uma fila sequencial com deque, inserindo à direita e retirando à esquerda para preservar a ordem de chegada.

FIFO: chegada e atendimento

Uma fila respeita a chegada

FIFO significa primeiro a entrar, primeiro a sair. Em uma fila, os novos itens entram por uma extremidade e o item mais antigo sai pela extremidade oposta.

Com deque, vamos inserir à direita com append e atender pela esquerda com popleft.

As duas pontas da fila

Cada operação acontece em uma ponta específica da estrutura.

Diagrama de uma fila horizontal: cartões A, B e C organizados da esquerda para a direita, com saída do cartão A à esquerda e entrada de um novo cartão à direita.

O item à esquerda é o próximo a sair; novos itens entram à direita.

Criar e operar uma deque

Importação e criação

deque pertence ao módulo collections, da biblioteca padrão: não é preciso instalar nada. Você pode começar vazia ou preencher a fila com um iterável.

Fila vazia e fila inicializada

python
from collections import deque

fila_vazia = deque()
fila = deque(["pedido-1", "pedido-2"])

fila.append("pedido-3")       # entra à direita
proximo = fila.popleft()       # sai pela esquerda

print(proximo)  # pedido-1
print(fila)     # deque(['pedido-2', 'pedido-3'])

Dica

Leia o estado da esquerda para a direita

Após os comandos, pedido-2 ficou à esquerda porque é o mais antigo entre os que ainda aguardam. Portanto, será o próximo retorno de popleft().

Consumir todos os itens com segurança

Enquanto houver itens

Em um cenário sequencial, use while fila para consumir a fila até ela ficar vazia. Uma deque vazia é avaliada como falsa, então o laço para antes de uma retirada inválida.

Processamento em ordem de chegada

python
from collections import deque

fila = deque(["validar", "calcular", "enviar"])

while fila:
    tarefa = fila.popleft()
    print(f"Processando: {tarefa}")

print(fila)  # deque([])

Atenção

Não retire de uma fila vazia

Chamar fila.popleft() quando não há itens gera IndexError. O teste while fila evita esse erro ao consumir todos os elementos.

Pratique a ordem FIFO

Ordene as saídas

Partindo de uma deque vazia, ocorrem estas operações: append("A"), append("B"), popleft(), append("C"), popleft(), popleft(). Ordene os valores retornados pelos três popleft() պատասխանados.

  1. C
  2. A
  3. B

Passo 2 de 7

Operar nas duas extremidades

Use os quatro métodos principais da deque para controlar por qual lado cada item entra e sai.

Uma estrutura, duas pontas

Além da fila FIFO

Uma deque tem extremidade esquerda e direita. No fluxo FIFO que você já usou, o item entra à direita com append e sai à esquerda com popleft.

Mas você pode escolher qualquer ponta para inserir ou retirar. A ordem final depende da combinação escolhida: retirar pela ponta oposta à entrada preserva a chegada; retirar pela mesma ponta traz primeiro o item mais recente.

Mapa das extremidades

Os quatro métodos atuam diretamente nas pontas da deque.

Diagrama de uma deque horizontal com setas indicando appendleft entrando à esquerda, popleft saindo à esquerda, append entrando à direita e pop saindo à direita.

Entrada e saída podem ocorrer tanto à esquerda quanto à direita.

Quatro operações, dois lados

Exemplo

Métodos e efeitos

from collections import deque

itens = deque(["B", "C"])

itens.append("D")       # direita: deque(["B", "C", "D"])
itens.appendleft("A")   # esquerda: deque(["A", "B", "C", "D"])

ultimo = itens.pop()      # retira "D" da direita
primeiro = itens.popleft() # retira "A" da esquerda

print(ultimo, primeiro)  # D A
print(itens)             # deque(["B", "C"])

Dica

Leitura rápida dos nomes

left significa esquerda. Portanto, appendleft insere à esquerda e popleft remove à esquerda. Sem left, append e pop operam à direita.

A combinação define a ordem

FIFO ou item mais recente?

Se os itens entram sempre pela direita:

  • popleft() retira pela esquerda, a ponta oposta: sai o item que chegou primeiro (FIFO).
  • pop() retira pela direita, a mesma ponta: sai o item que chegou por último.

O mesmo raciocínio vale ao inverter os lados: entrada e saída em pontas opostas preservam a ordem de chegada.

Exemplo

Comparando retiradas

from collections import deque

fila = deque(["A", "B", "C"])
print(fila.popleft())  # A: chegou primeiro

mais_recente = deque(["A", "B", "C"])
print(mais_recente.pop())  # C: chegou por último

Pratique o mapa da deque

Associe método e ação

Relacione cada método ao que ele faz.

Toque em um item e depois no par correspondente.

Escolha FIFO

Uma deque recebe "A", depois "B" e depois "C", sempre com append. Qual chamada retira "A" primeiro?

Passo 3 de 7

Comparar o custo de retirar do início

Entenda como os deslocamentos acumulados em uma lista tornam o esvaziamento mais caro do que em uma deque.

Retirar o primeiro item de uma lista desloca os demais

O custo de pop(0)

Em uma lista, os elementos ocupam posições consecutivas. Ao executar fila.pop(0), o primeiro item sai e todas as referências restantes precisam avançar uma posição para preencher o espaço vazio.

Assim, uma única retirada no início custa O(n): quanto mais itens restarem, mais deslocamentos serão necessários.

O espaço precisa ser preenchido

Compare o que acontece com os itens que permanecem após retirar o primeiro elemento.

Comparação entre uma lista cujos itens restantes deslizam uma posição à esquerda após a retirada inicial e uma deque da qual o primeiro item sai sem mover os outros.

Na lista, B, C e D mudam de posição; na deque, os itens restantes não precisam ser deslocados.

Exemplo

Uma retirada

fila_lista = ["A", "B", "C", "D"]
fila_lista.pop(0)  # remove "A"
# Restam ["B", "C", "D"]

Para chegar ao novo estado, as referências de "B", "C" e "D" foram deslocadas.

Na deque, as extremidades são baratas

Trabalho constante nas pontas

Uma deque foi feita para operar nas duas extremidades. Nas pontas, append, appendleft, pop e popleft têm custo aproximadamente constante: O(1).

Portanto, popleft() remove o item à esquerda sem deslocar todos os itens que ainda estão na fila.

Exemplo

Retirada FIFO com deque

from collections import deque

fila = deque(["A", "B", "C", "D"])
fila.popleft()  # remove "A"
# Restam deque(["B", "C", "D"])

A retirada continua obedecendo à ordem FIFO, mas o trabalho da operação não cresce com a quantidade de itens pendentes.

O efeito aparece ao esvaziar a fila

Somando todas as retiradas

Ao esvaziar uma lista com pop(0), a primeira retirada desloca quase todos os itens; a próxima desloca um pouco menos; e assim por diante. A soma se parece com:

(n − 1) + (n − 2) + ... + 1

Mesmo diminuindo a cada retirada, o total cresce em O(n²). Já n chamadas de popleft() com trabalho O(1) por chamada totalizam O(n).

Complete a causa

Esvaziar uma lista com pop(0) custa O(n²) porque os itens restantes são repetidamente ___.

Escolha pela carga, não por um número isolado

Assintótico não significa sempre perceptível

A diferença entre O(n²) e O(n) se torna mais importante conforme a fila cresce ou a operação se repete muitas vezes. Em uma fila muito pequena, ambos os códigos podem parecer igualmente rápidos na prática.

Ainda assim, se a carga prevê entradas e retiradas frequentes no início, usar deque evita que o custo cresça pelos deslocamentos repetidos.

Complete os custos

Para retirar todos os itens de uma fila com n elementos, list.pop(0) repetido tem custo total _, enquanto deque.popleft() repetido tem custo total _.

Passo 4 de 7

Usar maxlen sem perder itens indevidamente

Use uma deque limitada para históricos descartáveis e diferencie esse caso de uma fila de trabalho que precisa preservar todos os itens.

Um limite para o histórico

Capacidade fixa com maxlen

Ao criar uma deque, maxlen define a quantidade máxima de elementos que ela mantém. Isso é útil para um histórico recente: você aceita perder eventos antigos para conservar apenas os últimos.

Com inserções pela direita usando append, o elemento da esquerda é o mais antigo. Quando a deque já está cheia, uma nova entrada descarta automaticamente esse elemento mais antigo.

Entrada nova, saída antiga

Em um histórico de capacidade 3, a nova entrada ocupa a direita e o item mais antigo sai pela esquerda.

Diagrama de uma deque limitada a três posições: evento A, B e C; ao entrar o evento D pela direita, o evento A é descartado pela esquerda, restando B, C e D.

Com append em uma deque cheia, o descarte acontece na extremidade oposta à inserção.

Histórico dos três últimos eventos

Execute este exemplo e observe o conteúdo após cada inserção.

python
from collections import deque

historico = deque(maxlen=3)

for evento in ["login", "busca", "pagamento", "logout"]:
    historico.append(evento)
    print(list(historico))

# ['login']
# ['login', 'busca']
# ['login', 'busca', 'pagamento']
# ['busca', 'pagamento', 'logout']

A extremidade de entrada define o descarte

A regra depende da operação

O descarte não examina datas nem o conteúdo dos elementos. Ele ocorre sempre na extremidade oposta à inserção:

  • append(...) em deque cheia: descarta à esquerda.
  • appendleft(...) em deque cheia: descarta à direita.

A inserção acontece normalmente: maxlen não faz a deque esperar por espaço nem rejeita o novo item.

Inserindo pela esquerda

Agora o item inserido entra à esquerda; por isso, o item da direita é removido.

python
from collections import deque

ultimos = deque(["B", "C", "D"], maxlen=3)
ultimos.appendleft("A")

print(list(ultimos))
# ['A', 'B', 'C']

Atenção

Não use maxlen para pendências obrigatórias

Uma fila de trabalho representa itens que ainda precisam ser atendidos. Se nenhum item pode ser perdido, não defina maxlen: ao atingir o limite, uma nova inserção descarta uma pendência sem avisar. Use maxlen somente quando essa perda de itens antigos fizer parte explícita do contrato.

Preveja o resultado e escolha a estrutura

Histórico limitado

Qual será o conteúdo de list(historico) ao final deste código?

from collections import deque

historico = deque(maxlen=3)
for evento in ["A", "B", "C", "D"]:
    historico.append(evento)

Contrato correto

Você vai processar pedidos na ordem de chegada e cada pedido precisa ser atendido, mesmo que a quantidade cresça. Qual escolha é adequada?

Passo 5 de 7

Reconhecer quando a lista é mais adequada

Escolha lista ou deque observando quais operações dominam a carga de trabalho.

A operação frequente decide

Deque não substitui toda lista

A deque é uma escolha forte quando inserir e retirar nas extremidades domina a carga. Mas, se o programa consulta muitas posições variadas — como dados[37], dados[850] ou dados[-12] — a lista costuma ser mais adequada.

Em uma lista, o acesso por índice é O(1). Em uma deque, acessar as extremidades é aproximadamente O(1), mas chegar a uma posição no meio custa O(n), pois a estrutura precisa percorrer elementos até ela.

Padrões de acesso

Compare o caminho necessário para cada tipo de operação.

Diagrama comparando uma lista, com acessos diretos a posições espalhadas e uma fatia contígua, a uma deque, com operações rápidas apenas nas duas extremidades e um caminho longo até o elemento central.

Lista favorece índices e fatias; deque favorece entradas e saídas nas extremidades.

Índices, fatias e pilhas

Exemplo

Quando a lista é a escolha direta

leituras = [18, 21, 19, 23, 20, 22]

print(leituras[3])     # 23
print(leituras[1:4])   # [21, 19, 23]

Listas oferecem acesso O(1) por índice e suporte direto a fatias. Uma deque aceita indexação, mas não aceita fatias diretamente: fila[1:4] gera TypeError.

Pilha também pode ser lista

Para uma pilha, os itens entram e saem pelo final. Nesse padrão, uma lista é eficiente: append adiciona no final e pop() retira do final, sem deslocar os demais elementos.

pilha = []
pilha.append("primeiro")
pilha.append("último")
print(pilha.pop())  # último

O problema da lista surge ao usar pop(0) repetidamente para montar uma fila FIFO, não ao usar pop() no final para implementar uma pilha.

Escolha pelo padrão predominante

Estrutura para leituras por posição

Um painel mantém medições em ordem e, a cada atualização, consulta muitos índices espalhados e extrai trechos como medicoes[100:120]. Qual estrutura é mais adequada?

Decisão rápida

Associe o cenário à estrutura

Relacione cada padrão predominante à estrutura mais indicada.

Toque em um item e depois no par correspondente.

Passo 6 de 7

Medir uma carga completa de entrada e saída

Compare localmente uma fila implementada com lista e outra com deque, incluindo criação, entradas e retiradas FIFO em cada medição.

Desenhe uma comparação justa

Mesma carga útil, estruturas diferentes

Uma medição útil compara implementações que fazem o mesmo trabalho. Neste experimento, cada função:

  1. cria uma estrutura vazia;
  2. insere os números de 0 até n - 1;
  3. retira todos pela frente;
  4. devolve a sequência retirada.

Assim, criação, entrada e saída pertencem ao tempo medido. Cada chamada cria seu próprio estado: uma execução não reaproveita itens da anterior.

O que entra na medição

As duas rotas recebem os mesmos itens e precisam devolver a mesma sequência FIFO.

Diagrama comparando duas cargas completas: lista vazia recebe itens pela direita e remove pela esquerda com deslocamento dos itens restantes; deque vazia recebe pela direita e remove pela esquerda sem deslocamento visual. Ambas produzem a sequência de saída zero, um, dois e assim por diante.

Meça o ciclo completo — criar, inserir e retirar — em vez de cronometrar apenas uma operação isolada.

Dica

Valide antes de cronometrar

A igualdade das saídas é uma verificação de correção, não parte do benchmark. Se as funções não devolvem o mesmo resultado, comparar seus tempos não responde qual implementação faz a mesma tarefa mais rápido.

Execute o experimento local

Código completo

Crie um arquivo chamado comparar_filas.py, copie o código e execute no terminal com python comparar_filas.py. Ele usa somente a biblioteca padrão.

comparar_filas.py

python
from collections import deque
from timeit import Timer


def fila_lista(n: int) -> list[int]:
    fila: list[int] = []
    for item in range(n):
        fila.append(item)

    retirados: list[int] = []
    while fila:
        retirados.append(fila.pop(0))
    return retirados


def fila_deque(n: int) -> list[int]:
    fila: deque[int] = deque()
    for item in range(n):
        fila.append(item)

    retirados: list[int] = []
    while fila:
        retirados.append(fila.popleft())
    return retirados


def tempo_por_execucao(funcao, n: int, repeticoes: int = 3) -> float:
    temporizador = Timer(lambda: funcao(n))
    total = temporizador.timeit(number=repeticoes)
    return total / repeticoes


for tamanho in (1_000, 5_000, 10_000):
    esperado = list(range(tamanho))

    # Correção fora da região cronometrada.
    assert fila_lista(tamanho) == esperado
    assert fila_deque(tamanho) == esperado

    lista_s = tempo_por_execucao(fila_lista, tamanho)
    deque_s = tempo_por_execucao(fila_deque, tamanho)

    print(
        f"n={tamanho:>6}: "
        f"lista={lista_s:.6f} s | "
        f"deque={deque_s:.6f} s"
    )

Leia a tendência, não uma promessa

Como interpretar a saída

Compare os tempos por execução conforme n aumenta. É comum que a fila com deque cresça de forma bem mais favorável, porque popleft() evita os deslocamentos repetidos de pop(0) em uma lista.

Não procure uma razão fixa entre os números: máquina, versão do Python, carga do sistema e tamanhos escolhidos influenciam valores absolutos. O dado principal é a tendência em uma carga equivalente.

Este experimento evidencia tempo de execução, não consumo de memória. Trocar uma lista por deque não prova, por si só, que o programa usará menos memória.

Registre sua evidência

Execute o código no seu computador. Relate os tempos observados para pelo menos dois tamanhos, confirme o que os asserts verificaram e interprete a tendência. Inclua por que seus números não permitem concluir nada, sozinhos, sobre memória.

Escreva pelo menos 180 caracteres (0/180).

Passo 7 de 7

Aplicação final: processar pendências e manter um histórico

Aplique deque e lista em um fluxo completo: atenda pendências em ordem, retenha somente o histórico permitido e guarde resultados para consulta por posição.

O contrato determina a estrutura

Cenário

Você vai simular um atendimento de pendências. Todas as pendências devem ser atendidas na ordem de chegada, sem descarte. Em paralelo, o sistema deve mostrar somente os três últimos atendimentos. Por fim, os resultados precisam permitir consultas frequentes por índice, como resultados[0].

Três coleções, três contratos

Diagrama com uma fila de pendências sem limite, um histórico limitado aos três itens recentes e uma lista de resultados acessada por posições.

A fila preserva tudo; o histórico pode descartar o mais antigo; a lista favorece consultas por índice.

Dica

Decisão antes do código

maxlen=3 pertence apenas ao histórico: perder atendimentos antigos ali é parte do contrato. A fila de pendências fica sem maxlen, pois nenhum trabalho pode desaparecer.

Execute a aplicação localmente

Prática no seu computador

Crie um arquivo chamado atendimentos.py, copie o código abaixo e execute python atendimentos.py no terminal. As verificações com assert confirmam a ordem, a ausência de pendências e o descarte esperado no histórico.

atendimentos.py

python
from collections import deque

# Nenhuma pendência pode ser descartada.
pendencias = deque()
for tarefa in ["revisar cadastro", "emitir nota", "responder cliente", "fechar chamado"]:
    pendencias.append(tarefa)

# Somente os três atendimentos mais recentes devem permanecer visíveis.
historico = deque(maxlen=3)

# O contrato pede consultas frequentes por índice.
resultados = []

while pendencias:
    tarefa = pendencias.popleft()
    resultado = f"Concluído: {tarefa}"
    historico.append(tarefa)
    resultados.append(resultado)

# Verificações fora de qualquer medição de desempenho.
assert resultados == [
    "Concluído: revisar cadastro",
    "Concluído: emitir nota",
    "Concluído: responder cliente",
    "Concluído: fechar chamado",
]
assert not pendencias
assert list(historico) == [
    "emitir nota",
    "responder cliente",
    "fechar chamado",
]
assert resultados[2] == "Concluído: responder cliente"

print("Resultados:", resultados)
print("Pendências restantes:", list(pendencias))
print("Histórico recente:", list(historico))

Exemplo

Saída esperada

Resultados: ['Concluído: revisar cadastro', 'Concluído: emitir nota', 'Concluído: responder cliente', 'Concluído: fechar chamado']
Pendências restantes: []
Histórico recente: ['emitir nota', 'responder cliente', 'fechar chamado']

Confira o comportamento observado

Relate sua execução

Após executar o programa, relate: qual foi a ordem de atendimento, quais itens ficaram no histórico e por que cada uma das três coleções é adequada ao seu papel.

Escreva pelo menos 100 caracteres (0/100).

Síntese e conclusão

Resumo

Critérios para decidir

  • Use deque com append e popleft para uma fila FIFO: entradas e retiradas ocorrem nas extremidades.
  • Não use maxlen quando cada item pendente precisa ser preservado; o descarte automático é uma regra de negócio, não uma otimização neutra.
  • Use deque(maxlen=...) quando o contrato permite manter somente os registros mais recentes.
  • Prefira lista quando consultas por índice em posições variadas forem predominantes.
  • Avalie a carga completa e a operação dominante: uma estrutura rápida em uma operação não é automaticamente a melhor escolha para todo cenário.

Tutorial concluído

Parabéns! Você concluiu: Implementar filas eficientes com deque

Você integrou fila FIFO, histórico limitado e resultados indexáveis. Agora consegue escolher entre deque e lista conforme o padrão de acesso e a permissão de descarte.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso