Articulo de referencia

Análisis de algoritmos

Para buscar una entrada específica en una lista ordenada, se pueden utilizar tanto el algoritmo 2 ''n''"}},"i":0}}]}"> de búsqueda binaria como el lineal (que ignora el orden). ...

Para buscar una entrada específica en una lista ordenada, se pueden utilizar tanto el algoritmo de búsqueda binaria como el lineal (que ignora el orden). El análisis de ambos algoritmos muestra que requieren como máximo log₂n y n pasos de verificación, respectivamente, para una lista de tamaño n . En la lista de ejemplo de tamaño 33 que se muestra, la búsqueda de "Morin, Arthur" requiere 5 y 28 pasos con la búsqueda binaria (mostrada en cian ) y lineal ( magenta ), respectivamente.
Gráficas de funciones comúnmente utilizadas en el análisis de algoritmos, que muestran el número de operaciones N en función del tamaño de entrada n para cada función.

En informática , el análisis de algoritmos consiste en determinar su complejidad computacional : el tiempo, el almacenamiento u otros recursos necesarios para su ejecución. Generalmente, esto implica establecer una función que relacione el tamaño de la entrada del algoritmo con el número de pasos que realiza (su complejidad temporal ) o con el número de ubicaciones de almacenamiento que utiliza (su complejidad espacial ). Se dice que un algoritmo es eficiente cuando los valores de esta función son pequeños o crecen lentamente en comparación con el tamaño de la entrada. Diferentes entradas del mismo tamaño pueden provocar que el algoritmo tenga un comportamiento distinto, por lo que las descripciones del mejor, el peor y el caso promedio pueden resultar de interés práctico. Salvo que se especifique lo contrario, la función que describe el rendimiento de un algoritmo suele ser una cota superior , determinada a partir de las entradas del peor caso.

El término «análisis de algoritmos» fue acuñado por Donald Knuth . [ 1 ] El análisis de algoritmos es una parte importante de una teoría más amplia de la complejidad computacional , que proporciona estimaciones teóricas de los recursos necesarios para cualquier algoritmo que resuelva un problema computacional dado . Estas estimaciones ofrecen una perspectiva sobre posibles direcciones de búsqueda de algoritmos eficientes .

En el análisis teórico de algoritmos, es común estimar su complejidad en el sentido asintótico, es decir, estimar la función de complejidad para entradas arbitrariamente grandes. Para ello se utilizan las notaciones Big O , Big-omega y Big-theta . [ 2 ] Por ejemplo, se dice que la búsqueda binaria se ejecuta en un número de pasos proporcional al logaritmo del tamaño n de la lista ordenada que se busca, o en O (log n ) , coloquialmente "en tiempo logarítmico ". Generalmente se utilizan estimaciones asintóticas porque diferentes implementaciones del mismo algoritmo pueden diferir en eficiencia. Sin embargo, las eficiencias de dos implementaciones "razonables" cualesquiera de un algoritmo dado están relacionadas por un factor multiplicativo constante llamado constante oculta .

En ocasiones, se pueden calcular medidas exactas (no asintóticas) de eficiencia, pero generalmente requieren ciertas suposiciones sobre la implementación particular del algoritmo, denominadas modelo de computación . Un modelo de computación puede definirse en términos de una computadora abstracta , por ejemplo, una máquina de Turing , o postulando que ciertas operaciones se ejecutan en tiempo unitario. Por ejemplo, si la lista ordenada a la que aplicamos la búsqueda binaria tiene n elementos y podemos garantizar que cada búsqueda de un elemento en la lista se puede realizar en tiempo unitario, entonces se necesitan como máximo log₂ ( n ) + 1 unidades de tiempo para obtener una respuesta.

Modelos de costos

Las estimaciones de eficiencia temporal dependen de cómo definamos un paso. Para que el análisis se corresponda de forma útil con el tiempo de ejecución real, el tiempo necesario para realizar un paso debe estar acotado superiormente por una constante. Es importante tener cuidado; por ejemplo, algunos análisis consideran la suma de dos números como un solo paso. Esta suposición puede no ser válida en ciertos contextos. Por ejemplo, si los números involucrados en un cálculo pueden ser arbitrariamente grandes, el tiempo requerido para una sola suma ya no puede considerarse constante.

