Trilha de aprendizado · Nível 14 · Tutorial 7

Gerenciar prioridades com heapq

Mantenha uma coleção que permite retirar repetidamente o item de menor prioridade sem ordenar todos os elementos após cada alteração.

  • Nível: Intermediário
  • Duração: 20 min
  • 8 passos
Gerenciar prioridades com heapq

O que você vai percorrer

  1. Definir qual tarefa deve sair primeiro Compare a ordem de chegada com a ordem definida por prioridade e identifique a próxima tarefa disponível a cada retirada. 1 min
  2. Entender a heap mínima dentro de uma lista Veja como uma heap mínima organiza prioridades em uma lista sem transformar a lista em uma sequência totalmente ordenada. 2 min
  3. Construir, inserir e retirar com heapq Use heapify, heappush e heappop para manter uma heap mínima e observar a diferença entre consultar, retirar e percorrer sua lista interna. 3 min
  4. Resolver empates sem comparar as tarefas Use um contador de entrada para desempatar prioridades iguais sem exigir que as tarefas sejam comparáveis. 3 min
  5. Alterar prioridades sem deixar a heap inválida Atualize uma prioridade substituindo a entrada completa e reconstruindo a heap antes da próxima retirada. 2 min
  6. Comparar os custos conforme o uso Escolha entre heap e ordenação completa observando a mistura real de operações da sua carga. 3 min
  7. Medir cargas equivalentes Compare implementações equivalentes de fila de prioridades no seu computador, verificando primeiro o comportamento e depois os tempos. 3 min
  8. Aplicar e revisar uma fila de tarefas Integre as operações essenciais de heapq em uma fila de tarefas e verifique seu contrato de comportamento. 3 min

O que você vai aprender

  • Construir e atualizar uma fila de prioridades com heapq.
  • Explicar por que uma heap não é uma lista totalmente ordenada.
  • Resolver empates sem exigir comparação entre os objetos armazenados.
  • Comparar os custos da heap com os de reordenar a coleção a cada operação.

Antes de começar

  • Estimar tempo e memória com notação O grande
  • Medir trechos de código com timeit
  • Agrupar e desempacotar valores com tuplas
  • Definir igualdade e ordenação entre objetos
  • Limitar o consumo de iteradores com itertools

Passo 1 de 8

Definir qual tarefa deve sair primeiro

Compare a ordem de chegada com a ordem definida por prioridade e identifique a próxima tarefa disponível a cada retirada.

Prioridade decide a próxima saída

Nem sempre quem chegou primeiro sai primeiro

Em uma fila de prioridades, cada tarefa tem um número de prioridade. Neste tutorial, quanto menor o número, mais cedo a tarefa deve ser atendida.

Quando alguém pede a próxima tarefa, a coleção seleciona o menor valor de prioridade entre as tarefas disponíveis naquele momento. A ordem de chegada só seria decisiva se estivéssemos usando uma fila FIFO comum.

Chegada versus prioridade

Observe como a tarefa urgente pode ultrapassar uma tarefa que já estava aguardando.

Comparação visual entre uma fila FIFO que atende cartões pela chegada e uma fila de prioridades que atende primeiro o cartão com menor número.

FIFO segue a chegada; uma fila de prioridades seleciona o menor número disponível.

A próxima retirada depende do momento

Exemplo

Entradas e retiradas intercaladas

Considere estas ações:

  1. Chega "responder e-mail" — prioridade 4
  2. Chega "corrigir falha" — prioridade 1
  3. Retira uma tarefa → sai "corrigir falha"
  4. Chega "revisar relatório" — prioridade 2
  5. Retira uma tarefa → sai "revisar relatório"

Na primeira retirada, as prioridades disponíveis eram 4 e 1. Na segunda, a tarefa de prioridade 1 já tinha saído, e a prioridade 2 passou a ser a menor disponível.

Portanto, não basta olhar a ordem em que todas as tarefas chegaram: é preciso considerar quais ainda estão na coleção a cada retirada.

Dica

Regra prática

