No history yet

Fundamentos de Notación Asintótica

Midiendo la eficiencia del código

Al escribir código, no solo queremos que funcione, sino que funcione de manera eficiente. Pero, ¿cómo medimos la "eficiencia"? Podríamos usar un cronómetro para ver cuánto tarda un programa, pero ese método tiene problemas. El resultado cambiaría dependiendo de la velocidad del ordenador, del lenguaje de programación o incluso de los datos de entrada específicos.

Necesitamos una forma de analizar algoritmos que sea independiente del hardware y de los detalles de implementación. Aquí es donde entra el análisis asintótico. En lugar de medir el tiempo en segundos, contamos el número de operaciones que realiza un algoritmo en función del tamaño de su entrada, que llamamos nn.

El análisis asintótico nos permite describir la relación entre el tamaño de la entrada de un algoritmo (nn) y el número de pasos que este debe realizar.

Nos centramos en cómo se comporta el tiempo de ejecución a medida que nn se hace muy, muy grande. Este nos da una visión de alto nivel sobre la escalabilidad de un algoritmo. ¿Se ralentiza drásticamente a medida que los datos crecen, o se mantiene rápido y eficiente? La notación asintótica nos proporciona el lenguaje matemático para responder a esta pregunta.

Notación Big O: El límite superior

La notación más común en el análisis de algoritmos es la Big O (Gran O). Describe el límite superior del tiempo de ejecución de un algoritmo. En otras palabras, Big O establece una garantía: el rendimiento de tu algoritmo no será peor que una determinada tasa de crecimiento.

Puedes pensar en ello como el peor escenario posible. Si un algoritmo tiene una complejidad de O(n2)O(n^2), significa que, en el peor de los casos, el número de operaciones crecerá de forma cuadrática con respecto al tamaño de la entrada.

f(n)O(g(n))    c>0,n0>0 tal que 0f(n)cg(n) para todo nn0f(n) \in O(g(n)) \iff \exists c > 0, n_0 > 0 \text{ tal que } 0 \le f(n) \le c \cdot g(n) \text{ para todo } n \ge n_0

Omega y Theta: Límites Inferior y Ajustado

Mientras que Big O nos da el peor escenario, a veces es útil conocer el mejor escenario. Para esto, usamos la notación Big Omega (Omega\\Omega). Big Omega establece un límite inferior para el tiempo de ejecución de un algoritmo. Garantiza que el rendimiento no será mejor que una cierta tasa de crecimiento.

f(n)Ω(g(n))    c>0,n0>0 tal que 0cg(n)f(n) para todo nn0f(n) \in \Omega(g(n)) \iff \exists c > 0, n_0 > 0 \text{ tal que } 0 \le c \cdot g(n) \le f(n) \text{ para todo } n \ge n_0

Cuando un algoritmo tiene el mismo límite superior e inferior, decimos que tiene un . Usamos la notación Big Theta (Theta\\Theta) para esto. Si un algoritmo es Theta(g(n))\\Theta(g(n)), significa que su tiempo de ejecución crece a la misma tasa que g(n)g(n), tanto en el mejor como en el peor de los casos (ignorando factores constantes).

En esencia, f(n)Θ(g(n))f(n) \in \Theta(g(n)) es una afirmación más precisa que f(n)O(g(n))f(n) \in O(g(n)), ya que nos dice que el crecimiento del algoritmo está "atrapado" entre dos múltiplos de g(n)g(n).

Si un algoritmo es Θ(n)\Theta(n), también es O(n)O(n) y Ω(n)\Omega(n). Sin embargo, si un algoritmo es O(n2)O(n^2), no es necesariamente Ω(n2)\Omega(n^2). Podría ser Ω(n)\Omega(n) en el mejor de los casos.

Propiedades de la notación

La notación asintótica sigue algunas propiedades matemáticas útiles que simplifican el análisis. Dos de las más importantes son la transitividad y la suma.

Transitividad: Si una función f(n)f(n) está limitada por g(n)g(n), y g(n)g(n) está limitada por h(n)h(n), entonces f(n)f(n) también está limitada por h(n)h(n). Esto se aplica a OO, Ω\Omega y Θ\Theta.

PropiedadDescripción
Si f(n)O(g(n))f(n) \in O(g(n)) y g(n)O(h(n))g(n) \in O(h(n))entonces f(n)O(h(n))f(n) \in O(h(n))
Si f(n)Ω(g(n))f(n) \in \Omega(g(n)) y g(n)Ω(h(n))g(n) \in \Omega(h(n))entonces f(n)Ω(h(n))f(n) \in \Omega(h(n))
Si f(n)Θ(g(n))f(n) \in \Theta(g(n)) y g(n)Θ(h(n))g(n) \in \Theta(h(n))entonces f(n)Θ(h(n))f(n) \in \Theta(h(n))

Regla de la suma: Al analizar dos partes de un algoritmo que se ejecutan en secuencia, la complejidad total está determinada por la parte que tiene la tasa de crecimiento más lenta (la más grande). Simplemente nos quedamos con el término dominante.

Por ejemplo, si un algoritmo primero realiza una operación que tarda O(n)O(n) y luego otra que tarda O(n2)O(n^2), la complejidad total del algoritmo es O(n2)O(n^2). La parte O(n)O(n) se vuelve insignificante a medida que nn crece.

O(f(n))+O(g(n))=O(max(f(n),g(n)))O(f(n)) + O(g(n)) = O(\max(f(n), g(n)))

Estas definiciones y propiedades forman la base matemática para analizar y comparar la eficiencia de los algoritmos de una manera rigurosa y estandarizada.