230 likes | 347 Views
再帰関数. 科学科プログラミング. 数列の帰納的定義. 初期値 と 漸化式. a = 1 a = a + 2. 1. n. n-1. a はいくつですか?. 5. a = a + 2. 9. 4. 5. a + 2. 7. 3. a + 2. 5. 2. a + 2. 3. 1. 1. 再帰関数の例(1) 階乗計算. 関数の中で自分自身を呼びだして使うような関数 1 (n=0) fact(n) =
E N D
再帰関数 科学科プログラミング
数列の帰納的定義 初期値 と 漸化式 a = 1 a = a + 2 1 n n-1 a はいくつですか? 5 a = a + 2 9 4 5 a + 2 7 3 a + 2 5 2 a + 2 3 1 1
再帰関数の例(1) 階乗計算 • 関数の中で自分自身を呼びだして使うような関数 1 (n=0) fact(n) = n×fact(n-1) (n>0)
プロトタイプ宣言 関数factの呼び出し n=0 ならば 1 を返す 終了条件 そうでないときは,nとn-1の階乗の積を返す 再帰呼び出し 階乗の計算プログラム #include <stdio.h> int fact(int); int main(void) { int i; for ( i=0; i<=10; i++) { printf("%2dの階乗は%8dです\n",i,fact(i)); } return 0; } int fact(int n) { if (n==0) { return 1; } else { return n*fact(n-1); } }
関数 main() 関数の呼び出し fact(6) 関数fact() n=5 関数 fact() n=6 関数fact() n=4 関数の呼び出しと 関数からの戻り 関数の呼び出しと 関数からの戻り 関数の呼び出しと 関数からの戻り return n*fact(n-1) return n*fact(n-1) return n*fact(n-1) int fact(int n) { if (n==0) { return 1; } else { return n*fact(n-1); } }
関数fact() n=1 関数fact() n=2 関数の呼び出しと 関数からの戻り 関数の呼び出しと 関数からの戻り 2×1 3×2 関数fact() n=0 1 return n*fact(n-1) return n*fact(n-1) 2 1×1 関数からの戻り 1 return 1 関数fact() n=3 関数の呼び出しと 関数からの戻り return n*fact(n-1) int fact(int n) { if (n==0) { return 1; } else { return n*fact(n-1); } }
main関数 関数の呼び出し fact(6) fact関数 n=5 fact関数 n=6 fact関数 n=4 関数の呼び出しと 関数からの戻り 関数の呼び出しと 関数からの戻り 関数の呼び出しと 関数からの戻り 720 6×120 return n*fact(n-1) return n*fact(n-1) return n*fact(n-1) 120 4×6 5 ×24 1 24 int fact(int n) { if (n==0) { return 1; } else { return n*fact(n-1); } }
再帰関数の例(2) リストのメンバ関数 ある要素がリスト中にあるか否かを判定する関数 0 member(リスト,要素) = 1 要素がリスト中に ない場合 要素がリスト中に ある場合 (a b c d e) member 1 0 g c C言語で書くことは考えず,アルゴリズムを考えて下さい
再帰関数の例(2) リストのメンバ関数 準備: リストを引数とする次の2つの関数を考えます. リスト (a b c d e) f 先頭の要素:a 返り値 引数 g 先頭の要素を除いたリスト: (b c d e) 関数名
再帰関数の例(2) リストのメンバ関数 0( lstが空っぽの場合 ) member(lst, elm)=1( f(lst)==elmの場合 ) member( g(lst), elm)(それ以外) f(lst) : lstの先頭の要素を返す関数 g(lst) : lstから先頭の要素のみを除いたリスト を返す関数
再帰関数を使う場合の注意 (1)必ず終了条件が決められていること 関数が永久的に呼び出される場合、 無限ループに陥る場合もある. (2)できるだけシンプルな構造であること 関数自体を呼び込むので, あまり大きな構造や複雑な構造では 処理の動作に重みがかることもある.
整数だったら1小さい整数 リストだったら1短いリスト 再帰関数を考えるコツ(1) • 「仕事」を,同じタイプのちょっとだけ簡単なものに対する「仕事」と,それに付け加える簡単な別の仕事に分割する. nの階乗 → (n-1)の階乗 それにnをかける リストのメンバ → 先頭要素が該当要素なら1, そうでなければ,先頭要素をはずした リストのメンバ
再帰関数を考えるコツ(2) • 一番簡単になった時を考える (終了条件 を 必ず設定する)nの階乗 → 0の階乗は1 リストのメンバ → 空のリストの中には どんな要素も存在しない
再帰関数の例(3) • フィボナッチ数列の計算 F(1)=1, F(2)=1, F(n)=F(n-1)+F(n-2): n>2 のとき
そのまんま再帰プログラミング int fibo1(int n){ if(n==1 || n==2) { return 1; } else { return fibo1(n-1)+fibo1(n-2); }}
再帰を使わず繰り返しでプログラミング(32bit版)再帰を使わず繰り返しでプログラミング(32bit版) int fibo2(int n){ int fi2 = 1; /* fi-2 */ int fi1 = 1; /* fi-1 */ int fi = 1; /* fi */ int i; for(i=3; i<=n; i++) { fi = fi2 + fi1; fi2 = fi1; fi1 = fi; } return fi; } int はメモリ上では 32bit 確保されます よって,21億を超える計算は出来ません どこかで計算結果を表現できなくなります どこで計算結果が表現できなくなるか 実際に試してみてください
再帰を使わず繰り返しでプログラミング(64bit版)再帰を使わず繰り返しでプログラミング(64bit版) long long fibo2(int n){ long long fi2 = 1; /* fi-2 */ long long fi1 = 1; /* fi-1 */ long long fi = 1; /* fi */ int i; for(i=3; i<=n; i++) { fi = fi2 + fi1; fi2 = fi1; fi1 = fi; } return fi; } long long はメモリ上では 64bit 確保され, 約9223京まで扱えます printf をつかって表示したいときは, %lld (%とdの間は2つのL (小文字)) をつかいます
計算の大変さ (再帰を使わない場合) int fibo2(int n){ int fi2 = 1; /* fi-2 */ int fi1 = 1; /* fi-1 */ int fi = 1; /* fi */ int i; for(i=3; i<=n; i++) { fi = fi2 + fi1; fi2 = fi1; fi1 = fi; } return fi; } fibo(50)の計算の手間 初期値の設定など(50-2)回の繰り返し 1回につき3命令 大まかに見積もって, 150命令程度
計算の大変さ(再帰を使う場合) F(50)=12586269025 long long fibo1(int n) if(n==1 || n==2) { return 1; } else { return fibo1(n-1)+fibo1(n-2); }} N(1)とN(2)は1回の判定とreturnだけなので,N(1)=N(2)=1 と仮定する fibo1(n)の計算の手間をN(n)とすると,N(n)≒N(n-1)+N(n-2)+(判定や足し算の手間) フィボナッチ数の定義じゃん!
再帰使用上の注意 • 慣れれば再帰でアルゴリズムを考えると楽なことも多いんだけど,問題によっては再帰を使うとエライ目に遭うこともあります. • 基本的に「再帰でないと書けない」という問題はありません. • どんな時に再帰を避けるべきかについての細かな議論はここではしません. • 再帰を使わないでも簡単に書けそうな時は再帰を使わないくらいに思っておいて下さい.
どうしても再帰を使いたくなっちゃう問題 ハノイの塔どうしても再帰を使いたくなっちゃう問題 ハノイの塔 A C B 台に3本の棒A,B,Cが固定されている.Aの棒に何枚かの円盤が大きいものを下にして重ねて置かれている(図では3枚だけど,何枚でも可). このとき,次の規則に従って,円盤をAからBに移動させよ. 1)一回に一枚の円盤しか動かしてはいけません. 2)移動の途中で円盤の大小を逆に積んではいけません. 常に大きい方の円盤が下になるようにして下さい. 3)棒A,B,C以外のところに円盤を置いてはいけません.
ハノイの塔 C B A 3枚の円盤をA から B に移動するには 1. 黄色をAからBに 2. 赤色をAからCに 3. 黄色をBからCに 4. 青色をAからBに 5. 黄色をCからAに 6. 赤色をCからBに 7. 黄色をAからBに のように移動すればよい } 2枚の円盤をAからCに移動する } 一番下の円盤を移動する } 2枚の円盤をCからBに移動する
2番目に大きな円盤と その上にある円盤たち(β) 2番目に大きな円盤と その上にある円盤たち(β) 2番目に大きな円盤と その上にある円盤たち(β) C A B 一番大きな円盤(α) 一番大きな円盤(α) 手順: 1)βをCに移す 再帰! 2)αをBに移す 3)βをCに移す 再帰! 実際に予習項目ex5b で体験してもらっています