Big-O表記は理解できますが、多くの関数で計算する方法がわかりません。特に、フィボナッチ数列の単純なバージョンの計算の複雑さを理解しようと試みてきました。
int Fibonacci(int n)
{
if (n <= 1)
return n;
else
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
フィボナッチ数列の計算の複雑さはどのくらいで、どのように計算されますか?
Big-O表記は理解できますが、多くの関数で計算する方法がわかりません。特に、フィボナッチ数列の単純なバージョンの計算の複雑さを理解しようと試みてきました。
int Fibonacci(int n)
{
if (n <= 1)
return n;
else
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
フィボナッチ数列の計算の複雑さはどのくらいで、どのように計算されますか?
回答:
あなたは計算に時間関数をモデルFib(n)
計算するための時間の合計としてFib(n-1)
計算するには、プラス時間Fib(n-2)
(一緒にそれらを追加するには、プラス時間O(1)
)。これは、同じものを繰り返し評価するFib(n)
のに同じ時間がかかる、つまりメモを使用しないことを前提としています。
T(n<=1) = O(1)
T(n) = T(n-1) + T(n-2) + O(1)
この再帰関係を(たとえば、生成関数を使用して)解決すると、答えが得られます。
または、深さを持ちn
、この関数が漸近的になることを直感的に理解する再帰ツリーを描画することもできます。次に、帰納法によって推測を証明できます。O(2
n
)
ベース:n = 1
明らか
想定、したがって、T(n-1) = O(2
n-1
)
T(n) = T(n-1) + T(n-2) + O(1)
に等しい
T(n) = O(2
n-1
) + O(2
n-2
) + O(1) = O(2
n
)
ただし、コメントに記載されているように、これは厳しい制限ではありません。この機能についての興味深い事実は、T(n)が漸近的であることである同一の値としてFib(n)
、両方のように定義されているので
f(n) = f(n-1) + f(n-2)
。
再帰ツリーの葉は常に1を返します。の値はFib(n)
、再帰ツリーの葉によって返されるすべての値の合計で、葉の数と同じです。各葉は計算にO(1)を使用するため、T(n)
はに等しくなりFib(n) x O(1)
ます。したがって、この関数のタイトな境界はフィボナッチ数列自体(〜)です。上記で述べたように生成関数を使用することで、このタイトな境界を見つけることができます。θ(1.6
n
)
F(n)
完了するために実行する必要があるステートメントの数を自問してください。
以下の場合F(1)
、答えは1
(条件付きの最初の部分)。
以下の場合F(n)
、答えはありますF(n-1) + F(n-2)
。
それでは、どの関数がこれらのルールを満たしていますか?a n(a> 1)を試してください:
a n == a (n-1) + a (n-2)
(n-2)で除算する:
a 2 == a + 1
解決するa
と(1+sqrt(5))/2 = 1.6180339887
、黄金比とも呼ばれます。
したがって、指数関数的な時間がかかります。
私はpgaurとrickerbhに同意します。再帰的なフィボナッチの複雑さはO(2 ^ n)です。
私はかなり単純化して同じ結論に達しましたが、私はまだ有効な推論を信じています。
まず、N番目のフィボナッチ数を計算するときに、再帰的なフィボナッチ関数(F()が今後何回呼び出されるか)を把握することがすべてです。0からnのシーケンスで数値ごとに1回呼び出されると、O(n)が得られ、数値ごとにn回呼び出されると、O(n * n)、またはO(n ^ 2)が得られます。等々。
したがって、nに対してF()が呼び出されると、0からn-1までの任意の数に対してF()が呼び出される回数は、0に近づくにつれて大きくなります。
第一印象として、視覚的に言えば、与えられた数値に対してF()が呼び出されるたびにユニットを描画すると、一種のピラミッド形状がウェットになる(つまり、ユニットを水平方向に中央揃えすると) )。このようなもの:
n *
n-1 **
n-2 ****
...
2 ***********
1 ******************
0 ***************************
さて、問題は、nが大きくなるにつれて、このピラミッドのベースがどれだけ速く拡大するかです。
実際のケースを考えてみましょう、例えばF(6)
F(6) * <-- only once
F(5) * <-- only once too
F(4) **
F(3) ****
F(2) ********
F(1) **************** <-- 16
F(0) ******************************** <-- 32
F(0)が32回呼び出されることがわかります。これは2 ^ 5です。このサンプルケースでは2 ^(n-1)です。
ここで、F(x)が呼び出される回数を知りたいと思います。F(0)が呼び出される回数はその一部にすぎません。
すべての*をF(6)行からF(2)行にF(1)行に精神的に移動すると、F(1)行とF(0)行の長さが等しくなっていることがわかります。つまり、n = 6が2x32 = 64 = 2 ^ 6の場合、F()の合計時間が呼び出されます。
さて、複雑さに関しては:
O( F(6) ) = O(2^6)
O( F(n) ) = O(2^n)
MITでは、この特定の問題について非常に素晴らしい議論があります。5ページでは、加算が1計算単位をとると仮定すると、Fib(N)の計算に必要な時間はFib(N)の結果と非常に密接に関連していると彼らは指摘しています。
その結果、フィボナッチシリーズの非常に近い近似に直接スキップできます。
Fib(N) = (1/sqrt(5)) * 1.618^(N+1) (approximately)
したがって、単純なアルゴリズムの最悪の場合のパフォーマンスは
O((1/sqrt(5)) * 1.618^(N+1)) = O(1.618^(N+1))
PS:詳細については、WikipediaでN番目のフィボナッチ数の閉形式の表現についての議論があります。
あなたはそれを拡張して視覚化することができます
T(n) = T(n-1) + T(n-2) <
T(n-1) + T(n-1)
= 2*T(n-1)
= 2*2*T(n-2)
= 2*2*2*T(n-3)
....
= 2^i*T(n-i)
...
==> O(2^n)
<
最後に「より小」の文字があるのですか?どうやって手に入れたのT(n-1) + T(n-1)
?
T(n-1) > T(n-2)
だから私は変更T(n-2)
して置くことができますT(n-1)
。私はまだ有効な上限のみを取得しますT(n-1) + T(n-2)
証明の答えは良いですが、私は本当に自分自身を納得させるために常に手で数回の反復を行わなければなりません。そこで、ホワイトボードに小さな呼び出しツリーを描き、ノードの数を数え始めました。カウントを合計ノード、リーフノード、内部ノードに分割します。ここに私が得たものがあります:
IN | OUT | TOT | LEAF | INT
1 | 1 | 1 | 1 | 0
2 | 1 | 1 | 1 | 0
3 | 2 | 3 | 2 | 1
4 | 3 | 5 | 3 | 2
5 | 5 | 9 | 5 | 4
6 | 8 | 15 | 8 | 7
7 | 13 | 25 | 13 | 12
8 | 21 | 41 | 21 | 20
9 | 34 | 67 | 34 | 33
10 | 55 | 109 | 55 | 54
すぐに飛び出すのは、葉ノードの数がであることですfib(n)
。さらにいくつかの反復に気づいたのは、内部ノードの数がであることですfib(n) - 1
。したがって、ノードの総数は2 * fib(n) - 1
です。
計算の複雑さを分類するときに係数を削除するため、最終的な答えはθ(fib(n))
です。
1
単一のアキュムレータFib(n)
時間に追加するだけでなく、それがまだ正確であることは興味深いですθ(Fib(n))
。
0
が、:再帰ベースケースがある0
と1
彼らが行うので、Fib(n-1) + Fib(n-2)
。だから、おそらく3 * Fib(n) - 2
から、このリンク-唯一の答えは、全ノード数のため、より正確ではありません2 * Fib(n) - 1
。
再帰アルゴリズムの時間の複雑さは、再帰ツリーを描画することでより適切に推定できます。この場合、再帰ツリーを描画するための再帰関係はT(n)= T(n-1)+ T(n-2)+ O(1)になります。各ステップは、一定の時間を意味するO(1)を使用します。これは、ブロックの場合、nの値をチェックするための比較は1つだけなので、再帰ツリーは次のようになります。
n
(n-1) (n-2)
(n-2)(n-3) (n-3)(n-4) ...so on
ここで、上のツリーの各レベルがiで表されるとしましょう。
i
0 n
1 (n-1) (n-2)
2 (n-2) (n-3) (n-3) (n-4)
3 (n-3)(n-4) (n-4)(n-5) (n-4)(n-5) (n-5)(n-6)
特定のiの値でツリーが終了するとしましょう。その場合、ni = 1、つまりi = n-1の場合になり、ツリーの高さがn-1になります。ここで、ツリー内のn個のレイヤーのそれぞれについてどれだけの作業が行われたかを確認します。再帰関係で述べたように、各ステップにはO(1)時間かかることに注意してください。
2^0=1 n
2^1=2 (n-1) (n-2)
2^2=4 (n-2) (n-3) (n-3) (n-4)
2^3=8 (n-3)(n-4) (n-4)(n-5) (n-4)(n-5) (n-5)(n-6) ..so on
2^i for ith level
i = n-1は各レベルで行われる木の作業の高さであるため
i work
1 2^1
2 2^2
3 2^3..so on
したがって、行われた総作業量は、各レベルで行われた作業の合計になります。したがって、i = n-1なので、2 ^ 0 + 2 ^ 1 + 2 ^ 2 + 2 ^ 3 ... + 2 ^(n-1)になります。幾何級数では、この合計は2 ^ nなので、ここでの合計時間の複雑さはO(2 ^ n)です。
フィボナッチの単純な再帰バージョンは、計算が繰り返されるため、設計上指数関数的です。
ルートで計算しています:
F(n)はF(n-1)とF(n-2)に依存します
F(n-1)は再びF(n-2)とF(n-3)に依存します
F(n-2)は再びF(n-3)とF(n-4)に依存します
次に、各レベルで、計算で大量のデータを浪費している再帰呼び出しがあり、時間関数は次のようになります。
T(n)= T(n-1)+ T(n-2)+ C、C定数あり
T(n-1)= T(n-2)+ T(n-3)> T(n-2)次に
T(n)> 2 * T(n-2)
...
T(n)> 2 ^(n / 2)* T(1)= O(2 ^(n / 2))
これは下限であり、分析の目的には十分ですが、リアルタイム関数は同じフィボナッチ公式による定数の係数であり、閉じた形は黄金比の指数関数であることがわかっています。
さらに、次のような動的プログラミングを使用して、フィボナッチの最適化バージョンを見つけることができます。
static int fib(int n)
{
/* memory */
int f[] = new int[n+1];
int i;
/* Init */
f[0] = 0;
f[1] = 1;
/* Fill */
for (i = 2; i <= n; i++)
{
f[i] = f[i-1] + f[i-2];
}
return f[n];
}
これは最適化され、nステップしか実行しませんが、指数関数的でもあります。
コスト関数は、問題を解決するための入力サイズからステップ数まで定義されます。フィボナッチの動的バージョン(テーブルを計算するnステップ)または数値が素数かどうかを知る最も簡単なアルゴリズム(sqrt(n)で数値の有効な約数を分析)が表示された場合。これらのアルゴリズムはO(n)またはO(sqrt(n))であると考えるかもしれませんが、これは次の理由で単純に当てはまりません:アルゴリズムへの入力は数値です:n、バイナリ表記を使用して、整数nはlog2(n)であり、変数の変更を行う
m = log2(n) // your real input size
入力サイズの関数としてステップ数を見つけましょう
m = log2(n)
2^m = 2^log2(n) = n
次に、入力サイズの関数としてのアルゴリズムのコストは次のとおりです。
T(m) = n steps = 2^m steps
これがコストが指数関数的である理由です。
関数呼び出しを図式化することで計算するのは簡単です。nの各値に対して関数呼び出しを追加し、数がどのように増加するかを確認します。
ビッグOはO(Z ^ n)で、Zは黄金比または約1.62です。
レオナルド数とフィボナッチ数はどちらも、nを増やすとこの比率に近づきます。
他のBig Oの質問とは異なり、入力にばらつきはなく、アルゴリズムとアルゴリズムの実装の両方が明確に定義されています。
複雑な数学の束は必要ありません。以下の関数呼び出しを簡単に説明し、関数を数値に合わせます。
または、黄金比に精通している場合は、そのように認識します。
この答えは、f(n)= 2 ^ nに近づくと主張する受け入れられた答えよりも正確です。それは決してありません。f(n)= golden_ratio ^ nに近づきます。
2 (2 -> 1, 0)
4 (3 -> 2, 1) (2 -> 1, 0)
8 (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
(2 -> 1, 0)
14 (5 -> 4, 3) (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
(2 -> 1, 0)
(3 -> 2, 1) (2 -> 1, 0)
22 (6 -> 5, 4)
(5 -> 4, 3) (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
(2 -> 1, 0)
(3 -> 2, 1) (2 -> 1, 0)
(4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
(2 -> 1, 0)
http://www.ics.uci.edu/~eppstein/161/960109.html
時間(n)= 3F(n)-2