¿Qué causaría que un algoritmo tenga complejidad O(log n)?


Mi conocimiento de big-O es limitado, y cuando los términos de registro aparecen en la ecuación, me desconcierta aún más.

¿Puede alguien explicarme en términos simples lo que es un algoritmo O(log n)? ¿De dónde viene el logaritmo?

Esto surgió específicamente cuando estaba tratando de resolver esta pregunta de práctica de mitad de período:

Sea X(1..n) e Y (1..n) contiene dos listas de enteros, cada una ordenada en orden no decreciente. Dar un algoritmo de tiempo O (log n) para encontrar la mediana (o el enésimo entero más pequeño) de todos los elementos combinados de 2n. Por ejemplo, X = (4, 5, 7, 8, 9) y y = (3, 5, 8, 9, 10), entonces 7 es la mediana de la lista combinada (3, 4, 5, 5, 7, 8, 8, 9, 9, 10). [Sugerencia: utilice conceptos de búsqueda binaria]

Author: nbro, 2012-02-06

6 answers

Estoy de acuerdo en que es bastante raro la primera vez que ves un algoritmo O(log n)... ¿de dónde viene ese logaritmo? Sin embargo, resulta que hay varias maneras diferentes que usted puede obtener un término de registro para mostrar en notación big-O. Aquí están algunos:

Dividiendo repetidamente por una constante

Tome cualquier número n; digamos, 16. ¿Cuántas veces se puede dividir n por dos antes de obtener un número menor o igual a uno? Para 16, tenemos que

16 / 2 = 8
 8 / 2 = 4
 4 / 2 = 2
 2 / 2 = 1

Tenga en cuenta que esto termina tomando cuatro pasos para completar. Curiosamente, también tenemos ese registro2 16 = 4. Hmmm... ¿y el 128?

128 / 2 = 64
 64 / 2 = 32
 32 / 2 = 16
 16 / 2 = 8
  8 / 2 = 4
  4 / 2 = 2
  2 / 2 = 1

Esto tomó siete pasos, y registro2 128 = 7. ¿Es una coincidencia? ¡No! Hay una buena razón para esto. Supongamos que dividimos un número n por 2 i veces. Entonces obtenemos el número n / 2i. Si queremos resolver para el valor de i donde este valor es a lo sumo 1, obtenemos

N / 2i ≤ 1

N ≤ 2i

Log2 n ≤ i

En otras palabras, si elegimos un entero i tal que i ≥ log2 n, luego después de dividir n por la mitad i veces tendremos un valor que es a lo sumo 1. El i más pequeño para el que esto está garantizado es aproximadamente log2 n, así que si tenemos un algoritmo que divide por 2 hasta que el número se vuelve suficientemente pequeño, entonces podemos decir que termina en O (log n) Steps.

Un detalle importante es que no importa por qué constante estés dividiendo n (siempre y cuando sea mayor que uno); si divides por la constante k, tomará logk n pasos para llegar a 1. Por lo tanto, cualquier algoritmo que divida repetidamente el tamaño de entrada por alguna fracción necesitará iteraciones O(log n) para terminar. Esas iteraciones pueden tomar mucho tiempo y, por lo tanto, el tiempo de ejecución neto no necesita ser O(log n), pero el número de pasos será logarítmico.

Entonces, ¿dónde esto? Un ejemplo clásico es búsqueda binaria, un algoritmo rápido para buscar un valor en una matriz ordenada. El algoritmo funciona así:

  • Si el array está vacío, devuelve que el elemento no está presente en el array.
  • De lo contrario:
    • Mira el elemento central de la matriz.
    • Si es igual al elemento que estamos buscando, devuelve el éxito.
    • Si es mayor que el elemento que estamos buscando para:
      • Deseche la segunda mitad de la matriz.
      • Repeat
    • Si es menor que el elemento que estamos buscando:
      • Deseche la primera mitad de la matriz.
      • Repeat

Por ejemplo, para buscar 5 en el array

