피보나치 수열의 계산 복잡성


330

Big-O 표기법을 이해하지만 많은 함수에 대해 계산 방법을 모르겠습니다. 특히, 피보나치 시퀀스의 순진한 버전의 계산 복잡성을 알아 내려고 노력했습니다.

int Fibonacci(int n)
{
    if (n <= 1)
        return n;
    else
        return Fibonacci(n - 1) + Fibonacci(n - 2);
}

피보나치 수열의 계산 복잡성은 무엇이며 어떻게 계산됩니까?



3
매트릭스 형식 섹션 ( en.wikipedia.org/wiki/Fibonacci_number)을 참조하십시오 . 이 행렬을 수행함으로써 영리하게 O (lg n)에서 Fib (n)을 계산할 수 있습니다. 요령은 전력 기능을 수행하는 것입니다. 이 정확한 문제와 O (lg n)에서 해결하는 방법에 대한 iTunesU에 대한 좋은 강의가 있습니다. 이 과정은 MIT 강의 3의 알고리즘을 소개합니다. (절대적으로 무료이므로 관심있는 경우 확인하십시오)
Aly

1
위의 주석 중 어느 것도 매트릭스 형식이나 비재 귀적 계산과 같은 더 똑똑한 버전이 아니라 (포스트 코드에서) 순진 버전의 계산 복잡성에 관한 질문을 다루지 않습니다.
Josh Milthorpe

아주 좋은 비디오 여기 있는 모두 하한 복잡성 (2 ^ N / 2) 및 재귀 구현의 상한 복잡성 (2 ^ n)에 대해 이야기.
RBT

1
부가 정보 쿼리 : 피보나치 시리즈 의 순진한 구현이 반복적 이거나 재귀 적 이어야 합니까?
RBT

답변:


374

당신은 계산에 시간 함수를 모델링 Fib(n)계산 시간의 합계로 Fib(n-1)플러스 계산에 시간 Fib(n-2)함께 추가 할 더하기 시간 ( O(1)). 이는 동일한 평가에 대한 반복 된 평가가 동시에 Fib(n)걸리는 것으로 가정합니다 . 즉, 메모가 사용되지 않습니다.

T(n<=1) = O(1)

T(n) = T(n-1) + T(n-2) + O(1)

예를 들어 생성 함수를 사용하여이 되풀이 관계를 해결하면 결과가 나타납니다.

또는 재귀 트리를 그릴 수 있습니다. 재귀 트리는 깊이 n있고 직관적 으로이 기능이 무증상임을 알 수 있습니다. 그런 다음 귀납으로 추측을 증명할 수 있습니다.O(2n)

베이스 : n = 1명백하다

가정 , 따라서T(n-1) = O(2n-1)

T(n) = T(n-1) + T(n-2) + O(1) 어느

T(n) = O(2n-1) + O(2n-2) + O(1) = O(2n)

그러나 의견에서 언급했듯이 이것은 엄격한 경계가 아닙니다. 이 함수에 대한 흥미로운 사실은 T (n)이 다음과 같이 Fib(n)정의되기 때문에 무조건적 으로 값과 동일 하다는 것입니다.

f(n) = f(n-1) + f(n-2).

재귀 트리의 잎은 항상 1을 반환합니다. 값은 Fib(n)재귀 트리의 잎에 의해 반환 된 모든 값의 합계이며 잎 수와 같습니다. 각 리프는 계산하는 데 O (1)이 걸리므 T(n)로 같습니다 Fib(n) x O(1). 결과적으로이 함수의 밀접한 결합은 피보나치 수열 자체 (~ )입니다. 위에서 언급했듯이 생성 함수를 사용 하여이 빡빡한 경계를 찾을 수 있습니다.θ(1.6n)


29
또한 유도에 의한 증거. 좋은. +1
Andrew Rollings

바운드가 빡빡하지는 않지만.
Segfault 선장

@ 함장 Segfault : 네. 나는 대답을 명확히했다. 위에서 쓴 것처럼 GF 방법을 사용하여 단단히 묶일 수 있습니다.
Mehrdad Afshari

장난으로 StackOverflowException을 가져옵니다. n에 대한 작은 값으로 지수 시간을 쉽게 이해할 수 있습니다.
David Rodríguez-dribeas

