Explicar la relevancia de la complejidad asintótica de los algoritmos para la práctica del diseño de algoritmos.

40

En algoritmos y complejidad, nos centramos en la complejidad asintótica de los algoritmos, es decir, la cantidad de recursos que utiliza un algoritmo a medida que el tamaño de la entrada llega al infinito.

En la práctica, lo que se necesita es un algoritmo que funcione rápidamente en un número finito (aunque posiblemente muy grande) de instancias.

Un algoritmo que funciona bien en la práctica en el número finito de instancias que nos interesa no necesita tener una buena complejidad asintótica (el buen desempeño en un número finito de instancias no implica nada con respecto a la complejidad asintótica). Del mismo modo, un algoritmo con buena complejidad asintótica puede no funcionar bien en la práctica en el número finito de instancias que nos interesan (por ejemplo, debido a las constantes grandes).

¿Por qué utilizamos la complejidad asintótica? ¿Cómo se relacionan estos análisis asintóticos con el diseño de algoritmos en la práctica?

Kaveh
fuente
Otra pregunta relevante es: ¿por qué ignoramos los factores constantes ?
Raphael

Respuestas:

24

La pregunta interesante es: ¿cuál es la alternativa? El único otro método que conozco es probar / comparar. Programamos los algoritmos, dejamos que se ejecuten en (una muestra representativa de) el conjunto de entrada finita y comparamos los resultados. Hay un par de problemas con eso.

  • Los resultados no son generales en términos de máquinas. Ejecute su punto de referencia en otra computadora y obtendrá resultados diferentes con seguridad, cuantitativamente y tal vez incluso cualitativamente.
  • Los resultados no son generales en términos de lenguajes de programación. Diferentes idiomas pueden causar resultados muy diferentes.
  • Los resultados no son generales en términos de detalles de implementación. Literalmente compara programas , no algoritmos; Pequeños cambios en la implementación pueden causar grandes diferencias en el rendimiento.
  • Si el peor de los casos es raro, una muestra de entrada aleatoria puede no contener una mala instancia. Eso es justo si le preocupa el rendimiento promedio de los casos, pero algunos entornos requieren garantías para el peor de los casos.
  • En la práctica, los conjuntos de entrada cambian. Típicamente, las entradas se hacen más grandes con el tiempo. Si no repite su punto de referencia cada seis meses (sí, algunos datos crecen tan rápido), sus resultados pronto serán inútiles¹.

Dicho esto, ignorar todo tipo de efectos y constantes en el análisis es típico, pero puede llamarse perezoso (con respecto a la práctica). Sirve para comparar ideas algorítmicas más que para determinar el rendimiento de una implementación dada (incluso pseudocódigo). Es bien sabido por la comunidad que esto es grosero y que a menudo es necesaria una mirada más cercana; por ejemplo, Quicksort es menos eficiente que el tipo de inserción para (muy) pequeñas entradas. Para ser justos, un análisis más preciso suele ser difícil ².

Otra justificación a posteriori del punto de vista formal y abstracto es que, en este nivel, las cosas son a menudo más claras. Por lo tanto, décadas de estudio teórico han dado lugar a una gran cantidad de ideas algorítmicas y estructuras de datos que son útiles en la práctica. El algoritmo teóricamente óptimo no siempre es el que desea usar en la práctica; hay otras consideraciones, pero el rendimiento debe hacerse; piense en los montones de Fibonacci, y es posible que esta etiqueta ni siquiera sea única. Es difícil para un programador típico preocupado por optimizar las expresiones aritméticas que se le ocurra una nueva idea en este nivel (por no decir que no sucede); Sin embargo, ella puede (y debe) realizar esas optimizaciones sobre la idea asimilada.

Hay herramientas formales y teóricas para cerrar la brecha y practicar hasta cierto punto. Ejemplos son

  • considerando la jerarquía de memoria (y otras E / S),
  • analizando el caso promedio (cuando corresponda),
  • analizar números de declaraciones individuales (en lugar de medidas de costo abstractas) y
  • determinando factores constantes.

Por ejemplo, Knuth es conocido por contar literalmente el número de declaraciones diferentes (para una implementación dada en un modelo dado), lo que permite una comparación precisa de algoritmos. Ese enfoque es imposible en un nivel abstracto y difícil de hacer en modelos más complejos (piense en Java). Ver [4] para un ejemplo moderno.