1   3   5   7   9   11   13

Primero nos fijamos en el elemento central:

1   3   5   7   9   11   13
            ^

Desde 7 > 5 , y puesto que la matriz está ordenada, sabemos por un hecho que el número 5 no puede estar en el detrás de la mitad de la matriz, así que podemos descartarlo. Esto deja

1   3   5

Así que ahora nos fijamos en el elemento central aquí:

1   3   5
    ^

Desde 3

        5

De nuevo nos fijamos en el centro de esta matriz:

        5
        ^

Dado que este es exactamente el número que estamos buscando, podemos informar que 5 está de hecho en la matriz.

Entonces, ¿cuán eficiente es esto? Bueno, en cada iteración estamos tirando por lo menos la mitad de los elementos de matriz restantes. El algoritmo se detiene tan pronto como la matriz está vacía o encontramos el valor que queremos. En el peor de los casos, el elemento no está allí, por lo que seguimos reduciendo a la mitad el tamaño de la matriz hasta que nos quedemos sin elementos. ¿Cuánto tarda esto? Bueno, ya que seguimos cortando la matriz por la mitad una y otra vez, se hará en la mayoría de las iteraciones O(log n), ya que no podemos cortar la matriz por la mitad más de O(log n) veces antes de ejecutar fuera de elementos de matriz.

Algoritmos siguiendo la técnica general de divide y vencerás (cortar el problema en pedazos, resolver esas piezas, luego volver a armar el problema) tienden a tener términos logarítmicos en ellos por esta misma razón: no puede seguir cortando un objeto a la mitad más de O(log n) veces. Es posible que desee mirar combinar ordenar como un gran ejemplo de esto.

Valores de procesamiento de un dígito a la time

¿Cuántos dígitos hay en el número base-10 n? Bueno, si hay k dígitos en el número, entonces tendríamos que el dígito más grande es algún múltiplo de 10 k . El número de dígitos k más grande es 999...9, k veces, y esto es igual a 10k + 1 - 1. En consecuencia, si sabemos que n tiene k dígitos en él, entonces sabemos que el valor de n es a lo sumo 10k + 1 - 1. Si queremos resolver para k en términos de n, obtenemos

N ≤ 10 k + 1 - 1

N + 1 ≤ 10 k+1

Log10 (n + 1) ≤ k + 1

(log10 (n + 1)) - 1 ≤ k

De lo que obtenemos que k es aproximadamente el logaritmo base-10 de n. En otras palabras, el número de dígitos en n es O(log n).

Por ejemplo, pensemos en la complejidad de agregar dos números grandes que son demasiado grandes para caber en una palabra máquina. Supongamos que tenemos esos números representados en base 10, y llame a los números m y n. Una forma de agregarlos es a través del método de la escuela primaria: escriba los números un dígito a la vez, luego trabaje de derecha a izquierda. Por ejemplo, para agregar 1337 y 2065, comenzaríamos escribiendo los números como

    1  3  3  7
+   2  0  6  5
==============

Sumamos el último dígito y llevamos el 1:

          1
    1  3  3  7
+   2  0  6  5
==============
             2

Luego agregamos el penúltimo dígito ("penúltimo") y llevamos el 1:

       1  1
    1  3  3  7
+   2  0  6  5
==============
          0  2

A continuación, añadimos la tercera a la última ("antepenúltimo") dígito:

       1  1
    1  3  3  7
+   2  0  6  5
==============
       4  0  2

Finalmente, añadimos el cuarto al último ("preantepenúltimo"... Me encanta el inglés) digit:

       1  1
    1  3  3  7
+   2  0  6  5
==============
    3  4  0  2

Ahora, ¿cuánto trabajo hicimos? Hacemos un total de O(1) trabajo por dígito (es decir, una cantidad constante de trabajo), y hay O(max{log n, log m}) dígitos totales que deben procesarse. Esto da un total de complejidad O (max{log n, log m}), porque necesitamos visitar cada dígito en los dos números.