Generalmente se utilizan dos modelos de costos: [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ]

  • El modelo de costo uniforme , también llamado modelo de costo unitario (y variaciones similares), asigna un costo constante a cada operación de máquina, independientemente del tamaño de la cantidad de operaciones involucradas.
  • El modelo de costo logarítmico , también llamado medición de costo logarítmico (y variaciones similares), asigna un costo a cada operación de la máquina proporcional al número de bits involucrados.

Este último es más engorroso de usar, por lo que solo se emplea cuando es necesario, por ejemplo, en el análisis de algoritmos aritméticos de precisión arbitraria , como los que se utilizan en criptografía .

Un punto clave que a menudo se pasa por alto es que los límites inferiores publicados para los problemas suelen estar dados para un modelo de computación más restringido que el conjunto de operaciones que se podrían usar en la práctica y, por lo tanto, existen algoritmos más rápidos de lo que ingenuamente se pensaría posible. [ 8 ]

Análisis en tiempo de ejecución

El análisis del tiempo de ejecución es una clasificación teórica que estima y anticipa el aumento del tiempo de ejecución de un algoritmo a medida que aumenta el tamaño de su entrada (generalmente denotada como n ). La eficiencia del tiempo de ejecución es un tema de gran interés en la informática : un programa puede tardar segundos, horas o incluso años en completarse, dependiendo del algoritmo que implemente. Si bien las técnicas de perfilado de software pueden utilizarse para medir el tiempo de ejecución de un algoritmo en la práctica, no pueden proporcionar datos de tiempo para todas las posibles entradas; esto último solo se puede lograr mediante los métodos teóricos del análisis del tiempo de ejecución.

Limitaciones de las métricas empíricas

Dado que los algoritmos son independientes de la plataforma (es decir, un algoritmo determinado puede implementarse en un lenguaje de programación arbitrario en un ordenador arbitrario que ejecute un sistema operativo arbitrario ), existen inconvenientes significativos adicionales al utilizar un enfoque empírico para evaluar el rendimiento comparativo de un conjunto determinado de algoritmos.

Tomemos como ejemplo un programa que busca una entrada específica en una lista ordenada de tamaño n . Supongamos que este programa se implementa en la computadora A, una máquina de última generación, utilizando un algoritmo de búsqueda lineal , y en la computadora B, una máquina mucho más lenta, utilizando un algoritmo de búsqueda binaria . Las pruebas de rendimiento en ambas computadoras ejecutando sus respectivos programas podrían ser similares a las siguientes:

Basándonos en estas métricas, sería fácil llegar a la conclusión de que el ordenador A ejecuta un algoritmo mucho más eficiente que el del ordenador B. Sin embargo, si se aumenta el tamaño de la lista de entrada a un número suficiente, se demuestra de forma contundente que dicha conclusión es errónea:

El ordenador A, que ejecuta el programa de búsqueda lineal, presenta una tasa de crecimiento lineal . El tiempo de ejecución del programa es directamente proporcional al tamaño de su entrada. Duplicar el tamaño de la entrada duplica el tiempo de ejecución, cuadruplicarlo también, y así sucesivamente. Por otro lado, el ordenador B, que ejecuta el programa de búsqueda binaria, presenta una tasa de crecimiento logarítmica . Cuadruplicar el tamaño de la entrada solo aumenta el tiempo de ejecución en una cantidad constante (en este ejemplo, 50 000 ns). Aunque el ordenador A sea aparentemente más rápido, el ordenador B inevitablemente lo superará en tiempo de ejecución, ya que ejecuta un algoritmo con una tasa de crecimiento mucho menor.

Órdenes de crecimiento

De manera informal, se puede decir que un algoritmo presenta una tasa de crecimiento del orden de una función matemática si, más allá de un cierto tamaño de entrada n , la función f ( n ) multiplicada por una constante positiva proporciona un límite superior para el tiempo de ejecución de dicho algoritmo. En otras palabras, para un tamaño de entrada n dado mayor que algún n₀ y una constante c , el tiempo de ejecución de ese algoritmo nunca será mayor que c × f ( n ) . Este concepto se expresa frecuentemente utilizando la notación Big O. Por ejemplo, dado que el tiempo de ejecución del algoritmo de ordenación por inserción crece cuadráticamente a medida que aumenta el tamaño de su entrada, se puede decir que este algoritmo es de orden O ( ) .

