#include <stdio.h>
int factorial(int n)
{
int r;
if (n == 0) {
r = 1;
} else {
r = n * factorial(n-1);
}
return r;
}
int main(void)
{
printf("%d\n", factorial(10));
return 0;
}
|
このプログラムを実行すると、
#include <stdio.h>
int factorial(int n)
{
int r;
printf("enter (n=%d)\n", n);
if (n == 0) {
r = 1;
} else {
r = n * factorial(n-1);
}
printf("exit (n=%d, r=%d)\n", n, r);
return r;
}
int main(void)
{
printf("%d\n", factorial(10));
return 0;
}
|
ローカル変数の値が一般にスタック上に取られるのは、 再帰呼び出しを実現するためである。 単にその関数内の変数を他の部分で使われているものと区別するだけなら、 変数名を(コンパイラ内部で自動的に)何か他と衝突しない別のものに 置換するだけでよい。
ところで、この階乗を計算する関数は、言うまでも無く再帰呼び出しなど 使わなくても実現できるし、再帰を使わない方が効率が良い。 そこで、もう少し再帰を使う意味がある例を示す。

#include <stdio.h>
void hanoi(int from, int to, int work, int n)
{
if (n == 1) {
printf("move %d to %d\n", from, to);
return;
}
hanoi(from, work, to, n-1);
hanoi(from, to, work, 1);
hanoi(work, to, from, n-1);
}
int main(int argc, char **argv)
{
int n;
sscanf(argv[1], "%d", &n);
hanoi(0, 1, 2, n);
return 0;
}
|
こんな簡単なプログラムで解けてしまう。
hanoi2.c は、移動の様子を図示するように改造したもの。
クイーンは、

のように八方に動ける。動ける場所に他のクイーンがないような配置とは、 例えば

のようなものである。
このような配置は、全部で92通り、回転や裏返しを除くと12通り存在する。
(4 0 3 5 7 1 6 2)
という数値で表せる。これをboard[i]という配列に格納すると、 問題は、
#include <stdio.h>
int board[8];
void solve7()
{
int bi, j, f;
for (bi=0; bi<8; bi++) {
f = 0;
for (j=0; j<7; j++) {
if (bi == board[j]) f=1;
if (7 - j == bi - board[j]) f=1;
if (7 - j == board[j] - bi) f=1;
}
if (f == 1) continue;
board[7] = bi;
for (j=0; j<8; j++) {
printf("%d ", board[j]);
}
printf("\n");
}
}
void solve6()
{
int bi, j, f;
for (bi=0; bi<8; bi++) {
f = 0;
for (j=0; j<6; j++) {
if (bi == board[j]) f=1;
if (6 - j == bi - board[j]) f=1;
if (6 - j == board[j] - bi) f=1;
}
if (f == 1) continue;
board[6] = bi;
solve7();
}
}
void solve5()
{
int bi, j, f;
for (bi=0; bi<8; bi++) {
f = 0;
for (j=0; j<5; j++) {
if (bi == board[j]) f=1;
if (5 - j == bi - board[j]) f=1;
if (5 - j == board[j] - bi) f=1;
}
if (f == 1) continue;
board[5] = bi;
solve6();
}
}
void solve4()
{
int bi, j, f;
for (bi=0; bi<8; bi++) {
f = 0;
for (j=0; j<4; j++) {
if (bi == board[j]) f=1;
if (4 - j == bi - board[j]) f=1;
if (4 - j == board[j] - bi) f=1;
}
if (f == 1) continue;
board[4] = bi;
solve5();
}
}
void solve3()
{
int bi, j, f;
for (bi=0; bi<8; bi++) {
f = 0;
for (j=0; j<3; j++) {
if (bi == board[j]) f=1;
if (3 - j == bi - board[j]) f=1;
if (3 - j == board[j] - bi) f=1;
}
if (f == 1) continue;
board[3] = bi;
solve4();
}
}
void solve2()
{
int bi, j, f;
for (bi=0; bi<8; bi++) {
f = 0;
for (j=0; j<2; j++) {
if (bi == board[j]) f=1;
if (2 - j == bi - board[j]) f=1;
if (2 - j == board[j] - bi) f=1;
}
if (f == 1) continue;
board[2] = bi;
solve3();
}
}
void solve1()
{
int bi, j, f;
for (bi=0; bi<8; bi++) {
f = 0;
for (j=0; j<1; j++) {
if (bi == board[j]) f=1;
if (1 - j == bi - board[j]) f=1;
if (1 - j == board[j] - bi) f=1;
}
if (f == 1) continue;
board[1] = bi;
solve2();
}
}
void solve0()
{
int bi;
for (bi=0; bi<8; bi++) {
board[0] = bi;
solve1();
}
}
int main(void)
{
solve0();
return 0;
}
|
これを見るとほとんど同じような関数ばかりが並んでいることが分かる。 このような書き方ではバグが混入しやすいし、何よりも8クイーンでなく Nクイーンを解くようなプログラムを書くことが出来ない。
そこで、全て一つの関数で書いて、引数でその関数が何番目の関数かを 指定することにする。
#include <stdio.h>
#define N 8
int board[N];
void solve(int i)
{
int bi, j, f;
for (bi=0; bi<N; bi++) {
f = 0;
for (j=0; j<i; j++) {
if (bi == board[j]) f=1;
if (i - j == bi - board[j]) f=1;
if (i - j == board[j] - bi) f=1;
}
if (f == 1) continue;
board[i] = bi;
if (i == N - 1) {
for (j=0; j<N; j++) {
printf("%d ", board[j]);
}
printf("\n");
} else {
solve(i+1);
}
}
}
int main(void)
{
solve(0);
return 0;
}
|
極めて単純になり、かつNクイーンへの拡張も容易になったことが 分かるであろう。
このようなアルゴリズムを「バックトラック法」と呼ぶ。