Muchos algoritmos obtienen un término O (log n) en ellos al trabajar un dígito a la vez en alguna base. Un ejemplo clásico es orden de radix, que ordena enteros un dígito a la vez. Hay muchos sabores de radix sort, pero generalmente se ejecutan en el tiempo O (n log U), donde U es el entero más grande posible que se está ordenando. La razón de esto es que cada paso de la clase toma O(n) tiempo, y hay un total de iteraciones O(log U) necesarias para procesar cada uno de los dígitos O(log U) del número más grande que se ordena. Muchos avanzados los algoritmos, como el algoritmo de caminos más cortos de Gabow o la versión de escalado del algoritmo de flujo máximo de Ford-Fulkerson, tienen un término logarítmico en su complejidad porque trabajan un dígito a la vez.


En cuanto a su segunda pregunta sobre cómo resolver ese problema, es posible que desee mirar esta pregunta relacionada que explora una aplicación más avanzada. Dada la estructura general de los problemas que se describen aquí, ahora puede tener una mejor sentido de cómo pensar en los problemas cuando se sabe que hay un término de registro en el resultado, por lo que aconsejaría no mirar la respuesta hasta que haya dado un poco de pensamiento.

Espero que esto ayude!

 258
Author: templatetypedef,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:59

Cuando hablamos de descripciones big-Oh, generalmente estamos hablando del tiempo que se necesita para resolver problemas de un tamaño dado. Y por lo general, para problemas simples, ese tamaño se caracteriza sólo por el número de elementos de entrada, y que por lo general se llama n, o N. (Obviamente eso no siempre es cierto problems problemas con gráficos se caracterizan a menudo en números de vértices, V, y el número de aristas, E; pero por ahora, vamos a hablar de listas de objetos, con N objetos en el lista.)

Decimos que un problema "es grande-Oh de (alguna función de N)" si y solo si :

Para todos N > algunos N_0 arbitrarios, hay alguna constante c, tal que el tiempo de ejecución del algoritmo es menor que esa constante c veces (alguna función de N.)

En otras palabras, no piense en problemas pequeños donde la "sobrecarga constante" de configurar el problema importa, piense en problemas grandes. Y cuando se piensa en grandes problemas, grande-Oh de (alguna función de N) significa que el tiempo de ejecución es todavía siempre menor que algunas constantes multiplicadas por esa función. Siempre.

En resumen, esa función es un límite superior, hasta un factor constante.

Entonces, "big-Oh de log(n)" significa lo mismo que dije anteriormente, excepto que "alguna función de N" es reemplazada por "log(n)."

Entonces, tu problema te dice que pienses en la búsqueda binaria, así que pensemos en eso. Supongamos que tiene, digamos, una lista de N elementos que se ordenan en aumento orden. Desea averiguar si existe algún número dado en esa lista. Una forma de hacer lo que es no una búsqueda binaria es simplemente escanear cada elemento de la lista y ver si es su número de destino. Es posible que tenga suerte y lo encuentre en el primer intento. Pero en el peor de los casos, comprobarás N veces diferentes. Esto no es búsqueda binaria, y no es big-Oh de log(N) porque no hay manera de forzarlo en los criterios que esbozamos arriba.

Puede elegir esa constante arbitraria para ser c = 10, y si su lista tiene N=32 elementos, está bien: 10 * log (32) = 50, que es mayor que el tiempo de ejecución de 32. Pero si N = 64, 10 * log(64) = 60, que es menor que el tiempo de ejecución de 64. Usted puede elegir c=100, o 1000, o un gazillion, y usted todavía será capaz de encontrar algunos N que viola ese requisito. En otras palabras, no hay N_0.

