En matemáticas y ciencias de la computación, la notación Big O es una forma de comparar las tasas de crecimiento de diferentes funciones. A menudo se utiliza para comparar la eficiencia de diferentes algoritmos, lo que se hace calculando cuánta memoria se necesita y cuánto tiempo tarda en completarse.
La notación Big O se utiliza a menudo para identificar el grado de complejidad de un problema, también conocido como clase de complejidad del problema. El matemático Paul Bachmann (1837-1920) fue el primero en utilizar esta notación, en la segunda edición de su libro "Analytische Zahlentheorie", en 1896. Edmund Landau (1877-1938) popularizó la notación. Por esta razón, cuando se habla de los símbolos de Landau, se hace referencia a esta notación.
La notación Big O recibe su nombre del término "orden de la función", que se refiere al crecimiento de las funciones. La notación Big O se utiliza para encontrar el límite superior (la cantidad más alta posible) de la tasa de crecimiento de la función, lo que significa que calcula el tiempo más largo que tardará en convertir la entrada en la salida. Esto significa que un algoritmo puede agruparse según el tiempo que puede tardar en el peor de los casos, en el que se tomará siempre el camino más largo.
Más concretamente, dadas dos funciones positivas y
, decimos que
está en el gran O de
(escrito
) si para un tamaño suficientemente grande
, f
para alguna constante
.
Big O es una expresión que encuentra el tiempo de ejecución en el peor de los casos, mostrando la eficiencia de un algoritmo sin tener que ejecutar el programa en un ordenador. También es útil debido al hecho de que diferentes ordenadores pueden tener un hardware diferente, y por lo tanto necesitan diferentes cantidades de tiempo para completarlo. Dado que Big O siempre asume el peor caso, puede mostrar una medida consistente de la velocidad: independientemente del hardware, siempre se va a completar más rápido que
, porque tienen diferentes niveles de eficiencia.
Definición y matices prácticos
En términos prácticos, decir "f(n) ∈ O(g(n))" significa que existe una constante positiva k y un n0 tal que para todo n ≥ n0 se cumple f(n) ≤ k·g(n). Big O describe un límite superior asintótico: ignora constantes multiplicativas y términos de menor orden porque, al crecer n, esos términos dejan de ser relevantes frente al término dominante.
Es importante distinguir Big O de otras notaciones asintóticas:
- Big Omega (Ω): límite inferior asintótico. f(n) ∈ Ω(g(n)) si, para n suficientemente grande, f(n) ≥ c·g(n) para alguna constante c > 0.
- Big Theta (Θ): acotación ajustada. f(n) ∈ Θ(g(n)) si f(n) ∈ O(g(n)) y f(n) ∈ Ω(g(n)); es decir, g(n) es una cota superior y también una cota inferior hasta constantes.
Casos: peor, promedio y mejor
Big O suele usarse para expresar la complejidad en el peor de los casos, pero la notación en sí no requiere que sea el peor caso —simplemente indica una cota superior. Para análisis más precisos conviene distinguir:
- Peor caso (worst-case): tiempo máximo que puede tomar el algoritmo sobre entradas de tamaño n. Es la interpretación más común de Big O en documentación técnica.
- Caso promedio (average-case): tiempo esperado sobre una distribución de entradas; requiere modelar probabilísticamente las entradas.
- Mejor caso (best-case): tiempo mínimo posible; se expresa a menudo con Big Omega.
Ejemplos comunes de complejidad temporal
- O(1) — Constante: operaciones que no dependen del tamaño de la entrada (acceso a una posición de un arreglo, asignación simple).
- O(log n) — Logarítmica: búsquedas en estructuras ordenadas como binary search en un arreglo ordenado; cada paso reduce el problema exponencialmente.
- O(n) — Lineal: recorrer una lista una vez (ej.: búsqueda lineal).
- O(n log n): algoritmos de ordenación eficientes como merge sort o heapsort en promedio y en el peor caso.
- O(n²) — Cuadrática: algoritmos con bucles anidados sobre la entrada, como el bubble sort y muchos algoritmos ingenuos de comparación par a par.
- O(2ⁿ) — Exponencial: algoritmos que generan todas las combinaciones de subconjuntos (ej.: solución ingenua de problemas NP-completos), crece muy rápido.
- O(n!) — Factorial: típicamente aparece en problemas que requieren probar todas las permutaciones (ej.: el viajante de comercio con fuerza bruta).
Ejemplos prácticos
- Búsqueda lineal en un arreglo sin ordenar: O(n) en el peor caso.
- Búsqueda binaria en un arreglo ordenado: O(log n) en el peor caso.
- Merge sort (ordenación por mezcla): O(n log n) en el peor caso; usa memoria adicional para la mezcla.
- Bubble sort: O(n²) en tiempo; O(1) en espacio extra si se implementa in-place.
Complejidad espacial
Además del tiempo de ejecución, Big O puede describir la complejidad espacial: la cantidad de memoria adicional usada por un algoritmo en función del tamaño de la entrada. Por ejemplo, un algoritmo in-place puede tener complejidad espacial O(1), mientras que uno que necesita copiar la entrada puede requerir O(n).
Cómo determinar la Big O de un algoritmo
Pasos prácticos:
- Identificar las operaciones dominantes que crecen con n (bucles, recursiones).
- Contar el número de veces que se ejecutan esas operaciones en función de n.
- Sumar los costos y simplificar: eliminar constantes multiplicativas y términos de menor orden quedándote con el término de mayor crecimiento.
En algoritmos recursivos, es útil establecer una ecuación de recurrencia y resolverla (por ejemplo, usando el teorema maestro para divisiones recursivas típicas).
Consejos y consideraciones
- Big O describe comportamiento asintótico: para n pequeños, constantes y detalles de implementación pueden hacer que un algoritmo con peor Big O sea más rápido en la práctica.
- Optimizar la complejidad asintótica suele ser más relevante que microoptimizar constantes cuando se trabaja con grandes volúmenes de datos.
- Elegir la estructura de datos adecuada (árboles balanceados, tablas hash, heaps) puede cambiar drásticamente la complejidad de operaciones comunes.
- No confundir coste teórico con coste real: la elección de hardware, cachés y paralelismo también afecta el rendimiento.
En resumen, la notación Big O es una herramienta esencial para describir y comparar el comportamiento asintótico de algoritmos y funciones, ofreciendo una forma estandarizada de hablar sobre eficiencia y escalabilidad sin depender de implementaciones concretas o hardware.