VOL.整数の公式でフィボナッチ数列を求める 110 【場合の数攻略】 -フィボナッチ数列-
まず、「フィボナッチ数列」とはどんな数列かを確認しておきます。 整数の公式でフィボナッチ数列を求める
定義そのものは小学生が理解するのは無理だと思いますが、
結果としては
「最初の2項が0、1((1、1)あるいは(1、2)も中学受験ではあり)であり、以後どの項もその直前の2項の和となっているような数列」
です。
具体的には
0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,・・・
となります。
実際は「第何項がいくつか」ということが大切なので、上記のような数列を「表」にまとめます。
直前の2項を足せばその項がいくつかを求められるので、少し練習すればほとんどの人が書けるようになります。
そうなると「第何項がいくつか」ということを悩む必要はほぼなくなりますね。
そして、ここまで来て初めて「場合の数」の分野に入ってきたとも言えます。
単に「第何項がいくつか」という問題ならば、「規則性」の分野と考えられるからです。
「場合の数」の問題で「フィボナッチ数列」を使って解くものは、典型的なものが2つあります。
ひとつは「階段(1段づつか1つ飛ばし)」、
もうひとつは「長方形(1×2)の敷き詰め」です。
6年生はどちらも「基本問題」として学習済みだと思いますが、ピンとこない受験生はすぐに確認することをお勧めします。
ここまで見てきて
「フィボナッチ数列で解く場合の数(前の結果の和を利用して解くものまで範囲を広げます)」
の問題で難しいものは
-
整数の公式でフィボナッチ数列を求める
- 「階段」「長方形の敷き詰め」ではないがフィボナッチ数列(それと見抜くのが難しい)
- 単に直前の二項を足した数列ではないもの
- 最初、1辺が1cmの正方形を2つ並べて長方形をつくり、以降は長方形の長いほうの辺にその辺と一辺の長さが等しい正方形をくっつけ新たな長方形を作るという操作を繰り返したときの、辺の長さ。
※これは「フィボナッチ数列」の性質のひとつである「1番目からN番目までの項をそれぞれ二乗(その数同士を2回掛け合わせること)したものの和は、N番目の項と(N+1)番目の項の積に等しい」の証明になっています。 - 1つがいのウサギは、産まれて2か月後から毎月1つがいのウサギを産む。ウサギが死なないとしたときの、○○カ月後のつがいの数。
※これがフィボナッチさんが考案したとされる問題です。 - ある整数を「偶数なら2で割り、奇数なら1を加える」という操作をその数が1になるまで繰り返す。□回の操作で1になる整数の数。
- 「トリボナッチ数列」
階段の問題で「1段ずつ」「1段飛ばし」「2段飛ばし」の3種類が可能なら「トリボナッチ数列」になります。バスケットボールの点数の入り方も同様です。
※前2つを足すのが「フィボナッチ数列」なら、前3つを足すのが「トリボナッチ数列」です。
4つは「テトラナッチ数列」だそうです。 - 「長方形(1×2)と正方形(2×2)の敷き詰め」
例えば2×6の長方形に敷き詰める場合は端で分類し、以下のような表に整理します。
上の表を自分の力で書けるようになればこの手の問題は大抵解けるようになると思います。
大切なのは「前の結果を利用する」ということと「表に整理する」ということの2点です。
フィボナッチ数,メモ化,遅延評価
どちらが読みやすいだろうか.C言語で書いた場合,関数型スタイルでも命令型スタイルでも同じぐらいの行数になった.しかし,どちらがオリジナルの数学表現に近いだろうか.前者はオリジナルの数学表現を忠実にC言語へ翻訳(word-to-word 翻訳)したものである.一方,後者は同じアイディアを違う文化へ翻訳(idea-to-idea 翻訳)したものである.どちらが読みやすいかといえば,前者だろう.(もしあなたがCPUの気持ちになりきれるのならば,いまは後者のほうが圧倒的に読みやすいはずである.しかし保証する.あなたもいずれ前者のほうが読みやすいと感じるようになる.)
では,なぜほとんどのプログラマが命令型プログラミングを採用するのか.C言語によるフィボナッチ数の例で言えば — そしてほとんどのケースでは — 整数の公式でフィボナッチ数列を求める それは計算効率である.命令型のプログラムC2のほうはn番目のフィボナッチ数を求める計算時間はO(n)である.一方,再帰を使った関数型のプログラム1のほうはn番目のフィボナッチ数を求めるのに,n-1番目とn-2番目のフィボナッチ数をそれぞれ求めている.つまり1回の関数コールは2回の関数コールを引き起こす.n番目のフィボナッチ数を求めるにはO(2n)の計算時間がかかる.従って,プログラムC1は読みやすいとしても,コンパイラがどうにかして最適化してくれない限り,大きなnに対しては使い物にならない.
; プログラムS1
(define (fib n)
(if ( < n 3)
1
(+ (fib (- n 1)) (fib (- n 2)))))
fib n := 1 if n < 3
fib n := (n — 1) + fib (n — 2) otherwise
をSchemeで書いただけのものだ. (print (fib 100)) とすれば100番目のフィボナッチ数が印字される(はずである).
; プログラムS2
(define (memoize 整数の公式でフィボナッチ数列を求める f)
(let ([h (make-hash-table)])
(define (update n)
(let ([x (f n)])
(hash-table-put! h n x)
x))
(lambda (n)
(if (hash-table-exists? h n)
(hash-table-get h n)
(update n)))))
(print ((memoize fib) 100))
– プログラムH1
fib 1 = 1
fib 整数の公式でフィボナッチ数列を求める 2 = 1
fib n = fib (n — 1) + fib (n — 2)
コメント