Lección
La teoría — Búsqueda binaria: cuatro opciones, dieciséis bucles
La búsqueda binaria localiza un valor x en un vector ordenado manteniendo un intervalo de índices de lo a hi que debe contener x, si está presente. En cada paso lee el elemento central, a[mid], y conserva la mitad donde puede encontrarse x. El intervalo es cerrado cuando hi representa el último índice incluido (lo <= hi, con hi empezando en n − 1), y semiabierto cuando hi representa el primer índice posterior al intervalo (lo < hi, con hi empezando en n).
Qué significa cada símbolo
lo, hi- los extremos del intervalo que queda por buscar
mid- ⌊(lo + hi)/2⌋, el índice leído en este paso
n- el número de elementos, situados en los índices de 0 a n − 1
N- la longitud del mayor vector que prueba la comprobación: todos los n de 0 a N y todos los valores buscados, (N + 1)² casos
- Supone
- Los valores del vector son distintos, el bucle termina en cuanto a[mid] es igual a x y, tanto en la ejecución como en la comprobación, los enteros no tienen límite superior. La tabla de desbordamiento elimina esta última suposición y calcula con 32 bits, como hace Java.
- Falla cuando
- Si hay valores repetidos, los dos bucles correctos siguen devolviendo un índice que contiene x, aunque no necesariamente el primero. Para hallar la primera aparición, como exige una consulta de intervalo sobre un índice ordenado, hace falta un bucle que no se detenga al encontrar una igualdad: debe ser semiabierto y aplicar
hi = midsiempre que a[mid] ≥ x. Cuando termina, la respuesta es lo.
Problema resuelto al detalle
-
Por qué solo quedan en pie dos bucles 4 pasos
Pulsa Bloqueado en lo = mid. El veredicto puede probar vectores de hasta 256 elementos, pero no más. Demuestra, para todo n, que el bucle semiabierto con
lo = mid + 1siempre termina, que el mismo bucle conlo = midse bloquea cuando hi = lo + 1 y a[lo] < x, y qué exige ese razonamiento en el bucle cerrado.Abre este problema en la herramienta
-
En el bucle semiabierto, el cuerpo solo se ejecuta mientras lo < hi. En tal caso, mid, redondeado hacia abajo, es mayor o igual que lo y estrictamente menor que hi.
-
Por tanto,
hi = midreduce hi al menos en uno, ylo = mid + 1aumenta lo al menos en uno. En ambos casos, hi − lo comienza valiendo n y disminuye al menos en uno con cada iteración. Como no puede ser negativo, el bucle termina en un máximo de n iteraciones. -
Considera ahora
lo = mid. Cuando hi = lo + 1, mid es ⌊(2lo + 1)/2⌋ = lo. Si a[lo] < x, la actualización no mueve lo y la siguiente iteración repite exactamente la anterior. El caso predefinido llega a ese punto con lo = 6, hi = 7, a[6] = 13 y x = 14. -
El bucle cerrado se ejecuta mientras lo <= hi, así que admite lo = hi. Entonces mid = lo = hi: actualizar un extremo a mid no cambia nada. Las dos actualizaciones deben rebasar mid, algo seguro porque a[mid] acaba de leerse. En cambio, el intervalo semiabierto ya excluye hi. Allí,
hi = middescarta el elemento recién leído y todos los posteriores, que no pueden ser x porque a[mid] > x. Conhi = mid − 1también se descartaría a[mid − 1], aunque podría ser x.
Respuesta
Cada iteración debe acortar el intervalo, descartar únicamente índices que no puedan contener x y partir de un intervalo que abarque todo el vector. Con
lo <= hi, esto exige hi = n − 1,hi = mid − 1ylo = mid + 1; conlo < hi, exige hi = n,hi = midylo = mid + 1. El veredicto confirma que esos dos bucles funcionan hasta 256 elementos. El argumento vale para todo n, algo que ninguna prueba puede garantizar. En aritmética de 32 bits hace falta una premisa adicional: lo + hi debe caber en ese formato. La tercera observación muestra qué sucede cuando no se cumple. -
Ruta de aprendizaje
Por qué un algoritmo es correcto
Referencias (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.