再帰関数の複雑さの決定(Big O表記)


267

明日はコンピュータサイエンス中間期に参加します。これらの再帰関数の複雑さを判断する手助けが必要です。私は単純なケースを解決する方法を知っていますが、私はまだこれらの難しいケースを解決する方法を学習しようとしています。これらは、私が理解できなかった問題のほんの一例にすぎません。どんな助けでも大いに感謝され、私の研究に大いに役立ちます、ありがとう!

int recursiveFun1(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun1(n-1);
}

int recursiveFun2(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun2(n-5);
}

int recursiveFun3(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun3(n/5);
}

void recursiveFun4(int n, int m, int o)
{
    if (n <= 0)
    {
        printf("%d, %d\n",m, o);
    }
    else
    {
        recursiveFun4(n-1, m+1, o);
        recursiveFun4(n-1, m, o+1);
    }
}

int recursiveFun5(int n)
{
    for (i = 0; i < n; i += 2) {
        // do something
    }

    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun5(n-5);
}

4
毎回分析を行いたくない場合は、マスターメソッドと呼ばれるブラックボックス手法があります。ただし、入力の再帰的な分割はすべて、各インスタンスで同じサイズであるという前提があります。
Vivekクリシュナ


回答:


345

各関数のBig O表記の時間の複雑さは、数値順になっています。

  1. 最初の関数はO(n)、基本ケースに到達する前にn回再帰的に呼び出されるため、しばしば線形と呼ばれます。
  2. 2番目の関数は毎回n-5と呼ばれるため、関数を呼び出す前にnから5を差し引きますが、n-5もO(n)です。(実際にはn / 5回のオーダーと呼ばれます。また、O(n / 5)= O(n))。
  3. この関数はlog(n)の底5であり、関数を呼び出す前に5で除算するたびに、そのO(log(n))(底5)はしばしば対数と呼ばれ、Big O表記法と複雑性分析では底2が使用されます。
  4. 4回目は、n回繰り返されない限り、各関数呼び出しが2回自分自身を呼び出すため、これはO(2^n)、つまり指数関数的です。
  5. 最後の関数については、2ずつ増加しているため、forループはn / 2をとり、再帰はn-5をとり、forループが再帰的に呼び出されるため、時間の複雑さは(n-5)*(n / 2)=(2N-10)* N = 2nの^ 2 -漸近的振る舞いによる10N、最悪の場合のシナリオの考慮事項または上の大きなOがために努力していることを拘束、我々はだけなので、最大用語に興味を持っていますO(n^2)

    中期の頑張ってください;)


5番目についてのあなたの権利、nはforループでは減少しますが、4番目については、再帰を2回呼び出すたびにn ^ 2がツリーのように見えるとは思わないので、2 ^ nプラスになるはずです。以前のコメントで答えてください。
コーダー、

2
@MJGwaterループの実行時間をmとします。再帰的に1回実行すると、ループの実行にmがかかります。再帰的に2回実行すると、ループも2回実行されるため、2分かかるなどです。つまり、「^」ではなく「*」です。
bjc

3
@coder 5の説明は奇妙に思えます。2ずつインクリメントするとループがn/2繰り返さforれるのに、5 ずつデクリメントしてもn/5再帰呼び出しが行われないのはなぜですか。これでも結果は出ますO(n^2)が、より直感的な説明のようです。同じことをすることが不可欠なのに、なぜ減算と除算を組み合わせるのですか?
ジャック

1
@coderなので、#4の場合、関数定義に3つの再帰呼び出しがある場合、時間の複雑さはO(3 ^ n)になりますか?5回の再帰呼び出しの場合、O(5 ^ n)になりますよね?
rmutalik

1
@ジャックはい、私も同じことを考えていました。n/5ないはずn-5です。そして最終的には、全体がに低下しO(N^2)ます。
アヌジ

128