Si hacemos una búsqueda binaria, sin embargo, elegimos el elemento central y hacemos una comparación. Luego tiramos la mitad de los números, y lo hacemos de nuevo, y otra vez, y así sucesivamente. Si su N = 32, solo puede hacerlo alrededor de 5 veces, que es log (32). Si su N = 64, solo puede hacer esto alrededor de 6 veces, etc. Ahora puede elegir esa constante arbitraria c, de tal manera que el requisito siempre se cumple para valores grandes de N.

Con todo ese fondo, lo que O(log(N)) generalmente significa es que tienes alguna manera de hacer algo simple, lo que reduce el tamaño del problema a la mitad. Al igual que la búsqueda binaria está haciendo arriba. Una vez que se corta el problema en la mitad, puedes cortarla por la mitad otra vez, y otra vez, y otra vez. Pero, críticamente, lo que no puede hacer es algún paso de preprocesamiento que tomaría más tiempo que ese tiempo O(log(N)). Así que, por ejemplo, no puedes mezclar tus dos listas en una lista grande, a menos que también puedas encontrar una manera de hacerlo en tiempo O(log(N)).

(NOTA: Casi siempre, Log(N) significa log-base-dos, que es lo que asumo arriba.)

 7
Author: Novak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-05 21:45:05

En la siguiente solución, todas las líneas con una llamada recursiva se realizan en la mitad de los tamaños dados de los sub-arrays de X e Y. Otras líneas se hacen en un tiempo constante. La función recursiva es T(2n)=T(2n/2)+c=T(n)+c=O(lg(2n))=O(lgn).

Se comienza con la MEDIANA(X, 1, n, Y, 1, n).

MEDIAN(X, p, r, Y, i, k) 
if X[r]<Y[i]
    return X[r]
if Y[k]<X[p]
    return Y[k]
q=floor((p+r)/2)
j=floor((i+k)/2)
if r-p+1 is even
    if X[q+1]>Y[j] and Y[j+1]>X[q]
        if X[q]>Y[j]
            return X[q]
        else
            return Y[j]
    if X[q+1]<Y[j-1]
        return MEDIAN(X, q+1, r, Y, i, j)
    else
        return MEDIAN(X, p, q, Y, j+1, k)
else
    if X[q]>Y[j] and Y[j+1]>X[q-1]
        return Y[j]
    if Y[j]>X[q] and X[q+1]>Y[j-1]
        return X[q]
    if X[q+1]<Y[j-1]
        return MEDIAN(X, q, r, Y, i, j)
    else
        return MEDIAN(X, p, q, Y, j, k)
 4
Author: Avi Cohen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-13 19:29:48

Llamamos a la complejidad de tiempo O(log n), cuando la solución se basa en iteraciones sobre n, donde el trabajo realizado en cada iteración es una fracción de la iteración anterior, ya que el algoritmo trabaja hacia la solución.

 2
Author: Alex Worden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-09 17:51:49

Todavía no puedo comentar... necro es! La respuesta de Avi Cohen es incorrecta, intente:

X = 1 3 4 5 8
Y = 2 5 6 7 9

Ninguna de las condiciones son verdaderas, por lo que la MEDIANA(X, p, q, Y, j, k) cortará ambos cincos. Estas son secuencias no decrecientes, no todos los valores son distintos.

También pruebe este ejemplo de longitud par con valores distintos:

X = 1 3 4 7
Y = 2 5 6 8

Ahora la MEDIANA(X, p, q, Y, j+1, k) cortará los cuatro.

En su lugar ofrezco este algoritmo, lo llamo con MEDIANA (1, n, 1, n):

MEDIAN(startx, endx, starty, endy){
  if (startx == endx)
    return min(X[startx], y[starty])
  odd = (startx + endx) % 2     //0 if even, 1 if odd
  m = (startx+endx - odd)/2
  n = (starty+endy - odd)/2
  x = X[m]
  y = Y[n]
  if x == y
    //then there are n-2{+1} total elements smaller than or equal to both x and y
    //so this value is the nth smallest
    //we have found the median.
    return x
  if (x < y)
    //if we remove some numbers smaller then the median,
    //and remove the same amount of numbers bigger than the median,
    //the median will not change
    //we know the elements before x are smaller than the median,
    //and the elements after y are bigger than the median,
    //so we discard these and continue the search:
    return MEDIAN(m, endx, starty, n + 1 - odd)
  else  (x > y)
    return MEDIAN(startx, m + 1 - odd, n, endy)
}
 1
