Fibonacci
피보나치 수열이란 첫 번째와 두 번째 항이 1이고 세 번째 항부터는 이전 두 개의 숫자를 더한 점화식을 갖는 수열이다.
Fi+1=Fi+Fi−1F1=F2=1
흔히 재귀 함수나 memoization 등으로 한 번쯤 구현해봤겠지만 여기서는 속도에 집중해서 빠르게 구하는 방법들을 알아보자.
N번째 수 구하기
1. 무작정 연산 O(n)
피보나치수열의 정의에 맞게 초기화를 한 후 모든 항을 구한다.
int byLoop(int n) {
int f[3] = {0,1,1};
for(int i = 3; i <= n; i++) {
f[i%3] = f[(i-1)%3] + f[(i-2)%3];
}
return f[n%3];
}
2. 행렬을 이용한 연산 O(logn)
피보나치수열의 정의를 다시 보면서 행렬식으로 바꿔보자.
Fi+1=Fi+Fi−1Fi=Fi+0
여기서 각 항을 상수와 F 로 나눠서 보면 행렬의 곱으로 바꿀 수 있다.
[Fi+1Fi]=[1110][FiFi−1]=[1110]i[F1F0]=[1110]i[10]
이처럼 피보나치수열을 행렬의 식으로 바꾸고 오른쪽 끝 항을 계속 내리다 보면 피보나치의 정의에 있는 F0과 F1까지 변환했다. 이제 단순히 행렬 계산만 하면 된다. 기존의 수열 연산과 달리 행렬의 n승 연산은 log2(n) 만에 행렬 곱셈으로 아래와 같이 빠르게 구할 수 있다.
struct Matrix {
int n;
vector< vector > mat;
Matrix(int n): n(n) {
mat.resize(n, vector(n,0));
}
Matrix operator*(const Matrix &r) const {
Matrix res(n);
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
for(int k = 0; k < n; k++) {
res.mat[i][j] += mat[i][k] * r.mat[k][j];
}
}
}
return res;
}
};
Matrix exponentiation(Matrix m, int power) {
if(power == 0) {
Matrix id(m.n);
for(int i = 0; i < m.n; i++) id.mat[i][i] = 1;
return id;
}
Matrix half = exponentiation(m, power/2);
return power % 2 ? half * half * m : half * half;
}
3. 일반항을 이용한 연산
Fi+1=Fi+Fi−1F1=F2=1
위 두 개의 식을 이용해서 Fi 의 일반항을 구해보자.
α+β=1αβ=−1
새로운 방정식을 유도하기 위해 위와 같은 α와 β 를 정의한다.
Fi+1−Fi−Fi−1=0Fi+1−αFi=β(Fi−αFi−1)=β2(Fi−1−αFi−2)...=βi−1(F2−αF1)=βi−1(1−α)
위와 비슷하게 방정식을 또 하나 유도한다.
Fi+1−Fi−Fi−1=0Fi+1−βFi=α(Fi−βFi−1)=α2(Fi−1−βFi−2)...=αi−1(F2−βF1)=αi−1(1−β)
이렇게 새로운 방정식 두 개를 유도했다.
Fi+1−αFi=βi−1(1−α)Fi+1−βFi=αi−1(1−β)
위 식에서 아래식을 빼고 적절히 식을 정리하면 아래식이 된다.
Fi=1α−βαi−1(1−β)−βi−1(1−α)=αi−βiα−β
이로써 Fi 의 식을 F꼴이 아닌 α와 β로 이루어진 일반항으로 나타내었다.
위에 정의한 α와 β는 간단한 이차방정식이므로 근을 쉽게 구할 수 있다.
α=1+√52β=1−√52
이를 유도했던 식에 대입하면 완성이다.
Fi=1√5(1+√52)n−(1−√52)n
double byFormula(int n) {
double sqrt5 = sqrt(5);
return (1/sqrt5) * (pow((1+sqrt5)/2, n) - pow((1-sqrt5)/2, n));
}
주의
피보나치는 n = 48 만 되어도 integer(4byte) 범위를 초과하는 무시무시한 수열이다.
따라서 위 예제에서는 전혀 고려하지 않았지만 연산에 의한 오버플로우에 항상 주의해야 한다.
'알고리즘' 카테고리의 다른 글
모듈러 나눗셈 (0) | 2019.10.17 |
---|---|
이분 매칭 (Bipartite graph) (0) | 2019.10.17 |
벡터 외적으로 CCW (Counter ClockWise) 이해하기 (0) | 2019.10.17 |
스킵 리스트 (skip list) 동작 방법 (0) | 2019.10.17 |
Union-Find의 최적화 과정 (0) | 2019.10.16 |
댓글