フィボナッチ数を求めるのにO(1)とか言ってた人がいたが、実際のところはどうなのだろう。高校でも習う(今はどうか知らないが)公式を使えば結局べき乗になる。べき乗の計算はlog取って、掛け算して、expの計算をする。logってどうやって計算するのだろう。テーラー展開?テーラー展開だと、Fnを求めるのに、n桁のオーダーの掛け算をO(n)繰り返さなければならない。ややあやしいが、掛け算がO(nlogn)だとすると、logの計算をするのにO(n2logn)となる。単純に足し算していくと、n桁の足し算をn回だから、O(n)で済む。これを実証してみよう。
下のようにコードを書いてみた。大きな整数をintの配列にして、最後に基数変換するのがめんどうなので、一つのintで109まで表すことにして、加算を繰り返した。これでFib100〜Fib1000000まで(抜粋して)10回求めて、それにかかった時間の最大と最小を除いた8つの平均を取った。
グラフを見ると、O(n2)であることがわかる。
#include
#include
#include
#includeusing namespace std;
typedef unsigned int uint;
const int INT_LIMIT = (int)1e9;
void add(uint *a, const uint *b, int n);
void print_num(const uint *a, int n);int main(int argc, char **argv) {
int n;
try {
if(argc != 2) {
throw(1);
}
n = atoi(argv[1]);
if(n < 0)
throw(1);
}
catch(int e) {
cerr << "usage : fib n." << endl;
cerr << " n >= 0." << endl;
exit(1);
}
LARGE_INTEGER nFreq, nTime[2];
QueryPerformanceFrequency(&nFreq);
QueryPerformanceCounter(&nTime[0]);
const double phi = (1 + sqrt(5.)) / 2;
int num_items = (int)(n * (log(phi) / log( (double)INT_LIMIT)) + 1.5);
uint *a1 = (uint *)calloc(num_items, sizeof(uint));
uint *a2 = (uint *)calloc(num_items, sizeof(uint));
if(a1 == NULL || a2 == NULL)
exit(2);
a1[0] = 0;
a2[0] = 1;
for(int k = 2; k <= n; k++) {
uint *cur, *prev;
if(k & 1) { // odd
add(a2, a1, num_items);
}
else {
add(a1, a2, num_items);
}
}
print_num(n & 1 ? a2 : a1, num_items);
free(a1);
free(a2);
QueryPerformanceCounter(&nTime[1]);
int t1 = (nTime[1].QuadPart - nTime[0].QuadPart) * 1e6 / nFreq.QuadPart;
cerr << n << " " << t1 << " μs" << endl;
}void add(uint *a, const uint *b, int n) {
for(int i = 0; i < n; i++) {
a[i] += b[i];
if(a[i] >= INT_LIMIT) {
a[i] -= INT_LIMIT;
a[i+1]++;
}
}
}void print_num(const uint *a, int n) {
stringstream ss;
int mode = 0;
for(int i = n - 1; i >= 0; i--) {
if(mode == 0) {
if(a[i]) {
ss << a[i];
mode = 1;
}
}
else {
ss.width(9);
ss.fill('0');
ss << a[i];
}
}
cout << ss.str() << endl;
}