![]() |
|||||||||||||||||||
(1)末尾再帰 関数の最後で呼び出される再帰呼出しを 末尾再帰(tail recursion)といいます。 末尾再帰の場合,再帰呼び出しは簡単に除去できます。 再帰除去の前に, 階乗の計算の再帰呼び出しを観察してみましょう。 引数nの値は,呼び出すたびに1ずつ減算されて, 最終的に0になります。 ![]() 引数の値が0になると,リターン値が1になります。 リターンするたびに,それぞれの段階のnの値を乗じます。 すなわち,初期値を1にして1からnまでを乗じることになります。 これをプログラムに直すと,以下のようになります。 private long fact2(long n) { long r=1; for(long i=1;i <= n;i++) r *= i; return r; } (2)真に再帰的な場合 2回以上再帰呼び出しを行う関数を真に(genuinely)再帰的であるといいます。 真に再帰的な関数の動きを見るために,次のプログラムを例にとってみましょう。 private void test(int n) { if(n <= 0 ) return; test(n - 1); MessageBox.Show(n.ToString()); test(n - 2); } このプログラムは,特に意味のある処理ではありませんが, 再帰的な動きを観察することができるようにしたプログラムです。 引数を4として呼び出すと,次の青矢印のように呼び出しが行われ, 赤矢印のようにリターンします。 ![]() 表示は,赤丸数字の順,すなわち 1,2,3,1,4,1,2 の順に表示されます。 以前出てきた,2分木の場合の通りがけ順と同じような表示順序になっています。 (3)再帰的プログラムのループ化 末尾再帰は,簡単にループ化できますが,以下の下線部は再帰のまま残ってしまいます。 private void test(int n) { int nn = n; while (nn > 0 ) { test(nn - 1); MessageBox.Show(nn.ToString()); nn = nn - 2; } } 一般に,再帰的なプログラムを形式的にループ化するには,スタックを使います。 (2)の図の左側に進むときは,現在の値をスタックに積み,1ずつ減じます。 0以下になったら,スタックをポップ(リターン処理)してその値を表示します。 スタックが空になっているときは,終わらせます。 右側に進むときは,末尾再帰ですから,nを2だけ減じて繰返します。 private void test(int n) { int[] stk = new int[20]; int p = 0; int nn = n; bool flag = true; while(flag) // 継続するときtrue { while (nn > 0 ) { stk[p++] = nn; nn = nn - 1; } if(p == 0) flag = false; // スタック空のとき停止 else { nn = stk[--p]; MessageBox.Show(nn.ToString()); nn = nn - 2; } } } 呼出しtest(4)のときのスタック状態を以下に示します。 ![]() ![]() i
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() 上のタイトルをクリックします |
|||||||||||||||||||