¡Ayuda! Tengo una pregunta donde necesito analizar el Big-O de un algoritmo o algún código.
No estoy seguro de qué es Big-O o cómo se relaciona con Big-Theta u otros medios para analizar la complejidad de un algoritmo.
No estoy seguro de si Big-O se refiere al tiempo para ejecutar el código, o la cantidad de memoria que se necesita (compensaciones espacio / tiempo).
Tengo tarea de informática donde necesito tomar algunos bucles, quizás un algoritmo recursivo, y crear el Big-O para ello.
Estoy trabajando en un programa donde puedo elegir entre dos estructuras de datos o algoritmos con un Big-O conocido, y no estoy seguro de cuál elegir.
¿Cómo entiendo cómo calcular y aplicar Big-O a mi programa, tarea o conocimiento general de informática?
Nota: esta pregunta es un objetivo de engaño canónico para otras preguntas Big-O según lo determine la comunidad . Es intencionalmente amplio poder contener una gran cantidad de información útil para muchas preguntas Big-O. No utilice el hecho de que es tan amplio como una indicación de que preguntas similares son aceptables.
fuente
Respuestas:
El O (...) se refiere a la notación Big-O, que es una manera simple de describir cuántas operaciones requiere un algoritmo para hacer algo. Esto se conoce como complejidad de tiempo .
En la notación Big-O, el costo de un algoritmo está representado por su operación más costosa en grandes cantidades. Si un algoritmo tomara medidas, se representaría O (n 3 ). Un algoritmo que contó cada elemento en una lista funcionaría en tiempo O (n), llamado tiempo lineal.
n3 + n2 + n
Para obtener una lista de los nombres y ejemplos clásicos en Wikipedia: Órdenes de funciones comunes
Material relacionado:
fuente
¿Cuáles son las funciones asintóticas? ¿Qué es una asíntota, de todos modos?
Dada una función f (n) que describe la cantidad de recursos (tiempo de CPU, RAM, espacio en disco, etc.) consumidos por un algoritmo cuando se aplica a una entrada de tamaño de n , definimos hasta tres anotaciones asintóticas para describir su rendimiento para grande n .
Una asíntota (o función asintótica) es simplemente alguna otra función (o relación) g (n) a la que f (n) se acerca cada vez más a medida que n crece más y más, pero nunca alcanza del todo. La ventaja de hablar sobre funciones asintóticas es que, en general, son mucho más simples de hablar, incluso si la expresión para f (n) es extremadamente complicada. Las funciones asintóticas se usan como parte de las notaciones limitantes que restringen f (n) arriba o abajo.
(Nota: en el sentido empleado aquí, las funciones asintóticas solo se acercan a la función original después de corregir algún factor constante distinto de cero, ya que las tres notaciones grandes O / Θ / Ω ignoran estos factores constantes de su consideración).
¿Cuáles son las tres notaciones delimitadores asintóticas y en qué se diferencian?
Las tres notaciones se usan así:
f (n) = O (g (n))
donde f (n) aquí es la función de interés, y g (n) es alguna otra función asintótica con la que intenta aproximar f (n) . Esto no debe tomarse como una igualdad en un sentido riguroso, sino como una declaración formal entre qué tan rápido crece f (n) con respecto a n en comparación con g (n) , a medida que n se hace grande. Los puristas a menudo usan la notación alternativa f (n) ∈ O (g (n)) para enfatizar que el símbolo O (g (n)) es realmente una familia completa de funciones que comparten una tasa de crecimiento común.
La notación Big-ϴ (Theta) establece una igualdad en el crecimiento de f (n) hasta un factor constante (más sobre esto más adelante). Se comporta de manera similar a un
=
operador para las tasas de crecimiento.La notación Big-O describe un límite superior en el crecimiento de f (n) . Se comporta de manera similar a un
≤
operador para las tasas de crecimiento.La notación Big-Ω (Omega) describe un enlace inferior en un crecimiento de f (n) . Se comporta de manera similar a un
≥
operador para las tasas de crecimiento.Hay muchas otras notaciones asintóticas , pero no ocurren con tanta frecuencia en la literatura de informática.
Las anotaciones Big-O y sus características suelen ser una forma de comparar la complejidad del tiempo .
¿Qué es la complejidad del tiempo?
La complejidad del tiempo es un término elegante para la cantidad de tiempo T (n) que tarda un algoritmo en ejecutarse en función de su tamaño de entrada n . Esto se puede medir en la cantidad de tiempo real (por ejemplo, segundos), la cantidad de instrucciones de la CPU, etc. Por lo general, se supone que el algoritmo se ejecutará en su computadora de arquitectura von Neumann todos los días . Pero, por supuesto, puede usar la complejidad del tiempo para hablar sobre sistemas informáticos más exóticos, ¡donde las cosas pueden ser diferentes!
También es común hablar sobre la complejidad del espacio utilizando la notación Big-O. La complejidad del espacio es la cantidad de memoria (almacenamiento) requerida para completar el algoritmo, que podría ser RAM, disco, etc.
Puede darse el caso de que un algoritmo sea más lento pero use menos memoria, mientras que otro es más rápido pero usa más memoria. Cada uno puede ser más apropiado en diferentes circunstancias, si los recursos están limitados de manera diferente. Por ejemplo, un procesador integrado puede tener memoria limitada y favorecer el algoritmo más lento, mientras que un servidor en un centro de datos puede tener una gran cantidad de memoria y favorecer el algoritmo más rápido.
Calculando Big-ϴ
Calcular el Big-ϴ de un algoritmo es un tema que puede llenar un pequeño libro de texto o aproximadamente medio semestre de clase de pregrado: esta sección cubrirá los conceptos básicos.
Dada una función f (n) en pseudocódigo:
¿Cuál es la complejidad del tiempo?
El bucle externo se ejecuta n veces. Por cada vez que se ejecuta el bucle externo, el bucle interno se ejecuta n veces. Esto pone el tiempo de ejecución en T (n) = n 2 .
Considere una segunda función:
El bucle externo se ejecuta dos veces. El ciclo medio se ejecuta n veces. Por cada vez que se ejecuta el bucle intermedio, el bucle interno se ejecuta n veces. Esto pone el tiempo de ejecución en T (n) = 2n 2 .
Ahora la pregunta es, ¿cuál es el tiempo de ejecución asintótico de ambas funciones?
Para calcular esto, realizamos dos pasos:
La clave aquí es centrarse en los términos dominantes y simplificarlos .
T (n) = n 2 ∈ ϴ (n 2 )
T (n) = 2n 2 ∈ ϴ (n 2 )
Si tenemos otro algoritmo con varios términos, lo simplificaremos usando las mismas reglas:
T (n) = 2n 2 + 4n + 7 ∈ ϴ (n 2 )
La clave con todos estos algoritmos es que nos enfocamos en los términos más grandes y eliminamos constantes . No estamos viendo el tiempo real de ejecución, sino la relativa complejidad .
Cálculo de Big-Ω y Big-O
En primer lugar, tenga en cuenta que en la literatura informal , "Big-O" a menudo se trata como un sinónimo de Big-Θ, tal vez porque las letras griegas son difíciles de escribir. Entonces, si alguien de la nada te pide el Big-O de un algoritmo, probablemente quiera su Big-Θ.
Ahora, si realmente desea calcular Big-Ω y Big-O en los sentidos formales definidos anteriormente, tiene un problema importante: ¡hay infinitas descripciones de Big-Ω y Big-O para cualquier función! Es como preguntar cuáles son los números menores o iguales a 42. Hay muchas posibilidades
Para un algoritmo con T (n) ∈ ϴ (n 2 ) , cualquiera de los siguientes son afirmaciones formalmente válidas:
Pero es incorrecto indicar T (n) ∈ O (n) o T (n) ∈ Ω (n 3 ) .
¿Qué es la complejidad relativa? ¿Qué clases de algoritmos hay?
Si comparamos dos algoritmos diferentes, su complejidad a medida que la entrada llega al infinito normalmente aumentará. Si observamos diferentes tipos de algoritmos, pueden permanecer relativamente iguales (por ejemplo, diferiendo en un factor constante) o pueden divergir mucho. Esta es la razón para realizar el análisis Big-O: para determinar si un algoritmo funcionará razonablemente con entradas grandes.
Las clases de algoritmos se desglosan de la siguiente manera:
Θ (1) - constante. Por ejemplo, elegir el primer número en una lista siempre tomará la misma cantidad de tiempo.
Θ (n) - lineal. Por ejemplo, iterar una lista siempre tomará un tiempo proporcional al tamaño de la lista, n .
Θ (log (n)) - logarítmico (la base normalmente no importa). Los algoritmos que dividen el espacio de entrada en cada paso, como la búsqueda binaria, son ejemplos.
Θ (n × log (n)) - tiempos lineales logarítmicos ("linearithmic"). Estos algoritmos generalmente dividen y conquistan ( log (n) ) mientras siguen iterando ( n ) toda la entrada. Muchos algoritmos de clasificación populares (clasificación de fusión, Timsort) entran en esta categoría.
Θ (n m ) - polinomio ( n elevado a cualquier constante m ). Esta es una clase de complejidad muy común, que a menudo se encuentra en bucles anidados.
Θ (m n ) - exponencial (cualquier constante m elevada a n ). Muchos algoritmos recursivos y gráficos entran en esta categoría.
Θ (n!) - factorial. Ciertos algoritmos gráficos y combinatorios son de complejidad factorial.
¿Tiene esto algo que ver con el mejor / promedio / peor caso?
No. Big-O y su familia de notaciones hablan de una función matemática específica . Son herramientas matemáticas empleadas para ayudar a caracterizar la eficiencia de los algoritmos, pero la noción de mejor / promedio / peor de los casos no está relacionada con la teoría de las tasas de crecimiento descrita aquí.
Para hablar sobre el Big-O de un algoritmo, uno debe comprometerse con un modelo matemático específico de un algoritmo con exactamente un parámetro
n
, que se supone que describe el "tamaño" de la entrada, en cualquier sentido que sea útil. Pero en el mundo real, las entradas tienen mucha más estructura que solo sus longitudes. Si se trataba de un algoritmo de ordenación, podría alimentarse en las cuerdas"abcdef"
,"fedcba"
o"dbafce"
. Todos ellos son de longitud 6, pero uno de ellos ya está ordenado, uno está invertido y el último es solo una mezcla aleatoria. Algunos algoritmos de ordenación (como Timsort) funcionan mejor si la entrada ya está ordenada. Pero, ¿cómo se incorpora esta falta de homogeneidad en un modelo matemático?El enfoque típico es simplemente asumir que la entrada proviene de una distribución aleatoria y probabilística. Luego, promedia la complejidad del algoritmo sobre todas las entradas con longitud
n
. Esto le proporciona un modelo de complejidad de caso promedio del algoritmo. Desde aquí, puede usar las notaciones Big-O / Θ / Ω como de costumbre para describir el comportamiento promedio de los casos.Pero si le preocupan los ataques de denegación de servicio, es posible que tenga que ser más pesimista. En este caso, es más seguro asumir que las únicas entradas son las que causan la mayor cantidad de dolor a su algoritmo. Esto le da un modelo de complejidad del peor de los casos del algoritmo. Después, puede hablar sobre Big-O / Θ / Ω, etc. del peor de los modelos.
Del mismo modo, también puede centrar su interés exclusivamente en las entradas con las que su algoritmo tiene la menor cantidad de problemas para llegar al mejor modelo de caso , luego mirar Big-O / Θ / Ω, etc.
fuente
ϴ(n)
(tanto Big-O como Big-Ω) mientras que, en el peor de los casos, una complejidad temporal deϴ(n²)
(tanto Big-O como Big-Ω).Ω(1)
en el mejor, peor y promedio de los casos, pero eso se debe a que es un límite inferior y no tiene nada que ver con el mejor caso real del algoritmo.