Busca binária
A ideia
Seção intitulada “A ideia”Pense num jogo: eu escolho um número de 1 a 100 e você tenta adivinhar. A cada chute eu digo “é maior” ou “é menor”. Começar pelo 1 e subir de um em um funciona, mas pode levar 100 chutes. Chutar 50 elimina metade das opções de uma vez. Depois 25 ou 75, e assim por diante. Em no máximo 7 chutes você acerta.
A busca binária faz exatamente isso num array ordenado. Ela mantém um intervalo [low, high] onde o alvo ainda pode estar e, a cada passo, olha o elemento do meio:
- Se for o alvo, acabou.
- Se for menor que o alvo, o alvo só pode estar à direita:
low = mid + 1. - Se for maior, só pode estar à esquerda:
high = mid - 1.
Veja rodando
Seção intitulada “Veja rodando”Observe os ponteiros low, mid e high e como os elementos descartados vão apagando.
O array está ordenado. A busca começa com o intervalo inteiro: low = 0, high = 9.
function binarySearch(array: number[], target: number): number {
let low = 0
let high = array.length - 1
while (low <= high) {
const mid = Math.floor((low + high) / 2)
if (array[mid] === target) {
return mid
} else if (array[mid] < target) {
low = mid + 1
} else {
high = mid - 1
}
}
return -1
}- target
- 23
- low
- 0
- high
- 9
Agora você prevê
Seção intitulada “Agora você prevê”Antes de cada passo, clique no índice do meio que a busca vai examinar. Dica: mid = Math.floor((low + high) / 2). Este alvo não existe; quando o intervalo ficar vazio, escolha Nenhum.
O array está ordenado. A busca começa com o intervalo inteiro: low = 0, high = 11.
function binarySearch(array: number[], target: number): number {
let low = 0
let high = array.length - 1
while (low <= high) {
const mid = Math.floor((low + high) / 2)
if (array[mid] === target) {
return mid
} else if (array[mid] < target) {
low = mid + 1
} else {
high = mid - 1
}
}
return -1
}- target
- 50
- low
- 0
- high
- 11
O invariante
Seção intitulada “O invariante”O segredo para escrever uma busca binária sem erro é saber o que nunca muda durante o loop:
Se o alvo está no array, ele está entre
lowehigh(inclusive).
Toda atualização preserva isso. low = mid + 1 só acontece quando array[mid] < target, então nada à esquerda de mid + 1 pode ser o alvo. Quando low > high, o intervalo está vazio e o invariante garante que o alvo não existe.
Os erros mais comuns quebram justamente esse invariante:
while (low < high)em vez de<=: deixa de examinar o último elemento que sobrou.low = midem vez demid + 1: o intervalo pode parar de diminuir e virar um loop infinito.
Cada passo corta o intervalo pela metade. Começando com n elementos, depois de k passos sobram n / 2^k. Isso chega a 1 quando k = log₂ n.
| n | Busca linear (pior caso) | Busca binária (pior caso) |
|---|---|---|
| 100 | 100 | 7 |
| 1.000.000 | 1.000.000 | 20 |
| 1.000.000.000 | 1.000.000.000 | 30 |
Tempo O(log n), memória extra O(1).
Onde ela aparece
Seção intitulada “Onde ela aparece”git bisectacha o commit que introduziu um bug fazendo busca binária no histórico.- Índices de banco de dados (árvores B) aplicam a mesma ideia para achar uma linha entre milhões.
_.sortedIndexdo Lodash ebisectdo Python encontram onde inserir um valor mantendo a ordem.- Fora de arrays: “qual o menor tamanho de lote que não estoura a memória?” também se resolve dividindo o intervalo de respostas ao meio.
Teste sua memória
Seção intitulada “Teste sua memória”Pronto para praticar? Comece pelo livro da biblioteca.