Tiempo constante amortizado

Respuestas:

776

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).

Artelius
fuente
61
Solo una nota en términos de notación: un tiempo de ejecución constante amortizado de O (n) a menudo se escribe como O (n) +, en oposición a solo O (n). La adición del signo más indica que no se garantiza que el tiempo de ejecución sea O (n) y que en realidad puede exceder ese tiempo de ejecución.
Jeffpowrs
1
En términos de asignación de espacio, ¿es eso del montón?
committedandroider
3
No estoy de acuerdo con "realmente no te importa el peor de los casos". Depende del caso de uso. Si al final, solo le interesa el resultado de las operaciones citadas de 1 millón, realmente no le importa. Pero si se trata de una aplicación en tiempo real, que constantemente lee datos y luego responde a ellos, puede tener un gran problema, si procesar esos datos lleva 1 millón de veces más de lo normal una vez cada 1 millón de elementos de datos procesados.
Kai Petzke
2
@Jeffpowrs Pensé que O (n) era tiempo lineal y O (1) era tiempo constante . Entonces, ¿eso significa que O (1) + se amortizaría en tiempo constante y O (n) + se amortizaría en tiempo lineal ?
John Meyer
1
@JohnMeyer Sí.
Artelius el
55

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 )

Mats Fredriksson
fuente
11
"después de una cantidad suficientemente grande de operaciones": el tiempo amortizado constante no necesita esta condición. 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.
Rafał Dowgird
¿De dónde viene esta asignación del doble de la cantidad ? ¿No deberíamos asignar una entrada? ¿O es un ejemplo hipotético?
talekeDskobeDa
@talekeDskobaDa Este no es un ejemplo arbitrario, sino un algoritmo ampliamente utilizado. Si asignamos espacio para una entrada a la vez, como sugiere, el tiempo amortizado para insertar un solo valor sería O (n). Si duplicamos el espacio cuando se llena, el tiempo amortizado es mucho mejor, O (1). Para ser claros, el problema con la asignación de espacio para un elemento a la vez es que una matriz necesita un gran bloque de espacio continuo. Es fácil obtener un bloque más grande del sistema operativo, pero a menudo es imposible expandir un bloque existente porque puede haber otros datos almacenados directamente después de él.
Artelius
23

Para desarrollar una forma intuitiva de pensarlo, considere la inserción de elementos en una matriz dinámica (por ejemplo, std::vectoren C ++). Tracemos un gráfico que muestre la dependencia del número de operaciones (Y) necesarias para insertar N elementos en la matriz:

trama

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( Ces constante, b= 0 en nuestro caso). Por lo tanto, podemos decir que necesitamos gastar C*Noperaciones en promedio para agregar N elementos a la matriz, u C*1operaciones para agregar un elemento (tiempo constante amortizado).

Megamozg
fuente
14

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.

Manohar Reddy Poreddy
fuente
2
Ah, entonces es O (peor de los casos / # de operaciones). Me gusta esta respuesta la mejor.
nucleartido
1

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:

  1. Sé que la operación costosa será precedida por operaciones MÚLTIPLES DE BAJO COSTO.

  2. En aras del análisis, voy a sobrecargar algunas operaciones de bajo costo, TAL QUE SU COSTO ASIMTOTICO NO CAMBIE.

  3. Con estas operaciones de bajo costo aumentadas, puedo PROBAR QUE UNA OPERACIÓN GASTADA TIENE UN COSTO ASIMTOTICO MÁS PEQUEÑO.

  4. 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.

Misraji
fuente
Pensamientos interesantes
Lonnie Best
0

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 Timees 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.

Lonnie Best
fuente