場合についてはn <= 0T(n) = O(1)。したがって、時間の複雑さはいつに依存しn >= 0ます。

n >= 0以下の部分でケースを検討します。

1。

T(n) = a + T(n - 1)

ここで、aは定数です。

誘導によって:

T(n) = n * a + T(0) = n * a + b = O(n)

ここで、a、bは定数です。

2。

T(n) = a + T(n - 5)

ここで、aは定数です

誘導によって:

T(n) = ceil(n / 5) * a + T(k) = ceil(n / 5) * a + b = O(n)

ここで、a、bは定数で、k <= 0

3。

T(n) = a + T(n / 5)

ここで、aは定数です

誘導によって:

T(n) = a * log5(n) + T(0) = a * log5(n) + b = O(log n)

ここで、a、bは定数です

4。

T(n) = a + 2 * T(n - 1)

ここで、aは定数です

誘導によって:

T(n) = a + 2a + 4a + ... + 2^(n-1) * a + T(0) * 2^n 
     = a * 2^n - a + b * 2^n
     = (a + b) * 2^n - a
     = O(2 ^ n)

ここで、a、bは定数です。

5。

T(n) = n / 2 + T(n - 5)

ここで、nは定数です

n = 5q + rqとrが整数で、r = 0、1、2、3、4の場合、書き換え

T(5q + r) = (5q + r) / 2 + T(5 * (q - 1) + r)

q = (n - r) / 5あり、r <5であるため、定数と見なすことができるため、q = O(n)

誘導によって:

T(n) = T(5q + r)
     = (5q + r) / 2 + (5 * (q - 1) + r) / 2 + ... + r / 2 +  T(r)
     = 5 / 2 * (q + (q - 1) + ... + 1) +  1 / 2 * (q + 1) * r + T(r)
     = 5 / 4 * (q + 1) * q + 1 / 2 * (q + 1) * r + T(r)
     = 5 / 4 * q^2 + 5 / 4 * q + 1 / 2 * q * r + 1 / 2 * r + T(r)

r <4なので、定数bを見つけることができるため、 b >= T(r)

T(n) = T(5q + r)
     = 5 / 2 * q^2 + (5 / 4 + 1 / 2 * r) * q + 1 / 2 * r + b
     = 5 / 2 * O(n ^ 2) + (5 / 4 + 1 / 2 * r) * O(n) + 1 / 2 * r + b
     = O(n ^ 2)

1
私は最近、再帰的なフィボナッチ関数の時間と空間の複雑さの分析に関連するインタビューの質問に(そしてインタビューを拡張することによって)失敗しました。この答えは叙事詩であり、それは多くの助けとなりました、私はそれが大好きです、私はあなたに2票を投じることができればいいのにと思います。私はそれが古いことを知っていますが、スペースを計算するために似ているものはありますか-おそらくリンク、何かですか?
Dimitar Dimitrov 2013

No.4は同じ結果なのに次のような誘導ではないでしょうか?T(n)= a + 2T(n-1)= a + 2a + 4T(n-1)= 3a + 4a + 8T(n-1)= a *(2 ^ n-1)+ 2 ^ n * T(0)= a *(2 ^ n-1)+ b * 2 ^ n =(a + b)* 2 ^ n-a = O(2 ^ n)
Snowfish

27

再帰アルゴリズムの複雑さを概算するために私が見つけた最良の方法の1つは、再帰ツリーを描画することです。再帰的なツリーを取得したら、次のようにします。

