'2012/05'에 해당되는 글 1건

  1. 2012.05.26 사서 고생하는 피보나치 수열 구하기
자료2012. 5. 26. 21:08

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


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


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


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];
}


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


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로 시작하는 피보나치 수열을 구하는 좀 더 일반적인 함수는 다음과 같이 만들 수 있다.


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을 쓰고 싶은 사용자가 있으면 쓸 수 있도록 템플릿으로 만들어 줄 수도 있다.


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;
}


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


#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 이라는 클래스가 각각 수식, 숫자, 덧셈의 역할을 수행한다. 이렇게 하면 각 연산이 일어날 때에 뭔가 부가적인 일(자동으로 미분을 시켜준다던가...)을 하는 것이 가능하다.


#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