363

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

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

6 に答える 6

499

各関数の時間計算量(Big O表記):


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

この関数は、基本ケースに到達する前にn回再帰的に呼び出されるため、線形O(n)と呼ばれることがよくあります。


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

この関数は毎回n-5と呼ばれるため、関数を呼び出す前にnから5を差し引きますが、n-5もO(n)です。(実際にはn / 5回の順序と呼ばれます。そして、O(n / 5)= O(n))。


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

この関数はlog(n)base 5であり、関数を呼び出す前に5で割るたびに、その(base 5)は対数O(log(n))と呼ばれ、ほとんどの場合BigO表記と複雑さの分析ではbase2が使用されます。


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);
    }
}

ここでは、O(2^n)n繰り返されない限り、各関数呼び出しがそれ自体を2回呼び出すため、これは指数関数的です。



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

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

ここで、forループは2ずつ増加しているため、n / 2を取り、再帰はn / 5を取り、forループは再帰的に呼び出されるため、時間計算量は次のようになります。

(n / 5)*(n / 2)= n ^ 2/10、

漸近的な振る舞いと最悪のシナリオの考慮事項、または大きなOが目指している上限のために、私たちは最大の用語にのみ関心がありO(n^2)ます。


あなたの中間期に頑張ってください;)

于 2012-11-20T06:37:40.307 に答える
144

n <= 0、の場合T(n) = O(1)。したがって、時間計算量はいつになるかによって異なりますn >= 0

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

1.1。

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

ここで、aは定数です。

誘導による:

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

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

2.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.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.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.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)
于 2012-11-20T07:55:00.517 に答える
42

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

Complexity = length of tree from root node to leaf node * number of leaf nodes
  1. n最初の関数にはリーフノードの長さと数が1あるため、複雑さは次のようになります。n*1 = n
  2. n/52番目の関数は、リーフノードの長さと数を再び持つ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

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

于 2017-05-16T01:29:31.107 に答える
20

私たちはそれを数学的に証明することができます。これは私が上記の答えに欠けていたものです。

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

  1. T(n) = T(n-1) + 1これは、メソッドが完了するのにかかる時間は同じメソッドと同じですが、n-1を使用することを意味します。これは、一般的な操作が完了するのにかかる時間であるため、ここでT(n-1)追加します(を除く)。ここで、次のように検索します。完全に理解できるように、ある種の繰り返しを与えることができる関数を形成できるようになりました。メソッド内ではなく、右側を配置します。これにより、次のようになります。つまり、欠落しているものを見つける必要があります。次のステップは、再帰の終わりに正確にO(1)がかかるため、次のように主張することです。+ 1T(n-1)T(n-1)T(n-1) = T(n-1-1) + 1T(n-1) = ...T(n-1)T(n) = ...T(n) = T(n-1-1) + 1 + 1T(n) = T(n-2) + 2kT(n) = T(n-k) + kn-kn-k = 1n<=0。この単純な方程式から、次のことがわかりk = n - 1ます。k最後のメソッドに配置しましょう:これはT(n) = T(n-k) + k私たちに与えます:T(n) = 1 + n - 1これは正確にnまたはO(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)内部に配置しましょう: 。繰り返しますが、これは5の累乗で何を求めるのとまったく同じであり、nが得られます。答えは、(5を底とする対数)です。私たちの調査結果を次のように配置しましょう:これはT(n)T(n) = T(n/5^k) + kn/5^k = 1n = 5^klog5n = kT(n) = T(n/5^k) + kT(n) = 1 + lognO(logn)
  4. T(n) = 2T(n-1) + 1ここにあるものは基本的に以前と同じですが、今回はメソッドを2回再帰的に呼び出しているので、2を掛けます。T(n-1) = 2T(n-1-1) + 1どちらがであるかを見つけましょうT(n-1) = 2T(n-2) + 1。前と同じように次の場所に、私たちの発見を配置しましょう:T(n) = 2(2T(n-2)) + 1 + 1それはT(n) = 2^2T(n-2) + 2私たちに与えT(n) = 2^kT(n-k) + kます。であるとk主張して見つけましょう。次のように配置しましょう:これは大まかにですn-k = 1k = n - 1kT(n) = 2^(n-1) + n - 1O(2^n)
  5. T(n) = T(n-5) + n + 14とほぼ同じですが、ループnが1つあるため、ここで追加します。どれがであるかをfor見つけましょう。それを配置しましょう:これはであり、k:についても:これは大まかにです。これは私たちに与えるでしょう:これは大まかにです。T(n-5) = T(n-5-5) + n + 1T(n-5) = T(n - 2*5) + n + 1T(n) = T(n-2*5) + n + n + 1 + 1)T(n) = T(n-2*5) + 2n + 2)T(n) = T(n-k*5) + kn + k)n-5k = 1n = 5k + 1n = kT(n) = T(0) + n^2 + nO(n^2)