Complexity = length of tree from root node to leaf node * number of leaf nodes
  1. 最初の関数はnリーフノードの長さと数を持つ1ため、複雑さはn*1 = n
  2. 2番目の関数にはn/5リーフノードの長さと数が再び1含まれるため、複雑さが増しますn/5 * 1 = n/5。に近似する必要がありますn

  3. 3番目の関数の場合、nは再帰呼び出しごとに5で除算されるためlog(n)(base 5)、再帰ツリーの長さはになり、リーフノードの数も1になるため、複雑さはlog(n)(base 5) * 1 = log(n)(base 5)

  4. 第4の機能のためのすべてのノードは、2つのつの子ノードを有しているので、リーフノードの数に等しくなる(2^n)と、再帰的ツリーの長さであろうn複雑さになるよう(2^n) * n。しかし、のn前では重要ではないため(2^n)、無視することができ、複雑さはとしか言えません(2^n)

  5. 5番目の関数には、複雑さを導入する2つの要素があります。関数の再帰的な性質によって導入される複雑さとfor、各関数のループによって導入される複雑さ。上記の計算を行うと、関数の再帰的な性質によって導入される~ n複雑さはforループのために複雑になりますn。全体の複雑さはになりますn*n

注:これは複雑さを計算するための迅速で汚い方法です(公式はありません!)。これについてのフィードバックを聞きたいです。ありがとう。


正解です。4つ目の機能について質問があります。3つの再帰呼び出しがあった場合、答えは(3 ^ n)になります。それとも(2 ^ n)と言うだけですか?
Ben Forsrup、2018年

@Shubham:#4は私には正しくないようです。葉の数が2^n木の場合n、木の高さではなく、でなければなりませんlog n。高さは、ツリー内のノードの総数を表すlog n場合のみnです。しかし、そうではありません。
ジュリアンA.

@BenForsrup:各ノードには3つの子ノードがあるため、3 ^ nになります。これを確実にする最良の方法は、ダミー値を使用して自分で再帰ツリーを描画することです。
Shubham 2018年

#2はn / 5ではなくn-5である必要があります
Fintasys、

7

これは数学的に証明できますが、これは上記の回答で欠けていたものです。

これは、メソッドの計算方法を劇的に理解するのに役立ちます。方法を完全に理解するために、上から下に読むことをお勧めします。

  1. T(n) = T(n-1) + 1それがフィニッシュまでの方法にかかる時間が同じ方法と同じであることを意味しますが、N-1であるT(n-1)と我々は今追加し+ 1、それが(除いて完了するための一般的な操作にかかる時間だからT(n-1))。ここで、T(n-1)次のように検索しますT(n-1) = T(n-1-1) + 1。完全に理解できるように、ある種の繰り返しを提供できる関数を作成できるようになりました。メソッドT(n-1) = ...T(n-1)内部ではなく、右側に配置しますT(n) = ...T(n) = T(n-1-1) + 1 + 1これはT(n) = T(n-2) + 2、つまり、不足しているk:を見つける必要がありますT(n) = T(n-k) + k。次のステップは、取ることであるn-kこととする請求項n-k = 1ため再帰の終了時に、それがかかります正確O(1)場合n<=0。この簡単な方程式から、次のことがわかりk = n - 1ます。レッツ・場所k:私たちの最終的な方法でT(n) = T(n-k) + k私たちを与える:T(n) = 1 + n - 1正確ですnO(n)
  2. 1と同じです。自分でテストして、が得られることを確認できますO(n)
  3. T(n) = T(n/5) + 1前と同じように、このメソッドが完了するまでの時間は、同じメソッドの時間と同じn/5ですが、それがに制限されている理由T(n/5)です。レッツ・検索T(n/5)1のように:T(n/5) = T(n/5/5) + 1ですT(n/5) = T(n/5^2) + 1。レッツ・場所T(n/5)内部のT(n)最終的な計算のために:T(n) = T(n/5^k) + k。繰り返しますが、n/5^k = 1これはn = 5^k、5の累乗で何がnになるかを尋ねるのとまったく同じlog5n = kです。答えは(底5の対数)です。でレッツ・場所私たちの調査結果をT(n) = T(n/5^k) + k次のように:T(n) = 1 + lognありO(logn)
  4. T(n) = 2T(n-1) + 1私たちがここに持っていることは、私たちは2レッツ・検索することにより、複数のそのよう基本的に以前と同じですが、我々はメソッドを呼び出しているこの時間は、再帰的に2倍です。LETの場所、以前のように私たちの次の場所我々の発見:あるそれは私たちを提供します。レッツ・検索することを主張することによってどれがあります。レッツ・場所次のように:おおよそですT(n-1) = 2T(n-1-1) + 1T(n-1) = 2T(n-2) + 1T(n) = 2(2T(n-2)) + 1 + 1T(n) = 2^2T(n-2) + 2T(n) = 2^kT(n-k) + kkn-k = 1k = n - 1kT(n) = 2^(n-1) + n - 1O(2^n)
  5. T(n) = T(n-5) + n + 1これは4とほとんど同じですが、ループnが1つなので、追加しforます。T(n-5) = T(n-5-5) + n + 1どちらであるかを見つけましょうT(n-5) = T(n - 2*5) + n + 1。レッツ・場所を:T(n) = T(n-2*5) + n + n + 1 + 1)T(n) = T(n-2*5) + 2n + 2)あり、kのために:T(n) = T(n-k*5) + kn + k)再び:n-5k = 1あるn = 5k + 1それはおおよそですn = k。これは私達を与える:T(n) = T(0) + n^2 + nおおよそですO(n^2)