1
"또는, 깊이 n을 갖는 재귀 트리를 그릴 수 있으며이 함수가 무증상 O (2n)임을 직관적으로 파악할 수 있습니다." -이건 완전 허위입니다. 시간 복잡도는 O (golden_ratio ^ n)입니다. O (2 ^ n)에 가깝지 않습니다. 무한대에 도달 할 수 있다면 O (golden_ratio ^ n)에 가까워집니다. 그것은 점근선이있는 것입니다. 두 선 사이의 거리는 0에 가까워 야합니다.
bob

133

F(n)완료 하기 위해 얼마나 많은 명령문을 실행해야하는지 스스로에게 물어보십시오 .

에 대한 F(1)답은 1(조건부의 첫 번째 부분)입니다.

에 대한 F(n)대답은 F(n-1) + F(n-2)입니다.

그렇다면이 규칙을 충족시키는 기능은 무엇입니까? n (a> 1)을 시도하십시오 .

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

(n-2)로 나눕니다 .

a 2 == a + 1

에 대한 해결 a당신은 얻을 (1+sqrt(5))/2 = 1.6180339887그렇지 않으면로 알려진 황금 비율 .

따라서 지수 시간이 걸립니다.


8
유도에 의한 증거. 좋은. +1
Andrew Rollings

2
오답에 대한 30 개의 찬성? :-) 1 = F (1) = (1 + sqrt (5)) / 2 다음에입니까? 그리고 다른 해결책은 어떻습니까 (1-sqrt (5)) / 2?
Carsten S

1
아니요, 1은 1 + 1과 같지 않습니다. 이러한 규칙을 만족시키는 기능이 질문에 언급되어 있습니다.
molbdnilo

6
대답은 틀리지 않습니다. 그것은 비대칭 적으로 옳습니다. 다른 해결책은 부정적이므로 물리적으로 의미가 없습니다.
Da Teng

10
누군가 a ^ n == a ^ (n-1) + a ^ (n-2)가 이러한 규칙을 어떻게 만족시키는 지 설명 할 수 있습니까? 정확히 어떻게 만족합니까, 구체적으로 작성하십시오.
Frank

33

나는 pgaur와 rickerbh에 동의합니다. 재귀 피보나치의 복잡성은 O (2 ^ n)입니다.

나는 다소 단순한 방법으로 같은 결론에 도달했지만 여전히 타당한 추론을 믿는다.

먼저, N 번째 피보나치 수를 계산할 때 재귀 피보나치 함수 (여기부터 F ())가 몇 번이나 호출되는지 파악해야합니다. 순서 0에서 n까지 숫자 당 한 번 호출되면 O (n)이되고 각 숫자에 대해 n 번 호출되면 O (n * n) 또는 O (n ^ 2)가됩니다. 등등.

따라서 숫자 n에 대해 F ()를 호출하면 0에 접근함에 따라 0과 n-1 사이의 주어진 숫자에 대해 F ()가 호출 된 횟수가 증가합니다.

첫인상으로, 시각적으로 표현하면 주어진 숫자에 대해 F ()가 호출 될 때마다 단위를 그리면 피라미드 모양이 젖어 있습니다 (즉, 단위를 가로로 가운데에 놓으면) ). 이 같은:

n              *
n-1            **
n-2           ****  
...
2           ***********
1       ******************
0    ***************************

이제 문제는 n이 자라면서이 피라미드의 밑 부분이 얼마나 빨리 확대 되는가하는 것입니다.

F (6)와 같은 실제 사례를 보자

F(6)                 *  <-- only once
F(5)                 *  <-- only once too
F(4)                 ** 
F(3)                ****
F(2)              ********
F(1)          ****************           <-- 16
F(0)  ********************************    <-- 32

우리는 F (0)이 32 번 호출되는 것을 보았습니다. 2 ^ 5입니다.이 샘플의 경우 2 ^ (n-1)입니다.

이제 F (x)가 몇 번이나 호출되는지 알고 싶습니다. F (0)이 호출 된 횟수가 그 일부일 뿐이라는 것을 알 수 있습니다.