残りの回答を読むことをお勧めします。これにより、より良い視点が得られます。それらの大きなOを勝ち取って頑張ってください:)

于 2020-02-22T16:41:18.880 に答える
8

ここで重要なのは、コールツリーを視覚化することです。それが完了すると、複雑さは次のようになります。

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 (this is special case of a single branch tree)

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

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

したがって、上記の例の結果は次のとおりです。


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

まず、コールツリーについて考えます。

n     level 1
n-1   level 2
n-2   level 3
n-3   level 4
... ~ n levels -> L = n

ここで、ノードあたりの子の数はC = 1であり、レベルの数はL = n+1です。残りの関数の複雑さはO(1)です。したがって、全体の複雑さはL * O(1)=(n + 1)* O(1)= O(n)です。


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

ここでの呼び出しツリーは次のとおりです。

n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5

ここでもC=1ですが、L = n/5です。残りの関数の複雑さはO(1)です。したがって、全体の複雑さはL * O(1)=(n / 5)* O(1)= O(n)です。


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

コールツリーは次のとおりです。

n
n/5
n/5^2
n/5^3
... ~ log5(n) levels -> L = log5(n)

したがって、C = 1、L = log(n)です。残りの関数の複雑さはO(1)です。したがって、全体の複雑さはL * O(1)= log5(n)* O(1)= O(log(n))です。


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);
    }
}

ここでは、呼び出しツリーはより複雑です。

               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

ここで、ノードあたりの子の数はC = 2ですが、L=nです。残りの関数の複雑さはO(1)です。今回は、C> 1であるため、呼び出しツリー内のノード数の完全な式を使用します。したがって、全体の複雑さは(C ^ L-1)/(C-1)* O(1)=(2 ^ n-1)です。 )* O(1)= O(2 ^ n)


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

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

繰り返しますが、呼び出しツリーは次のとおりです。

n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5

ここで、C = 1、L = n/5です。残りの関数の複雑さはO(n)です。したがって、全体の複雑さはL * O(1)=(n / 5)* O(n)= O(n ^ 2)です。

于 2020-04-18T22:18:38.350 に答える
2

受け入れられた答え(recursivefn5)について、一部の人々が説明に問題を抱えていることがわかります。ですから、私は自分の知る限り明確にしようと思います。

  1. forループはn/2回実行されます。これは、各反復でi(カウンター)が2倍に増加するためです。つまり、n = 10とすると、forループは10/2 = 5回実行されます。つまり、iが0の場合です。それぞれ、2、4、6、8。

  2. 同じ点で、再帰呼び出しは、呼び出されるたびに5分の1に削減されます。つまり、n/5回実行されます。ここでも、n = 10と仮定すると、再帰呼び出しは10/5 = 2回実行されます。つまり、nが10と5の場合、基本ケースにヒットして終了します。

  3. 合計実行時間を計算すると、再帰関数を呼び出すたびにforループがn/2回実行されます。再帰的なfxnはn/5回実行されるため(上​​記の2)、forループは(n / 2)*(n / 5)=(n ^ 2)/ 10回実行されます。これは、全体的なBigOランタイムに相当します。 O(n ^ 2)-定数(1/10)を無視します。..

于 2020-12-30T04:35:08.517 に答える