Big O: límite superior
"Big O" ( ) es, con mucho, el más común. Cuando se analiza la complejidad de un algoritmo, la mayoría de las veces, lo que importa es tener un límite superior de la rapidez con que crece el tiempo de ejecución run cuando aumenta el tamaño de la entrada. Básicamente queremos saber que ejecutar el algoritmo no va a tomar "demasiado tiempo". No podemos expresar esto en unidades de tiempo reales (segundos), porque eso dependería de la implementación precisa (la forma en que se escribe el programa, qué tan bueno es el compilador, qué tan rápido es el procesador de la máquina, ...). Así que evaluamos lo que no depende de tales detalles, que es cuánto tiempo lleva ejecutar el algoritmo cuando lo alimentamos con una entrada más grande. Y nos importa principalmente cuando podemos estar seguros de que el programa está terminado, por lo que generalmente queremos saber que tomará tal cantidad de tiempo o menos.O
Decir que un algoritmo tiene un tiempo de ejecución de para un tamaño de entrada significa que existe una constante tal que el algoritmo se completa en la mayoría de los pasos , es decir, el tiempo de ejecución del algoritmo crece como máximo tan rápido como (hasta un factor de escala). Observando el tiempo de ejecución del algoritmo para el tamaño de entrada , significa informalmente que hasta cierto factor de escala.n K KO(f(n))nKf T ( n ) n O ( n ) T ( n ) ≤ f ( n )Kf(n)fT(n)nO(n)T(n)≤f(n)
Límite inferior
A veces, es útil tener más información que un límite superior. es lo contrario de : expresa que una función crece al menos tan rápido como otra. significa que para alguna constante , o dicho de manera informal, hasta a algún factor de escala.O T ( n ) = Ω ( g ( n ) ) T ( N ) ≥ K ′ g ( n ) K ′ T ( n ) ≥ g ( n )ΩOT(n)=Ω(g(n))T(N)≥K′g(n)K′T(n)≥g(n)
Cuando el tiempo de ejecución del algoritmo se puede determinar con precisión, combina y : expresa que se conoce la tasa de crecimiento de una función, hasta un factor de escala. significa que para algunas constantes y . Hablando informalmente, hasta cierto factor de escala.O Ω T ( n ) = Θ ( h ( n ) ) K h ( n ) ≥ T ( n ) ≥ K ′ h ( n ) K K ′ T ( n ) ≈ h ( n )ΘOΩT(n)=Θ(h(n))Kh(n)≥T(n)≥K′h(n)KK′T(n)≈h(n)
Consideraciones adicionales
El "pequeño" y se usan con mucha menos frecuencia en el análisis de complejidad. El pequeño es más fuerte que el gran ; donde indica un crecimiento que no es más rápido, indica que el crecimiento es estrictamente más lento. Por el contrario, indica un crecimiento estrictamente más rápido.ω o O O o ωoωoOOoω
He sido un poco informal en la discusión anterior. Wikipedia tiene definiciones formales y un enfoque más matemático.
Tenga en cuenta que el uso del signo igual en y similares es un nombre inapropiado. Estrictamente hablando, es un conjunto de funciones de la variable , y deberíamos escribir .T(n)=O(f(n))O(f(n))nT∈O(f)
Ejemplo: algunos algoritmos de clasificación
Como esto es bastante seco, déjame darte un ejemplo. La mayoría de los algoritmos de clasificación tienen un tiempo de ejecución cuadrático en el peor de los casos, es decir, para una entrada de tamaño , el tiempo de ejecución del algoritmo es . Por ejemplo, la selección de selección tiene un tiempo de ejecución , porque la selección del elemento requiere comparaciones, para un total de comparaciones. De hecho, el número de comparaciones siempre es exactamente , que crece como . Por lo tanto, podemos ser más precisos sobre la complejidad temporal del tipo de selección: es .nO(n2)O(n2)kn−kn(n−1)/2n(n−1)/2n2Θ(n2)
Ahora toma el tipo de fusión . La ordenación por fusión también es cuadrática ( ). Esto es cierto, pero no muy preciso. De hecho, la clasificación de fusión tiene un tiempo de ejecución de en el peor de los casos. Al igual que el ordenamiento por selección, el flujo de trabajo del ordenamiento por fusión es esencialmente independiente de la forma de la entrada, y su tiempo de ejecución es siempre hasta un factor multiplicativo constante, es decir, es .O(n2)O(nlg(n))nlg(n)Θ(nlg(n))
A continuación, considere la clasificación rápida . Quicksort es más complejo. Ciertamente es . Además, el peor caso de quicksort es cuadrático: el peor de los casos es . Sin embargo, el mejor caso de clasificación rápida (cuando la entrada ya está ordenada) es lineal: lo mejor que podemos decir para un límite inferior de clasificación rápida en general es . No repetiré la prueba aquí, pero la complejidad promedio de la clasificación rápida (el promedio tomado de todas las permutaciones posibles de la entrada) es .O(n2)Θ(n2)Ω(n)Θ(nlg(n))
Hay resultados generales sobre la complejidad de los algoritmos de clasificación en entornos comunes. Suponga que un algoritmo de clasificación solo puede comparar dos elementos a la vez, con un resultado de sí o no (ya sea o ). Entonces es obvio que el tiempo de ejecución de cualquier algoritmo de clasificación es siempre (donde es el número de elementos para clasificar), porque el algoritmo tiene que comparar cada elemento al menos una vez para saber dónde encajará. Este límite inferior se puede cumplir, por ejemplo, si la entrada ya está ordenada y el algoritmo simplemente compara cada elemento con el siguiente y los mantiene en orden (es decir, las comparaciones ). Lo que es menos obvio es que el tiempo de ejecución máximo es necesariamentex≤yx>yΩ(n)nn−1Ω(nlg(n)) . Es posible que el algoritmo a veces haga menos comparaciones, pero tiene que haber una constante tal que para cualquier tamaño de entrada , haya al menos una entrada en la que el algoritmo haga más de comparaciones La idea de la prueba es construir el árbol de decisión del algoritmo, es decir, seguir las decisiones que el algoritmo toma del resultado de cada comparación. Como cada comparación devuelve un resultado de sí o no, el árbol de decisión es un árbol binario. Hayposibles permutaciones de la entrada, y el algoritmo necesita distinguir entre todas ellas, por lo que el tamaño del árbol de decisión esKnKnlg(n)n!n!. Dado que el árbol es un árbol binario, se necesita una profundidad de para adaptarse a todos estos nodos. La profundidad es el número máximo de decisiones que toma el algoritmo, por lo que ejecutar el algoritmo implica al menos esta cantidad de comparaciones: el tiempo máximo de ejecución es .Θ(lg(n!))=Θ(nlg(n))Ω(nlg(n))
¹ U otro consumo de recursos como el espacio de memoria. En esta respuesta, solo considero el tiempo de ejecución.