La notación Big O es una forma conveniente de expresar el peor escenario posible para un algoritmo dado, aunque también se puede utilizar para expresar el caso promedio ; por ejemplo, el peor escenario posible para quicksort es O ( n 2 ) , pero el tiempo de ejecución del caso promedio es O ( n log n ) .

Órdenes de crecimiento empíricas

Suponiendo que el tiempo de ejecución sigue la regla de potencia, t kn a , el parámetro a se puede encontrar [ 9 ] tomando mediciones empíricas del tiempo de ejecución t 1 y t 2 en algunos puntos de tamaño del problema n 1 y n 2 , y resolviendo la ecuación t 2 / t 1 = ( n 2 / n 1 ) a con respecto a a , es decir, a = log( t 2 / t 1 )/log( n 2 / n 1 ) . En otras palabras, esto mide la pendiente de la línea empírica en el gráfico log-log del tiempo de ejecución frente al tamaño de entrada, en algún punto de tamaño. Si el orden de crecimiento sigue efectivamente la regla de potencia (y por lo tanto la línea en el gráfico log-log es efectivamente una línea recta), el valor empírico de a permanecerá constante en diferentes rangos, y si no, cambiará (y la línea es una línea curva), pero aún puede servir para comparar dos algoritmos cualesquiera dados en cuanto a sus órdenes locales empíricos de comportamiento de crecimiento. Aplicado a la tabla anterior:

Se observa claramente que el primer algoritmo presenta un crecimiento lineal, siguiendo efectivamente la regla de potencia. Los valores empíricos del segundo disminuyen rápidamente, lo que sugiere que sigue otra regla de crecimiento y, en cualquier caso, presenta órdenes de crecimiento locales mucho menores (y que siguen mejorando), empíricamente, que el primero.

Evaluación de la complejidad en tiempo de ejecución

La complejidad temporal de un algoritmo en el peor de los casos puede evaluarse a veces examinando su estructura y haciendo algunas suposiciones simplificadoras. Considere el siguiente pseudocódigo :

1. Obtener un número entero positivo n de la entrada . 2. Si n > 10, 3. Imprimir "Esto podría tardar un poco..." 4 para i = 1 a n 5 para j = 1 a i 6 imprimir i * j 7 imprimir "¡Hecho!"

Un ordenador determinado tardará una cantidad de tiempo discreta en ejecutar cada una de las instrucciones de este algoritmo. Digamos que las acciones del paso 1 consumen como máximo T₁ , el paso 2 consume como máximo T₂ , y así sucesivamente .

En el algoritmo anterior, los pasos 1, 2 y 7 se ejecutarán solo una vez. Para una evaluación en el peor de los casos, se debe asumir que el paso 3 también se ejecutará. Por lo tanto, el tiempo total para ejecutar los pasos 1 a 3 y el paso 7 es:

T1+T2+T3+T7.{\displaystyle T_{1}+T_{2}+T_{3}+T_{7}.\,}

Los bucles en los pasos 4, 5 y 6 son más difíciles de evaluar. La prueba del bucle externo en el paso 4 se ejecutará ( n + 1 ) veces, [ 10 ] lo que consumirá T 4 ( n + 1 ) tiempo. El bucle interno, por otro lado, está gobernado por el valor de j, que itera de 1 a i . En la primera pasada por el bucle externo, j itera de 1 a 1: El bucle interno hace una pasada, por lo que ejecutar el cuerpo del bucle interno (paso 6) consume T 6 tiempo, y la prueba del bucle interno (paso 5) consume 2 T 5 tiempo. Durante la siguiente pasada por el bucle externo, j itera de 1 a 2: el bucle interno hace dos pasadas, por lo que ejecutar el cuerpo del bucle interno (paso 6) consume 2 T 6 tiempo, y la prueba del bucle interno (paso 5) consume 3 T 5 tiempo.

En conjunto, el tiempo total necesario para ejecutar el cuerpo del bucle interno se puede expresar como una progresión aritmética :

T6+2T6+3T6++(norte1)T6+norteT6{\displaystyle T_{6}+2T_{6}+3T_{6}+\cdots +(n-1)T_{6}+nT_{6}}