Para descobrir a próxima saída, liste apenas as tarefas que já chegaram e ainda não foram retiradas. Entre elas, escolha a de menor prioridade numérica.

Pratique a decisão por prioridade

Ordene as três retiradas

As ações ocorrem nesta ordem: chega A (prioridade 3); chega B (prioridade 1); retira; chega C (prioridade 2); retira; retira.

Coloque as tarefas na ordem em que serão retiradas.

  1. C (prioridade 2)
  2. A (prioridade 3)
  3. B (prioridade 1)

Passo 2 de 8

Entender a heap mínima dentro de uma lista

Veja como uma heap mínima organiza prioridades em uma lista sem transformar a lista em uma sequência totalmente ordenada.

Uma árvore guardada em uma lista

Representação compacta

Uma heap mínima pode ser vista como uma árvore binária completa: os níveis são preenchidos da esquerda para a direita. Mas, em Python, ela é armazenada em uma lista comum — não são criados objetos separados para cada nó.

Exemplo de lista: [1, 4, 3, 9, 7, 8]. O valor no índice 0 representa a raiz da árvore.

Lista e árvore são a mesma heap

Leia a lista por níveis: primeiro a raiz, depois seus filhos, depois os nós do nível seguinte.

Diagrama relacionando a lista [1, 4, 3, 9, 7, 8] a uma árvore binária completa: 1 na raiz; 4 e 3 como filhos; 9 e 7 abaixo de 4; 8 abaixo de 3.

A posição de cada elemento na lista determina sua relação de pai e filhos.

Exemplo

Índices dos filhos

Para um valor no índice i:

  • filho à esquerda: 2i + 1
  • filho à direita: 2i + 2

Na lista [1, 4, 3, 9, 7, 8], o índice 1 contém 4. Seus filhos ficam nos índices 3 e 4: os valores 9 e 7.

Esses índices só são filhos quando existem dentro do tamanho da lista.

A regra local da heap mínima

Invariante

Em uma heap mínima de números comparáveis, nenhum pai é maior que seus filhos. Essa é a invariante que a estrutura preserva.

Como a raiz é ancestral de todos os elementos, essa regra garante que o menor valor esteja no índice 0. Porém, ela compara cada nó apenas com seus filhos; não exige comparar todos os pares da lista.

Relações que importam

Observe que cada ligação pai-filho respeita a regra, embora os valores fora dessas ligações não estejam em ordem crescente na lista.

Árvore de heap mínima com raiz 1, filhos 4 e 3, e folhas 9, 7 e 8; setas destacam apenas relações pai-filho em que o pai é menor ou igual ao filho.

A heap impõe uma regra local entre pai e filhos.

Dica

Não confunda com lista ordenada

A lista [1, 4, 3, 9, 7, 8] é uma heap válida, mas não está totalmente ordenada: 4 aparece antes de 3. O que importa é que 1 seja menor que 4 e 3, que 4 não seja maior que 9 e 7, e que 3 não seja maior que 8.

Reconheça uma heap válida

Heap não é lista ordenada

A lista [1, 4, 3, 9, 7, 8] pode representar uma heap mínima válida, mesmo porque 4 vem antes de 3.

Há várias disposições válidas

A forma interna não é única

Os mesmos valores podem formar heaps mínimas diferentes. Por exemplo, [1, 3, 4, 8, 7, 9] também é válida: cada pai continua menor ou igual aos próprios filhos.

Portanto, ao verificar uma heap, não exija uma ordem específica após o índice 0. Verifique as relações entre cada pai e seus filhos.

Encontre a violação

A lista [1, 6, 4, 2, 8] representa uma heap mínima válida.

Resumo

Essencial deste step

  • Uma heap mínima é uma árvore binária completa armazenada em uma lista.
  • Para o índice i, os filhos possíveis estão em 2i + 1 e 2i + 2.
  • A regra é local: pai menor ou igual aos filhos.
  • O menor elemento fica no índice 0, mas o restante da lista não precisa estar ordenado.
  • Os mesmos valores podem ter mais de uma organização interna válida.

Passo 3 de 8

Construir, inserir e retirar com heapq

