Trilha 03 · Algoritmos e complexidade

Busca linear e busca binária

Como encontrar um elemento em uma lista? A busca linear olha um por um — simples, mas lenta: O(n). A busca binária divide a lista ao meio a cada passo — exige lista ordenada, mas é drasticamente mais rápida: O(log n). Para 1 bilhão de elementos, a diferença é entre 1 bilhão e 30 comparações.

① Intuição

Achar um nome na lista telefônica

A lista telefônica está ordenada por nome. Se você quer achar "Silva", não começa do "A" e vai letra a letra (busca linear). Você abre no meio, vê "Moreira", e sabe que "Silva" está depois. Abre na metade desse restante, e assim por diante. Isso é busca binária: cada passo elimina metade das possibilidades.

Mas se a lista não está ordenada — como uma lista de compras aleatória — não tem como fazer isso. Você precisa olhar item por item. É a busca linear: necessária quando não há ordem, rápida o suficiente para listas pequenas.

A condição crítica: busca binária só funciona em dados ordenados. Se a lista muda frequentemente, o custo de re-ordenar (O(n log n)) pode superar o ganho da busca. A escolha entre as duas depende de quantas buscas você fará em relação a quantas inserções.
② Visualização interativa

Veja cada passo de cada algoritmo

Escolha o algoritmo, selecione o valor a buscar, e avance passo a passo. Observe quantas comparações cada um precisa.

Buscar:
30
71
112
143
184
225
276
317
358
399
4310
4811
5212
5713
6114
6515
7016
7417
7918
8319
Passo 1: mid=9, arr[9]=39 → buscar à esquerda | janela [0..19]
Binária: máx 5 passos — O(log n)
Linear: máx 20 passos — O(n)
1 / 5
③ Explicação técnica

Busca linear — O(n)

# Busca linear — O(n) — funciona em qualquer lista
def busca_linear(lista, alvo):
    for i, v in enumerate(lista):
        if v == alvo:
            return i    # encontrado no índice i
    return -1       # não encontrado

# Melhor caso: O(1) — achou na primeira posição
# Pior caso:  O(n) — elemento ausente ou na última posição
# Requisito: NENHUM — funciona em listas não ordenadas

Busca binária — O(log n)

# Busca binária — O(log n) — exige lista ORDENADA
def busca_binaria(lista, alvo):
    l, r = 0, len(lista) - 1

    while l <= r:
        m = (l + r) // 2        # índice do meio

        if   lista[m] == alvo: return m    # achou!
        elif lista[m] <  alvo: l = m + 1  # alvo está à direita
        else:                  r = m - 1  # alvo está à esquerda

    return -1  # não encontrado

# Para n = 1.000.000: máx 20 comparações (log₂(10⁶) ≈ 20)
# Para n = 1.000.000.000: máx 30 comparações (log₂(10⁹) ≈ 30)

# Python já tem bisect_left/bisect_right:
import bisect
pos = bisect.bisect_left(lista_ordenada, alvo)
encontrado = pos < len(lista_ordenada) and lista_ordenada[pos] == alvo
Por que log₂? Cada passo divide o problema ao meio. Começando com n elementos: após 1 passo restam n/2; após 2 passos, n/4; após k passos, n/2ᵏ. O processo para quando resta 1 elemento: n/2ᵏ = 1 → k = log₂(n). Para n=1.000.000: log₂(10⁶) ≈ 20 passos. Para n=10⁹: apenas ~30 passos. Esse crescimento logarítmico é o motivo de a busca binária ser tão poderosa.
④ Projeto para programar

Implementando e comparando

Mini projeto: implemente ambas as buscas em Python. Para cada n ∈ {100, 1.000, 10.000, 100.000}, gere uma lista ordenada e conte o número de comparações no pior caso (buscar um elemento que não existe). Construa uma tabela com os resultados. O que acontece com o ratio linear/binária conforme n cresce?

Projeto principal: implemente um sistema de autocomplete simples: dado um dicionário com 200.000 palavras (use /usr/share/dict/words no Linux/Mac), encontre todas as palavras que começam com um prefixo digitado. Compare: busca linear (percorre tudo) vs busca binária para achar a posição inicial + scan. Meça o tempo com timeit.

Desafio extra: implemente busca binária de forma recursiva. Explique por que a versão iterativa é preferida na prática (dica: overhead de chamada de função e stack overflow para n muito grande). Depois implemente busca ternária (divide em 3 partes) e compare o número de comparações com a binária — surpreenda-se com o resultado.

⑤ Exercícios rápidos

Teste sua intuição

No pior caso, quantas comparações a busca binária faz em uma lista de 100 elementos?
O que acontece se você aplicar busca binária em uma lista não ordenada?
Quando vale a pena usar busca binária em vez de linear?
⑥ Aplicações no mundo real

Onde você encontra isso

📚

git bisect — encontrando bugs em histórico

O comando git bisect usa busca binária no histórico de commits para achar qual commit introduziu um bug. Com 1.000 commits, você precisaria testar apenas ~10 versões. Você marca "bom" e "ruim" em cada passo, e o git automaticamente divide o histórico ao meio. É literalmente busca binária aplicada a controle de versão.

🗄️

Índices B-tree em bancos de dados

Índices em PostgreSQL, MySQL, e SQLite são implementados como B-trees (árvores B) — uma generalização da busca binária para armazenamento em disco. Com bilhões de registros, uma query WHERE id = 12345 encontra o resultado em ~4-5 leituras de disco (altura da árvore ≈ log n), em vez de fazer full table scan. Cada "nó" da árvore corresponde a uma página de disco.

🎮

Game development — detecção de colisão

Em jogos com milhares de objetos, verificar colisão entre todos os pares é O(n²) — impossível em tempo real. A solução usa estruturas de busca espacial (como BSP trees, octrees ou BVH) que dividem o espaço recursivamente, como busca binária em 3D. Cada frame, apenas objetos em regiões próximas são verificados: O(n log n) em vez de O(n²).

← Anterior: Notação Big O Próxima: Ordenação →