フィボナッチ数列のn番目の項を計算できるさまざまな方法で(Javaで)コードを実装しようとしましたが、学んだことを確認したいと思っています。
反復実装は次のとおりです。
public int iterativeFibonacci(int n)
{
if ( n == 1 ) return 0;
else if ( n == 2 ) return 1;
int i = 0, j = 1, sum = 0;
for ( ; (n-2) != 0; --n )
{
sum = i + j;
i = j;
j = sum;
}
return sum;
}
再帰的な実装は次のとおりです:-
public int recursiveFibonacci(int n)
{
if ( n == 1 ) return 0;
else if ( n == 2 ) return 1;
return recursiveFibonacci(n-1) + recursiveFibonacci(n-2);
}
メモ化された実装は次のとおりです:-
public int memoizedFibonacci(int n)
{
if ( n <= 0 ) return -1;
else if ( n == 1 ) return 0;
else if ( n == 2 ) return 1;
if ( memory[n-1] == 0 )
memory[n-1] = memoizedFibonacci(n-1);
if ( memory[n-2] == 0 )
memory[n-2] = memoizedFibonacci(n-2);
return memory[n-1]+memory[n-2];
}
これらの実装のBig-Oを理解しようとすると、少し疑問があります。 反復実装はO(n)であると私は信じています。これは、N-2回ループするためです。
再帰関数には再計算された値があるので、O(n ^ 2)だと思います。
メモ化された関数では、メモ化に基づいて値の半分以上にアクセスします。 問題空間をわずかに減らすのに一定の時間がかかる場合、アルゴリズムはO(log N)ですそしてアルゴリズムはO(N)問題のあるスペースを一定の量だけ減らすのに一定の時間がかかる場合。記憶された実装はO(n)複雑であると私は信じていますか?もしそうなら、反復的な実装は、3つすべての中で最良ではないでしょうか?(メモ化に必要な追加のメモリを使用しないため)。
再帰バージョンは多項式時間ではありません-指数関数的です φで厳密に制限されていますn ここで、φは黄金比(≈1.618034)です。再帰バージョンは[〜#〜] o [〜#〜](n)メモリを使用します(使用量はスタックから取得されます)。
メモ化バージョンは、各数値が1回しか計算されないため、最初の実行時に[〜#〜] o [〜#〜](n)時間がかかります。ただし、代わりに、現在の実装では[〜#〜] o [〜#〜](n)メモリも必要です(n =計算された値を格納すること、および最初の実行時のスタックから取得されます)。何度も実行すると、時間計算量は[〜#〜] o [〜#〜]([〜#〜] m [〜#〜] + q)ここで、[〜#〜] m [〜#〜]はすべての入力の最大値ですnおよびq =はクエリの数です。メモリの複雑さは[〜#〜] o [〜#〜]([〜#〜] m [〜#〜])になります。これは、次の配列に由来します。計算されたすべての値を保持します。
反復実装は、1回の実行を検討する場合に最適です。これは、[〜#〜] o [〜#〜](n)でも実行されますが、一定量を使用するためです。計算するメモリの[〜#〜] o [〜#〜](1)。多数の実行の場合、すべてが再計算されるため、パフォーマンスはメモ化バージョンほど良くない可能性があります。
(ただし、実際には、パフォーマンスとメモリの問題が発生するずっと前に、64ビット整数でも数値がオーバーフローする可能性が高いため、完全な数値を計算する場合は、加算にかかる時間を正確に分析する必要があります) 。
Plesivが述べたように、フィボナッチ数は、行列の乗算(と同じトリックを使用)によって[〜#〜] o [〜#〜](log n)で計算することもできます。すべてのステップで指数を半分にすることによる高速なべき乗)。
行列乗算を使用してフィボナッチ数を見つけるためのJava実装は次のとおりです。
private static int fibonacci(int n)
{
if(n <= 1)
return n;
int[][] f = new int[][]{{1,1},{1,0}};
//for(int i = 2; i<=n;i++)
power(f,n-1);
return f[0][0];
}
// method to calculate power of the initial matrix (M = [][]{{1,1},{1,0}})
private static void power(int[][] f, int n)
{
int[][] m = new int[][]{{1,1},{1,0}};
for(int i = 2; i<= n; i++)
multiply(f, m);
}
// method to multiply two matrices
private static void multiply(int[][] f, int[][] m)
{
int x = f[0][0] * m[0][0] + f[0][1] * m[1][0];
int y = f[0][0] * m[0][1] + f[0][1] * m[1][1];
int z = f[1][0] * m[0][0] + f[1][1] * m[1][0];
int w = f[1][0] * m[0][1] + f[1][1] * m[1][1];
f[0][0] = x;
f[0][1] = y;
f[1][0] = z;
f[1][1] = w;
}
フィボナッチ数を計算するための行列指数法よりもさらに高速な方法は、高速ダブリング法です。両方の方法の償却時間計算量はO(logn)です。この方法は次の式に従いますF(2n) = F(n)[2 * F(n + 1)– F(n)] F(2n + 1)= F(n) 2 + F(n + 1)2
そのようなJava FastDoublingメソッドの実装は次のとおりです。
private static void fastDoubling(int n, int[] ans)
{
int a, b,c,d;
// base case
if(n == 0)
{
ans[0] = 0;
ans[1] = 1;
return;
}
fastDoubling((n/2), ans);
a = ans[0]; /* F(n) */
b = ans[1]; /* F(n+1) */
c = 2*b-a;
if(c < 0)
c += MOD;
c = (a*c) % MOD; /* F(2n) */
d = (a*a + b*b) % MOD ; /* F(2n+1) */
if(n%2 == 0)
{
ans[0] = c;
ans[1] = d;
}
else
{
ans[0] = d;
ans[1] = (c+d);
}
}