web-dev-qa-db-ja.com

フィボナッチ数列がO(logn)ではなくBig O(2 ^ n)であるのはなぜですか?

少し前に離散数学(マスター定理Big Theta/Omega/Oについて学びました)を取りましたが、O(logn)とO(2 ^)の違いを忘れたようです。 n)(Big Ohの理論的な意味ではありません)私は一般的に、マージやクイックソートなどのアルゴリズムはO(nlogn))であると理解しています。サブ配列は、ツリーを再帰的にバックアップする前のサイズ1であり、高さlogn + 1の再帰ツリーを提供します。ただし、n/b ^ x = 1を使用して再帰ツリーの高さを計算する場合(サブ問題のサイズが答えで述べたように1になりました ここ )木の高さはlog(n)であることが常にわかるようです。

再帰を使用してフィボナッチ数列を解くと、サイズlognのツリーも得られると思いますが、何らかの理由で、アルゴリズムのBig OはO(2 ^ n)です。おそらく違いは、各サブ問題のすべてのfib番号を覚えて、実際のfib番号を取得する必要があるためだと考えていました。つまり、各ノードの値を呼び出す必要がありますが、マージソートでは、値は各ノードのも使用(または少なくともソート)する必要があります。これは、ツリーの各レベルで行われた比較に基づいて特定のノードにのみアクセスするバイナリ検索とは異なります。したがって、これが混乱の原因であると思います。

具体的には、フィボナッチ数列の時間計算量がマージ/クイックソートなどのアルゴリズムと異なる原因は何ですか?

7
loremIpsum1771

他の答えは正しいですが、明確にしないでください-フィボナッチアルゴリズムと分割統治アルゴリズムの大きな違いはどこから来ているのですか?実際、両方のクラスの関数の再帰ツリーの形状は同じです。これは二分木です。

理解する秘訣は実際には非常に単純です。再帰ツリーのsizeを入力サイズnの関数として考えてください。

二分木 についてのいくつかの基本的な事実を思い出してください:最初に:

  • 葉の数nは二分木であり、葉以外のノードの数に1を加えた数に等しくなります。したがって、二分木のサイズは2n-1です。
  • perfectバイナリツリーでは、すべての非リーフノードに2つの子があります。
  • hの葉を持つ完全な二分木の高さnは、log(n)に等しく、ランダムな二分木の場合:h = O(log(n))、および縮退した場合二分木_h = n-1_。

直感的に:

  • 再帰的アルゴリズムを使用してn要素の配列をソートするために、再帰ツリーにはnleavesがあります。したがって、ツリーのwidthnツリーの高さは、平均してO(log(n))であり、O(n)最悪の場合。

  • 再帰アルゴリズムを使用してフィボナッチ数列要素kを計算する場合、再帰ツリーにはkレベルがあります(理由を確認するため) 、fib(k)fib(k-1)を呼び出し、fib(k-2)を呼び出すことなどを考慮してください。したがって、ツリーのheightkになります。再帰ツリーのノードの幅と数の下限を見積もるには、fib(k)fib(k-2)を呼び出すため、完全なものがあることを考慮してください。 再帰ツリーの一部としての高さ_k/2_の二分木。抽出された場合、その完全なサブツリーは2になりますk/2 リーフノード。したがって、再帰ツリーのwidthは、少なくともO(2^{k/2})、または同等に2^O(k)

決定的な違いは次のとおりです。

  • 分割統治アルゴリズムの場合、入力サイズは二分木のwidthです。
  • フィボナッチアルゴリズムの場合、入力サイズはツリーの高さです。

したがって、ツリー内のノードの数は、最初のケースではO(n)ですが、2番目のケースでは2^O(n)です。フィボナッチツリーは、入力サイズと比較してはるかに大きい

あなたが言及する マスター定理 ;ただし、この定理は、入力が再帰の各レベルで実際に分割されるアルゴリズムにのみ適用されるため、フィボナッチの複雑さを分析するために適用することはできません。フィボナッチは入力を分割しません。実際、レベルiの関数は、次のレベル_i+1_のほぼ2倍の入力を生成します。

14
kfx

質問の核心である「なぜマージソートではなくフィボナッチなのか」に取り組むには、この決定的な違いに焦点を当てる必要があります。

  • マージソートから取得するツリーには、レベルごとにN個の要素があり、log(n)レベルがあります。
  • F(N)の式にF(n-1)が存在するため、フィボナッチから取得するツリーにはNレベルがあり、各レベルの要素数は大きく異なる可能性があります。非常に低い(根の近く、または最も低い葉の近く)か、非常に高い可能性があります。これは、もちろん、同じ値の計算が繰り返されるためです。

「繰り返し計算」の意味を理解するには、F(6)の計算のツリーを見てください。

fib(6) computation tree
フィボナッチツリーの画像: http://composedprograms.com/pages/28-efficiency.html

F(3)が計算されているのを何回見ますか?

5
wil93

次の実装を検討してください

int fib(int n)
{
    if(n < 2)
      return n;
    return fib(n-1) + fib(n-2)
}

T(n) fibfib(n)を計算するために実行する操作の数を示しましょう。fib(n)fib(n-1)fib(n-2)を呼び出しているため、T(n)は少なくともT(n-1) + T(n-2)です。これはT(n) > fib(n)を意味します。fib(n)の直接式があります。これはnの累乗の定数です。したがってT(n)は少なくとも指数関数的です。QED。

3
Armen Tsirunyan

再帰的アルゴリズムを使用すると、フィボナッチ(N)に対して約2 ^ Nの演算(加算)があります。 それではO(2 ^ N)です。

キャッシュ(メモ化)を使用すると、約N回の操作が可能になり、O(N)

複雑なアルゴリズムO(N log N)は、多くの場合、すべてのアイテム(O(N))の反復、分割再帰、およびマージの組み合わせです... 2で分割=>ログN回の再帰を実行します。

私の理解では、あなたの推論の誤りは、再帰的実装を使用してf(n)を評価することです。ここで、fはフィボナッチ数列を示し、入力サイズは2分の1(またはその他のファクター)、そうではありません。以前に計算された値を再利用する可能性がないため、各呼び出し(「基本ケース」0および1を除く)は正確に2つの再帰呼び出しを使用します。 Wikipedia でのマスター定理の提示に照らして、漸化式

f(n) = f (n-1) + f(n-2)

マスター定理が適用できない場合です。

2
Codor

マージソートの時間計算量はO(n log(n))です。クイックソートのベストケースはO(n log(n))、ワーストケースはO(n ^ 2)です。

他の回答は、ナイーブな再帰フィボナッチがO(2 ^ n)である理由を説明しています。

フィボナッチ(n)がO(log(n))である可能性があることを読んだ場合、これは、行列法またはリュカ数列法のいずれかを使用して反復および反復二乗を使用して計算された場合に可能です。リュカ数列法のコード例(各ループでnが2で除算されることに注意してください):

/* lucas sequence method */
int fib(int n) {
    int a, b, p, q, qq, aq;
    a = q = 1;
    b = p = 0;
    while(1) {
        if(n & 1) {
            aq = a*q;
            a = b*q + aq + a*p;
            b = b*p + aq;
        }
        n /= 2;
        if(n == 0)
            break;
        qq = q*q;
        q = 2*p*q + qq;
        p = p*p + qq;
    }
    return b;
}
2
rcgldr