Siempre habrá una brecha entre la teoría y la práctica. Actualmente estamos trabajando en una herramienta³ con el objetivo de combinar lo mejor de ambos mundos para hacer predicciones sólidas tanto para los costos algorítmicos como para el tiempo de ejecución (en promedio), pero hasta ahora no hemos podido eliminar escenarios en los que un algoritmo tiene mayor cuesta un tiempo de ejecución menor (en algunas máquinas) que uno equivalente (aunque podemos detectar eso y respaldar la búsqueda del motivo).

Recomiendo a los profesionales que utilicen la teoría para filtrar el espacio de los algoritmos antes de ejecutar puntos de referencia:

if ( input size forever bounded? ) {
  benchmark available implementations, choose best
  schedule new benchmarks for when machine changes
}
else {
  benchmark implementations of all asymptotically good algorithms
  choose the best
  schedule new benchmarks for when machine changes or inputs grow significantly
}

  1. Puede haber cambios locos en el rendimiento absoluto y relativo una vez que aumenta el número de errores de caché, lo que generalmente ocurre cuando las entradas crecen pero la máquina permanece igual.
  2. Al igual que en, los principales investigadores en el campo no pueden hacerlo.
  3. Encuentra la herramienta aquí . Un ejemplo de uso ha sido publicado en Ingeniería Java 7 Dual Pivot Quicksort Using MaLiJAn por S. Wild et al. (2012) [ preimpresión ]
  4. Análisis de casos promedio de Quicksort dual Pivot de Java 7 por S. Wild y M. Nebel (2012) - [ preprint ]
Rafael
fuente
3
Podría decirse que el simple acto de estudiar la teoría de los algoritmos agudizará su ojo y entrenará su cerebro de abstracción para los algoritmos, brindándole otra herramienta para evaluar el código en la programación diaria. Extraiga del código, evalúe el principio, mejórelo y vuelva a traducir al código. Ejemplo: "Ah, ya veo, quiere programar un diccionario. Pero esencialmente programa listas; ¿por qué no prueba los árboles?"
Raphael
Los límites del análisis asintótico se hacen evidentes una vez que profundizas; Quicksort es un ejemplo destacado .
Raphael
1
FWIW, he escrito una instantánea más reciente de mis opiniones sobre la notación de Landau aquí .
Raphael
11

Supongo que esta pregunta surge de la enseñanza de un curso que incluye análisis asintótico. Hay varias respuestas posibles sobre por qué este material se enseña en clases introductorias:

  • El análisis asintótico es una abstracción matemática que se entrega al análisis. Como (posiblemente) matemáticos, queremos poder analizar algoritmos, y la única forma de domar su complejidad es usar análisis asintótico.

  • Evaluar el rendimiento asintótico de un algoritmo señala algunos principios que son útiles en la práctica: por ejemplo, concéntrese en esa parte del código que toma la mayor parte del tiempo y descarte cualquier parte del código que tome una parte del tiempo asintóticamente insignificante .

  • Algunas de las técnicas de análisis asintótico son útiles. Me refiero principalmente al llamado "teorema maestro", que en muchas circunstancias es una buena descripción de la realidad.

  • También hay una razón histórica: cuando las personas comenzaron a analizar algoritmos, pensaron sinceramente que la complejidad asintótica refleja el uso práctico. Sin embargo, finalmente se demostró que estaban equivocados. Lo mismo sucedió con P como la clase de problemas solucionables de manera eficiente, y NP como la clase de problemas intratables, los cuales son engañosos en la práctica.

Personalmente, creo que el análisis asintótico es una parte razonable del plan de estudios. Las partes más cuestionables incluyen la teoría del lenguaje formal y la teoría de la complejidad (cualquier cosa que tenga que ver con una máquina de Turing). Algunas personas argumentan que si bien estos temas no son útiles para el aspirante a programador per se, inculcan en ella un cierto pensamiento mental que es necesario para ser un buen practicante. Otros sostienen que la teoría a veces influye en la práctica, y estos casos raros son suficientes para justificar la enseñanza de estas materias bastante arcanas a la audiencia general de informática. Prefiero que aprendan historia o literatura, o cualquier otro tema en el que estén realmente interesados; ambos son tan relevantes para sus perspectivas laborales futuras, y más importantes para ellos como seres humanos.

