Anoche estaba discutiendo con otro programador que, aunque algo puede ser O (1), una operación que es O (n) puede superarlo si hay una gran constante en el algoritmo O (1). No estuvo de acuerdo, así que lo traje aquí.
¿Hay ejemplos de algoritmos que superen en gran medida a los de la clase a continuación? Por ejemplo, O (n) es más rápido que O (1) u O (n 2 ) es más rápido que O (n).
Matemáticamente, esto se puede demostrar para una función con límites superiores asintóticos, cuando se ignoran los factores constantes, pero ¿existen tales algoritmos en la naturaleza? ¿Y dónde encontraría ejemplos de ellos? ¿Para qué tipos de situaciones se usan?
algorithms
big-o
KyleWpppd
fuente
fuente
n
suficientemente grande como para compensar la constante (que es el punto de la notación big-O).Respuestas:
Búsquedas en tablas de datos fijas muy pequeñas. Una tabla hash optimizada puede ser O (1) y aún más lenta que una búsqueda binaria o incluso una búsqueda lineal debido al costo del cálculo del hash.
fuente
Multiplicación matricial. El algoritmo ingenuo O (n ^ 3) a menudo se usa en la práctica como más rápido que el O de Strassen (n ^ 2.8) para matrices pequeñas; y Strassen se usa en lugar del algoritmo O (n ^ 2.3) Coppersmith – Winograd para matrices más grandes.
fuente
Un ejemplo simple es la diferencia entre varios algoritmos de clasificación. Mergesort, Heapsort y algunos otros son O (n log n) . Quicksort es O (n ^ 2) peor de los casos. Pero a menudo Quicksort es más rápido y, de hecho, funciona en promedio como O (n log n) . Más información .
Otro ejemplo es la generación de un solo número de Fibonacci. El algoritmo iterativo es O (n) , mientras que el algoritmo basado en matriz es O (log n) . Aún así, para el primer par de miles de números de Fibonacci, el algoritmo iterativo es probablemente más rápido. ¡Esto también depende de la implementación, por supuesto!
Los algoritmos con un mejor rendimiento asintótico pueden contener operaciones costosas que no son necesarias con un algoritmo con peor rendimiento pero operaciones más simples. Al final, la notación O solo nos dice algo sobre el rendimiento cuando el argumento en el que opera aumenta dramáticamente (se acerca al infinito).
fuente
Nota: Lea los comentarios de @ back2dos a continuación y otros gurús, ya que de hecho son más útiles que lo que he escrito. Gracias por todos los colaboradores.
Creo que en el cuadro a continuación (tomado de: notación Big O , busque "La naturaleza pesimista de los algoritmos:"), puede ver que O (log n) no siempre es mejor que decir, O (n). Entonces, supongo que su argumento es válido.
fuente
y = 1
,y = log x
etc., y la intersección dey = 1
yy = x
es en realidad el punto(1,1)
. Si esto fuera realmente correcto, de lo que te diría, que los algoritmos de mayor complejidad pueden ser más rápidos para 0 a 2 entradas, que es algo que a la gente apenas le importaría. Lo que el gráfico no tiene en cuenta por completo (y de dónde proviene la diferencia de rendimiento perceptible en cuestión) son factores constantes.Para valores prácticos de
n
, sí. Esto surge mucho en la teoría de CS. A menudo hay un algoritmo complicado que técnicamente tiene un mejor rendimiento big-Oh, pero los factores constantes son tan grandes que lo hacen impracticable.Una vez hice que mi profesor de geometría computacional describiera un algoritmo para triangular un polígono en tiempo lineal, pero terminó con "muy complicado. ¡No creo que nadie lo haya implementado realmente" (!!).
Además, los montones de fibonacci tienen mejores características que los montones normales, pero no son muy populares porque no funcionan tan bien en la práctica como los montones normales. Esto puede conectarse en cascada a otros algoritmos que usan montones, por ejemplo, los caminos más cortos de Dijkstra son matemáticamente más rápidos con un montón de Fibonacci, pero generalmente no en la práctica.
fuente
Compare la inserción en una lista vinculada y la inserción en una matriz redimensionable.
La cantidad de datos tiene que ser bastante grande para que la inserción de la lista vinculada O (1) valga la pena.
Una lista vinculada tiene una sobrecarga adicional para los siguientes punteros y desreferencias. Una matriz redimensionable tiene que copiar datos. Esa copia es O (n), pero en la práctica es muy rápida.
fuente
La notación Big-Oh se usa para describir la tasa de crecimiento de una función, por lo que es posible que un algoritmo O (1) sea más rápido, pero solo hasta cierto punto (el factor constante).
Notaciones comunes:
O (1): el número de iteraciones (a veces se puede referir a esto como tiempo de usuario empleado por la función) no depende del tamaño de la entrada y, de hecho, es constante.
O (n): el número de iteraciones crece en una proporción lineal al tamaño de la entrada. Significado: si el algoritmo itera a través de cualquier entrada N, 2 * N veces, todavía se considera O (n).
O (n ^ 2) (cuadrático): el número de iteraciones es el tamaño de entrada al cuadrado.
fuente
Las bibliotecas Regex generalmente se implementan para hacer un seguimiento inverso que tiene el peor tiempo exponencial en lugar de la generación DFA que tiene una complejidad de
O(nm)
.El retroceso ingenuo puede tener un mejor rendimiento cuando la entrada permanece en el camino rápido o falla sin la necesidad de retroceder excesivamente.
(Aunque esta decisión no solo se basa en el rendimiento, también es permitir referencias posteriores).
fuente
Un
O(1)
algoritmo:Un
O(n)
algoritmo:Claramente, para cualquier valor de
n
dónden < one_million
, elO(n)
algoritmo dado en el ejemplo será más rápido que elO(1)
algoritmo.Si bien este ejemplo es un poco gracioso, es equivalente en espíritu al siguiente ejemplo:
Usted debe conocer las constantes y coeficientes en su
O
expresión, y usted debe saber el rango esperado den
, a fin de determinar a priori qué algoritmo va a terminar siendo más rápido.De lo contrario, debe comparar los dos algoritmos con valores de
n
en el rango esperado para determinar a posteriori qué algoritmo terminó siendo más rápido.fuente
Clasificación:
El orden de inserción es O (n ^ 2) pero supera a otros algoritmos de clasificación O (n * log (n)) para un pequeño número de elementos.
Esta es la razón por la cual la mayoría de las implementaciones de clasificación utilizan una combinación de dos algoritmos. Por ejemplo, utilice la ordenación por fusión para desglosar las matrices grandes hasta que alcancen un tamaño determinado, luego use la ordenación por inserción para ordenar las unidades más pequeñas y fusionarlas nuevamente con la ordenación por fusión.
Consulte Timsort la implementación predeterminada actual de la ordenación de Python y Java 7 que utiliza esta técnica.
fuente
El algoritmo de unificación utilizado en la práctica es exponencial en el peor de los casos, para algunas entradas patológicas.
Existe un algoritmo de unificación polinómica , pero en la práctica es demasiado lento.
fuente
La clasificación de burbujas en la memoria puede superar a la clasificación rápida cuando el programa se intercambia al disco o necesita leer cada elemento del disco al comparar.
Este debería ser un ejemplo con el que pueda relacionarse.
fuente
A menudo, los algoritmos más avanzados suponen una cierta cantidad de configuración (costosa). Si solo necesita ejecutarlo una vez, podría estar mejor con el método de fuerza bruta.
Por ejemplo: la búsqueda binaria y la búsqueda de la tabla hash son mucho más rápidas por búsqueda que una búsqueda lineal, pero requieren que ordene la lista o cree la tabla hash, respectivamente.
El tipo le costará N log (N) y la tabla hash costará al menos N. Ahora si va a realizar cientos o miles de búsquedas, eso sigue siendo un ahorro amortizado. Pero si solo necesita hacer una o dos búsquedas, podría tener sentido hacer una búsqueda lineal y ahorrar el costo de inicio.
fuente
El descifrado es a menudo 0 (1). Por ejemplo, el espacio clave para DES es 2 ^ 56, por lo que el descifrado de cualquier mensaje es una operación de tiempo constante. Es solo que tienes un factor de 2 ^ 56, así que es una constante realmente grande.
fuente
Me vienen a la mente diferentes implementaciones de conjuntos. Uno de los más ingenuos es implementarlo sobre un vector, lo que significa
remove
que,contains
y por lo tanto, tambiénadd
todos toman O (N).Una alternativa es implementarlo sobre algún hash de propósito general, que asigna los hashes de entrada a los valores de entrada. Tal implementación de conjunto se realiza con O (1) para
add
,contains
yremove
.Si suponemos que N es aproximadamente 10, entonces la primera implementación es probablemente más rápida. Todo lo que tiene que hacer para encontrar un elemento es comparar 10 valores con uno.
La otra implementación tendrá que iniciar todo tipo de transformaciones inteligentes, que pueden ser mucho más caras, que hacer 10 comparaciones. Con toda la sobrecarga, incluso puede tener errores de caché y, en teoría, realmente no importa cuán rápida sea su solución.
Esto no significa que la peor implementación que se te ocurra superará a una decente, si N es lo suficientemente pequeña. Simplemente significa para un N suficientemente pequeño, que una implementación ingenua, con poca huella y sobrecarga, puede requerir menos instrucciones y causar menos errores de caché que una implementación que prioriza la escalabilidad y, por lo tanto, será más rápida.
Realmente no puedes saber qué tan rápido es algo en un escenario del mundo real, hasta que lo pones en uno y simplemente lo mides. A menudo los resultados son sorprendentes (al menos para mí).
fuente
Sí, para un N. adecuadamente pequeño Siempre habrá una N, por encima de la cual siempre tendrá el orden O (1) <O (lg N) <O (N) <O (N log N) <O (N ^ c ) <O (c ^ N) (donde O (1) <O (lg N) significa que en un algoritmo O (1) tomará menos operaciones cuando N es adecuadamente grande y c es una constante fija que es mayor que 1 )
Digamos que un algoritmo O (1) particular toma exactamente f (N) = 10 ^ 100 (un googol) operaciones y un algoritmo O (N) toma exactamente g (N) = 2 N + 5 operaciones. El algoritmo O (N) dará un mayor rendimiento hasta que N sea aproximadamente un googol (en realidad cuando N> (10 ^ 100 - 5) / 2), por lo que si solo esperaba que N estuviera en el rango de 1000 a mil millones sufriría una penalización importante usando el algoritmo O (1).
O para una comparación realista, digamos que está multiplicando números de n dígitos. El algoritmo de Karatsuba es a lo sumo 3 n ^ (lg 3) operaciones (que es aproximadamente O (n ^ 1.585)) mientras que el algoritmo de Schönhage-Strassen es O (N log N log log N) que es un orden más rápido , pero para citar wikipedia:
Entonces, si está multiplicando números de 500 dígitos, no tiene sentido usar el algoritmo que es "más rápido" por grandes argumentos O.
EDITAR: Puede encontrar determinar f (N) comparado g (N), tomando el límite N-> infinito de f (N) / g (N). Si el límite es 0, entonces f (N) <g (N), si el límite es infinito, entonces f (N)> g (N), y si el límite es alguna otra constante, entonces f (N) ~ g (N) en términos de notación O grande.
fuente
El método simplex para la programación lineal puede ser exponencial en el peor de los casos, mientras que los algoritmos de punto interior relativamente nuevos pueden ser polinómicos.
Sin embargo, en la práctica, el peor caso exponencial para el método simplex no aparece: el método simplex es rápido y confiable, mientras que los primeros algoritmos de punto interior eran demasiado lentos para ser competitivos. (Ahora hay algoritmos de puntos interiores más modernos que son competitivos, pero el método simple también lo es ...)
fuente
El algoritmo de Ukkonen para construir intentos de sufijo es O (n log n). Tiene la ventaja de estar "en línea", es decir, puede agregar más texto de forma incremental.
Recientemente, otros algoritmos más complejos han afirmado ser más rápidos en la práctica, en gran parte porque su acceso a la memoria tiene una mayor localidad, lo que mejora la utilización de la memoria caché del procesador y evita los bloqueos de la tubería de la CPU. Véase, por ejemplo, esta encuesta , que afirma que el 70-80% del tiempo de procesamiento se gasta esperando memoria, y este documento describe el algoritmo "wotd".
Los intentos de sufijo son importantes en genética (para la coincidencia de secuencias de genes) y, algo menos importante, en la implementación de diccionarios Scrabble.
fuente
Siempre existe el algoritmo más rápido y más corto para cualquier problema bien definido . Sin embargo, es puramente teóricamente el algoritmo más rápido (asintóticamente).
Dado cualquier descripción de un problema P y una instancia para ese problema I , enumera todos los algoritmos posibles A y pruebas Pr , la comprobación de cada uno de tales pares si Pr es una prueba válida que A es el algoritmo asintóticamente más rápida para P . Si encuentra una prueba de este tipo, a continuación, ejecuta una de I .
La búsqueda de este par a prueba de problemas tiene una complejidad O (1) (para un problema fijo P ), por lo que siempre utiliza el algoritmo asintóticamente más rápido para el problema. Sin embargo, dado que esta constante es tan indescriptiblemente enorme en casi todos los casos, este método es completamente inútil en la práctica.
fuente
Muchos lenguajes / frameworks usan una coincidencia de patrones ingenua para unir cadenas en lugar de KMP . Buscamos cuerdas como Tom, Nueva York en lugar de ababaabababababaababababababab.
fuente