Temps amorti constant


Réponses:


776

Temps amorti expliqué en termes simples:

Si vous effectuez une opération un million de fois, vous ne vous souciez pas vraiment du pire ou du meilleur des cas - ce qui vous importe, c'est combien de temps est pris au total lorsque vous répétez l'opération un million de fois .

Donc, peu importe si l'opération est très lente de temps en temps, tant que "de temps en temps" est assez rare pour que la lenteur soit diluée. Le temps essentiellement amorti signifie «temps moyen pris par opération, si vous effectuez de nombreuses opérations». Le temps amorti ne doit pas être constant; vous pouvez avoir un temps amorti linéaire et logarithmique ou autre.

Prenons l'exemple des tableaux d'un tableau dynamique, auquel vous ajoutez à plusieurs reprises de nouveaux éléments. Normalement, l'ajout d'un élément prend un temps constant (c'est-à-dire O(1)). Mais chaque fois que la baie est pleine, vous allouez deux fois plus d'espace, copiez vos données dans la nouvelle région et libérez l'ancien espace. En supposant que les allocations et les libérations s'exécutent en temps constant, ce processus d'agrandissement prend du O(n)temps où n est la taille actuelle du tableau.

Ainsi, chaque fois que vous agrandissez, vous prenez environ deux fois plus de temps que le dernier agrandissement. Mais vous avez également attendu deux fois plus longtemps avant de le faire! Le coût de chaque agrandissement peut ainsi être "réparti" entre les insertions. Cela signifie qu'à long terme, le temps total nécessaire pour ajouter m éléments au tableau est O(m), et donc le temps amorti (c'est-à-dire le temps par insertion) O(1).


61
Juste une note en termes de notation: un temps d'exécution constant amorti de O (n) est souvent écrit comme O (n) +, par opposition à juste O (n). L'ajout du signe plus indique que ce temps d'exécution n'est pas garanti comme étant O (n) et peut réellement dépasser ce temps d'exécution.
Jeffpowrs

1
En termes d'allocation d'espace, est-ce du tas?
committedandroider

3
Je ne suis pas d'accord avec "vous ne vous souciez pas vraiment du pire des cas". Cela dépend du cas d'utilisation. Si au final, vous ne vous intéressez qu'au résultat des opérations cotées à 1 million, vous ne vous souciez pas vraiment. Mais s'il s'agit d'une application en temps réel, qui lit constamment des données et y répond, vous pourriez avoir un gros problème, si le traitement de ces données prend 1 million de fois plus longtemps que la normale une fois tous les 1 million d'éléments de données traités!
Kai Petzke

2
@Jeffpowrs Je pensais que O (n) était un temps linéaire et O (1) était un temps constant . Cela signifie-t-il donc que O (1) + serait amorti en temps constant et O (n) + serait amorti en temps linéaire ?
John Meyer

1
@JohnMeyer Oui.
Artelius

55

Cela signifie qu'avec le temps, le pire des scénarios sera par défaut O (1), ou temps constant. Un exemple courant est le tableau dynamique. Si nous avons déjà alloué de la mémoire pour une nouvelle entrée, l'ajouter sera O (1). Si nous ne l'avons pas alloué, nous le ferons en allouant, disons, le double du montant actuel. Cette insertion particulière ne sera pas O (1), mais plutôt autre chose.

Ce qui est important, c'est que l'algorithme garantit qu'après une séquence d'opérations les opérations coûteuses seront amorties et rendra ainsi toute l'opération O (1).

Ou en termes plus stricts,

Il y a une constante c, telle que pour chaque séquence d'opérations (également une se terminant par une opération coûteuse) de longueur L, le temps n'est pas supérieur à c * L (Merci Rafał Dowgird )


11
"après un nombre d'opérations suffisamment important" - le temps amorti constant n'a pas besoin de cette condition. Il y a une constante c, telle que pour chaque séquence d'opérations (également une se terminant par une opération coûteuse) de longueur L, le temps n'est pas supérieur à c * L.
Rafał Dowgird