Yuval Filmus
fuente
Gracias Yuval La motivación que más le interesa es cómo explicar a los estudiantes la utilidad del análisis asintótico y su relevancia para la práctica de diseñar y usar algoritmos en aplicaciones reales (donde la mayoría de las veces está claro que solo estamos interesados ​​en un finito aunque posiblemente gran número de instancias), sin justificar el currículum.
Kaveh
1
Estoy confundido por tu premisa. Parece que asumes que el grupo objetivo son matemáticos y aspirantes a programadores, lo cual es una combinación extraña y que no caracteriza a los informáticos. (Además, no comparto su punto de vista sobre los idiomas formales, pero ese es otro tema).
Raphael
Por el contrario, supongo que el grupo objetivo son los aspirantes a programadores. Sin embargo, gran parte del plan de estudios está allí por el bien de los científicos teóricos de la informática. Por supuesto, estos dos grupos tienen necesidades en conflicto. Dado que la mayoría de los estudiantes universitarios son posibles programadores, creo que el plan de estudios debería estar orientado hacia ellos, pero algunos académicos no están de acuerdo. Quizás quieran enseñar a los futuros profesores. Quizás puedas explicar su punto de vista.
Yuval Filmus
3
@YuvalFilmus A menudo he explicado que no creo que CS = TCS + Programming. Si enseñas un curso de CS (en una universidad) y la mayoría de tus estudiantes quieren ser programadores, algo está roto (en mi humilde opinión). Yo diría que cualquier informático puede beneficiarse de una educación sólida en algoritmos, lenguajes formales e incluso alguna teoría de la complejidad (y muchas otras cosas, como el funcionamiento de los compiladores y las CPU).
Raphael
2
@Wildcard Arquitectura de computadora, gráficos de computadora, IA, investigación de lenguaje de programación, ... ¡la lista es interminable! TCS realmente es un nicho, y la programación no es más que una herramienta para (la mayoría) de los investigadores de CS.
Raphael
7

Hay dos razones serias para usar el análisis asintótico de los tiempos de ejecución:

  • n

  • para permitir la trazabilidad matemática. Los casos en los que es posible encontrar expresiones exactas para el recuento de operaciones son excepcionales. Estudiar las asintóticas abre más posibilidades (como las aproximaciones asintóticas de funciones complicadas son útiles).

Y hay muchos otros (como la independencia de la máquina, el significado, la comparabilidad ...).

Yves Daoust
fuente
n
Bueno, no creo que sea una regla en absoluto. Cuantos más datos deseches, más débiles serán las declaraciones que puedas hacer. La perspectiva asintótica (y, más aún, "big-oh") crea enunciados como "Quicksort es más rápido que Insertionsort" que, si no es falso, tampoco es del todo cierto. (Sí, digo que el análisis de algoritmos a menudo se enseña mal, en mi humilde opinión.)
Raphael
6

Como se señaló en la respuesta de Raphael, el cálculo exacto del peor tiempo de ejecución puede ser muy difícil. El cálculo exacto también puede ser innecesario ya que el modelo de RAM ya introduce aproximaciones. Por ejemplo, ¿todas las operaciones realmente toman el mismo tiempo? Las implementaciones específicas (hardware, optimizaciones) pueden acelerar un algoritmo por factores constantes. Queremos entender qué tan efectivo es un algoritmo independiente de estos factores. Esta es una gran motivación para el uso del análisis asintótico.

Juho
fuente
3

Porque los asintóticos son "simples" (bueno, más simple que hacer el análisis exacto para casos finitos, de todos modos).

Compare, por ejemplo, el enciclopédico "The Art of Computer Programming" de Knuth, que hace un análisis detallado de todos los algoritmos importantes (y muchos no tan importantes) con el análisis de la regla general que a menudo es suficiente para obtener una estimación asintótica ( o simplemente un límite), como se practica en la mayoría de los libros de "algoritmos".

Ciertamente tienes razón. Si el problema es lo suficientemente importante, puede justificarse un análisis de estilo Knuth (o quizás un poco menos detallado). En muchos casos, basta con una pista de la complejidad asintótica (quizás promedio con dispersión) ajustada a los datos experimentales. En la mayoría de los casos , hacer una clasificación aproximada de algoritmos competitivos, como una primera ronda de eliminación de malezas que compara las asintóticas, puede ser lo suficientemente preciso. Y si no hay contendientes, recibir las malas noticias del costo exacto en detalle es solo masoquismo.

vonbrand
fuente
2
Esta es solo la mitad de la verdad: primero, parece que escribes con "big-oh" en mente (que la pregunta no menciona). En segundo lugar, los asintóticos "big-oh" son conocidos por fallar espectacularmente en las "rondas de eliminación" cuando se seleccionan algoritmos: las entradas son finitas en realidad.
Raphael
3

