LoginSignup
10
10

More than 5 years have passed since last update.

C言語で再帰的な総当りプログラムを書く

Posted at

1. 総当りプログラムの概要

N桁のM進数が取りうるすべての整数を出力します。

たとえば3桁の2進数なら、
{000, 001, 010, 011, 100, 101, 110, 111}
を出力します。

forループ1回で、1桁のM進数が取りうるすべての整数を出力できるので、
総当りプログラムをforループで書くと、実にN重ループのコードになってしまいます。

今回はそんな問題を、再帰構造にすることで綺麗にまとめてしまいましょう。

2. ソースコード

はじめにソースコードを提示します。
マクロの値を変えることで、パラメータを変更できます。

  • MAX_IDX : N桁
  • MAX_VAL : M進数
  • INIT : M進数の初期値(基本は0)
recursive_brute_force.c
#include <stdio.h>

#define MAX_IDX 2
#define MAX_VAL 1
#define INIT 0

void print_a(int* a){

    int i = 0;

    for (i = 0; i < MAX_IDX+1; i++){
        printf("%d ", a[i]);
    }
    printf("\n");

}

int check_init(int* a){

    int i;

    for (i = 0; i <= MAX_IDX; i++){
        if (a[i] != INIT) return 0;
    }

    return 1;

}

int recursive_increment(int idx, int* a){

    if (idx < MAX_IDX) {
        while (1){
            a[idx] += recursive_increment(idx + 1, a);
            if (a[idx] > MAX_VAL){
                a[idx] = INIT;
                return 1;
            }
            if (check_init(a)) break;
        }
        return 0;
    }
    else {
        print_a(a);
        if (a[idx] == MAX_VAL){
            a[idx] = INIT;
            return 1;
        }
        else{
            a[idx]++;
            return 0;
        }
    }

    return -1;
}

int main(void){

    int a[MAX_IDX + 1];
    int buf, i;

    for (i = 0; i < MAX_IDX + 1; i++){
        a[i] = INIT;
    }

    buf = recursive_increment(0, a);

    return 0;
}

3. 解説

上記のソース(recursive_brute_force.c)の詳細を説明します。

3.1. アプローチ

まずは簡単な例として、3桁の2進数を考えましょう。

冒頭に示したとおり、この場合に取りうる値は
{000, 001, 010, 011, 100, 101, 110, 111}
となります。

この集合を総当りで表示するには、次のアルゴリズムが考えられます。

  1. 取りうる最も低い値(000)で初期化する。2に進む。
  2. 1桁目をインクリメントする。3に進む。
  3. 1桁目が最大値(1)を超えたら、4に進む。そうでない場合、2に進む。
  4. 1桁目を初期値(0)にし、2桁目をインクリメントする。2桁目が最大値を超えたら、5に進む。そうでない場合、2に進む。
  5. 1桁目と2桁目を初期値にし、3桁目をインクリメントする。3桁目が最大値を超えたら、終了する。そうでない場合、2に進む。

当たり前と言えば当たり前ですが、このアルゴリズムが基本になります。
上の例では3桁の2進数と状況を限定しているので、手計算でも、forループでも簡単に計算できますが、
一般化するとなると少々難しくなります。

3.2. 再帰的に一般化

3.1.で示したアルゴリズムを再帰的に一般化すると、下記のようになります。

  1. 取りうる最も低い値で初期化する。桁を表わす変数idxを0で初期化する(idx=0は最上位桁)。2に進む。
  2. idxが最下位桁を指していなければ、再帰する。idxをインクリメントし、再び2に進む。最下位桁なら、3に進む。
  3. 最下位桁の値が最大値なら、最下位桁を初期値にし、1を返す。それ以外なら、最下位桁をインクリメントし、0を返す。4に進む。
  4. 返り値をidx桁に足す。最大値を超えたら、idx桁を初期値にし、1を返して、再び4に進む。それ以外なら、5に進む。
  5. すべての桁が初期値なら、0を返して、4に進む。

スタック上の関数がmain関数のみとなったとき、このアルゴリズムは終了します。

再帰をするたびにidxがインクリメントしてより低い桁を指すようになり、
再帰から返るたびに桁がひとつずつ上がっていく、というイメージが理解できれば、掴みやすいです。

10
10
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10