재귀 함수의 복잡성 결정 (Big O 표기법)


267

나는 내일 Computer Science Midterm을 가지고 있으며 이러한 재귀 함수의 복잡성을 결정하는 데 도움이 필요합니다. 간단한 사례를 해결하는 방법을 알고 있지만 여전히 어려운 사례를 해결하는 방법을 배우려고 노력하고 있습니다. 이것들은 내가 알아낼 수없는 몇 가지 예제 문제 일뿐입니다. 도움을 주시면 제 연구에 큰 도움이 될 것입니다. 감사합니다!

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
매번 분석을 수행하지 않으려면 Master 메소드라는 블랙 박스 기술이 있습니다. 그러나 모든 재귀 적 입력 분할은 각 인스턴스에서 동일한 크기라고 가정합니다.
Vivek Krishna


답변:


345

각 함수에 대한 Big O 표기법의 시간 복잡도는 숫자 순서입니다.

  1. 첫 번째 함수는 기본 사례에 도달하기 전에 n 번의 재귀 적으로 호출되므로 선형O(n) 이라고도 합니다.
  2. 두 번째 함수는 매번 n-5라고 불리우므로 함수를 호출하기 전에 n에서 5를 빼지 만 n-5도입니다 O(n). (실제로 n / 5 번의 순서라고하며 O (n / 5) = O (n)).
  3. 이 함수는 log (n) base 5이며, 함수를 호출하기 전에 5로 나눌 때마다 O(log(n))(base 5) ( 대수 라고도 함) 및 Big O 표기법 및 복잡도 분석은 base 2를 사용합니다.
  4. 네 번째에서는 n 또는 recurs되지 않는 한 각 함수 호출이 자신을 두 번 호출하기 때문에 O(2^n), 즉 지수 입니다.
  5. 마지막 함수의 경우 for 루프는 2 씩 증가하기 때문에 n / 2가 걸리고 재귀는 n-5가되고 for 루프는 재귀 적으로 호출되므로 시간 복잡도는 (n-5) * (n / 2) = (2n-10) * n = 2n ^ 2- 10n은 점근 적 행동과 최악의 시나리오 고려 사항 또는 big O가 노력하고있는 상한으로 인해 가장 큰 항에만 관심이 O(n^2)있습니다.

    중간에 행운을 빕니다;)


다섯 번째에 대한 당신의 권리, for 루프의 경우 n이 줄어들지 만 네 번째의 경우 재귀를 두 번 호출 할 때마다 트리와 같은 n ^ 2를 생각하지 않으므로 2 ^ n 더하기 이전 의견에 답하십시오.
코더

2
@MJGwater 루프의 실행 시간을 m이라고합시다. 재귀 실행이 1 회 실행되면 루프를 실행하는 데 m이 걸립니다. 재귀 실행이 두 번 실행되면 루프도 2 번 실행되므로 2m가 소요됩니다. 따라서 '^'이 아니라 '*'입니다.
bjc

3
@coder 5에 대한 설명이 이상하게 보입니다. 2 씩 증가 n/2하면 for루프 가 반복 되는 경우 5 씩 감소하면 왜 n/5재귀 호출이 발생하지 않습니까? 이것은 여전히 ​​결과가 될 O(n^2)것이지만보다 직관적 인 설명처럼 보입니다. 빼기와 나누기가 같은 일을하는 데 꼭 필요한 이유는 무엇입니까?
Jack

1
함수 # 4에서 재귀 호출이 3 번 있다면 @coder이므로 시간 복잡도는 O (3 ^ n)입니까? 그리고 5 회 재귀 호출의 경우 O (5 ^ n)입니다. 맞습니까?
rmutalik

1
@ 잭 그래, 나도 같은 궁금했다. 해서는 n/5안됩니다 n-5. 그리고 결국에는 전체가로 귀결됩니다 O(N^2).
Anuj

128

