誰かが素人の言葉で償却された複雑さを説明できますか?私はオンラインで正確な定義を見つけるのに苦労してきましたが、それがアルゴリズムの分析にどのように完全に関連するのかわかりません。たとえ外部的に参照されていても、有用なものは大歓迎です。
償却された複雑度は操作のシーケンスで評価された操作ごとの総費用。
アイデアは、シーケンス全体の総費用を保証する一方で、個々の操作を償却原価よりもはるかに高くすることです。
例:
C++の動作_std::vector<>
_。 Push_back()
がベクトルサイズを事前に割り当てられた値より大きくすると、割り当てられた長さが2倍になります。
そのため、単一のPush_back()
の実行にはO(N)
時間がかかる場合があります(配列の内容が新しいメモリ割り当てにコピーされるため)。
ただし、割り当てのサイズが2倍になったため、Push_back()
への次の_N-1
_呼び出しは、それぞれ実行にO(1)
時間かかります。そのため、N
操作の合計にはO(N)
時間かかります。これにより、Push_back()
に、操作ごとにO(1)
の償却コストが与えられます。
特に指定がない限り、償却された複雑さは、一連の操作に対する漸近的な最悪の場合の保証です。これの意味は:
償却されていない複雑度と同様に、償却された複雑度に使用されるbig-O表記は、固定の初期オーバーヘッドと一定のパフォーマンス要因の両方を無視します。したがって、big-Oの償却パフォーマンスを評価するために、一般に、償却された操作のシーケンスは、固定起動費用を償却するのに「十分に長い」と想定できます。具体的には、_std::vector<>
_の例では、実際にN
の追加操作が発生するかどうかを心配する必要がないのはこのためです。
任意の長さに加えて、償却分析は、コストを測定している操作のシーケンスについて仮定しません-任意の可能なシーケンスの最悪ケースの保証です操作の。悪意のある攻撃者が操作をどれほどひどく選択したとしても、償却分析では、十分に長い一連の操作が償却コストの合計よりも一貫して高くならないことを保証する必要があります。これが(限定子として特に言及されていない限り)「確率」と「平均ケース」が償却分析に関連していない理由です-通常の最悪ケースのビッグO分析に関連している以上です!
償却分析では、一連のデータ構造操作の実行に必要な時間が、実行されたすべての操作にわたって平均されます...償却分析は、確率が関係しないという点で平均ケース分析とは異なります。償却分析では、最悪の場合の各操作の平均パフォーマンスが保証されます。
(Cormen et al。、「Introduction to Algorithms」から)
これは、時間が平均化されることと、平均ケース分析ではないことの両方を示しているため、少し混乱するかもしれません。それで、これを金融のアナロジーで説明してみましょう(実際、「償却」は銀行と会計に最も一般的に関連する言葉です。)
あなたが宝くじを操作しているとします。 (宝くじを購入するのではなく、すぐに入手します。宝くじ自体を操作します。)100,000枚のチケットを印刷し、それぞれ1通貨単位で販売します。これらのチケットの1つは、購入者に40,000通貨単位の資格を与えます。
ここで、すべてのチケットを販売できると仮定すると、60,000通貨単位を獲得することになります。100,000通貨単位の売り上げから40,000通貨単位の賞金を差し引いたものです。あなたにとって、各チケットの価値は0.60通貨単位で、すべてのチケットで償却されます。これは信頼できる値です。あなたはそれに銀行することができます。自分でチケットを販売するのにうんざりしていて、誰かが一緒に来て、それぞれ0.30通貨単位で販売することを申し出た場合、あなたはあなたがどこにいるかを正確に知っています。
宝くじの購入者にとって、状況は異なります。購入者は、宝くじを購入するときに0.60通貨単位の損失が予想されます。しかし、それは確率的なことです。購入者は、30年の間毎日10枚の宝くじを購入することがあります(100,000枚を少し超えるチケット)。または、1日1枚のチケットを自発的に購入し、39,999通貨単位を獲得する場合があります。
データ構造分析に適用して、最初のケースについて話します。最初のケースでは、その種のすべての操作に対して何らかのデータ構造操作(挿入など)のコストを償却します。平均ケース分析では、すべての操作の総コストを計算することはできませんが、単一の操作の予想コストの確率的分析を提供することができる確率的操作(検索など)の期待値を扱います。
償却分析は、高コストの運用がまれな状況に適用されるとよく言われますが、それはよくあることです。しかしいつもではない。たとえば、2つのスタックで構成される先入れ先出し(FIFO)キューである、いわゆる「バンカーのキュー」を考えてみましょう。 (これは、古典的な機能データ構造です。不変の単一リンクノードから安価なLIFOスタックを構築できますが、安価なFIFOはそれほど明白ではありません)。操作は次のように実装されます。
put(x): Push x on the right-hand stack.
y=get(): If the left-hand stack is empty:
Pop each element off the right-hand stack and
Push it onto the left-hand stack. This effectively
reverses the right-hand stack onto the left-hand stack.
Pop and return the top element of the left-hand stack.
ここで、put
とget
の償却コストはO(1)
であると主張します。空のキューで開始および終了すると仮定します。分析は簡単です。私は常に右側のスタックにput
を、左側のスタックからget
を使用します。したがって、If
句を除き、各put
はPush
であり、各get
はpop
であり、両方ともO(1)
。 If
節を実行する回数はわかりません-put
sとget
sのパターンによって異なりますが、すべての要素が正確に移動することがわかります右側のスタックから左側のスタックに1回。したがって、n put
sおよびn get
sのシーケンス全体の合計コストは、n Push
es、n pop
s、およびn move
sです。 、ここでmove
はpop
が後に続くPush
:つまり、2n put
sおよびget
sは2n Push
esおよび2n pop
s。したがって、単一のput
(またはget
)の償却コストは、1つのPush
と1つのpop
です。
銀行のキューは、正確に償却された複雑性分析(および「償却済み」という単語と金融との関連付け)のために呼び出されることに注意してください。銀行家のキューは、よくあるインタビューの質問であったものへの答えですが、今ではあまりにも有名だと考えられていると思います:償却されたO(1)時間で次の3つの操作を実装するキューを考え出します:
1)キューの最も古い要素を取得して削除します。
2)新しい要素をキューに入れ、
3)現在の最大要素の値を見つけます。
「償却された複雑さ」の原則は、実行すると非常に複雑になる場合がありますが、あまり頻繁に行われないため、「複雑ではない」と見なされることです。たとえば、2^n
の挿入ごとに1回と言うように、時々バランスを取る必要があるバイナリツリーを作成する場合、ツリーのバランスは非常に複雑ですが、n回の挿入ごとに1回しか発生しません。 256、次に512番目、1024番目など)。他のすべての挿入では、複雑さはO(1)-はい、n個の挿入ごとに1回O(n)を取りますが、1/n
確率のみです-したがって、 O(n)に1/nを掛けてO(1)を取得します。つまり、「O(1)の複雑さの償却」と言われています-要素を追加するにつれて、ツリーのリバランスにかかる時間が最小限に抑えられるためです。
償却とは、繰り返し実行することで分割されることを意味します。最悪の場合の動作は、頻繁に発生しないことが保証されています。たとえば、最も遅いケースがO(N)であるが、その発生の可能性がちょうどO(1/N)であり、そうでなければプロセスがO(1)である場合、アルゴリズムはまだ償却定数O(1) time。各O(N)実行がN個の他の実行に分割されるようにするための作業を考慮してください。
コンセプトは、合計時間を分割するのに十分な実行があることに依存します。アルゴリズムが一度だけ実行される場合、または実行のたびに期限を満たす必要がある場合、最悪の場合の複雑さがより重要になります。
ソートされていない配列のk番目に小さい要素を見つけようとしているとします。配列の並べ替えはO(n logn)になります。したがって、k番目に小さい番号を見つけることは、インデックスを見つけることだけなので、O(1)になります。
配列は既にソートされているため、再度ソートする必要はありません。最悪のシナリオに複数回遭遇することはありません。
K番目に小さいものを見つけようとするクエリをn回実行すると、O(1)を支配するため、O(n logn)のままになります。各操作の時間を平均すると、次のようになります。
(n logn)/ nまたはO(logn)。したがって、時間の複雑さ/操作の数。
これは償却された複雑さです。
これがうまくいくと思う、ただそれを学ぶだけだ..
これは、アルゴリズム内のさまざまなブランチのワーストケースの複雑度に、そのブランチを実行し、結果を追加する確率を掛けることにいくらか似ています。そのため、分岐が行われる可能性が非常に低い場合、複雑さへの貢献度は低くなります。