Use heapify, heappush e heappop para manter uma heap mínima e observar a diferença entre consultar, retirar e percorrer sua lista interna.

Transforme uma lista em heap

heapify reorganiza a própria lista

Importe o módulo heapq e chame heapq.heapify(valores) para transformar uma lista existente em uma heap mínima. A função modifica a lista recebida e retorna None; portanto, não faça valores = heapq.heapify(valores).

Depois da transformação, o menor valor está em valores[0]. Os demais índices representam uma heap válida, mas não uma lista totalmente ordenada.

Heap válida não é lista ordenada

A raiz da heap contém o menor valor, enquanto os outros valores só precisam respeitar a relação entre pai e filhos.

Diagrama comparando uma árvore de heap mínima com sua representação em lista: raiz com valor 1 e valores inferiores organizados sem ordem global entre todos os índices.

A posição zero guarda o menor valor; a sequência interna pode parecer desordenada.

Construção a partir de valores existentes

Execute este trecho e observe o retorno de heapify.

python
import heapq

valores = [8, 3, 6, 1, 7]
resultado = heapq.heapify(valores)

print(resultado)  # None
print(valores)     # heap válida; a ordem interna não é ordenação completa
print(valores[0])  # 1

Inserir, consultar e retirar

As três operações essenciais

Uma lista vazia ([]) já é uma heap válida. Use heappush(heap, valor) para inserir e heappop(heap) para retirar e obter o menor valor disponível. Ambas preservam a invariante da heap.

Use heap[0] apenas para consultar o menor: essa leitura não remove nada. Tanto heap[0] quanto heappop(heap) levantam IndexError se a heap estiver vazia.

Uma heap começa vazia

Compare a consulta com a retirada no mesmo estado da heap.

python
import heapq

heap = []

for prioridade in [5, 2, 8, 1]:
    heapq.heappush(heap, prioridade)

print(heap)       # representação interna de uma heap
print(heap[0])    # consulta: mostra 1, sem remover
print(heap)       # ainda contém 1

proxima = heapq.heappop(heap)
print(proxima)    # 1: menor valor retirado
print(heap)       # agora não contém mais 1

if heap:
    print(heap[0])
else:
    print("Não há prioridades pendentes.")

Escolha a operação

Retirada do próximo valor

Complete a chamada para retirar o menor valor da heap:

proxima = heapq._____(heap)

Consulta sem remoção

Complete a expressão que consulta o menor valor sem removê-lo:

menor = heap_____

Extraia sem perder a coleção original

Retirar consome a heap

Extrações sucessivas com heappop produzem os valores em ordem crescente. Porém, ao esvaziar a heap, seus elementos são consumidos.

Se precisar manter a heap original, crie uma cópia com copia = heap.copy() e retire da cópia. Não conclua nada pela ordem exibida na lista interna: valide o comportamento pela sequência de valores retirados.

Prática local: execute o script completo

No seu computador, salve como prioridades.py e execute com python prioridades.py. Compare a lista interna com a sequência de extrações.

python
import heapq

prioridades = [9, 4, 1, 7, 3]
heapq.heapify(prioridades)

print("Heap interna:", prioridades)
print("Próxima sem retirar:", prioridades[0])
print("Após consultar:", prioridades)

copia = prioridades.copy()
extraidas = []

while copia:
    extraidas.append(heapq.heappop(copia))

print("Extraídas:", extraidas)
print("Cópia após extrair:", copia)
print("Heap original preservada:", prioridades)

try:
    heapq.heappop(copia)
except IndexError:
    print("Não é possível retirar de uma heap vazia.")

Relate sua observação

Após executar o script, relate: qual foi a diferença entre consultar e retirar, qual sequência foi extraída e como ficaram a cópia e a heap original no final.

Escreva pelo menos 80 caracteres (0/80).

Passo 4 de 8

Resolver empates sem comparar as tarefas

Use um contador de entrada para desempatar prioridades iguais sem exigir que as tarefas sejam comparáveis.

O problema do empate

Prioridade igual não basta

