バックトラック法(深さ優先探索)で迷路を解く。


問題

3*3のマス目で、1が壁、0が道で表現されている。(0,0)から(2,2)にたどりつくことができるか探索せよ。

解法

バックトラック法で解く。可能性がある道を深堀していき、行き止まりなら分岐点へ戻って進む。この探索の方法を別名で深さ優先探索という。再帰で実装できる。

この問題ではないけど、隣接行列が与えられたときは、もっと簡単に実装できる。

木構造で迷路は表現できる。スタートをルートとして、次に進めるマスを子として木に追加していくと想像する。帰りがけ順に走査するとどこかでゴールにたどり着く。たどり着かなければ道がなかったことになる。

2次元配列ではなく、1次元配列で迷路を取得した。本当は4方向に動けるべきだけど、2方向に限定した。いろいろと端折ってますすみません。

#include <stdio.h>
#define N 3*3
#define LEN 3

int meiro[N] = { 0,0,0,
                 0,1,0,
                 0,1,0 };
// int visited[N];
const int goal = 8;
int success = 0;

int visit(int i){
    printf("%d ",i);

    // if no way return -1
    if( (i+1)%LEN !=0 && !meiro[i+1]) // ひとつ右に行ったとき、(右が端でない && 訪問済みでない && 行き止まりでない)とき
        visit(i+1);
    if( i+LEN < N && !meiro[i+LEN]) // ひとつ下に行ったとき、(下が範囲を超えていない && 訪問済みでない && 行き止まりでない)とき
        visit(i+LEN);
    printf("\n");
    if(i==goal){
        success = 1;
    }
    return success;
}

int main(){
    int r = visit(0);
    printf("\nsuccess: %d\n",r);
}


上のコードだと、全探索してしまうため、ゴールにたどり着いたら探索をやめたい。それを満たす改良版は以下。

#include <stdio.h>
#define N 3*3
#define LEN 3

// ゴールに達したら終了する。

int meiro[N] = { 0,0,0,
                 0,1,0,
                 0,1,0 };
// int visited[N];
const int goal = 8;

int visit(int i){
    printf("%d ",i);
    if(i==goal){
        return 1;
    }else{
        if( (i+1)%LEN !=0 && !meiro[i+1]) // ひとつ右に行ったとき、(右が端でない && 行き止まりでない)とき
            if (visit(i+1) == 1) return 1;
        if( i+LEN < N && !meiro[i+LEN]) // ひとつ下に行ったとき、(下が範囲を超えていない && 行き止まりでない)とき
            if (visit(i+LEN) == 1) return 1;
    }
    return 0;
}

int main(){
    int r = visit(0);
    printf("\nsuccess: %d\n",r);
}

# result
$ ./a.out
0 1 2 5 8
success: 1

ちなみに端に1を敷き詰めて番兵を作ってしまえば、端かどうかの判定がいらない。

11111
10001
10101
10101
11111

こんな感じで。