que puede ser factorizado [ 11 ] como

[1+2+3++(norte1)+norte]T6=[12(norte2+norte)]T6{\displaystyle \left[1+2+3+\cdots +(n-1)+n\right]T_{6}=\left[{\frac {1}{2}}(n^{2}+n)\right]T_{6}}

El tiempo total necesario para ejecutar la prueba del bucle interno se puede evaluar de forma similar:

2T5+3T5+4T5++(norte1)T5+norteT5+(norte+1)T5=T5+2T5+3T5+4T5++(norte1)T5+norteT5+(norte+1)T5T5{\displaystyle {\begin{aligned}&2T_{5}+3T_{5}+4T_{5}+\cdots +(n-1)T_{5}+nT_{5}+(n+1)T_{5}\\={}&T_{5}+2T_{5}+3T_{5}+4T_{5}+\cdots +(n-1)T_{5}+nT_{5}+(n+1)T_{5}-T_{5}\end{aligned}}}

lo cual puede ser factorizado como

T5[1+2+3++(norte1)+norte+(norte+1)]T5=[12(norte2+norte)]T5+(norte+1)T5T5=[12(norte2+norte)]T5+norteT5=[12(norte2+3norte)]T5{\displaystyle {\begin{aligned}&T_{5}\left[1+2+3+\cdots +(n-1)+n+(n+1)\right]-T_{5}\\={}&\left[{\frac {1}{2}}(n^{2}+n)\right]T_{5}+(n+1)T_{5}-T_{5}\\={}&\left[{\frac {1}{2}}(n^{2}+n)\right]T_{5}+nT_{5}\\={}&\left[{\frac {1}{2}}(n^{2}+3n)\right]T_{5}\end{aligned}}}

Por lo tanto, el tiempo total de ejecución de este algoritmo es:

F(norte)=T1+T2+T3+T7+(norte+1)T4+[12(norte2+norte)]T6+[12(norte2+3norte)]T5{\displaystyle f(n)=T_{1}+T_{2}+T_{3}+T_{7}+(n+1)T_{4}+\left[{\frac {1}{2}}(n^{2}+n)\right]T_{6}+\left[{\frac {1}{2}}(n^{2}+3n)\right]T_{5}}

lo cual se reduce a

F(norte)=[12(norte2+norte)]T6+[12(norte2+3norte)]T5+(norte+1)T4+T1+T2+T3+T7{\displaystyle f(n)=\left[{\frac {1}{2}}(n^{2}+n)\right]T_{6}+\left[{\frac {1}{2}}(n^{2}+3n)\right]T_{5}+(n+1)T_{4}+T_{1}+T_{2}+T_{3}+T_{7}}

Como regla general , se puede suponer que el término de orden más alto en cualquier función dada domina su tasa de crecimiento y, por lo tanto, define su orden de ejecución. En este ejemplo, n² es el término de orden más alto, por lo que se puede concluir que f ( n ) = O ( ) . Formalmente , esto se puede demostrar de la siguiente manera:

Demuestra que[12(norte2+norte)]T6+[12(norte2+3norte)]T5+(norte+1)T4+T1+T2+T3+T7donorte2, nortenorte0{\displaystyle \left[{\frac {1}{2}}(n^{2}+n)\right]T_{6}+\left[{\frac {1}{2}}(n^{2}+3n)\right]T_{5}+(n+1)T_{4}+T_{1}+T_{2}+T_{3}+T_{7}\leq cn^{2},\ n\geq n_{0}}

[12(norte2+norte)]T6+[12(norte2+3norte)]T5+(norte+1)T4+T1+T2+T3+T7(norte2+norte)T6+(norte2+3norte)T5+(norte+1)T4+T1+T2+T3+T7 (para norte0){\displaystyle {\begin{aligned}&\left[{\frac {1}{2}}(n^{2}+n)\right]T_{6}+\left[{\frac {1}{2}}(n^{2}+3n)\right]T_{5}+(n+1)T_{4}+T_{1}+T_{2}+T_{3}+T_{7}\\\leq {}&(n^{2}+n)T_{6}+(n^{2}+3n)T_{5}+(n+1)T_{4}+T_{1}+T_{2}+T_{3}+T_{7}\ ({\text{para }}n\geq 0)\end{aligned}}}

