자료2012. 5. 26. 21:08

처음 프로그래밍을 배울 때, 특히 재귀함수를 다룰 때 팩토리얼 다음으로 자주 등장하는 예제는 피보나치 수열이다.


1
2
3
4
int fibonacci(int n) {
    if (n == 0 || n == 1) return 1;
    return fibonacci(n-1) + fibonacci(n-2);
}


이렇게 하면 n이 커질 때 너무 오래 걸리기 때문에 배열을 사용해서 계산하는 법이 이어서 등장하기도 한다.


1
2
3
4
5
6
7
8
9
int fibonacci(int n) {
    int a[N];  // N = 관심 있는 n의 최대 크기+1
    a[0] = 1;
    a[1] = 1;
    for(int i=2; i<=n; i++) {
        a[i] = a[i-1] + a[i-2];
    }
    return a[n];
}


이런 방식을 다이나믹 프로그래밍이라고 하는데, 아래처럼 하면 메모리 사용량을 조금 아낄 수 있다.


1
2
3
4
5
6
7
8
9
int fibonacci(int n) {
    int a = 0, b = 1, c = 1;
    for(int i=0; i<n; i++) {
        c = a+b;
        a=b;
        b=c;
    }
    return c;
}


1, 1 대신에 다른 숫자 a, b로 시작하는 피보나치 수열을 구하는 좀 더 일반적인 함수는 다음과 같이 만들 수 있다.


1
2
3
4
5
6
7
8
9
int fibonacci(int a, int b, int N) {
    int c = b;
    for(int i=1; i<N; i++) {
        c = a + b;
        a = b;
        b = c;
    }
    return c;
}


피보나치 수열은 꽤 빠르게 증가하는데 int 타입은 21억까지밖에 표현을 못한다. long이나 double을 쓰고 싶은 사용자가 있으면 쓸 수 있도록 템플릿으로 만들어 줄 수도 있다.


1
2
3
4
5
6
7
8
9
10
template <typename T>
T fibonacci(T a, T b, int N) {
    T c = b;
    for(int i=1; i<N; i++) {
        c = a + b;
        a = b;
        b = c;
    }
    return c;
}


한편 템플릿 기능을 활용하면 다음처럼 색다른 방식으로 피보나치 수열을 구할 수 있다. 이런 방식을 템플릿 메타프로그래밍이라고 한다. 이 방식의 장점은 계산이 컴파일하면서 일어나기 때문에 실행할 땐 아무런 피보나치 계산도 일어나지 않는다는 점이다. 사실 이런 방식으로 컴퓨터의 모든 연산을 수행하는 것이 가능하다!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;
 
template <int n>
struct Fibonacci { enum { value = Fibonacci<n-1>::value + Fibonacci<n-2>::value }; };
 
template <>
struct Fibonacci<0> { enum { value = 1 }; };
 
template <>
struct Fibonacci<1> { enum { value = 1 }; };
 
int main() {
    cout << Fibonacci<0>::value << endl;
    cout << Fibonacci<1>::value << endl;
    cout << Fibonacci<2>::value << endl;
    cout << Fibonacci<3>::value << endl;
    cout << Fibonacci<4>::value << endl;
    cout << Fibonacci<5>::value << endl;
    cout << Fibonacci<6>::value << endl;
    cout << Fibonacci<7>::value << endl;
    cout << Fibonacci<8>::value << endl;
    cout << Fibonacci<9>::value << endl;
    cout << Fibonacci<10>::value << endl;
    cout << Fibonacci<11>::value << endl;
}


CRTP라는 이상한 방식을 사용해서 조금 변태같은 방법으로 피보나치 수열을 구할 수도 있다. Expression, Integer, Sum 이라는 클래스가 각각 수식, 숫자, 덧셈의 역할을 수행한다. 이렇게 하면 각 연산이 일어날 때에 뭔가 부가적인 일(자동으로 미분을 시켜준다던가...)을 하는 것이 가능하다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
using namespace std;
 
template <typename T>
struct Expression {
    const T& cast() const { return static_cast<const T&>(*this); }
    int value() const { return cast().value(); }
};
 
struct Integer : Expression<Integer> {
    template<typename T>
    Integer& operator=(const Expression<T> &rhs) {
        N = rhs.value();
        return *this;
    }
    Integer(int N=0) : N(N) {}
    int value() const { return N; }
private:
    int N;
};
 
template <typename L, typename R>
struct Add : public Expression<Add<L,R> > {
    Add(const Expression<L> &lhs, const Expression<R> &rhs) : lhs(lhs.cast()), rhs(rhs.cast()) {}
    int value() const { return lhs.value() + rhs.value(); }
private:
    const L& lhs;
    const R& rhs;
};
 
template <typename L, typename R>
Add<L,R> operator+(const Expression<L>& lhs, const Expression<R>& rhs) {
    return Add<L,R>(lhs, rhs);
}
 
template <typename T>
T fibonacci(T a, T b, int N) {
    T c = b;
    for(int i=1; i<N; i++) {
        c = a + b;
        a = b;
        b = c;
    }
    return c;
}
 
int main() {
    Integer a = 1, b = 1;
    for(int i=0; i<12; i++) {
        Integer f = fibonacci(a, b, i);
        cout << f.value() << "\t" << fibonacci(1, 1, i) << endl;
    }
 
    return 0;
}


Posted by jongwook