우리가 모든 *를 F (6)에서 F (2) 행으로 F (1) 행으로 정신적으로 이동 시키면, F (1)과 F (0) 행의 길이가 같다는 것을 알 수 있습니다. 즉, n = 6이 2x32 = 64 = 2 ^ 6 일 때 F ()가 호출되는 총 횟수입니다.

이제 복잡성 측면에서 :

O( F(6) ) = O(2^6)
O( F(n) ) = O(2^n)

3
F (3)은 3 번만 호출되고 4 번은 호출되지 않습니다. 두 번째 피라미드가 잘못되었습니다.
Avik

2
F (3) = 3, F (2) = 5, F (1) = 8, F (0) = 5. 나는 그것을 고칠 것이지만,이 답변을 편집하여 구할 수는 없다고 생각합니다.
Bernhard Barker

31

MIT 에서이 특정 문제에 대해 아주 좋은 토론이 있습니다. 5 페이지에서 추가에 하나의 계산 단위가 필요하다고 가정하면 Fib (N)을 계산하는 데 필요한 시간은 Fib (N)의 결과와 매우 밀접한 관련이 있습니다.

결과적으로 피보나치 계열의 매우 가까운 근사치로 바로 건너 뛸 수 있습니다.

Fib(N) = (1/sqrt(5)) * 1.618^(N+1) (approximately)

따라서 순진 알고리즘의 최악의 성능은

O((1/sqrt(5)) * 1.618^(N+1)) = O(1.618^(N+1))

추신 : 자세한 정보를 원하시면 Wikipedia에서 N 번째 피보나치 수닫힌 형태 표현에 대한 토론이 있습니다 .


강의 링크에 감사드립니다. 아주 좋은 관찰
SwimBikeRun 1

16

당신은 그것을 확장하고 visulization 할 수 있습니다

     T(n) = T(n-1) + T(n-2) <
     T(n-1) + T(n-1) 

     = 2*T(n-1)   
     = 2*2*T(n-2)
     = 2*2*2*T(n-3)
     ....
     = 2^i*T(n-i)
     ...
     ==> O(2^n)

1
첫 줄을 이해합니다. 그러나 왜 <마지막에 문자보다 작 습니까? 어떻게 얻었 T(n-1) + T(n-1)습니까?
Quazi Irfan

@QuaziIrfan : D는 화살표입니다. -> [(이상)). 마지막 줄에 대한 혼란을 드려 죄송합니다]. 첫 번째 줄은 ... T(n-1) > T(n-2)그래서 변경 T(n-2)하고 넣을 수 있습니다 T(n-1). 난 단지 높은 여전히 유효한 구속 얻을 것이다T(n-1) + T(n-2)
토니 Tannous

10

그것은 하단 2^(n/2)에 2 ^ n에 의해 하단에 묶여있다 (다른 주석에서 언급 된 바와 같이). 그리고 재귀 구현의 흥미로운 사실은 Fib (n) 자체의 엄격한 점근 적 경계가 있다는 것입니다. 이러한 사실을 요약 할 수 있습니다.

T(n) = Ω(2^(n/2))  (lower bound)
T(n) = O(2^n)   (upper bound)
T(n) = Θ(Fib(n)) (tight bound)

타이트 바운드는 원하는 경우 닫힌 형태를 .


10

증명 답변은 좋지만, 항상 자신을 확신시키기 위해 손으로 몇 번 반복해야합니다. 그래서 화이트 보드에 작은 호출 트리를 그리고 노드 계산을 시작했습니다. 카운트를 총 노드, 리프 노드 및 내부 노드로 나눕니다. 내가 가진 것은 다음과 같습니다.

IN | OUT | TOT | LEAF | INT
 1 |   1 |   1 |   1  |   0
 2 |   1 |   1 |   1  |   0
 3 |   2 |   3 |   2  |   1
 4 |   3 |   5 |   3  |   2
 5 |   5 |   9 |   5  |   4
 6 |   8 |  15 |   8  |   7
 7 |  13 |  25 |  13  |  12
 8 |  21 |  41 |  21  |  20
 9 |  34 |  67 |  34  |  33
10 |  55 | 109 |  55  |  54