残りの回答を読むことをお勧めします。これにより、より良い視点が得られます。それらの大きなOを獲得する幸運:)


1

ここで重要なのは、呼び出しツリーを視覚化することです。一度実行すると、複雑さは次のようになります。

nodes of the call tree * complexity of other code in the function

後者の項は、通常の反復関数の場合と同じ方法で計算できます。

代わりに、完全なツリーの合計ノードは次のように計算されます

                  C^L - 1
                  -------  , when C>1
               /   C - 1
              /
 # of nodes =
              \    
               \ 
                  L        , when C=1

ここで、Cは各ノードの子の数、Lはツリーのレベルの数(ルートを含む)です。

ツリーを視覚化するのは簡単です。最初の呼び出し(ルートノード)から開始し、関数の再帰呼び出しの数と同じ数の子を描画します。サブコールに渡されるパラメータを「ノードの値」として記述することも役立ちます。

したがって、上記の例では:

  1. ここでの呼び出しツリーはC = 1、L = n + 1です。残りの関数の複雑さはO(1)です。したがって、総複雑度はL * O(1)=(n + 1)* O(1)= O(n)です。
n     level 1
n-1   level 2
n-2   level 3
n-3   level 4
... ~ n levels -> L = n
  1. ここでの呼び出しツリーはC = 1、L = n / 5です。残りの関数の複雑さはO(1)です。したがって、総複雑度はL * O(1)=(n / 5)* O(1)= O(n)です。
n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5
  1. ここでの呼び出しツリーはC = 1、L = log(n)です。残りの関数の複雑さはO(1)です。したがって、総複雑度はL * O(1)= log5(n)* O(1)= O(log(n))です。
n
n/5
n/5^2
n/5^3
... ~ log5(n) levels -> L = log5(n)
  1. ここでの呼び出しツリーはC = 2、L = nです。残りの関数の複雑さはO(1)です。今回は、C> 1であるため、コールツリーのノード数に完全な式を使用します。したがって、全体の複雑さは(C ^ L-1)/(C-1)* O(1)=(2 ^ n-1 )* O(1)= O(2 ^ n)
               n                   level 1
      n-1             n-1          level 2
  n-2     n-2     n-2     n-2      ...
n-3 n-3 n-3 n-3 n-3 n-3 n-3 n-3    ...     
              ...                ~ n levels -> L = n
  1. ここでの呼び出しツリーはC = 1、L = n / 5です。残りの関数の複雑さはO(n)です。したがって、総複雑度はL * O(1)=(n / 5)* O(n)= O(n ^ 2)です。
n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.