여기서 사건의 경우 n <= 0, T(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

삼.

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) / 5r이 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
나는 최근에 재귀 피보나치 함수의 시간과 공간의 복잡성을 분석하는 것과 관련된 인터뷰 질문에 (그리고 인터뷰를 연장함으로써) 실패했습니다. 이 답변은 서사적이며 많은 도움이되었습니다. 좋아합니다. 두 번 투표 할 수 있으면 좋겠습니다. 나는 그것이 오래되었다는 것을 알고 있지만 공간을 계산하는 데 비슷한 것이 있습니까?
Dimitar Dimitrov

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

재귀 알고리즘의 복잡성을 근사화하는 가장 좋은 방법 중 하나는 재귀 트리를 그리는 것입니다. 재귀 트리가 완성되면 :

Complexity = length of tree from root node to leaf node * number of leaf nodes
  1. 첫 번째 함수는 길이 n와 리프 노드 수를 가지 1므로 복잡도는n*1 = n
  2. 두 번째 함수는 n/5리프 노드 의 길이 와 수를 다시 가지 1므로 복잡성이 커집니다 n/5 * 1 = n/5. 대략적으로n

  3. 세 번째 함수의 경우 n모든 재귀 호출마다 5로 나뉘어 지기 때문에 재귀 트리의 길이는 log(n)(base 5)1이고 리프 노드의 수는 1이므로 복잡도는log(n)(base 5) * 1 = log(n)(base 5)

  4. 네 번째 함수의 경우 모든 노드에 두 개의 자식 노드가 있으므로 리프 노드의 수는 같고 (2^n)재귀 트리의 길이는 n복잡 할 것 (2^n) * n입니다. 그러나 n앞에서는 의미 (2^n)가 없으므로 무시할 수 있고 복잡성 만 말할 수 있습니다 (2^n).

  5. 다섯 번째 함수에는 복잡성을 나타내는 두 가지 요소가 있습니다. 재귀 함수의 기능으로 인해 발생하는 복잡성과 for각 함수에서 루프로 발생하는 복잡도 위의 계산을 수행하면 재귀 함수의 기능 ~ n으로 인한 복잡성과 for 루프로 인한 복잡성이 발생합니다 n. 총 복잡성은 다음과 같습니다 n*n.

참고 : 이것은 복잡성을 계산하는 빠르고 더러운 방법입니다 (공식 없음). 이에 대한 의견을 듣고 싶습니다. 감사.


훌륭한 답변! 네 번째 기능에 대한 질문이 있습니다. 재귀 호출이 세 번 있었으면 대답은 (3 ^ n)입니다. 아니면 여전히 (2 ^ n)이라고 말 하시겠습니까?
벤 Forsrup

@Shubham : # 4는 나에게 옳지 않은 것 같습니다. 잎의 수가 2^n나무의 높이가 n아니 어야합니다 log n. 높이는 트리의 총 노드 수를 나타내는 log n경우 에만 해당됩니다 n. 그러나 그렇지 않습니다.
Julian A.

@BenForsrup : 모든 노드에 세 개의 자식 노드가 있기 때문에 3 ^ n이됩니다. 이를 확인하는 가장 좋은 방법은 재귀 트리를 더미 값으로 그리는 것입니다.
Shubham

# 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정확히 어떤 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)내부 T(n)에 배치합시다 : T(n) = T(n/5^k) + k. 다시 이전과 같이, n/5^k = 1n = 5^k5의 힘, n은 우리에게 무슨 요구대로 정확히 인 대답은 log5n = k(기본 5 로그). 하자가 우리의 조사 결과를 놓고 T(n) = T(n/5^k) + k다음과 같은 : T(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 = 1인을 k = n - 1. 하자의 장소 k다음과 같은 : T(n) = 2^(n-1) + n - 1약이다O(2^n)
  5. T(n) = T(n-5) + n + 1거의 4와 같지만 이제 n하나의 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.