He estado aprendiendo más sobre la notación Big O y cómo calcularla en función de cómo se escribe un algoritmo. Encontré un conjunto interesante de "reglas" para calcular una notación Big O de algoritmos y quería ver si estaba en el camino correcto o muy lejos.
Notación O grande: N
function(n) {
For(var a = 0; i <= n; i++) { // It's N because it's just a single loop
// Do stuff
}
}
Notación O grande: N 2
function(n, b) {
For(var a = 0; a <= n; a++) {
For(var c = 0; i <= b; c++) { // It's N squared because it's two nested loops
// Do stuff
}
}
}
Notación O grande: 2N
function(n, b) {
For(var a = 0; a <= n; a++) {
// Do stuff
}
For(var c = 0; i <= b; c++) { // It's 2N the loops are outside each other
// Do stuff
}
}
Notación O grande: NLogN
function(n) {
n.sort(); // The NLogN comes from the sort?
For(var a = 0; i <= n; i++) {
// Do stuff
}
}
¿Son correctos mis ejemplos y la notación posterior? ¿Hay anotaciones adicionales que debo tener en cuenta?
algorithms
big-o
Levi Hackwith
fuente
fuente
2N
notación big-O.Respuestas:
Formalmente, la notación big-O describe el grado de complejidad.
Para calcular la notación big-O:
2N² + 3N
2N²
N²
En otras palabras, dos bucles con otro anidado dentro, luego otros tres bucles no anidados es O (N²)
Por supuesto, esto supone que lo que tienes en tus bucles son instrucciones simples. Si tiene, por ejemplo,
sort()
dentro del bucle, deberá multiplicar la complejidad del bucle por la complejidad de lasort()
implementación que está utilizando su idioma / biblioteca subyacente.fuente
2N³
enN
. "eliminar todas las constantes aditivas y multiplicativas" estaría más cerca de la verdad.2N = N+N
.Si desea analizar estos algoritmos, debe definir // dostuff, ya que eso realmente puede cambiar el resultado. Supongamos que dostuff requiere un número constante de operaciones O (1).
Aquí hay algunos ejemplos con esta nueva notación:
Para su primer ejemplo, el recorrido lineal: ¡esto es correcto!
EN):
¿Por qué es lineal (O (n))? A medida que agregamos elementos adicionales a la entrada (matriz), la cantidad de operaciones que ocurren aumenta proporcionalmente al número de elementos que agregamos.
Entonces, si se necesita una operación para incrementar un número entero en algún lugar de la memoria, podemos modelar el trabajo que realiza el ciclo con f (x) = 5x = 5 operaciones adicionales. Para 20 elementos adicionales, hacemos 20 operaciones adicionales. Una sola pasada de una matriz tiende a ser lineal. También lo son los algoritmos como la ordenación de cubetas, que pueden explotar la estructura de datos para hacer una ordenación en una sola pasada de una matriz.
Su segundo ejemplo también sería correcto y se ve así:
O (N ^ 2):
En este caso, por cada elemento adicional en la primera matriz, i, tenemos que procesar TODO j. Agregar 1 a i en realidad agrega (longitud de j) a j. Por lo tanto, tienes razón! Este patrón es O (n ^ 2), o en nuestro ejemplo en realidad es O (i * j) (o n ^ 2 si i == j, que a menudo es el caso con operaciones matriciales o una estructura de datos cuadrada.
Su tercer ejemplo es donde las cosas cambian dependiendo del dostuff; Si el código está escrito y hacer cosas es una constante, en realidad es solo O (n) porque tenemos 2 pases de una matriz de tamaño n, y 2n se reduce a n. Los bucles que se encuentran uno fuera del otro no son el factor clave que puede producir un código 2 ^ n; Aquí hay un ejemplo de una función que es 2 ^ n:
Esta función es 2 ^ n, porque cada llamada a la función produce DOS llamadas adicionales a la función (Fibonacci). Cada vez que llamamos a la función, la cantidad de trabajo que tenemos que hacer se duplica ¡Esto crece súper rápido, como cortar la cabeza de una hidra y hacer que broten dos nuevas cada vez!
Para su ejemplo final, si está utilizando una ordenación nlgn como merge-sort, tiene razón en que este código será O (nlgn). Sin embargo, puede explotar la estructura de los datos para desarrollar tipos más rápidos en situaciones específicas (como en un rango de valores conocido y limitado, como 1-100). Sin embargo, tiene razón al pensar que domina el código de orden superior; entonces, si una ordenación O (nlgn) está al lado de cualquier operación que tome menos tiempo de O (nlgn), la complejidad del tiempo total será O (nlgn).
En JavaScript (al menos en Firefox), la ordenación predeterminada en Array.prototype.sort () es de hecho MergeSort, por lo que está buscando O (nlgn) para su escenario final.
fuente
Su segundo ejemplo (bucle externo de 0 a n , bucle interno de 0 a b ) sería O ( nb ), no O ( n 2 ). La regla es que estás calculando algo n veces, y para cada uno de ellos estás calculando algo más b veces, por lo tanto, el crecimiento de esta función depende únicamente del crecimiento de n * b .
Su tercer ejemplo es solo O ( n ): puede eliminar todas las constantes ya que no crecen con n y el crecimiento es de lo que se trata la notación Big-O.
En cuanto a su último ejemplo, sí, su notación Big-O ciertamente vendrá del método de clasificación que será, si se basa en la comparación (como suele ser el caso), en su forma más eficiente, O ( n * logn ) .
fuente
Recuerde que esta es una representación aproximada del tiempo de ejecución. La "regla general" es aproximada porque es imprecisa pero ofrece una buena aproximación de primer orden para fines de evaluación.
El tiempo de ejecución real dependerá de cuánto espacio de almacenamiento dinámico, qué tan rápido es el procesador, el conjunto de instrucciones, el uso de operadores de incremento de prefijo o post-arreglo, etc., yadda. El análisis adecuado del tiempo de ejecución permitirá determinar la aceptación, pero tener conocimiento de los conceptos básicos le permite programar desde el principio.
Estoy de acuerdo en que está en el camino correcto para comprender cómo Big-O se racionaliza de un libro de texto a una aplicación práctica. Ese puede ser el obstáculo difícil de superar.
La tasa de crecimiento asintótica se vuelve importante en grandes conjuntos de datos y programas grandes, por lo que, para ejemplos típicos, demuestra que no es tan importante para la sintaxis y la lógica adecuadas.
fuente
Grande oh, por definición significa: para una función f (t) existe una función c * g (t) donde c es una constante arbitraria tal que f (t) <= c * g (t) para t> n donde n es una constante arbitraria, entonces f (t) existe en O (g (t)). Esta es una notación matemática que se usa en informática para analizar algoritmos. Si está confundido, recomendaría buscar relaciones de cierre, de esa manera puede ver en una vista más detallada cómo estos algoritmos obtienen estos valores de gran oh.
Algunas consecuencias de esta definición: O (n) es en realidad congruente con O (2n).
También hay muchos tipos diferentes de algoritmos de clasificación. El valor mínimo de Big-Oh para un tipo de comparación es O (nlogn), sin embargo, hay muchos tipos con peor big-oh. Por ejemplo, el orden de selección tiene O (n ^ 2). Algún tipo de no comparación puede tener mejores valores big-oh. Un tipo de depósito, por ejemplo, tiene O (n).
fuente