Uma entrada como (prioridade, tarefa) funciona enquanto as prioridades são diferentes. Mas tuplas são comparadas da esquerda para a direita: se duas prioridades empatam, Python tenta comparar as próprias tarefas para decidir a ordem.

Isso é um problema quando a tarefa é um dicionário, pois dicionários não possuem uma ordenação entre si.

Como a comparação avança

Em um empate, a comparação alcança o objeto armazenado.

Diagrama de duas tuplas com prioridade igual. A comparação confirma a igualdade das prioridades e avança para dois dicionários, onde a comparação não é definida.

Com (prioridade, tarefa), prioridades iguais fazem a comparação chegar à tarefa.

Empate que causa erro

Execute este exemplo em um arquivo Python ou no interpretador.

python
import heapq

fila = []
heapq.heappush(fila, (1, {"nome": "enviar relatório"}))
heapq.heappush(fila, (1, {"nome": "responder cliente"}))
# TypeError: '<' not supported between instances of 'dict' and 'dict'

Inclua um critério de desempate

Prioridade, contador e tarefa

Armazene cada entrada como (prioridade, contador, tarefa). O contador é obtido de um único itertools.count() associado à fila.

Com a mesma prioridade, o menor contador vence. Assim, a heap decide o empate antes de precisar comparar a tarefa. Como o contador cresce a cada entrada, ele preserva a ordem de chegada entre tarefas de mesma prioridade.

Ordem dos critérios

A heap compara os campos da tupla nesta sequência.

Diagrama com três colunas conectadas: prioridade, contador de chegada e tarefa. Setas indicam que a tarefa só seria alcançada depois dos dois critérios anteriores.

O contador resolve o empate; a tarefa pode ser qualquer objeto, inclusive um dicionário.

Dica

Estabilidade é uma escolha

heapq não adiciona estabilidade automaticamente. A ordem de chegada nos empates existe aqui porque você incluiu um contador crescente como segundo critério da tupla.

Fila estável com dicionários

Inserir e retirar tarefas

O desempacotamento ignora os critérios auxiliares quando a tarefa é retirada.

python
import heapq
from itertools import count

fila = []
proximo_id = count()

def adicionar(prioridade, tarefa):
    entrada = (prioridade, next(proximo_id), tarefa)
    heapq.heappush(fila, entrada)

adicionar(2, {"nome": "revisar orçamento"})
adicionar(1, {"nome": "responder cliente"})
adicionar(1, {"nome": "corrigir erro"})

while fila:
    _, _, tarefa = heapq.heappop(fila)
    print(tarefa["nome"])

# responder cliente
# corrigir erro
# revisar orçamento

Exemplo

Por que essa saída?

As duas primeiras tarefas retiradas têm prioridade 1, menor que 2. Entre elas, “responder cliente” entrou antes e recebeu contador menor; por isso sai antes de “corrigir erro”. Os dicionários nunca precisam ser comparados.

Pratique o padrão

Complete a entrada

Complete a expressão para inserir uma tarefa com prioridade, contador e item:

entrada = (prioridade, ________, tarefa)

Verifique no seu computador

Execute o código da tela anterior. Explique a ordem impressa e por que não ocorre TypeError, embora as tarefas sejam dicionários.

Escreva pelo menos 60 caracteres (0/60).

Passo 5 de 8

Alterar prioridades sem deixar a heap inválida

Atualize uma prioridade substituindo a entrada completa e reconstruindo a heap antes da próxima retirada.

Uma troca pode quebrar a regra da heap

A lista ainda existe; a heap pode não

Uma entrada da fila tem a forma (prioridade, contador, tarefa). Se você localizar uma entrada e a substituir por outra com prioridade diferente, todos os itens continuam na lista — mas a relação entre pai e filhos pode deixar de ser válida.

Por isso, não consulte fila[0] nem chame heappop() depois da substituição até restaurar a heap com heapify(fila).

Antes e depois de alterar uma entrada

A nova prioridade 0 foi colocada em uma posição cujo pai tem prioridade 2. Isso viola a regra: um pai não pode ser maior que um filho.