D'où vient cette allocation de deux fois le montant ? Ne devrions-nous pas allouer une entrée? Ou s'agit-il d'un exemple hypothétique?
talekeDskobeDa

@talekeDskobaDa Ce n'est pas un exemple arbitraire, mais un algorithme largement utilisé. Si nous allouions de l'espace pour une entrée à la fois comme vous le suggérez, le temps amorti pour l'insertion d'une seule valeur serait O (n). Si on double l'espace quand il est plein, le temps amorti est bien meilleur, O (1). Pour être clair, le problème avec l'allocation d'espace pour un élément à la fois est qu'un tableau a besoin d'un grand bloc d'espace continu. Il est facile d'obtenir un bloc plus grand à partir du système d'exploitation, mais il est souvent impossible d'étendre un bloc existant car il peut y avoir d'autres données stockées directement après celui-ci.
Artelius

23

Pour développer une manière intuitive d'y penser, pensez à insérer des éléments dans un tableau dynamique (par exemple std::vectoren C ++). Tracons un graphique qui montre la dépendance du nombre d'opérations (Y) nécessaires pour insérer N éléments dans le tableau:

terrain

Les parties verticales du graphique noir correspondent à des réallocations de mémoire afin d'étendre un tableau. Ici, nous pouvons voir que cette dépendance peut être grossièrement représentée comme une ligne. Et cette équation de ligne est Y=C*N + b( Cest constante, b= 0 dans notre cas). Par conséquent, nous pouvons dire que nous devons dépenser des C*Nopérations en moyenne pour ajouter N éléments au tableau, ou des C*1opérations pour ajouter un élément (temps constant amorti).


14

J'ai trouvé ci-dessous l'explication de Wikipedia utile, après avoir répété la lecture 3 fois:

Source: https://en.wikipedia.org/wiki/Amortized_analysis#Dynamic_Array

"Tableau dynamique

Analyse amortie de l'opération Push pour un tableau dynamique

Considérez un tableau dynamique qui grandit à mesure que d'autres éléments lui sont ajoutés, comme une ArrayList en Java. Si nous commencions avec un tableau dynamique de taille 4, il faudrait un temps constant pour y pousser quatre éléments. Pourtant, pousser un cinquième élément sur ce tableau prendrait plus de temps car le tableau devrait créer un nouveau tableau de la taille actuelle (8), copier les anciens éléments dans le nouveau tableau, puis ajouter le nouvel élément. Les trois opérations de poussée suivantes prendraient de la même manière un temps constant, puis l'ajout ultérieur nécessiterait un autre doublement lent de la taille de la matrice.

En général, si nous considérons un nombre arbitraire de poussées n vers un tableau de taille n, nous remarquons que les opérations de poussée prennent un temps constant, sauf pour la dernière qui prend O (n) pour effectuer l'opération de doublage de taille. Puisqu'il y avait n opérations au total, nous pouvons prendre la moyenne de cela et constater que pour pousser des éléments sur le tableau dynamique prend: O (n / n) = O (1), temps constant. "

À ma connaissance, comme une histoire simple:

Supposons que vous ayez beaucoup d'argent. Et, vous voulez les empiler dans une pièce. Et, vous avez de longues mains et jambes, aussi longtemps que vous en avez besoin maintenant ou à l'avenir. Et, vous devez tout remplir dans une seule pièce, il est donc facile de le verrouiller.

Donc, vous allez directement au bout / coin de la pièce et commencez à les empiler. Lorsque vous les empilez, la pièce manque lentement d'espace. Cependant, en les remplissant, il était facile de les empiler. J'ai l'argent, mettez l'argent. Facile. C'est O (1). Nous n'avons pas besoin de déplacer d'argent précédent.