Sea k una constante mayor o igual que [ T 1 .. T 7 ]. T6(norte2+norte)+T5(norte2+3norte)+(norte+1)T4+T1+T2+T3+T7k(norte2+norte)+k(norte2+3norte)+knorte+5k=2knorte2+5knorte+5k2knorte2+5knorte2+5knorte2 (para norte1)=12knorte2{\displaystyle {\begin{aligned}&T_{6}(n^{2}+n)+T_{5}(n^{2}+3n)+(n+1)T_{4}+T_{1}+T_{2}+T_{3}+T_{7}\leq k(n^{2}+n)+k(n^{2}+3n)+kn+5k\\={}&2kn^{2}+5kn+5k\leq 2kn^{2}+5kn^{2}+5kn^{2}\ ({\text{para }}n\geq 1)=12kn^{2}\end{aligned}}} Por lo tanto[12(norte2+norte)]T6+[12(norte2+3norte)]T5+(norte+1)T4+T1+T2+T3+T7donorte2,nortenorte0 para do=12k,norte0=1{\displaystyle \left[{\frac {1}{2}}(n^{2}+n)\right]T_{6}+\left[{\frac {1}{2}}(n^{2}+3n)\right]T_{5}+(n+1)T_{4}+T_{1}+T_{2}+T_{3}+T_{7}\leq cn^{2},n\geq n_{0}{\text{ para }}c=12k,n_{0}=1}

Un enfoque más elegante para analizar este algoritmo sería declarar que [ T 1 .. T 7 ] son ​​todos iguales a una unidad de tiempo, en un sistema de unidades elegido de modo que una unidad sea mayor o igual que los tiempos reales de estos pasos. Esto significaría que el tiempo de ejecución del algoritmo se descompone de la siguiente manera: [ 12 ]

4+i=1nortei4+i=1nortenorte=4+norte25norte2 (para norte1)=O(norte2).{\displaystyle 4+\sum _{i=1}^{n}i\leq 4+\sum _{i=1}^{n}n=4+n^{2}\leq 5n^{2}\ ({\text{for }}n\geq 1)=O(n^{2}).}

Análisis de la tasa de crecimiento de otros recursos

La metodología de análisis en tiempo de ejecución también puede utilizarse para predecir otras tasas de crecimiento, como el consumo de espacio de memoria . Como ejemplo, consideremos el siguiente pseudocódigo que gestiona y reasigna el uso de memoria por parte de un programa en función del tamaño de un archivo que dicho programa gestiona:

Mientras el archivo permanezca abierto: sea n = tamaño del archivo. Por cada 100.000 kilobytes de aumento en el tamaño del archivo, se duplica la cantidad de memoria reservada.

En este caso, a medida que aumenta el tamaño del archivo n, la memoria se consumirá a una tasa de crecimiento exponencial , del orden de O (2 n ) . Esta es una tasa de crecimiento extremadamente rápida y probablemente inmanejable para el consumo de recursos de memoria .

Pertinencia

El análisis de algoritmos es importante en la práctica, ya que el uso accidental o involuntario de un algoritmo ineficiente puede afectar significativamente el rendimiento del sistema. En aplicaciones donde el tiempo es crucial, un algoritmo que tarda demasiado en ejecutarse puede hacer que sus resultados queden obsoletos o sean inútiles. Un algoritmo ineficiente también puede llegar a requerir una cantidad excesiva de potencia de cálculo o almacenamiento para su ejecución, lo que, de nuevo, lo vuelve prácticamente inservible.

Factores constantes

El análisis de algoritmos se centra generalmente en el rendimiento asintótico, sobre todo a nivel elemental, pero en aplicaciones prácticas los factores constantes son importantes, y los datos del mundo real suelen tener un tamaño limitado. El límite suele ser el tamaño de la memoria direccionable, por lo que en máquinas de 32 bits 2 × 32 = 4 GiB (mayor si se utiliza memoria segmentada ) y en máquinas de 64 bits 2 × 64 = 16 EiB. Así pues, dado un tamaño limitado, un orden de crecimiento (de tiempo o espacio) puede sustituirse por un factor constante, y en este sentido todos los algoritmos prácticos son O (1) para una constante suficientemente grande o para datos suficientemente pequeños.