Aquí, por análisis asintótico, supongo que queremos decir el comportamiento del algoritmo a medida que el tamaño de la entrada llega al infinito.

La razón por la que utilizamos el análisis asintótico es porque es útil para predecir el comportamiento de los algoritmos en la práctica . Las predicciones nos permiten tomar decisiones, por ejemplo, cuando tenemos diferentes algoritmos para un problema, ¿cuál debemos usar? (Ser útil no significa que siempre sea correcto).

La misma pregunta puede hacerse sobre cualquier modelo simplificado del mundo real. ¿Por qué utilizamos modelos matemáticos simplificados del mundo real?

Piensa en la física. La física newtoniana clásica no es tan buena como la física relativista para predecir el mundo real. Pero es un modelo lo suficientemente bueno para construir automóviles, rascacielos, submarinos, aviones, puentes, etc. Hay casos en los que no es lo suficientemente bueno, por ejemplo, si queremos construir un satélite o enviar una sonda espacial a Plutón o predecir el movimiento. de objetos celestes masivos como estrellas y planetas u objetos de muy alta velocidad como electrones. Es importante saber cuáles son los límites de un modelo.

  1. Por lo general, es una aproximación suficientemente buena del mundo real. En la práctica, vemos a menudo que un algoritmo con un mejor análisis asintótico funciona mejor en la práctica. Rara vez se da el caso de que un algoritmo tenga un mejor comportamiento asintótico. Por lo tanto, si las entradas pueden ser lo suficientemente grandes, normalmente podemos confiar en el análisis asintótico como primera predicción del comportamiento de los algoritmos. No es así si sabemos que las entradas van a ser pequeñas. Dependiendo del rendimiento que queramos, es posible que tengamos que hacer un análisis más cuidadoso, por ejemplo, si tenemos información sobre la distribución de las entradas que se proporcionará el algoritmo, podemos hacer un análisis más cuidadoso para lograr los objetivos que tenemos (por ejemplo, rápido en 99 % de entradas). El punto es que, como primer paso, el análisis asintótico es un buen punto de partida. En la práctica, también debemos hacer pruebas de rendimiento, pero tenga en cuenta que también tiene sus propios problemas.

  2. AAAtiene mejor complejidad asintótica. ¿Qué ninguno de ellos es mejor que el otro en todas las entradas? Entonces se vuelve más complicado y depende de lo que nos importa. ¿Nos importan los insumos grandes o pequeños? Si nos interesan las entradas grandes, entonces no es común que un algoritmo tenga una mejor complejidad asintótica pero se comporte peor en las entradas grandes que nos importan. Si nos interesan más las entradas pequeñas, entonces el análisis asintótico podría no ser tan útil. Deberíamos comparar el tiempo de ejecución de los algoritmos en las entradas que nos interesan. En la práctica, para tareas complicadas con requisitos complicados, el análisis asintótico puede no ser tan útil. Para problemas básicos simples que cubren los libros de texto de algoritmos, es bastante útil.

En resumen, la complejidad asintótica es una aproximación relativamente fácil de calcular de la complejidad real de los algoritmos para tareas básicas simples (problemas en un libro de texto de algoritmos). A medida que creamos programas más complicados, los requisitos de rendimiento cambian y se vuelven más complicados y el análisis asintótico puede no ser tan útil.


Es bueno comparar el análisis asintótico con otros enfoques para predecir el rendimiento de los algoritmos y compararlos. Un enfoque común son las pruebas de rendimiento contra entradas aleatorias o de referencia. Es común cuando calcular la complejidad asintótica es difícil o inviable, por ejemplo, cuando usamos la heurística como en la resolución de SAT. Otro caso es cuando los requisitos son más complicados, por ejemplo, cuando el rendimiento de un programa depende de factores externos y nuestro objetivo podría ser tener algo que termine dentro de algunos límites de tiempo fijos (por ejemplo, piense en actualizar la interfaz que se muestra a un usuario) en el 99% de los casos. entradas

Pero tenga en cuenta que el análisis de rendimiento también tiene sus problemas. No proporciona beneficiarios matemáticos sobre el rendimiento en menos, realmente ejecutamos la prueba de rendimiento en todas las entradas que se le darán al algoritmo (a menudo inviable desde el punto de vista computacional) (y a menudo no es posible decidir que algunas entradas nunca se darán). Si ponemos a prueba en contra de una muestra aleatoria o una referencia implícita estamos asumiendo cierta regularidad sobre el rendimiento de los algoritmos, es decir, el algoritmo llevará a cabo de manera similar en otros insumos que no formaban parte de la prueba de rendimiento.

