Carlos Arévalo
← All topics[ ¿Cómo funciona…? (How does it work?) ]

Búsqueda binaria: adivina un número del 1 al 100 en 7 intentos

Por qué partir a la mitad gana a revisar uno por uno, cuántos intentos necesitas para cualquier tamaño y dónde se usa la idea todos los días.

Season 1
6 min read
These topics are available in Spanish only for now.

Juega primero

Piensa un número del 1 al 100 y responde con honestidad. La página siempre pregunta por el número del medio de lo que queda, y nunca necesita más de 7 intentos.

Piensa un número del 1 al 100Intento 1 · máximo 7

Quedan 100 posibles: del 1 al 100.

¿Es el 50?

El truco: descartar la mitad

Si pregunto por el 50 y me dices “es mayor”, de un solo golpe descarto del 1 al 50. Con cada respuesta, lo que queda por revisar se parte a la mitad: 100, 50, 25, 12, 6, 3, 1. Revisar uno por uno, en cambio, solo descarta un número por intento.

Esto solo funciona si los datos están ordenados. En una lista desordenada, saber que el 50 “no es” no te dice nada sobre dónde buscar después.

¿Por qué 7 intentos?

Con cada intento, la cantidad de números que puedes cubrir se duplica (más uno, el que preguntas). Con k intentos distingues hasta 2^k − 1 números:

IntentosNúmeros que cubres
11
23
37
415
531
663
7127

Con 6 intentos llegas a 63, que no alcanza para 100; con 7 llegas a 127, que sí. La operación que responde “¿cuántas veces tengo que duplicar para llegar a tanto?” es el logaritmo en base 2: los intentos que necesitas son ⌈log₂(n + 1)⌉. Esa es la matemática del colegio trabajando.

Lo que de verdad importa: cómo crece

Mueve el control. Revisar uno por uno crece igual que la lista; partir a la mitad solo suma un intento cada vez que la lista se duplica. Con un millón de datos, la diferencia es entre un millón de intentos y 20.

Uno por unohasta 100intentos: crece igual que la lista.
A la mitadhasta 7intentos: uno más cada vez que la lista se duplica.
7 de 20 pasos: los que necesita un millón de datos.

En código

def buscar(lista, objetivo):
    izq, der = 0, len(lista) - 1
    while izq <= der:
        medio = (izq + der) // 2
        if lista[medio] == objetivo:
            return medio
        if lista[medio] < objetivo:
            izq = medio + 1
        else:
            der = medio - 1
    return -1
  • izq y der marcan el rango que todavía puede tener el objetivo; al empezar, la lista entera.
  • medio es el elemento del centro (// divide y descarta los decimales).
  • Si el del medio es menor que el objetivo, todo lo de su izquierda también lo es: el rango empieza después de él. Si es mayor, termina antes.
  • Si el rango se queda vacío (izq > der), el objetivo no está y se devuelve -1.

Para comprobar que lo entendiste, explícalo con tus palabras: ¿qué pasaría si la condición fuera izq < der en lugar de izq <= der? Pruébalo con una lista de un solo elemento.

¿Dónde se usa?

  • Bases de datos: sus índices son árboles que mantienen los datos ordenados y descartan partes enteras en cada paso. En vez de partir en dos, parten en cientos de ramas, pero la idea es la misma.
  • Encontrar el cambio que rompió un programa: git bisect prueba la versión del medio de la historia y descarta la mitad buena o la mala, hasta dar con el cambio culpable.
  • Un diccionario de papel: nadie lo lee página por página; lo abres por la mitad y decides hacia qué lado ir.

Cuidado con

  • Usarla en datos desordenados: no da error, simplemente responde mal.
  • Los bordes: un + 1 o un - 1 de menos y el programa se queda en un bucle infinito o se salta el objetivo. Es el error más común al escribirla.
  • En lenguajes con enteros de tamaño fijo (Java, C), (izq + der) / 2 puede desbordarse con listas enormes; por eso se escribe izq + (der - izq) / 2. En Python no pasa, porque sus enteros crecen lo que haga falta.
Tarea para la casa

Adivina el día del año en que nació alguien (del 1 al 366). ¿Cuántos intentos necesitas, como máximo?

Ver la respuesta
9 intentos. Con 8 cubres 2⁸ − 1 = 255 días, que no alcanza; con 9 cubres 2⁹ − 1 = 511, que sí.