즉시 도약하는 것은 리프 노드의 수가입니다 fib(n). 주목할 몇 가지 반복이 더 필요한 것은 내부 노드의 수가fib(n) - 1 . 따라서 총 노드 수는 2 * fib(n) - 1입니다.

계산 복잡성을 분류 할 때 계수를 삭제하므로 최종 답은 θ(fib(n))입니다.


(아니요, 저는 화이트 보드에 전체 10- 딥 콜 트리를 그리지 않았습니다. 단지 5- 딥입니다.);)
benkc

좋아, 나는 재귀 Fib가 얼마나 많은 추가 기능을 추가했는지 궁금했다. 1단일 누산기 Fib(n)시간에 추가 하는 것뿐만 아니라 여전히 정확하다는 것은 흥미 롭습니다 θ(Fib(n)).
Peter Cordes

0그러나 재귀 기본 사례는 0and 1이므로 일부 (대부분의) 재귀 구현에는 추가하는 데 시간이 걸립니다 Fib(n-1) + Fib(n-2). 그래서 아마도 3 * Fib(n) - 2에서 이 링크 전용 대답은 노드의 총 개수가 아니라보다 정확한입니다 2 * Fib(n) - 1.
Peter Cordes

리프 노드에서 동일한 결과를 얻을 수 없습니다. 0에서 시작 : F (0)-> 1 잎 (자체); F (1)-> 1 잎 (자체); F (2)-> 2 잎 (F (1) 및 F (0)); F (3)-> 3 잎; F (5)-> 8 잎; 등
alexlomba87

9

재귀 알고리즘의 시간 복잡도는 재귀 트리를 그려서 더 잘 추정 할 수 있습니다.이 경우 재귀 트리를 그리기위한 재귀 관계는 T (n) = T (n-1) + T (n-2) + O (1)입니다. 각 단계는 일정한 시간을 의미하는 O (1)을 취 합니다. 블록의 경우 n의 값을 확인하기 위해 한 번만 비교하기 때문에 재귀 트리는 다음과 같습니다.

          n
   (n-1)      (n-2)
(n-2)(n-3) (n-3)(n-4) ...so on

여기서 위의 각 레벨은 i로 표시됩니다.

i
0                        n
1            (n-1)                 (n-2)
2        (n-2)    (n-3)      (n-3)     (n-4)
3   (n-3)(n-4) (n-4)(n-5) (n-4)(n-5) (n-5)(n-6)

i의 특정 값에서 나무가 끝나는 경우, 그 경우는 ni = 1 일 때이므로 i = n-1이됩니다. 이는 나무의 높이가 n-1임을 의미합니다. 이제 트리에서 n 개의 각 레이어에 대해 얼마나 많은 작업이 수행되는지 살펴 보겠습니다. 각 단계는 반복 관계에 명시된대로 O (1) 시간이 걸립니다.

2^0=1                        n
2^1=2            (n-1)                 (n-2)
2^2=4        (n-2)    (n-3)      (n-3)     (n-4)
2^3=8   (n-3)(n-4) (n-4)(n-5) (n-4)(n-5) (n-5)(n-6)    ..so on
2^i for ith level

i = n-1이 각 레벨에서 수행되는 트리 작업의 높이이므로

i work
1 2^1
2 2^2
3 2^3..so on

따라서 총 작업 수는 각 수준에서 수행 된 작업의 합계이므로 i = n-1이므로 2 ^ 0 + 2 ^ 1 + 2 ^ 2 + 2 ^ 3 ... + 2 ^ (n-1)이됩니다. 기하 급수적으로이 합은 2 ^ n이므로 총 시간 복잡도는 O (2 ^ n)입니다.


2

글쎄, O(2^n)이 기능에서와 같이 재귀에만 상당한 시간이 걸리고 있습니다 (분할 및 정복). 우리는 잎이 우리가 레벨에 도달하면 접근 할 때까지 즉, 위의 기능이 나무에 계속 볼 수 F(n-(n-1))F(1). 여기에서 각 깊이의 트리에서 발생하는 시간 복잡성을 정리할 때 요약 시리즈는 다음과 같습니다.

1+2+4+.......(n-1)
= 1((2^n)-1)/(2-1)
=2^n -1

그 순서는 2^n [ O(2^n) ]입니다.


1

