再帰アルゴリズムの時間の複雑さを計算するにはどうすればよいですか?
int pow1(int x,int n) {
if(n==0){
return 1;
}
else{
return x * pow1(x, n-1);
}
}
int pow2(int x,int n) {
if(n==0){
return 1;
}
else if(n&1){
int p = pow2(x, (n-1)/2)
return x * p * p;
}
else {
int p = pow2(x, n/2)
return p * p;
}
}
再帰関数の分析(またはそれらの評価さえ)は、重要な作業です。 (私の意見では)良い紹介はドン・クヌース コンクリート数学 にあります。
ただし、これらの例を分析してみましょう。
関数に必要な時間を与える関数を定義します。 t(n)
がpow(x,n)
に必要な時間、つまりn
の関数を表すとしましょう。
次に、そのt(0)=c
を結論付けることができます。これは、pow(x,0)
を呼び出す場合、(_n==0
_)かどうかを確認し、1を返す必要があるためです。これは、定数で実行できます。時間(したがって、定数c
)。
次に、_n>0
_という別のケースを考えます。ここではt(n) = d + t(n-1)
を取得します。これは、_n==1
_を再度チェックし、_pow(x, n-1
_を計算する必要があるため、(t(n-1)
)、結果にx
を乗算する必要があるためです。チェックと乗算は一定の時間(定数d
)で実行でき、pow
の再帰的な計算にはt(n-1)
が必要です。
これで、t(n)
という用語を「拡張」できます。
_t(n) =
d + t(n-1) =
d + (d + t(n-2)) =
d + d + t(n-2) =
d + d + d + t(n-3) =
... =
d + d + d + ... + t(1) =
d + d + d + ... + c
_
では、t(1)
に到達するまでにはどのくらい時間がかかりますか? t(n)
から開始し、各ステップで1を引くため、t(n-(n-1)) = t(1)
に到達するには_n-1
_ステップが必要です。これは、一方で、定数d
の_n-1
_倍を取得し、t(1)
がc
に評価されることを意味します。
したがって、以下を取得します。
_t(n) =
...
d + d + d + ... + c =
(n-1) * d + c
_
したがって、O(n)の要素であるt(n)=(n-1) * d + c
を取得します。
_pow2
_は Masters theorem を使用して実行できます。アルゴリズムの時間関数は単調増加していると想定できるためです。これで、t(n)
の計算に必要な時間pow2(x,n)
が得られました。
_t(0) = c (since constant time needed for computation of pow(x,0))
_
_n>0
_の場合、
_ / t((n-1)/2) + d if n is odd (d is constant cost)
t(n) = <
\ t(n/2) + d if n is even (d is constant cost)
_
上記は、次のように「簡略化」できます。
_t(n) = floor(t(n/2)) + d <= t(n/2) + d (since t is monotonically increasing)
_
したがって、t(n) <= t(n/2) + d
を取得します。これは、マスター定理を使用してt(n) = O(log n)
に解くことができます(Wikipediaリンクの「一般的なアルゴリズムへの適用」のセクション、例「バイナリ検索」を参照)。
それは最も単純なものなので、pow1から始めましょう。
O(1)で単一の実行が行われる関数があります。 (条件チェック、戻り、乗算は一定の時間です。)
あなたが残したのはあなたの再帰です。あなたがする必要があるのは、関数が自分自身を呼び出すことになる頻度を分析することです。 pow1では、N回発生します。 N * O(1)= O(N)。
Pow2の場合も同じ原則です。関数の1回の実行はO(1)で実行されます。ただし、今回は毎回Nを半分にします。つまり、log2(N)回実行されます-実際にはビットごとに1回実行されます。 log2(N)* O(1)= O(log(N))。
あなたに役立つかもしれない何かは、再帰が常に反復として表現できるという事実を活用することです(常に非常に単純ではありませんが、それは可能です。私たちはpow1を
result = 1;
while(n != 0)
{
result = result*n;
n = n - 1;
}
これで、代わりに反復アルゴリズムができました。そのように分析する方が簡単な場合があります。
少し複雑かもしれませんが、通常は 修士定理 を使用する方法だと思います。
再帰を無視する両方の関数の複雑さはO(1)です
最初のアルゴリズムの場合、pow1(x、n)の複雑度はO(n))です。これは、再帰の深さがnと線形に相関するためです。
2番目の複雑さはO(log n)です。ここでは、約log2(n)回再帰しています。 2をスローすると、log nが取得されます。
したがって、xをn乗するのではないかと思います。 pow1はO(n)を取ります。
Xの値を変更することはありませんが、1になるまで毎回nから1を取得します(そして、次に戻るだけです)これは、再帰呼び出しをn回行うことを意味します。