Une fois que la pièce manque d'espace. Nous avons besoin d'une autre pièce, qui est plus grande. Ici, il y a un problème, car nous ne pouvons avoir qu'une seule chambre, nous ne pouvons donc avoir qu'une seule serrure, nous devons déplacer tout l'argent existant dans cette pièce dans la nouvelle salle plus grande. Alors, déplacez tout l'argent, d'une petite pièce à une plus grande pièce. Autrement dit, empilez-les tous à nouveau. Donc, nous devons déplacer tout l'argent précédent. Donc, c'est O (N). (en supposant que N est le nombre total d'argent de l'argent précédent)

En d'autres termes, c'était facile jusqu'à N, seulement 1 opération, mais quand nous devons déménager dans une pièce plus grande, nous avons fait N opérations. Donc, en d'autres termes, si nous faisons la moyenne, c'est 1 insert au début et 1 mouvement de plus en se déplaçant dans une autre pièce. Total de 2 opérations, un insert, un mouvement.

En supposant que N est grand comme 1 million même dans la petite pièce, les 2 opérations par rapport à N (1 million) ne sont pas vraiment un nombre comparable, il est donc considéré comme constant ou O (1).

En supposant que nous faisons tout ce qui précède dans une autre pièce plus grande et que nous devons à nouveau bouger. C'est encore le même. disons, N2 (disons, 1 milliard) est le nouveau montant d'argent dans la plus grande salle

Donc, nous avons N2 (qui inclut N du précédent car nous passons tous de la petite à la plus grande pièce)

Nous n'avons encore besoin que de 2 opérations, l'une est insérée dans une pièce plus grande, puis une autre opération de déplacement pour passer à une pièce encore plus grande.

Donc, même pour N2 (1 milliard), c'est 2 opérations pour chacune. ce qui n'est plus rien. Donc, c'est constant, ou O (1)

Ainsi, lorsque le N augmente de N à N2, ou autre, cela n'a pas beaucoup d'importance. Il est toujours constant, ou O (1) opérations nécessaires pour chacun des N.


Supposons maintenant que vous avez N comme 1, très petit, le compte d'argent est petit et que vous avez une très petite pièce, qui ne pourra contenir qu'un seul compte d'argent.

Dès que vous remplissez l'argent dans la salle, la salle est remplie.

Lorsque vous allez dans la plus grande salle, supposez qu'elle ne peut contenir qu'un seul argent de plus, soit un total de 2 comptes. Cela signifie que le précédent a déplacé de l'argent et 1 de plus. Et encore une fois, il est rempli.

De cette façon, le N croît lentement, et ce n'est plus O constant (1), puisque nous déplaçons tout l'argent de la pièce précédente, mais ne pouvons contenir qu'un seul argent de plus.

Au bout de 100 fois, la nouvelle chambre peut contenir 100 pièces d'argent par rapport à la précédente et 1 pièce de plus qu'elle peut accueillir. Ceci est O (N), puisque O (N + 1) est O (N), c'est-à-dire que le degré de 100 ou 101 est le même, les deux sont des centaines, contrairement à l'histoire précédente de, des millions et des milliards .

C'est donc une manière inefficace d'allouer des salles (ou de la mémoire / RAM) pour notre argent (variables).


Donc, une bonne façon est d'allouer plus de place, avec des pouvoirs de 2.

1ère taille de la pièce = fits 1 comte d'argent
2ème chambre size = fits 4 Nombre d'argent
3ème chambre taille = fits 8 comte d'argent
4ème chambre taille = fits 16 comte d'argent
5ème chambre taille = fits 32 comptes d'argent
6e chambre taille = fits 64
pièces d'argent 7ème chambre = correspond à 128
pièces d'argent 8ème pièce = correspond à 256
pièces d'argent 9ème pièce = convient à 512
pièces d'argent 10ème pièce = correspond à 1024
pièces d'argent 11ème pièce = correspond à 2 048 pièces d'argent
. ..
16ème taille de pièce = convient à 65 536 pièces d'argent
...
32ème pièce = correspond à 4 294 967 296 pièces d'argent
...
64ème pièce = correspond à 18 446 744 073 709 551 616 pièces d'argent

