Big-O 표기법을 이해하지만 많은 함수에 대해 계산 방법을 모르겠습니다. 특히, 피보나치 시퀀스의 순진한 버전의 계산 복잡성을 알아 내려고 노력했습니다.
int Fibonacci(int n)
{
if (n <= 1)
return n;
else
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
피보나치 수열의 계산 복잡성은 무엇이며 어떻게 계산됩니까?
Big-O 표기법을 이해하지만 많은 함수에 대해 계산 방법을 모르겠습니다. 특히, 피보나치 시퀀스의 순진한 버전의 계산 복잡성을 알아 내려고 노력했습니다.
int Fibonacci(int n)
{
if (n <= 1)
return n;
else
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
피보나치 수열의 계산 복잡성은 무엇이며 어떻게 계산됩니까?
답변:
당신은 계산에 시간 함수를 모델링 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(2
n
)
베이스 : n = 1
명백하다
가정 , 따라서T(n-1) = O(2
n-1
)
T(n) = T(n-1) + T(n-2) + O(1)
어느
T(n) = O(2
n-1
) + O(2
n-2
) + O(1) = O(2
n
)
그러나 의견에서 언급했듯이 이것은 엄격한 경계가 아닙니다. 이 함수에 대한 흥미로운 사실은 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.6
n
)
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
그렇지 않으면로 알려진 황금 비율 .
따라서 지수 시간이 걸립니다.
나는 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)
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 번째 피보나치 수 의 닫힌 형태 표현에 대한 토론이 있습니다 .
당신은 그것을 확장하고 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)
<
마지막에 문자보다 작 습니까? 어떻게 얻었 T(n-1) + T(n-1)
습니까?
T(n-1) > T(n-2)
그래서 변경 T(n-2)
하고 넣을 수 있습니다 T(n-1)
. 난 단지 높은 여전히 유효한 구속 얻을 것이다T(n-1) + T(n-2)
증명 답변은 좋지만, 항상 자신을 확신시키기 위해 손으로 몇 번 반복해야합니다. 그래서 화이트 보드에 작은 호출 트리를 그리고 노드 계산을 시작했습니다. 카운트를 총 노드, 리프 노드 및 내부 노드로 나눕니다. 내가 가진 것은 다음과 같습니다.
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))
입니다.
1
단일 누산기 Fib(n)
시간에 추가 하는 것뿐만 아니라 여전히 정확하다는 것은 흥미 롭습니다 θ(Fib(n))
.
0
그러나 재귀 기본 사례는 0
and 1
이므로 일부 (대부분의) 재귀 구현에는 추가하는 데 시간이 걸립니다 Fib(n-1) + Fib(n-2)
. 그래서 아마도 3 * Fib(n) - 2
에서 이 링크 전용 대답은 노드의 총 개수가 아니라보다 정확한입니다 2 * Fib(n) - 1
.
재귀 알고리즘의 시간 복잡도는 재귀 트리를 그려서 더 잘 추정 할 수 있습니다.이 경우 재귀 트리를 그리기위한 재귀 관계는 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)입니다.
피보나치의 순진 재귀 버전은 계산의 반복으로 인해 설계 상 지수 적입니다.
루트에서 당신은 컴퓨팅하고 있습니다 :
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 , 이진 표기법을 사용하여 정수 n 은 log2 (n) 이며 변수 변경은
m = log2(n) // your real input size
입력 크기의 함수로 걸음 수를 찾으십시오.
m = log2(n)
2^m = 2^log2(n) = n
입력 크기의 함수로서 알고리즘 비용은 다음과 같습니다.
T(m) = n steps = 2^m steps
이것이 비용이 기하 급수적 인 이유입니다.
함수 호출을 다이어그램으로 계산하는 것은 간단합니다. 각 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)
http://www.ics.uci.edu/~eppstein/161/960109.html
시간 (n) = 3F (n)-2