回答:
簡単な言葉で説明される償却時間:
百万回言う操作を行ったとしても、その操作の最悪の場合や最良の場合は特に気にしない-何百万回操作を繰り返したときに合計時間がどれだけかかるかが重要です。 。
したがって、「たまに1回」がまれに速度低下を解消できる限り、操作が時々非常に遅いかどうかは問題ではありません。基本的に償却時間とは、「多くの操作を行う場合の、操作ごとにかかる平均時間」を意味します。償却時間は一定である必要はありません。あなたは線形および対数の償却時間または他の何でも持つことができます。
新しい項目を繰り返し追加する動的配列のマットの例を見てみましょう。通常、アイテムの追加には一定の時間がかかります(つまり、O(1)
)。ただし、アレイがいっぱいになるたびに、2倍のスペースを割り当て、データを新しいリージョンにコピーして、古いスペースを解放します。割り当てと解放が一定の時間で実行されると仮定すると、この拡大プロセスにはO(n)
時間がかかります。nは配列の現在のサイズです。
したがって、拡大するたびに、最後の拡大の約2倍の時間がかかります。しかし、それを行う前に、2倍も待っていました!したがって、各拡大のコストは、挿入の間で「分散」することができます。つまり、長期的には、mアイテムを配列に追加するのにかかる合計時間はO(m)
であるため、償却時間(つまり、挿入ごとの時間)はになりO(1)
ます。
時間の経過とともに、最悪のシナリオはデフォルトでO(1)または一定の時間になることを意味します。一般的な例は、動的配列です。新しいエントリにすでにメモリを割り当てている場合は、追加するとO(1)になります。割り当てていない場合は、たとえば現在の2倍の量を割り当てます。この特定の挿入はなりません O(1)のではなく、何か他のものも。
重要なのは、一連の操作の後、高価な操作が償却され、操作全体がO(1)になることをアルゴリズムが保証することです。
またはもっと厳密に言えば
定数cがあり 、長さLのすべての操作シーケンス(コストのかかる操作で終了する操作)でも、時間はc * L以下になります(ありがとうRafałDowgird)
それについて直感的に考える方法を開発するには、動的配列(たとえばstd::vector
C ++)への要素の挿入を検討してください。配列にN個の要素を挿入するために必要な操作数(Y)の依存関係を示すグラフをプロットしてみましょう。
黒いグラフの垂直部分は、配列を拡張するためのメモリの再割り当てに対応しています。ここでは、この依存関係がおおまかに線で表されていることがわかります。そして、この線方程式はY=C*N + b
(C
は定数です、b
私たちの場合= 0です)。したがってC*N
、配列にN個の要素を追加するには平均で操作を費やす必要があり、C*1
1つの要素を追加する操作(一定時間の償却)を費やす必要があると言えます。
3回繰り返し読んだ後、私は以下のウィキペディアの説明が役に立ったと思いました:
出典:https : //en.wikipedia.org/wiki/Amortized_analysis#Dynamic_Array
「ダイナミックアレイ
動的配列のプッシュ操作の償却分析
JavaのArrayListなど、要素が追加されるにつれてサイズが大きくなる動的配列について考えてみます。サイズ4の動的配列から始めた場合、4つの要素をプッシュするのに一定の時間がかかります。ただし、5番目の要素をその配列にプッシュすると、配列が現在のサイズの2倍の新しい配列(8)を作成し、古い要素を新しい配列にコピーして、新しい要素を追加する必要があるため、時間がかかります。次の3つのプッシュ操作も同様に一定の時間がかかり、その後の追加では、配列サイズをさらに2倍遅くする必要があります。
一般に、サイズnの配列への任意の数のプッシュnを考えると、サイズ倍化操作を実行するためにO(n)時間を要する最後のプッシュ操作を除いて、プッシュ操作には一定の時間がかかることに気づきます。合計n回の操作があったので、これの平均を取り、動的配列に要素をプッシュするために、O(n / n)= O(1)、一定時間を要することがわかります。」
簡単な話として私の理解に:
あなたはたくさんのお金を持っていると仮定します。そして、あなたはそれらを部屋に積み重ねたいです。そして、あなたは、あなたが現在または将来必要とする限り、長い手と足を持っています。また、1つの部屋にすべて記入する必要があるため、簡単にロックできます。
それで、あなたは部屋の端/コーナーに直接行き、それらを積み重ね始めます。それらを積み重ねるにつれて、ゆっくりと部屋はスペースを使い果たします。しかし、あなたが記入すると、それらを積み重ねることは簡単でした。お金を得た、お金を入れた。簡単です。O(1)です。以前のお金を移動する必要はありません。
部屋がスペースを使い果たしたら。もっと広い部屋が必要です。ここに問題があります。1つの部屋しか持てないため、1つのロックしか持てないため、その部屋にあるすべての既存のお金を新しい大きな部屋に移動する必要があります。それで、すべてのお金を小さな部屋から大きな部屋に移動します。つまり、それらすべてを再度スタックします。したがって、以前のお金をすべて移動する必要があります。なので、O(N)です。(Nが前のお金の合計数であると仮定)
つまり、Nまでは1回の操作で簡単でしたが、より広い部屋に移動する必要がある場合は、N回の操作を行いました。つまり、平均すると、最初に1つの挿入があり、別の部屋に移動している間にもう1つ移動するということです。合計2つの操作、1つの挿入、1つの移動。
小さな部屋でもNが100万のように大きいと仮定すると、N(100万)と比較した2つの演算は実際には同等の数ではないため、定数またはO(1)と見なされます。
上記のすべてを別の大きな部屋で行い、再び移動する必要があると仮定します。それはまだ同じです。たとえば、N2(たとえば、10億)は、より広い部屋での新しい金額です。
したがって、N2があります(すべてを小さい部屋から大きい部屋に移動するため、前のNが含まれます)。
必要な操作は2つだけです。1つはより大きな部屋に挿入する操作で、もう1つはさらに大きな部屋に移動する移動操作です。
したがって、N2(10億)の場合でも、それぞれ2オペレーションです。それはまた何もありません。したがって、定数、またはO(1)
したがって、NがNからN2またはその他に増加するとき、それはそれほど重要ではありません。それはまだ一定であり、Nのそれぞれに必要なO(1)操作です。
ここで、Nが1と非常に小さく、お金の数が少ないと仮定します。部屋は非常に小さいため、1カウントだけに収まります。
部屋にお金を入れるとすぐに、部屋がいっぱいになります。
あなたがより大きな部屋に行くとき、それがその中にもう1つのお金、合計2カウントのお金しか入れることができないと仮定してください。つまり、以前のお金ともう1つを移動しました。そして再びそれは満たされています。
このように、Nはゆっくりと成長し、以前の部屋からすべてのお金を移動しているため、一定のO(1)ではありませんが、あと1つしか収まりません。
100回後、新しい部屋は、以前の部屋の100カウントと、それが収容できる追加のお金に適合します。これはO(N)です。O(N + 1)はO(N)です。つまり、100または101の次数は同じで、以前の1から100万と1から10億のストーリーとは対照的に、どちらも100です。 。
したがって、これは私たちのお金(変数)に部屋(またはメモリ/ RAM)を割り当てる効率の悪い方法です。
したがって、2の累乗でより多くの部屋を割り当てるのが良い方法です。
1番目の部屋のサイズ= 1カウントのお金に合う
2番目の部屋のサイズ= 4カウントのお金に合う
3番目の部屋のサイズ= 8カウントのお金に
合う4番目の部屋のサイズ= 16お金に
合う5番目の部屋のサイズ= 32お金に
合う6番目の部屋のサイズ=合うお金の64数
7部屋の大きさ=フィットマネーの128数
8部屋の大きさ=フィットマネーの256数
第九の部屋の大きさ=フィットマネーの512数
10部屋のサイズ=フィットマネーの1024数
11部屋のサイズ=フィットお金の2048数
。 ..
16番目の部屋のサイズ= 65,536カウントに
相当
...
32 番目の部屋のサイズ= 4,294,967,296カウントに相当
...
64番目の部屋のサイズ= 18,446,744,073,709,551,616に相当
なぜこれが良いのですか?それは、最初はゆっくりと、後で遅くなるように、つまり、RAMのメモリ量と比較して大きくなるように見えるためです。
最初のケースでは良いですが、お金ごとに行われる作業の総量は固定されており(2)、部屋のサイズ(N)に匹敵しないため、これは役に立ちます。最初の段階で取った部屋も多すぎる可能性があります。大きな(100万)最初のケースで節約するためにその分多くのお金を得ることができるかどうかに応じて、私たちは十分に使用しない場合があります。
ただし、最後のケースでは、2の累乗は、RAMの制限で増加します。そのため、2の累乗で増加しますが、どちらのArmotized分析も一定であり、現在の限られたRAMに適しています。
上記の説明は、複数の操作にわたって「平均」を取るという考えである集計分析に適用されます。それらがBankers-methodまたはPhysicists Methods of Amortized分析にどのように適用されるかはわかりません。
今。正解は正確にはわかりません。しかし、それは両方のPhysicists + Bankerのメソッドの主要な条件と関係があります:
(運用の償却原価の合計)> =(運用の実際原価の合計)。
私が直面する主な問題は、運用の償却後漸近コストが通常の漸近コストと異なることを考えると、償却後コストの重要性をどのように評価するかわかりません。
それは誰かが私の償却原価を与えるとき、私はそれが通常の漸近的原価と同じではないことを知っています。その場合、償却原価からどのような結論を導きますか?
他の操作が過小請求されている一方で、一部の操作が過充電になっている場合があるため、1つの仮説は、個々の操作の償却原価を引用しても意味がないということです。
例:フィボナッチヒープの場合、「ヒープの可能性を高めるために以前の操作で行われた作業」によってコストが削減されるため、Decreeasing-Keyの償却コストをO(1)と見積もることは意味がありません。
または
償却原価について次のように理由付けられる別の仮説を立てることができます。
費用のかかる操作の前に、MULTIPLE LOW-COST操作が行われることを知っています。
分析のために、いくつかの低コスト操作を過剰に充電します。そのような漸近的コストは変更されません。
これらの増加した低コスト操作により、費用のかかる操作の漸近的コストが小さいことを証明できます。
したがって、n回の操作のコストの漸近的限界を改善または減少させました。
したがって、償却原価分析+償却原価範囲は、高価な操作にのみ適用できるようになりました。安価な操作には、通常の漸近的コストと同じ漸近的償却コストがあります。
関数のパフォーマンスは、「関数呼び出しの合計数」を「それらのすべての呼び出しにかかった合計時間」に分割することで平均化できます。呼び出しごとに時間がかかる関数でも、この方法で平均化できます。
したがって、で実行される関数の本質Constant Amortized Time
は、この「平均時間」が上限に達し、呼び出しの数が増え続けても超えられないことです。特定の呼び出しはパフォーマンスが異なる場合がありますが、長期的には、この平均時間はますます大きくなることはありません。
これは、でパフォーマンスを実行することの本質的なメリットですConstant Amortized Time
。