Pourquoi est-ce mieux? Parce qu'il semble croître lentement au début, et plus vite plus tard, c'est-à-dire par rapport à la quantité de mémoire dans notre RAM.

Cela est utile car, dans le premier cas, même s'il est bon, le montant total de travail à faire par argent est fixe (2) et n'est pas comparable à la taille de la pièce (N), la pièce que nous avons prise dans les étapes initiales pourrait être trop gros (1 million) que nous pourrions ne pas utiliser pleinement selon que nous pourrions obtenir autant d'argent à économiser dans le premier cas.

Cependant, dans le dernier cas, puissances de 2, il croît dans les limites de notre RAM. Et donc, en augmentant les puissances de 2, l'analyse Armotized reste constante et elle est favorable à la RAM limitée que nous avons à ce jour.


2
Ah, c'est donc O (pire cas / nombre d'opérations). J'aime mieux cette réponse.
Nucleartide

1

Les explications ci-dessus s'appliquent à l'analyse agrégée, l'idée de prendre "une moyenne" sur plusieurs opérations. Je ne sais pas comment ils s'appliquent à la méthode des banquiers ou aux méthodes d'analyse des amortisseurs des physiciens.

Maintenant. Je ne suis pas exactement sûr de la bonne réponse. Mais cela aurait à voir avec la condition principale des deux méthodes Physiciens + Banquier:

(Somme du coût d'exploitation amorti)> = (Somme du coût d'exploitation réel).

La principale difficulté à laquelle je fais face est que, étant donné que les coûts d'exploitation asymptotiques amortis diffèrent du coût asymptotique normal, je ne sais pas comment évaluer l'importance des coûts amortis.

C'est quand quelqu'un me donne un coût amorti, je sais que ce n'est pas la même chose que le coût asymptotique normal. Quelles conclusions puis-je tirer du coût amorti alors?

Étant donné que certaines opérations sont surfacturées alors que d'autres sont sous-facturées, une hypothèse pourrait être que la cotation des coûts amortis des opérations individuelles n'aurait aucun sens.

Par exemple: pour un tas de fibonacci, citer le coût amorti d'une clé décroissante juste pour être O (1) n'a aucun sens puisque les coûts sont réduits par "le travail effectué par des opérations antérieures pour augmenter le potentiel du tas".

OU

Nous pourrions avoir une autre hypothèse qui raisonne sur les coûts amortis comme suit:

  1. Je sais que l'opération coûteuse va être précédée de MULTIPLES OPÉRATIONS À FAIBLE COÛT.

  2. À des fins d'analyse, je vais surcharger certaines opérations à faible coût, TELLES QUE LEUR COÛT ASYMPTOTIQUE NE CHANGE PAS.

  3. Grâce à ces opérations à faible coût accrues, je peux prouver que les opérations coûteuses ont un coût asyptoétique plus petit.

  4. J'ai donc amélioré / diminué l'ASYMPTOTIC-BOUND du coût de n opérations.

Ainsi, l'analyse du coût amorti + les limites du coût amorti ne s'appliquent désormais qu'aux opérations coûteuses. Les opérations bon marché ont le même coût amorti asymptotique que leur coût asymptotique normal.


Pensées intéressantes.
Lonnie Best

0

Les performances de n'importe quelle fonction peuvent être moyennées en divisant le "nombre total d'appels de fonction" en "le temps total pris pour tous les appels effectués". Même les fonctions qui prennent de plus en plus de temps pour chaque appel que vous effectuez peuvent être moyennées de cette manière.

Ainsi, l'essence d'une fonction qui s'exécute Constant Amortized Timeest que ce «temps moyen» atteint un plafond qui n'est pas dépassé à mesure que le nombre d'appels continue d'augmenter. Tout appel particulier peut varier en termes de performances, mais à long terme, ce temps moyen ne continuera pas de plus en plus.

C'est le mérite essentiel de quelque chose qui fonctionne à Constant Amortized Time.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.