計算量爆発しろ(違
先ほどのfib(n)は、nの値が大きくなると計算量が指数関数的に爆発してしまいます。
例えばfib(5)を実行すると、以下のように展開して計算されます。
fib(5) -> fib(4) + fib(3) -> fib(3) + fib(2) + fib(2) + fib(1) -> fib(2) + fib(1) + fib(1) + fib(0) + fib(1) + fib(0) + 1 -> fib(1) + fib(0) + 1 + 1 + 0 + 1 + 0 + 1 -> 1 + 0 + 1 + 1 + 0 + 1 + 0 + 1 -> 5
展開中にfib(0)〜fib(3)を何度も呼び出しているのが分かると思います。
fib(1)なんかはトータルで5回も計算しています。
また、fib(6)を計算しようとすると、上記のfib(5)とfib(4)が呼び出され、さらに関数呼び出しの回数が増えてしまいます。
fib(30)とか計算しようとすると時間がかかりすぎて固まります。
動的計画法を使ってみた
というわけで、計算量を抑えた動的計画法版のfib(n)も定義してみました。
書いてみた結果がこれだよ。
fib <- function(n){ if (n == 0) { return(0) } x <- numeric(max(n,2)) x[1] <- x[2] <- 1 m <- 3 while (m <= n){ x[m] <- x[m-1] + x[m-2] m <- m + 1 } x[n] }
また非常にC言語っぽいプログラムですみません。
Rは1オリジンの言語なので、x[0] <- 0みたいなことができませんでした。
1オリジン爆発しろ。
この関数では、fib(3)、fib(4)、fib(5)、...と下から順に計算していき、最終的にfib(n)を求めます。
numeric()でn次元(最低でも二次元)のゼロベクトルを用意し、そこにフィボナッチ数の計算結果をどんどん蓄えていきます。
こうすれば、計算途中でfim(m)を求めるときに、一度計算したfib(m-1)とfib(m-2)を再計算せずに値を求めていくことが可能になります。
nに比例した回数の計算を行うので、この関数の計算量はO(n)です。
fib(30)も一瞬で計算できます。
動的計画法いいよ動的計画法。
ちなみに再帰関数の場合でもメモ化すれば計算量を減らすことができます。
メモ化(Wikipedia)
http://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E5%8C%96
以上
あまりRの練習になりませんでした。
何の練習だこれ。