El segundo problema con las pruebas de rendimiento es que dependen del entorno de prueba. Es decir, el rendimiento de un programa no está determinado solo por las entradas, sino por factores externos (por ejemplo, tipo de máquina, sistema operativo, eficiencia del algoritmo codificado, utilización de la CPU, tiempos de acceso a la memoria, etc.), algunos de los cuales pueden variar entre diferentes ejecuciones de La prueba en la misma máquina. Una vez más, estamos asumiendo que los entornos particulares en los que se llevan a cabo las pruebas de rendimiento son similares al entorno real a menos que hagamos las pruebas de rendimiento en todos los entornos en los que podemos ejecutar el programa (y cómo podemos predecir en qué máquinas alguien podría ejecutar una clasificación) algoritmo en 10 años?).

Θ(nlgn)Θ(n2)Θ(lgn)O(n)

Kaveh
fuente
Continuemos esta discusión en el chat .
Raphael
Me gusta esta respuesta lo suficiente como para votar ahora. Dos notas: 1) Usaría "costo" en lugar de "complejidad" aquí. Parcialmente por razones de mascotas, pero también porque hay muchas medidas de costos concebibles (lo que complica todas las consideraciones que mencionas). 2) Es posible que desee hacer un pase de pulido de idiomas. ;)
Raphael
@ Raphael, gracias. Estoy planeando hacer otra edición pronto. :)
Kaveh
-2

O(n2)O(nlogn)O(norte2) para que termine en comparación con quicksort.

ahora imagine que la espera se repite en el código tantas veces como se llama al código. ¿Cómo cuantificar / justificar matemáticamente esta aparente superioridad del algoritmo de clasificación rápida? (es decir, ¿su nombre está realmente justificado o es solo un eslogan de marketing?) a través de mediciones de complejidad asintótica. uno se queda mirando las animaciones subjetivamente, sintiendo que la clasificación de burbujas es de alguna manera un algoritmo más débil y el análisis de complejidad asintótica puede probar esto cuantitativamente. pero tenga en cuenta que el análisis de complejidad asintótica es solo una herramienta en la bolsa de herramientas para analizar algoritmos y no siempre es la mejor.

y vale la pena mirar el código de lado a lado también. Bubblesort parece ser conceptualmente más simple y no utiliza la recursividad. quicksort no se comprende tan inmediatamente como el principio de pivote "mediana de 3". Bubblesort podría implementarse solo en bucles sin una subrutina, mientras que Quicksort podría tener al menos una subrutina. Esto muestra el patrón de que una mayor sofisticación del código a veces puede mejorar la complejidad asintótica a expensas de la simplicidad del código. a veces hay un cambio extremo similar al concepto de rendimientos marginales decrecientes (originario de la economía) donde grandes cantidades de complejidad de código [que requieren documentos completos llenos de thms y pruebas para justificar] solo compra mejoras muy pequeñas en la complejidad asintótica. esto se muestra como un ejemplo especialmente conmultiplicación de matrices e incluso se puede graficar .

vzn
fuente
Hay mucho territorio entre "mirar las animaciones" y el análisis formal, como los puntos de referencia extensivos de tiempo de ejecución. En realidad, son un área válida propia, ya que no tenemos teoría para explicar todas las cosas que influyen en los tiempos de ejecución.
Raphael
@raphael cubriste el benchmarking en tu respuesta; Es una buena respuesta. pero tenga en cuenta que la animación / visualización puede estar estrechamente relacionada con la evaluación comparativa. en realidad, hay muchas explicaciones de lo que influye en los tiempos de ejecución [cubiertos en otras respuestas] pero, hasta cierto punto, su "ruido" y su complejidad asintótica "suaviza / promedia el ruido". ese es otro ejercicio para ver cómo realmente lo hace.
vzn
Sin embargo, las animaciones no filtran el ruido. Además, el ojo humano se engaña fácilmente, y simplemente no es factible ver animaciones para una muestra de tamaño razonable de listas de tamaño razonable (digamos, 1000 listas de tamaños en millones para comparar los algoritmos de clasificación) y decidir cuál algoritmo fue más rápido (en el promedio).
Raphael
nortenorte
norte