피보나치의 순진 재귀 버전은 계산의 반복으로 인해 설계 상 지수 적입니다.

루트에서 당신은 컴퓨팅하고 있습니다 :

F (n)은 F (n-1) 및 F (n-2)에 따라 다름

F (n-1)은 다시 F (n-2)와 F (n-3)에 의존

F (n-2)는 다시 F (n-3) 및 F (n-4)에 의존

계산에 많은 데이터를 낭비하는 각 레벨 2 재귀 호출을 수행하면 시간 함수는 다음과 같습니다.

T (n) = T (n-1) + T (n-2) + C, C 상수

T (n-1) = T (n-2) + T (n-3)> T (n-2)

T (n)> 2 * T (n-2)

...

T (n)> 2 ^ (n / 2) * T (1) = O (2 ^ (n / 2))

이것은 분석의 목적으로는 충분하지만 실시간 함수는 동일한 피보나치 공식과 닫힌 형태에 의해 상수의 요소라는 하한입니다. 는 황금비의 지수 인 것으로 알려져 있습니다.

또한 다음과 같은 동적 프로그래밍을 사용하여 피보나치의 최적화 된 버전을 찾을 수 있습니다.

static int fib(int n)
{
    /* memory */
    int f[] = new int[n+1];
    int i;

    /* Init */
    f[0] = 0;
    f[1] = 1;

    /* Fill */
    for (i = 2; i <= n; i++)
    {
        f[i] = f[i-1] + f[i-2];
    }

    return f[n];
}

그것은 최적화되어 있으며 n 만 수행합니다. 단계 만 수행하지만 지수 적입니다.

비용 함수는 입력 크기에서 문제 해결 단계 수까지 정의됩니다. 피보나치의 동적 버전 ( 테이블을 계산하는 n 단계) 또는 숫자가 소수인지 알아내는 가장 쉬운 알고리즘을 볼 때 (숫자 의 유효한 제수를 분석하기위한 sqrt (n) ). 이러한 알고리즘은 O (n) 또는 O (sqrt (n)) 라고 생각할 수 있지만 다음과 같은 이유로 단순히 사실이 아닙니다. 알고리즘에 대한 입력은 숫자입니다 : n , 이진 표기법을 사용하여 정수 nlog2 (n) 이며 변수 변경은

m = log2(n) // your real input size

입력 크기의 함수로 걸음 수를 찾으십시오.

m = log2(n)
2^m = 2^log2(n) = n

입력 크기의 함수로서 알고리즘 비용은 다음과 같습니다.

T(m) = n steps = 2^m steps

이것이 비용이 기하 급수적 인 이유입니다.


1

함수 호출을 다이어그램으로 계산하는 것은 간단합니다. 각 n 값에 대한 함수 호출을 추가하고 숫자가 어떻게 증가하는지 살펴보십시오.

Big O는 O (Z ^ n)이며 여기서 Z는 황금비 또는 약 1.62입니다.

우리가 n을 증가 시키면 레오나르도 숫자와 피보나치 숫자는이 비율에 접근합니다.

다른 Big O 질문과 달리 입력에는 변동성이 없으며 알고리즘과 알고리즘의 구현이 명확하게 정의되어 있습니다.

복잡한 수학이 필요하지 않습니다. 아래의 함수 호출을 간단히 설명하고 함수를 숫자에 맞추십시오.

또는 황금 비율에 익숙한 경우이를 황금 비율로 인식합니다.

이 답변은 f (n) = 2 ^ n에 접근한다고 주장하는 허용되는 답변보다 더 정확합니다. 결코하지 않습니다. f (n) = golden_ratio ^ n에 접근합니다.

2 (2 -> 1, 0)

4 (3 -> 2, 1) (2 -> 1, 0)

8 (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
            (2 -> 1, 0)


14 (5 -> 4, 3) (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
            (2 -> 1, 0)

            (3 -> 2, 1) (2 -> 1, 0)

22 (6 -> 5, 4)
            (5 -> 4, 3) (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
                        (2 -> 1, 0)

                        (3 -> 2, 1) (2 -> 1, 0)

            (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
                        (2 -> 1, 0)

1
황금비에 대한 주장에 대한 자료를 제공해 줄 수 있습니까?
니코 하세

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.