Comparação entre uma heap mínima válida e a mesma estrutura após uma entrada receber prioridade menor sem reorganização; a segunda estrutura mostra um pai de prioridade 2 acima de um filho de prioridade 0.

Trocar uma entrada não reorganiza automaticamente os elementos da lista.

Atenção

Não altere só a tarefa

A heap compara a prioridade armazenada na tupla, não um campo dentro do dicionário da tarefa. Mudar, por exemplo, tarefa["prioridade"] não muda o primeiro valor da tupla já presente na fila. Crie uma nova tupla com a prioridade desejada e substitua a entrada inteira.

Atualização simples para mudanças ocasionais

Localize, substitua e reconstrua

heapq não fornece uma operação para encontrar uma tarefa arbitrária e alterar sua prioridade. Para atualizações ocasionais, faça uma varredura da lista, substitua a entrada e execute heapify.

Ao substituir, mantenha o contador original. Assim, se duas tarefas tiverem a mesma prioridade, a que entrou primeiro continua saindo primeiro.

Repriorizar uma tarefa

A função recebe uma heap de entradas (prioridade, contador, tarefa) e atualiza uma tarefa identificada por "id".

python
import heapq


def atualizar_prioridade(fila, id_tarefa, nova_prioridade):
    for indice, (prioridade, contador, tarefa) in enumerate(fila):
        if tarefa["id"] == id_tarefa:
            fila[indice] = (nova_prioridade, contador, tarefa)
            heapq.heapify(fila)
            return

    raise KeyError(f"Tarefa não encontrada: {id_tarefa}")


fila = [
    (1, 0, {"id": "relatorio", "titulo": "Enviar relatório"}),
    (2, 1, {"id": "backup", "titulo": "Fazer backup"}),
    (4, 2, {"id": "revisao", "titulo": "Revisar proposta"}),
]
heapq.heapify(fila)

atualizar_prioridade(fila, "revisao", 0)
prioridade, _, tarefa = heapq.heappop(fila)
print(prioridade, tarefa["id"])
# 0 revisao

Dica

O que o contador preserva

Na substituição, use (nova_prioridade, contador, tarefa), e não um contador novo. O contador existente registra a chegada original da tarefa e mantém a política de desempate estável.

Sequência segura de atualização

Coloque as ações na ordem correta

Uma tarefa já presente precisa receber uma nova prioridade. Organize a estratégia simples e segura.

  1. Substituir a entrada por (nova_prioridade, contador_original, tarefa).
  2. Executar heapq.heapify(fila).
  3. Consultar fila[0] ou retirar com heapq.heappop(fila).
  4. Localizar a entrada da tarefa por varredura da lista.

Preveja a próxima retirada

Depois da reconstrução

Considere as entradas abaixo, em que o segundo número é o contador:

fila = [(1, 0, "A"), (2, 1, "B"), (4, 2, "C")]

A entrada de C é substituída por (0, 2, "C") e então heapq.heapify(fila) é executado. Qual item será retornado pela próxima chamada a heappop(fila)?

Passo 6 de 8

Comparar os custos conforme o uso

Escolha entre heap e ordenação completa observando a mistura real de operações da sua carga.

Custos que orientam a escolha

Heap: mantenha o próximo menor disponível

Para uma lista já preenchida, heapq.heapify(lista) constrói a heap em O(n). Inserir com heappush e retirar o menor com heappop custa O(log n); consultar o menor em heap[0] custa O(1).

Esses custos assumem comparações de custo constante. O crescimento interno da lista também tem custo amortizado, como em outras listas Python.

Heap e lista ordenada respondem a cargas diferentes

Comparação visual entre uma heap, que mantém somente o menor elemento no topo, e uma lista em ordem decrescente, que precisa ser reorganizada após uma inserção.

Na heap, o menor fica acessível no topo; na lista ordenada, toda a ordem precisa continuar válida após novas entradas.

Exemplo

Construção inicial: duas opções

import heapq

valores = [8, 3, 6, 1, 7]
heapq.heapify(valores)  # O(n), altera a própria lista

