Pythonでフィボナッチ数列を計算する
Pythonでフィボナッチ数列を計算します。
フィボナッチ数列の基本的な計算方法
定義通りフィボナッチ数列を計算する
最もシンプルな方法によるフィボナッチ数列の計算
フィボナッチ数列は次の式で与えられる数列です。
F0 = 0,
F1 = 1,
Fn = Fn-1 + Fn-2 (n ≥ 0)
定義通りの方法で100以下のフィボナッチ数列を計算します。
- #1 定義通りの方法でn以下のフィボナッチ数列を計算する
- a, b = 0, 1
- while b < 100:
- print(b, end=' ')
- a, b = b, a+b
1 1 2 3 5 8 13 21 34 55 89
$a=F_1$、$b=F_2$からはじめ、whileループで$a$に$F_2$を代入し、bに$F_1+F_2$を計算して代入することを同時に行うのが4行目です。昔のプログラミング言語では、tempのような変数を定義し、次のようにする必要がありました。
temp = b
b = a+temp
a = temp
フィボナッチ数列を関数でリストとして返す
#1のプログラムを$F_1$から$F_{10}$までというように、n個のフィボナッチ数列のリストにして戻す関数に作りかえます。
- #2 n番目までのフィボナッチ数列をリストにする関数
- def fib_l(cnt):
- a, b = 0, 1
- fib_l=[]
- while cnt:
- cnt-=1
- fib_l.append(b)
- a, b = b, a+b
- return fib_l
- fib_l(10)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
fib_lというリストを定義し、引数cntに対し値を計算するごとにリストに追加するとともに、cntを1つずつ減らします。while cnt:となっているので、cntが0になったらループから抜けて、戻り値としてリストを返します。
再帰関数を使いフィボナッチ数列を計算する
再帰関数でn番目のフィボナッチ数列を計算する
再帰関数を使ってフィボナッチ数列を計算することができます。
- #3 再帰関数でn番目のフィボナッチ数列を計算する
- def fib_r(seq):
- if seq <= 1:
- return seq
- return(fib_r(seq-1) + fib_r(seq-2))
- fib_r(10)
結果は55となり正しく計算されています。
フィボナッチ数列の定義、$F_n = F_{n-1} + F_{n-2}$を表現したのが5行目です。fib_r(seq)が実行されるとすぐに5行目に入りfib_r(seq-1)を呼び出し、最初の関数の実行が一時中断されます。同様にfib_r(seq-2)、 fib_r(seq-3)とfib_rがseqの値を減らしながら呼び出した関数の処理を中断して次々と実行されます。seqが2として実行されると5行目fib_r(1)とfib_r(0)は、3行目のif節に入るので、戻り値としてそれぞれ1,0を返します。関数fib(2)は戻り値を返して終了すると、反転してこれまで中断されていた関数が次々と値を返していきます。最終的に、定義通り1つ前と2つ前の数値を足し上げていきます。
再帰関数の過程を詳しく見る
この数式はフィボナッチ数列の定義をそのまま表していますが、これでなぜ正しく計算できるのか次の3か所にprint文を入れて検証します。
1 関数に入った時に引数seqをptint
2 if文で関数を抜けるときの戻り値をrtnとして戻す
2 2つの関数の合計を返り値としてrtnとして戻す
また、実行順番を示すためiをカウンタとして同時に表示します。
- #4 再帰関数でn番目のフィボナッチ数列を計算する
- def fib(seq):
- global i
- i+=1
- print('#'+str(i),'fibに入る時 seq=',seq)
- if seq <= 1:
- i+=1
- print('#'+str(i),'1つ目のreturn=',seq)
- return seq
- rtn=fib(seq-1) + fib(seq-2)
- i+=1
- print('#'+str(i),'2つ目のreturn=',rtn)
- return rtn
- i=0
- fib(4)
#1 fibに入る時 seq= 4 #2 fibに入る時 seq= 3 #3 fibに入る時 seq= 2 #4 fibに入る時 seq= 1 #5 1つ目のreturn= 1 #6 fibに入る時 seq= 0 #7 1つ目のreturn= 0 #8 2つ目のreturn= 1 #9 fibに入る時 seq= 1 #10 1つ目のreturn= 1 #11 2つ目のreturn= 2 #12 fibに入る時 seq= 2 #13 fibに入る時 seq= 1 #14 1つ目のreturn= 1 #15 fibに入る時 seq= 0 #16 1つ目のreturn= 0 #17 2つ目のreturn= 1 #18 2つ目のreturn= 3
このように、再帰関数を使うとfib(4)で9回のプログラムが実行されます。プログラミングの練習としては興味深いものがありますが、このままでは実用に耐えるものではありません。
SymPYモジュールによるフィボナッチ数列の計算
フィボナッチ数列は数学的にはとても大切なものなので、当然SymPyモジュールに専用の関数が用意されています。
- #5 SymPyモジュールによるフィボナッチ数列の計算
- import sympy
- fib = sympy.fibonacci(10)
- fib
55
$F_{10}$が1から10までの合計と同じ55というのは、ちょっと不思議な感じがします。SymPyモジュールにはn番目の数値を求めるfibonacciしかないので、10番目までをリストで求める場合には次のようにリスト内包表記を使います。
- #6 SymPyモジュールによるフィボナッチ数列の計算を配列にする
- [sympy.fibonacci(n) for n in range(1, 11)]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
ビネーの公式によるフィボナッチ数列
フィボナッチ数列は漸化式としてとらえると、n番目の数列を一発で求める方法がビネーの公式です。
10番目の値を計算するためには、次の通り実行します。
- #7 ビネーの公式によるフィボナッチ数列の計算
- seq=10
- (((1+5**0.5)/2)**seq-((1-5**0.5)/2)**seq)/(5**0.5)
55.000000000000014
小数点以下で誤差が発生して気持ち悪いので次のように小数点以下を切り捨てる処理を入れた関数を作成します。これを#7のようにリスト内包表記で実行すると次の通りです。
- #8ビネーの公式によるフィボナッチ数列の計算を関数にする
- def binet(seq):
- fib=(((1+5**0.5)/2)**seq-((1-5**0.5)/2)**seq)/(5**0.5)
- return int(round(fib))
- [binet(n) for n in range(1, 11)]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
結果は正しいようです。