Author: Wolfzoon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-20 14:15:46

El término Log aparece muy a menudo en el análisis de complejidad de algoritmos. He aquí algunas explicaciones:

1. ¿Cómo representas un número?

Tomemos el número X = 245436. Esta notación de "245436" contiene información implícita. Making that information explicit:

X = 2 * 10 ^ 5 + 4 * 10 ^ 4 + 5 * 10 ^ 3 + 4 * 10 ^ 2 + 3 * 10 ^ 1 + 6 * 10 ^ 0

Que es la expansión decimal del número. Por lo tanto, la cantidad mínima de información necesitamos representar este número es 6 dígitos. Esto no es coincidencia, ya que cualquier número menor que 10^d puede ser representado en d dígitos.

Entonces, ¿cuántos dígitos se requieren para representar X? Eso es igual al exponente más grande de 10 en X más 1.

==> 10 ^ d > X
= = > log (10 ^ d) > log(X)
= = > d * log (10) > log (X)
= = > d > log (X) / / Y el registro aparece de nuevo...
= = > d = suelo (log (x)) + 1

También tenga en cuenta que esta es la forma más concisa de denotar el número en este rango. Cualquier reducción conducirá a la pérdida de información, ya que un dígito faltante se puede asignar a otros 10 números. Por ejemplo: 12 * se puede asignar a 120, 121, 122, ..., 129.

2. ¿Cómo buscar un número en (0, N-1)?

Tomando N = 10^d, usamos nuestra observación más importante:

La cantidad mínima de información para identificar de forma única un valor en un rango entre 0 y N-1 = log(N) dígitos.

Esto implica que, cuando se le pide que busque un número en la línea entera, que va desde 0 a N - 1, necesitamos al menos log (N) intenta encontrarlo. ¿Por qué? Cualquier algoritmo de búsqueda tendrá que elegir un dígito tras otro en su búsqueda del número.

El número mínimo de dígitos que necesita elegir es log(N). Por lo tanto, el número mínimo de operaciones tomadas para buscar un número en un espacio de tamaño N es log (N).

¿Puede adivinar las complejidades de orden de búsqueda binaria, búsqueda ternaria o búsqueda deca?
Es O(log (N))!

3. ¿Cómo ordenar un conjunto de números?

Cuando se le pide ordenar un conjunto de números A en una matriz B, esto es lo que parece- >

Elementos permutados

Cada elemento en el array original tiene que ser asignado a su índice correspondiente en el array ordenado. Por lo tanto, para el primer elemento, tenemos n puestos. Para encontrar correctamente el índice correspondiente en este rango de 0 a n-1, necesitamos operaciones log log (n).

El siguiente elemento necesita operaciones log(n-1), el siguiente log(n-2) y así sucesivamente. El total llega a ser:

==> log(n) + log(n - 1) + log(n - 2) + ... + registro(1)

el Uso de log(a) + log(b) = log(a * b),

==> log(n!)

Esto puede ser aproximada a nlog(n) - n.
Que es O(n*log(n))!

De ahí que concluyamos que no puede haber ningún algoritmo de ordenación que pueda hacer mejor que O(n * log (n)). Y algunos algoritmos que tienen esta complejidad son los populares Merge Sort y Heap Sort!

Estas son algunas de las razones por las que vemos log(n) aparecer tan a menudo en el análisis de complejidad de los algoritmos. Lo mismo se puede extender a los números binarios. Hice un video sobre eso aquí.
¿Por qué el log (n) aparece tan a menudo durante el análisis de complejidad del algoritmo?

¡Salud!
 1
Author: Gaurav Sen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-28 08:54:55