heap = []
for valor in [8, 3, 6, 1, 7]:
    heapq.heappush(heap, valor)  # n inserções: O(n log n)

Se todos os valores já estão disponíveis, heapify é a construção mais adequada assintoticamente.

Nem toda operação favorece a heap

Atualizar e ordenar tudo têm outros custos

A estratégia simples de atualização ensinada antes — localizar uma tarefa, substituir a entrada e chamar heapify — custa O(n): a varredura é O(n) e a reconstrução também é O(n). Não é uma atualização O(log n).

Também não use uma heap esperando ordenar uma coleção inteira mais rápido: retirar seus n elementos exige n operações O(log n), totalizando O(n log n).

E a lista mantida ordenada?

Uma lista em ordem decrescente permite retirar o menor pelo final com pop(), em O(1). Porém, inserir uma nova tarefa exige encontrar e abrir espaço na posição correta, com custo O(n) para manter a ordenação.

Reordenar a coleção com list.sort() pode custar até O(n log n) a cada ordenação. Na prática, o tempo também depende da ordem atual dos dados, pois a ordenação do Python é adaptável. Portanto, meça cargas equivalentes antes de concluir qual alternativa é mais rápida.

Associe a operação ao custo

Considere comparações de custo constante e a estratégia simples de atualização por reconstrução.

Toque em um item e depois no par correspondente.

Escolha pela carga de trabalho

Regra prática

Use uma heap quando novas tarefas chegam enquanto você precisa consultar ou retirar repetidamente a próxima de menor prioridade.

Para um lote estático, sem novas entradas entre a preparação e o consumo, ordenar uma única vez pode ser mais simples e adequado. Se há muitas mudanças arbitrárias de prioridade, a atualização simples por varredura e heapify pode deixar de ser atraente.

Decida para uma carga incremental

Um sistema recebe tarefas continuamente. Após algumas inserções, ele precisa retirar a tarefa de menor prioridade; então recebe mais tarefas e repete esse ciclo. Qual estratégia combina melhor com essa carga?

Decisão em um lote estático

Decida para um lote fechado

Você recebe 200 mil tarefas de uma vez e, somente depois de receber todas, precisa processá-las integralmente em ordem crescente de prioridade. Não haverá inserções nem alterações durante o processamento. Qual opção é uma escolha razoável?

Passo 7 de 8

Medir cargas equivalentes

Compare implementações equivalentes de fila de prioridades no seu computador, verificando primeiro o comportamento e depois os tempos.

Compare o trabalho, não apenas o relógio

Duas estratégias, mesmo contrato

Para uma comparação justa, as duas soluções precisam aplicar o mesmo critério: menor prioridade primeiro e, em caso de empate, ordem de entrada.

A estratégia com heapq mantém a heap a cada inserção. A outra mantém uma lista em ordem decrescente após cada inserção; assim, seu menor item fica no final e sai com pop().

Antes de medir, confirme que ambas produzem exatamente a mesma sequência de retiradas.

O que muda entre as estratégias

As duas estruturas recebem as mesmas entradas e entregam as mesmas saídas. A diferença está no trabalho feito entre uma retirada e outra.

Diagrama comparando uma heap mínima que se ajusta em cada inserção com uma lista que é reordenada após cada inserção; ambas retiram o item de menor prioridade.

A heap preserva apenas a condição necessária para encontrar o menor; a lista alternativa é reordenada por inteiro a cada nova entrada.

Execute uma comparação reproduzível

Um script, duas cargas

Crie um arquivo, por exemplo comparar_heap.py, cole o código abaixo e execute python comparar_heap.py no seu computador.

As cargas são determinísticas. Cada chamada das funções cria sua própria estrutura e seu próprio count(): uma execução medida não reutiliza o estado da anterior.

comparar_heap.py

O script valida a equivalência antes de imprimir o menor tempo por execução em cada cenário.

python
from heapq import heapify, heappop, heappush
from itertools import count
from timeit import Timer

