Lição
A teoria — Pesquisa binária: quatro escolhas, dezasseis ciclos
A pesquisa binária encontra um valor x num vetor ordenado mantendo um intervalo de índices, de lo a hi, que terá de conter x se este estiver presente. Em cada iteração, lê o elemento central, a[mid], e conserva a metade onde x pode estar. O intervalo é fechado quando hi é o último índice ainda incluído (lo <= hi, com hi a começar em n − 1) e semiaberto quando hi é o primeiro índice posterior ao intervalo (lo < hi, com hi a começar em n).
O que significa cada símbolo
lo, hi- os extremos do intervalo ainda por pesquisar
mid- ⌊(lo + hi)/2⌋, o índice lido nesta iteração
n- o número de elementos, nos índices de 0 a n − 1
N- o comprimento do maior vetor testado pelo veredito: todos os n de 0 a N e todos os valores procurados, num total de (N + 1)² casos
- Pressupõe
- Os valores do vetor são distintos, o ciclo devolve o resultado assim que a[mid] é igual a x e, na execução e no veredito, os inteiros não têm limite superior. A tabela de excesso de capacidade abandona esta última hipótese e efetua os cálculos em 32 bits, como Java.
- Falha quando
- Com valores repetidos, os dois ciclos corretos continuam a devolver um índice que contém x, mas não necessariamente o primeiro. Para encontrar a primeira ocorrência, como exige uma consulta por intervalo num índice ordenado, é preciso um ciclo que não pare quando encontra uma igualdade: um intervalo semiaberto,
hi = midsempre que a[mid] ≥ x, sendo lo a resposta quando o ciclo termina.
Problema resolvido na íntegra
-
Porque só restam dois ciclos 4 passos
Carregue em Preso em lo = mid. O verificador só consegue testar vetores com até 256 elementos. Demonstre, para qualquer n, que o ciclo semiaberto com
lo = mid + 1termina sempre, que o mesmo ciclo comlo = midfica preso quando hi = lo + 1 e a[lo] < x, e determine o que este raciocínio exige do ciclo fechado.Abrir este problema na ferramenta
-
No ciclo semiaberto, o corpo só é executado enquanto lo < hi. Como mid é arredondado para baixo, é pelo menos igual a lo e estritamente menor do que hi.
-
Assim,
hi = midreduz hi em pelo menos uma unidade, enquantolo = mid + 1aumenta lo em pelo menos uma unidade. Em ambos os casos, hi − lo começa em n e diminui pelo menos uma unidade a cada passagem. Como não pode ficar abaixo de zero, o ciclo termina em no máximo n passagens. -
Considere agora
lo = mid. Quando hi = lo + 1, mid é ⌊(2lo + 1)/2⌋ = lo. Se a[lo] < x, a atualização não altera lo, e a passagem seguinte repete exatamente a anterior. O exemplo predefinido chega a esse ponto com lo = 6, hi = 7, a[6] = 13 e x = 14. -
O ciclo fechado é executado enquanto lo <= hi, portanto admite lo = hi. Nesse caso, mid = lo = hi: uma atualização para mid não altera nada, logo ambas as atualizações têm de ultrapassar mid. Podem fazê-lo porque a[mid] acabou de ser lido. O intervalo semiaberto já exclui hi; nesse ciclo,
hi = midelimina o elemento acabado de ler e todos os que estão acima dele, pois a[mid] > x exclui-os. Jáhi = mid − 1eliminaria também a[mid − 1], que pode ser x.
Resposta
Cada passagem tem de reduzir o intervalo, eliminar apenas índices que não podem conter x e partir de um intervalo que abranja todo o vetor. Para
lo <= hi, isso exige hi = n − 1,hi = mid − 1elo = mid + 1; paralo < hi, exige hi = n,hi = midelo = mid + 1. O verificador confirma estes dois ciclos até 256 elementos, mas o argumento é válido para qualquer n, coisa que nenhum teste consegue provar. Com aritmética de 32 bits, falta ainda supor que lo + hi cabe no tipo usado. A terceira observação mostra o que acontece quando essa condição não se verifica. -
Percurso de aprendizagem
Por que razão um algoritmo está correto
Referências (2)
- The overflow in the JDK's binary search, the nine years it went unnoticed, and Bentley's proved-correct version carrying the same line; also the source of the 1946 and 1962 dates the first insight quotes from Programming Pearls: Joshua Bloch, "Extra, Extra - Read All About It: Nearly All Binary Searches and Mergesorts are Broken." Google Research Blog, 2 June 2006.
- The bug report the page reproduces: java.util.Arrays.binarySearch on a 1,100,000,000-byte array, searching for its last element, throwing ArrayIndexOutOfBoundsException: -1064671149: OpenJDK bug JDK-5045582, "(coll) binarySearch() fails for size larger than 1<<30." Filed 11 May 2004 against Java 5.0, resolved (fixed in 6) 28 April 2006.