再帰の除去の演習
再帰形式で書かれたプログラムは、ループ(for文や while文の構造)で書き換えることができる。簡単なもの(末尾再帰)は機械的に、複雑なものもスタックを用いてデータの管理をすることでだいたい機械的に変換することができる。
再帰はプログラムの本質を理解しやすく表現できるが、途中の計算状態の保存をプログラム処理系(コールスタック)に任せるため、メモリの使用効率が上がらなかったり、その上限の制約をうけたりする課題がある。こうしたときに、再帰をループで書き換える
スタックの深さの限界を次のプログラムで確かめてみよう。
code: callstackdepthtest.c
func(int n){
printf("%d\n", n); fflush(stdout);
func(n+1);
}
int main(void){
func(0);
}
ループ for (), while() も無限にできるわけではないが、それ単体ではコールスタック(メモリ)を使わないので、再帰に比べるとより大きな繰り返しができる。また、計算速度もコールスタックの処理をしない分、すこしだけ速い。
末尾再帰
再帰の中で、機械的に除去できるものが末尾再帰である。
プログラムの最後だけで再帰をするものである。
上記のスタックの深さテストも末尾再帰になっている。
まず、末尾が再帰だけになっていることをより分かりやすく示して書き直そう。また繰り返し(再帰)の終了条件/末端条件がなかったので追加した。
code: callstackdepthtestpart.c
func(int n){
if(n>LOOPMAX) return; // 末端条件
printf("%d\n", n); fflush(stdout);
n = n+1
func(n);
}
もとの最後の一行は func(n+1) で、n+1の答えを func( ) に渡す、という二つのことをしていたので、これを二行に分割した。
つぎのように再帰を除去できる。
code: recursiondeleted01.c
int main(void){
int n;
n=0
while(1){
if(n>LOOPMAX) break;
pirntf("%d\n", n); fflush(stdout);
n=n+1;
// 次の繰り返しへ/whileへ戻る
}
}
C言語ではあまり推奨されないが、 while(1) の代わりに、goto文を使って表すこともある。goto はプログラムの(同じ関数内の)任意の場所へのジャンプであり、飛び先の場所にはラベル xxxx: をつける。ラベルは switch( ) 文でも使われる。
code: resusiondeleted02.c
int main(void){
int n;
n=0
loop:
if(n>LOOPMAX) goto end; // 終了時は end: へジャンプする
pirntf("%d\n", n); fflush(stdout);
n=n+1;
goto loop; // 次の繰り返しへ/ loop: へ戻る
end: // 終了のときの飛び出し用ラベル
}
クイズ
再帰の代わりにwhileを使うと繰り返せる回数はどのくらいになるだろうか
演習
1)aからbまでの総和を計算するプログラムを末尾再帰で書こう
プログラムのヒント。末尾再帰は再帰の行がシンプルになる。
code: sumrecursion.c
void sum(int a, int b, int s){
// 、、、、なにかする
// sをつかって総和を求める
a=a+1;
sum(a, b, s);
}
2)末尾再帰のプログラムを、再帰を除去して while または goto で書こう。