最近、かなーり古いアルゴリズム本でアルゴリズムの復習をしたりしている。
やっぱ再帰は苦手だな〜。という愚痴。
以下はC言語で、要素数4つのint
配列、{0, 0, 0, 0}
の要素の合計値が1以上4以下になる場合の配列の組み合わせを示したもの。
中々理解出来なかったのでメモしておく。
#include <stdio.h> #include <stdlib.h> int number_using[] = {0,0,0,0}; void make_numbers(int begin, int sum){ int i, j; if( sum > 4){ return; } for(i=begin; i<4; i++){ number_using[i]++; printf("{ "); for(j=0; j<4; j++){ printf("%d ", number_using[j]); } printf("}\n"); make_numbers( i, sum+1 ); number_using[i]--; } } int main(void){ make_numbers(0, 1); return EXIT_SUCCESS; }
つまり、こーんな感じの配列を羅列するのを再帰を使って書く。
{ 1 0 0 0 } { 2 0 0 0 } { 3 0 0 0 } { 4 0 0 0 } { 3 1 0 0 } { 3 0 1 0 } { 3 0 0 1 } { 2 1 0 0 } { 2 2 0 0 } { 2 1 1 0 } { 2 1 0 1 } ... { 0 0 0 3 } { 0 0 0 4 }
まず、配列内の合計値sum
が4超えた場合、呼び出し元に処理を返す。
if( sum > 4){ return; }
このループi
は配列のindexを表す。よってindex:0
の要素から処理していくことになる。
for(i=begin; i<4; i++){ }
要素:0
に対してsum=2
とした関数を呼び出す。
void make_numbers(int begin, int sum){ int i, j; if( sum > 4){ return; } for(i=begin; i<4; i++){ make_numbers( i, sum+1 ); } }
つまり、こうすることによって、i=0
を維持したまま、sum
に値を加えていく。
{ 1 0 0 0 } { 2 0 0 0 } { 3 0 0 0 } { 4 0 0 0 }
sumが境界値を超えると、一つ前の呼び出し元、sumの値が3
の時の処理に戻り、ループが一つ進むみi=1
となる。
{ 3 1 0 0 }
これが終わるとi=2
, i=3
と続き、ループが一周すると、i=0
, sum=2
の時の処理に戻ってくる。
{ 3 0 1 0 } { 3 0 0 1 }
それで再びsumの値を1加えていきーが続いていき、一番頭の呼び出しもとループのi
が4に到達した時に処理が終了する。
{ 2 1 0 0 } { 2 2 0 0 } { 2 1 1 0 } { 2 1 0 1 } ... { 0 0 0 3 } { 0 0 0 4 }
あとは、その時処理している関数内のi
に対して、値を足してあげ、再帰関数から処理が戻ってくると値を減らしてあげればよい。
int number_using[] = {0,0,0,0}; void make_numbers(int begin, int sum){ int i, j; if( sum > 4){ return; } for(i=begin; i<4; i++){ number_using[i]++; make_numbers( i, sum+1 ); number_using[i]--; } }
再帰は深さ優先であることを念頭に置いて、関数の入れ子をツリーとして図示すると分かり易いかも。なんて思ってたりもした。
ただ、理屈は理解しても、すぐにこんなの書けないよ〜という自分の脳みそ...
リンク
リンク