Esta interpretación es principalmente útil para funciones que crecen extremadamente lento: el logaritmo iterado (binario) (log * ) es menor que 5 para todos los datos prácticos (2 65536 bits); el logaritmo binario (log log n ) es menor que 6 para prácticamente todos los datos prácticos (2 64 bits); y el logaritmo binario (log n ) es menor que 64 para prácticamente todos los datos prácticos (2 64 bits). Sin embargo, un algoritmo con complejidad no constante puede ser más eficiente que un algoritmo con complejidad constante en datos prácticos si la sobrecarga del algoritmo de tiempo constante resulta en un factor constante mayor, por ejemplo, se puede tenerK>kregistroregistronorte{\displaystyle K>k\log \log n}con tal queK/k>6{\displaystyle K/k>6}ynorte<226=264{\displaystyle n<2^{2^{6}}=2^{64}}.

Para grandes conjuntos de datos, no se pueden ignorar los factores lineales o cuadráticos, pero para conjuntos de datos pequeños, un algoritmo asintóticamente ineficiente puede ser más eficiente. Esto se utiliza particularmente en algoritmos híbridos , como Timsort , que utilizan un algoritmo asintóticamente eficiente (en este caso , ordenación por fusión , con complejidad temporal).norteregistronorte{\displaystyle n\log n}), pero cambiamos a un algoritmo asintóticamente ineficiente (aquí ordenación por inserción , con complejidad temporalnorte2{\displaystyle n^{2}}) para conjuntos de datos pequeños, ya que el algoritmo más simple es más rápido con conjuntos de datos pequeños.

Véase también

Notas

  1. "Knuth: Noticias recientes" . 28 de agosto de 2016. Archivado del original el 28 de agosto de 2016.
  2. Cormen, Thomas H., ed. (2009). Introducción a los algoritmos (3.ª ed.). Cambridge, Mass: MIT Press. pp. 44–52 . ISBN   978-0-262-03384-8OCLC 311310321 
  3. Alfred V. Aho; John E. Hopcroft; Jeffrey D. Ullman (1974). El diseño y análisis de algoritmos informáticos . Addison-Wesley Pub. Co. ISBN 9780201000290., sección 1.3
  4. Juraj Hromkovič (2004). Informática teórica: introducción a autómatas, computabilidad, complejidad, algoritmia, aleatorización, comunicación y criptografía . Springer. pp. 177–178 . ISBN  978-3-540-14015-3.
  5. Giorgio Ausiello (1999). Complejidad y aproximación: problemas de optimización combinatoria y sus propiedades de aproximabilidad . Springer. pp. 3–8 . ISBN  978-3-540-65431-5.
  6. Wegener, Ingo (2005), Teoría de la complejidad: explorando los límites de los algoritmos eficientes , Berlín, Nueva York: Springer-Verlag , pág. 20, ISBN  978-3-540-21045-0
  7. Robert Endre Tarjan (1983). Estructuras de datos y algoritmos de red . SIAM. págs. 3–7 . ISBN  978-0-89871-187-5.
  8. ↑ ¿ Ejemplos del precio de la abstracción? , cstheory.stackexchange.com
  9. Cómo evitar el abuso de O y los sobornos. Archivado el 8 de marzo de 2017 en Wayback Machine , en el blog "La carta perdida de Gödel y P=NP" de RJ Lipton, profesor de Ciencias de la Computación en Georgia Tech, que relata una idea de Robert Sedgewick.
  10. Se requiere un paso adicional para finalizar el bucle for, por lo tanto, n + 1 y no n ejecuciones.
  11. Se puede demostrar por inducción que1+2+3++(norte1)+norte=norte(norte+1)2{\displaystyle 1+2+3+\cdots +(n-1)+n={\frac {n(n+1)}{2}}}
  12. Este enfoque, a diferencia del anterior, ignora el tiempo constante consumido por las pruebas de bucle que finalizan sus respectivos bucles, pero es trivial demostrar que dicha omisión no afecta el resultado final.

Referencias

  • Logotipo de Wikimedia CommonsContenido multimedia relacionado con el análisis de algoritmos en Wikimedia Commons.
Obtenido de " https://en.wikipedia.org/w/index.php?title=Analysis_of_algorithms&oldid=1316448807 "