# Dados determinísticos fora da região medida.
ENTRADAS_LOTE = [
    ((indice * 37) % 23 + 1, f"lote-{indice}")
    for indice in range(2_000)
]
INICIAIS = [
    ((indice * 17) % 19 + 1, f"inicial-{indice}")
    for indice in range(80)
]
INTERCALADAS = [
    ((indice * 29) % 19 + 1, f"nova-{indice}")
    for indice in range(1_000)
]


def lote_heap(entradas):
    heap = []
    contador = count()
    for prioridade, tarefa in entradas:
        heappush(heap, (prioridade, next(contador), tarefa))
    return [heappop(heap)[2] for _ in range(len(heap))]


def lote_lista_ordenada_uma_vez(entradas):
    contador = count()
    lista = [
        (prioridade, next(contador), tarefa)
        for prioridade, tarefa in entradas
    ]
    lista.sort(reverse=True)
    return [lista.pop()[2] for _ in range(len(lista))]


def intercalada_heap(iniciais, entradas):
    heap = []
    contador = count()
    for prioridade, tarefa in iniciais:
        heappush(heap, (prioridade, next(contador), tarefa))

    saidas = []
    for prioridade, tarefa in entradas:
        heappush(heap, (prioridade, next(contador), tarefa))
        saidas.append(heappop(heap)[2])
    return saidas


def intercalada_lista_reordenada(iniciais, entradas):
    lista = []
    contador = count()
    for prioridade, tarefa in iniciais:
        lista.append((prioridade, next(contador), tarefa))
        lista.sort(reverse=True)

    saidas = []
    for prioridade, tarefa in entradas:
        lista.append((prioridade, next(contador), tarefa))
        lista.sort(reverse=True)
        saidas.append(lista.pop()[2])
    return saidas


def medir(funcao, *argumentos, numero=3, repeticoes=5):
    tempos = Timer(lambda: funcao(*argumentos)).repeat(
        repeat=repeticoes,
        number=numero,
    )
    return min(tempos) / numero


# Verifique comportamento antes de comparar duração.
assert lote_heap(ENTRADAS_LOTE) == lote_lista_ordenada_uma_vez(ENTRADAS_LOTE)
assert intercalada_heap(INICIAIS, INTERCALADAS) == intercalada_lista_reordenada(
    INICIAIS, INTERCALADAS
)
print("Saídas equivalentes: confirmado")

print("\nLote: entradas e depois todas as retiradas")
print(f"heapq:              {medir(lote_heap, ENTRADAS_LOTE):.6f} s")
print(
    "lista ordenada uma vez: "
    f"{medir(lote_lista_ordenada_uma_vez, ENTRADAS_LOTE):.6f} s"
)

print("\nCarga intercalada: uma inserção e uma retirada por vez")
print(f"heapq:              {medir(intercalada_heap, INICIAIS, INTERCALADAS):.6f} s")
print(
    "lista reordenada:   "
    f"{medir(intercalada_lista_reordenada, INICIAIS, INTERCALADAS):.6f} s"
)

Leia os resultados no contexto da carga

Lote estático e fluxo incremental são problemas diferentes

No cenário de lote, todas as entradas já existem antes da primeira retirada. A lista pode ser ordenada uma única vez, portanto essa alternativa deve entrar na comparação.

Na carga intercalada, cada inserção exige que a lista alternativa seja reordenada novamente. É justamente nesse padrão de manutenção incremental que uma heap costuma ser apropriada.

Não conclua que heapq sempre vence: os tempos dependem do tamanho, da mistura de operações, da máquina e dos custos incluídos no experimento.

Dica

Mude uma condição por vez

Depois da primeira execução, experimente alterar apenas o tamanho das listas, por exemplo de 2_000 para 20_000. Mantenha os mesmos dados, a mesma sequência de operações e a verificação com assert. Se mudar tudo ao mesmo tempo, você não saberá o que explicou a diferença.

Registre uma conclusão verificável

Sua medição local

Execute o script. As saídas foram equivalentes? Registre os tempos que você observou para o lote e para a carga intercalada. Em seguida, diga qual estratégia pareceu mais adequada em cada carga e delimite sua conclusão às condições do teste.

Escreva pelo menos 120 caracteres (0/120).

