¿Es esta una "regla" adecuada para identificar la notación "Big O" de un algoritmo?

30

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?

Levi Hackwith
fuente
3
Llámalo una regla general en lugar de una fórmula, y probablemente estés en el camino correcto. Por supuesto, depende completamente de lo que haga exactamente "hacer cosas". Log (N) generalmente proviene de algoritmos que realizan algún tipo de particionamiento binario / tipo árbol. Aquí hay una excelente publicación de blog sobre el tema.
Daniel B
15
No existe la 2Nnotación big-O.
vartec
15
@ JörgWMittag porque O (2n) = O (n) por definición de Big O
monstruo de trinquete el
3
@ JörgWMittag: este realmente no es el lugar para trollear.
vartec
3
@vartec - No creo que JörgWMittag estuviera cursando a propósito. En mi investigación reciente, he notado mucha confusión entre la notación estricta de Big-O y "vernáculo común" que mezcla Big-O, Theta y los otros derivados. No digo que el uso común sea correcto; solo que sucede mucho.

Respuestas:

27

Formalmente, la notación big-O describe el grado de complejidad.

Para calcular la notación big-O:

  1. Identificar la fórmula para la complejidad del algoritmo. Digamos, por ejemplo, dos bucles con otro anidado dentro, luego otros tres bucles no anidados:2N² + 3N
  2. eliminar todo excepto el término más alto: 2N²
  3. eliminar todas las constantes:

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 la sort()implementación que está utilizando su idioma / biblioteca subyacente.

vartec
fuente
Estrictamente hablando, "eliminar todas las constantes" se convertiría 2N³en N. "eliminar todas las constantes aditivas y multiplicativas" estaría más cerca de la verdad.
Joachim Sauer
@JoachimSauer: N² = N * N, no hay constante allí.
vartec
@vartec: de acuerdo con el mismo argumento 2N = N+N.
Joachim Sauer
2
@JoachimSauer, su "estrictamente hablando" como absolutamente no convencional. Ver en.wikipedia.org/wiki/Constant_(matemáticas) . Cuando se habla de polinomios, "constante" siempre se refiere solo a coeficientes, no a exponentes.
Ben Lee
1
@vartec, mira mi comentario arriba. Su uso de "constante" aquí fue absolutamente correcto y convencional.
Ben Lee
7

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):

for (int i = 0; i < myArray.length; i++) {
    myArray[i] += 1;
}

¿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):

for (int i = 0; i < myArray.length; i++) {
    for (int j = 0; j < myArray.length; j++) {
        myArray[i][j] += 1;
    }
}

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:

var fibonacci = function (n) {
    if (n == 1 || n == 2) {
        return 1;
    }

    else {
        return (fibonacci(n-2) + fibonacci(n-1));
    }
}

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.

Bryce Danz
fuente
¿Es su ejemplo de Fibonacci realmente Fibonacci? Sé que esto no argumenta en contra del punto que estaba tratando de hacer, pero el nombre puede ser engañoso para los demás y, por lo tanto, puede ser una distracción si en realidad no es Fibonacci.
Paul Nikonowicz
1

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 ) .

Benjamin Brumfield
fuente
0

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.

Pulposo
fuente
-1

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).

Jared C Miller
fuente