¿Qué se entiende por "tiempo amortizado constante" cuando se habla de la complejidad del tiempo de un algoritmo?
¿Qué se entiende por "tiempo amortizado constante" cuando se habla de la complejidad del tiempo de un algoritmo?
Respuestas:
Tiempo amortizado explicado en términos simples:
Si realiza una operación, digamos un millón de veces, realmente no le importa el peor de los casos o el mejor de esas operaciones; lo que le importa es cuánto tiempo se toma en total cuando repite la operación un millón de veces .
Por lo tanto, no importa si la operación es muy lenta de vez en cuando, siempre y cuando "de vez en cuando" sea lo suficientemente raro como para que la lentitud se diluya. El tiempo esencialmente amortizado significa "tiempo promedio por operación, si realiza muchas operaciones". El tiempo amortizado no tiene que ser constante; puede tener tiempo amortizado lineal y logarítmico o cualquier otra cosa.
Tomemos el ejemplo de las esteras de una matriz dinámica, a la que agrega repetidamente nuevos elementos. Normalmente, agregar un elemento lleva tiempo constante (es decir, O(1)
). Pero cada vez que la matriz está llena, asigna el doble de espacio, copia sus datos en la nueva región y libera el espacio anterior. Suponiendo que las asignaciones y las liberaciones se ejecutan en tiempo constante, este proceso de ampliación lleva O(n)
tiempo donde n es el tamaño actual de la matriz.
Por lo tanto, cada vez que amplía, toma aproximadamente el doble de tiempo que la última ampliación. ¡Pero también has esperado el doble de tiempo antes de hacerlo! El costo de cada ampliación se puede "distribuir" entre las inserciones. Esto significa que a largo plazo, el tiempo total necesario para agregar m elementos a la matriz es O(m)
, por lo que el tiempo amortizado (es decir, el tiempo por inserción) es O(1)
.
Significa que con el tiempo, el peor de los casos pasará a O (1), o tiempo constante. Un ejemplo común es la matriz dinámica. Si ya hemos asignado memoria para una nueva entrada, agregarla será O (1). Si no lo hemos asignado, lo haremos asignando, digamos, el doble de la cantidad actual. Esta inserción particular no será O (1), sino algo más.
Lo importante es que el algoritmo garantiza que, después de una secuencia de operaciones, las operaciones costosas se amortizarán y, por lo tanto, la operación completa O (1).
O en términos más estrictos,
Hay una constante c, de modo que para cada secuencia de operaciones (también una que termina con una operación costosa) de longitud L, el tiempo no es mayor que c * L (Gracias Rafał Dowgird )
Para desarrollar una forma intuitiva de pensarlo, considere la inserción de elementos en una matriz dinámica (por ejemplo, std::vector
en C ++). Tracemos un gráfico que muestre la dependencia del número de operaciones (Y) necesarias para insertar N elementos en la matriz:
Las partes verticales del gráfico negro corresponden a reasignaciones de memoria para expandir una matriz. Aquí podemos ver que esta dependencia puede representarse aproximadamente como una línea. Y esta ecuación de línea es Y=C*N + b
( C
es constante, b
= 0 en nuestro caso). Por lo tanto, podemos decir que necesitamos gastar C*N
operaciones en promedio para agregar N elementos a la matriz, u C*1
operaciones para agregar un elemento (tiempo constante amortizado).
La siguiente explicación de Wikipedia me pareció útil, después de repetir la lectura 3 veces:
Fuente: https://en.wikipedia.org/wiki/Amortized_analysis#Dynamic_Array
"Matriz dinámica
Análisis amortizado de la operación Push para una matriz dinámica
Considere una matriz dinámica que crece en tamaño a medida que se le agregan más elementos, como una ArrayList en Java. Si comenzamos con una matriz dinámica de tamaño 4, llevaría tiempo constante insertar cuatro elementos en ella. Sin embargo, empujar un quinto elemento sobre esa matriz tomaría más tiempo ya que la matriz tendría que crear una nueva matriz del doble del tamaño actual (8), copiar los elementos antiguos en la nueva matriz y luego agregar el nuevo elemento. Las siguientes tres operaciones de inserción tomarían un tiempo constante de manera similar, y luego la adición posterior requeriría otra duplicación lenta del tamaño de la matriz.
En general, si consideramos un número arbitrario de empujes n a una matriz de tamaño n, notamos que las operaciones de empuje toman tiempo constante, excepto el último que toma O (n) tiempo para realizar la operación de duplicación de tamaño. Como había n operaciones en total, podemos tomar el promedio de esto y encontrar que para insertar elementos en la matriz dinámica se requiere: O (n / n) = O (1), tiempo constante ".
A mi entender como una historia simple:
Suponga que tiene mucho dinero. Y quieres apilarlos en una habitación. Y tiene manos y piernas largas, tanto tiempo como necesite ahora o en el futuro. Y, tiene que llenar todo en una habitación, por lo que es fácil bloquearlo.
Entonces, vas directamente al final / esquina de la habitación y comienzas a apilarlos. A medida que los apilas, lentamente la habitación se quedará sin espacio. Sin embargo, a medida que lo llena, fue fácil apilarlos. Tengo el dinero, pon el dinero. Fácil. Es O (1). No necesitamos mover ningún dinero anterior.
Una vez que la habitación se queda sin espacio. Necesitamos otra habitación, que es más grande. Aquí hay un problema, ya que solo podemos tener 1 habitación, por lo que solo podemos tener 1 cerradura, necesitamos mover todo el dinero existente en esa habitación a la nueva habitación más grande. Por lo tanto, mueva todo el dinero, de una habitación pequeña a una habitación más grande. Es decir, apilarlos todos de nuevo. Entonces, NECESITAMOS mover todo el dinero anterior. Entonces, es O (N). (suponiendo que N es el recuento total de dinero del dinero anterior)
En otras palabras, fue fácil hasta N, solo 1 operación, pero cuando necesitamos mudarnos a una habitación más grande, realizamos N operaciones. En otras palabras, si promediamos, es 1 inserción al comienzo y 1 movimiento más mientras se mueve a otra habitación. Total de 2 operaciones, una inserción, un movimiento.
Suponiendo que N es grande como 1 millón incluso en la sala pequeña, las 2 operaciones en comparación con N (1 millón) no son realmente un número comparable, por lo que se considera constante u O (1).
Asumiendo cuando hacemos todo lo anterior en otra habitación más grande, y nuevamente necesitamos movernos. Sigue siendo lo mismo. digamos, N2 (digamos, mil millones) es una nueva cantidad de dinero en la sala más grande
Entonces, tenemos N2 (que incluye N de anteriores, ya que nos movemos de una habitación pequeña a una más grande)
Todavía necesitamos solo 2 operaciones, una es insertar en una habitación más grande, luego otra operación de movimiento para movernos a una habitación aún más grande.
Entonces, incluso para N2 (mil millones), son 2 operaciones para cada uno. que no es nada otra vez Entonces, es constante, u O (1)
Entonces, a medida que el N aumenta de N a N2, u otro, no importa mucho. Todavía es constante, o se requieren operaciones O (1) para cada una de las N.
Ahora suponga que tiene N como 1, muy pequeño, el recuento de dinero es pequeño y tiene una habitación muy pequeña, que solo se ajustará a 1 recuento de dinero.
Tan pronto como llenes el dinero en la habitación, la habitación se llena.
Cuando vaya a la sala más grande, suponga que solo puede caber un dinero más en ella, un total de 2 cuentas de dinero. Eso significa que el anterior movió dinero y 1 más. Y de nuevo está lleno.
De esta manera, el N está creciendo lentamente, y ya no es más constante O (1), ya que estamos moviendo todo el dinero de la habitación anterior, pero solo podemos acomodar 1 dinero más.
Después de 100 veces, la nueva sala tiene capacidad para 100 recuentos de dinero del anterior y 1 dinero más que puede acomodar. Esto es O (N), ya que O (N + 1) es O (N), es decir, el grado de 100 o 101 es el mismo, ambos son cientos, a diferencia de la historia anterior, unos a millones y unos a miles de millones .
Entonces, esta es una forma ineficiente de asignar salas (o memoria / RAM) para nuestro dinero (variables).
Entonces, una buena manera es asignar más espacio, con potencias de 2.
Tamaño de la 1ra habitación = caben 1 cuenta de dinero
Tamaño de la 2da habitación = caben 4 cuentas de dinero
Tamaño de la 3ra habitación = caben 8 cuentas de dinero
4to tamaño de la habitación = caben 16 cuentas de dinero
5to tamaño de la habitación = caben 32 cuentas de dinero
6to tamaño de la habitación = caben 64 recuento de dinero
Séptimo tamaño de habitación = se ajusta 128 recuento de dinero
8 ° tamaño de habitación = se ajusta 256 recuento de dinero
9 ° tamaño de habitación = se ajusta 512 recuento de dinero
10 ° tamaño de habitación = cabe 1024 recuento de dinero
11 ° tamaño de habitación = se ajusta 2,048 recuento de dinero
. ..
tamaño de la sala 16 = se ajusta a 65.536 cuentas de dinero
...
tamaño de la habitación 32 = se adapta a 4.294.967.296 cuentas de dinero
...
64 tamaño de la habitación = se adapta a 18.446.744.073.709.551.616 cuentas de dinero
¿Por qué es esto mejor? Porque parece crecer lentamente al principio, y más rápido más tarde, es decir, en comparación con la cantidad de memoria en nuestra RAM.
Esto es útil porque, en el primer caso, aunque es bueno, la cantidad total de trabajo que se debe hacer por dinero es fija (2) y no es comparable al tamaño de la habitación (N), la habitación que ocupamos en las etapas iniciales podría ser demasiado grande (1 millón) que puede que no usemos completamente dependiendo de si podemos obtener tanto dinero para ahorrar en el primer caso.
Sin embargo, en el último caso, potencias de 2, crece en los límites de nuestra RAM. Y así, aumentando en potencias de 2, tanto el análisis armotizado permanece constante y es amigable para la RAM limitada que tenemos a partir de hoy.
Las explicaciones anteriores se aplican al Análisis Agregado, la idea de tomar "un promedio" sobre múltiples operaciones. No estoy seguro de cómo se aplican al método de los banqueros o los métodos físicos de análisis amortizado.
Ahora. No estoy exactamente seguro de la respuesta correcta. Pero tendría que ver con la condición principal de los dos métodos de Physicists + Banker:
(Suma del costo de operaciones amortizado)> = (Suma del costo de operaciones real).
La principal dificultad que enfrento es que, dado que los costos de las operaciones asintóticas amortizadas difieren del costo asintótico normal, no estoy seguro de cómo calificar la importancia de los costos amortizados.
Es entonces cuando alguien me da un costo amortizado, sé que no es lo mismo que el costo asintótico normal ¿Qué conclusiones debo sacar del costo amortizado entonces?
Como tenemos el caso de que algunas operaciones se sobrecarguen mientras que otras operaciones están sobrecargadas, una hipótesis podría ser que citar los costos amortizados de las operaciones individuales no tendría sentido.
Por ejemplo: para un montón de Fibonacci, citar el costo amortizado de la Clave decreciente para que sea O (1) no tiene sentido ya que los costos se reducen por el "trabajo realizado por operaciones anteriores para aumentar el potencial del montón".
O
Podríamos tener otra hipótesis que razona sobre los costos amortizados de la siguiente manera:
Sé que la operación costosa será precedida por operaciones MÚLTIPLES DE BAJO COSTO.
En aras del análisis, voy a sobrecargar algunas operaciones de bajo costo, TAL QUE SU COSTO ASIMTOTICO NO CAMBIE.
Con estas operaciones de bajo costo aumentadas, puedo PROBAR QUE UNA OPERACIÓN GASTADA TIENE UN COSTO ASIMTOTICO MÁS PEQUEÑO.
Por lo tanto, he mejorado / disminuido el límite asintético del costo de n operaciones.
Por lo tanto, el análisis de costo amortizado + los límites de costo amortizado ahora son aplicables solo a las operaciones costosas. Las operaciones baratas tienen el mismo costo asintótico amortizado que su costo asintótico normal.
El rendimiento de cualquier función se puede promediar dividiendo el "número total de llamadas de función" en el "tiempo total empleado para todas las llamadas realizadas". Incluso las funciones que tardan más y más en cada llamada que realiza pueden promediarse de esta manera.
Por lo tanto, la esencia de una función que se desempeña en Constant Amortized Time
es que este "tiempo promedio" alcanza un límite que no se excede a medida que aumenta el número de llamadas. Cualquier llamada particular puede variar en rendimiento, pero a la larga este tiempo promedio no seguirá creciendo más y más.
Este es el mérito esencial de algo que funciona en Constant Amortized Time
.