Passo 8 de 8

Aplicar e revisar uma fila de tarefas

Integre as operações essenciais de heapq em uma fila de tarefas e verifique seu contrato de comportamento.

Contrato da fila de tarefas

O que deve ser testado

Nesta aplicação, cada tarefa é um dicionário, mas a heap armazena tuplas no formato (prioridade, contador, tarefa). O contrato é observável pelas retiradas: menor prioridade numérica sai primeiro e, em caso de empate, a tarefa que entrou antes sai antes.

Após alterar uma prioridade, preserve o contador original e execute heapify. Não teste uma ordem específica da lista interna: uma heap válida só garante que o menor elemento está na posição zero.

Da entrada à retirada

Diagrama de uma fila de prioridades: tarefas entram como tuplas de prioridade, contador e dicionário; uma atualização é seguida por heapify; as retiradas mostram prioridade menor primeiro e chegada primeiro nos empates.

O contador resolve empates antes que Python precise comparar os dicionários das tarefas.

Execute um exemplo completo

Prática local

Copie o código para um arquivo Python e execute-o. Os asserts verificam a ordem de atendimento, inclusive o empate entre as prioridades 1.

fila_tarefas.py

A atualização localiza a entrada, preserva seu contador e reconstrói a heap antes das próximas retiradas.

python
import heapq
from itertools import count

fila = []
contador = count()


def adicionar(prioridade, tarefa):
    entrada = (prioridade, next(contador), tarefa)
    heapq.heappush(fila, entrada)


def alterar_prioridade(id_tarefa, nova_prioridade):
    for indice, (prioridade, ordem, tarefa) in enumerate(fila):
        if tarefa["id"] == id_tarefa:
            # Mantém a ordem original para desempates futuros.
            fila[indice] = (nova_prioridade, ordem, tarefa)
            heapq.heapify(fila)
            return
    raise KeyError(f"Tarefa não encontrada: {id_tarefa}")


def atender_proxima():
    prioridade, ordem, tarefa = heapq.heappop(fila)
    return tarefa["id"]


adicionar(2, {"id": "backup", "descricao": "Salvar dados"})
adicionar(1, {"id": "email", "descricao": "Responder cliente"})
adicionar(2, {"id": "relatorio", "descricao": "Preparar relatório"})
adicionar(1, {"id": "urgente", "descricao": "Corrigir falha"})

# "relatorio" entrou com prioridade 2, mas agora deve ser atendido primeiro.
alterar_prioridade("relatorio", 0)

ordem_atendida = []
while fila:
    ordem_atendida.append(atender_proxima())

print(ordem_atendida)
assert ordem_atendida == ["relatorio", "email", "urgente", "backup"]

Confira o comportamento, não o layout

Registre sua observação

Depois de executar o script, registre a ordem observada. Explique por que email vem antes de urgente e por que um teste não deve comparar a lista interna da heap com uma lista totalmente ordenada.

Escreva pelo menos 80 caracteres (0/80).

Escolha e revisão final

Resumo

Quando esta estratégia faz sentido

Use uma heap quando o programa recebe novas tarefas e precisa descobrir repetidamente a próxima de menor prioridade.

  • A entrada (prioridade, contador, tarefa) permite guardar dicionários e mantém uma política estável nos empates.
  • heapify é uma solução simples para uma alteração ocasional de prioridade após substituir a entrada e preservar seu contador.
  • A reconstrução e a busca pela tarefa custam O(n); para muitas atualizações, essa estratégia simples pode deixar de ser adequada.
  • A heap oferece consulta ao menor em O(1) e inserção ou retirada em O(log n), sem manter toda a lista ordenada.
  • Para um lote estático sem novas entradas, ordenar uma vez também pode ser uma escolha adequada. Meça cargas equivalentes antes de decidir.

Tutorial concluído

Parabéns! Você concluiu: Gerenciar prioridades com heapq

Muito bem! Agora você pode usar heapq para atender repetidamente a menor prioridade, testar o comportamento pelas retiradas e escolher a estrutura considerando a mistura real de operações.

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