迷宮尋路問題全解


1、深度優先搜索(DFS)+回溯

最基本的板子:

void DFS(int x,int y)
{
    if (x,y都與目標點相同)
    {
        得到一個解;
    }
    else
    {
        for (int i = 1; i <= 四個方向; i++)
            if (滿足進一步搜索條件)
            {
                為進一步搜索所需要的狀態打上標記;
                DFS(to_x, to_y);
                恢復到打標記前的狀態;//也就是回溯一步
            }
    }
}

適用類型①:求可行解數量

https://www.luogu.org/problemnew/show/P1605

#include <iostream>
using namespace std;

//上下左右
int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} };

int m[10][10];

int N, M, T, cnt;
int SX, SY, EX, EY;

void DFS(int x, int y) {
    if (x < 1 || x > N || y < 1 || y > M) return;
    if (x == EX && y == EY) {
        cnt++;
        return;
    }
    for (int i = 0; i < 4; i++) {
        if (m[x + direction[i][0]][y + direction[i][1]] != 1) {
            m[x + direction[i][0]][y + direction[i][1]] = 1;
            DFS(x + direction[i][0], y + direction[i][1]);
            m[x + direction[i][0]][y + direction[i][1]] = 0;
        }
    }
}
int main() {
    cin >> N >> M >> T;
    cin >> SX >> SY >> EX >> EY;
    for (int i = 0; i < T; i++) {
        int x, y;
        cin >> x >> y;
        m[x][y] = 1;
    }
    m[SX][SY] = 1;
    DFS(SX, SY);
    cout << cnt << endl;
    return 0;
}
View Code

適用類型②:輸出所有可行解

例題:https://www.luogu.org/problemnew/show/P1238

這類題目需要注意的是,要知道搜索前進方向的順序,比如本題是:上左右下。

如果題目夠嚴謹的話,一定會寫出來的,但如果沒寫的話,只能根據題目樣例去判斷。

#include <iostream>
using namespace std;

//上左右下
int direction[4][2] = { {-1,0},{0,-1},{0,1},{1,0} };

int m[20][20];
int path[250][2];

int N, M, T, cnt;
int SX, SY, EX, EY;

void DFS(int x, int y,int k) {
    if (x < 1 || x > M || y < 1 || y > N) return;
    if (x == EX && y == EY) {
        cnt++;
        for (int i = 0; i < k; i++) {
            cout << "(" << path[i][0] << "," << path[i][1] << ")";
            if (i != k - 1)cout << "->";
        }
        cout << endl;
        return;
    }

    for (int i = 0; i < 4; i++) {
        int tox = x + direction[i][0], toy = y + direction[i][1];
        if (m[tox][toy] == 1) {
            m[tox][toy] = 0;
            path[k][0] = tox;
            path[k][1] = toy;
            DFS(tox, toy, k + 1);
            m[tox][toy] = 1;
        }
    }
}
int main() {
    cin >> M >> N;
    for (int i = 1; i <= M; i++) {
        for (int j = 1; j <= N; j++) {
            cin >> m[i][j];
        }
    }
    cin >> SX >> SY >> EX >> EY;
    m[SX][SY] = 0;
    path[0][0] = SX, path[0][1] = SY;
    DFS(SX, SY, 1);
    if (cnt == 0)cout << "-1" << endl;
    return 0;
}
View Code

注意:

  • 當 m , n 較大時,無法勝任,撐死在15左右就嗝屁了(還得是迷宮中障礙的位置比較配合的情況,一般大於10,就要慎重考慮該不該用DFS了)。
  • 搜索前進方向的順序是可能會影響到效率的,如果起點在左上部分,終點在右下部分,理想情況下,優先選擇右方向和下方向,可以根據起點和終點的位置關系,調整方向數組的順序,效率會更高。
  • 找到的第一條解,不一定是最短的,應該說一般都不是。

2、廣度優先搜索(BFS)

適用類型①:最短路徑的長度

題目鏈接:走迷宮

#include <iostream>
#include <stdio.h>
#include <queue>
using namespace std;

#define PAIR make_pair
//上下左右
int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} };

char m[20][20];
int cnt[250];
int head = 0, tail = 1;
int startx = 0, starty = 1, endx = 9, endy = 8;

void BFS(int x, int y) {
    queue<pair<int, int>>q;
    q.push(PAIR(x, y));
    while (!q.empty()) {
        x = q.front().first; y = q.front().second;
        q.pop();
        for (int i = 0; i < 4; i++) {
            int tox = x + direction[i][0], toy = y + direction[i][1];
            if (m[tox][toy] == '.' && (tox >= startx && tox <= endx && toy >= starty && toy <= endy)) {
                if (tox == endx && toy == endy) {
                    cout << cnt[head] + 1 << endl;//獲得當前的層數
                    return;
                }
                //cnt[head]記錄當前到了第幾層BFS
                cnt[tail++] = cnt[head] + 1;
                m[tox][toy] = '#';
                q.push(PAIR(tox, toy));
            }
        }
        head++;
    }
}
int main() {
    while (true) {
        head = 0, tail = 1;
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                if (scanf("%c", &m[i][j]) == EOF) return 0;
            }
            getchar();
        }
        m[startx][starty] = '#';
        BFS(startx, starty);
    }
    return 0;
}
View Code

適用類型②:找到最短的一條路徑

題目鏈接:http://poj.org/problem?id=3984

注意:

①:BFS搜到的第一條一定是最短的,但是最短的不一定只有一條。題目未說明有唯一解的,要注意題目對解的要求。

②:如果不止一個解,一般題目會給定按照字典序(上下左右用U D L R表示)、優先向某個方向等要求,輸出指定解。

③:BFS沒有辦法想DFS那樣直接把路徑存下來;只能把每個點的前驅記下來,這樣最后到了終點,得到的路徑正好是是反過來的。兩種選擇,1、自行處理倒過來輸出。2、BFS直接倒過來搜,即從終點向起點搜,負負得正

#include <iostream>
#include <queue>
using namespace std;

#define PAIR make_pair
//上下左右
int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} };

int m[20][20];
int path[15][15][2];

void BFS(int x, int y) {
    queue<pair<int, int>>q;
    q.push(PAIR(x, y));
    while (!q.empty()) {
        x = q.front().first; y = q.front().second;
        q.pop();
        for (int i = 0; i < 4; i++) {
            int tox = x + direction[i][0], toy = y + direction[i][1];
            if (m[tox][toy] == 0 && (tox >= 0 && tox <= 4 && toy >= 0 && toy <= 4)) {

                if (tox == 0 && toy == 0) {
                    cout << "(" << 0 << ", " << 0 << ")" << endl;
                    while (!(x == 4 && y == 4)) {
                        cout << "(" << x << ", " << y << ")" << endl;
                        int from_x = path[x][y][0];
                        int from_y = path[x][y][1];
                        x = from_x; y = from_y;
                    }
                    cout << "(" << 4 << ", " << 4 << ")" << endl;
                    return;
                }

                m[tox][toy] = 1;
                path[tox][toy][0] = x;
                path[tox][toy][1] = y;
                q.push(PAIR(tox, toy));
            }
        }
    }
}
int main() {
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            cin >> m[i][j];
        }
    }
    m[4][4] = 0;
    BFS(4, 4);
    return 0;
}
View Code

優化:雙向BFS

正向BFS,與反向BFS用不同的值取標記地圖,第一次相遇時(某一方發現對方的標記值),一定是一條最短的路徑。這個時候,path中記錄着兩段方向相反的路徑,輸出的時候需要處理。

同樣的,如果題目要求是按照某種順序、優先某個方向;那么反向的BFS只要反着來就行了。

#include <iostream>
#include <stack>
#include <queue>
using namespace std;

#define PAIR make_pair

int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} };

int m[20][20];
int path[15][15][2];
bool flag = false;
int SX = 0, SY = 0, EX = 4, EY = 4;
void move_one_step(queue<pair<int,int>>&q,int sign) {
    int x = q.front().first, y = q.front().second; q.pop();
    for (int i = 0; i < 4; i++) {
        int tox = x + direction[i][0], toy = y + direction[i][1];
        if (m[tox][toy] == 0 && (tox >= SX && tox <= EX && toy >= SX && toy <= EY)) {
            m[tox][toy] = sign;
            path[tox][toy][0] = x;
            path[tox][toy][1] = y;
            q.push(PAIR(tox, toy));
        }
        //發現對方標記值
        else if (m[tox][toy] == -sign) {
            int tempx = tox, tempy = toy;
            /*輸出 起點 到相遇點*/
            stack<pair<int, int>>s;
            while (!(tox == 0 && toy == 0)) {
                int t1 = tempx, t2 = tempy;
                s.push(PAIR(tempx, tempy));
                tempx = path[t1][t2][0];
                tempy = path[t1][t2][1];
            }
            while (!s.empty()) {
                int xx = s.top().first, yy = s.top().second;
                s.pop();
                cout << "(" << xx << ", " << yy << ")" << endl;
            }
            /*----------------*/
            /*從相遇點到終點*/
            tempx = x, tempy = y;
            while (!(tempx == 4 && tempy == 4)) {
                int t1 = tempx, t2 = tempy;
                cout << "(" << tempx << ", " << tempy << ")" << endl;;
                tempx = path[t1][t2][0];
                tempy = path[t1][t2][1];
            }
            flag = true;
            return;
        }
    }
}

void BFS(queue<pair<int, int>>&f, queue<pair<int, int>>&r) {
    int i = 1;
    while ((!f.empty() || !r.empty()) && !flag) {
        if (i & 1)
            move_one_step(f, 2);
        else
            move_one_step(r, -2);
        i++;
    }
}
int main() {
    queue<pair<int, int>>f, r;

    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            cin >> m[i][j];
        }
    }
    f.push(PAIR(SX, SY));
    r.push(PAIR(EX, EY));
    m[EX][EY] = -2;
    m[SX][SY] = 2;
    cout << "(" << SX << ", " << SY << ")" << endl;
    BFS(f, r);
    cout << "(" << EX << ", " << EY << ")" << endl;
    return 0;
}
View Code

 3、A*搜索

A*找到的第一個解不一定是最短的,所以A*不能用來去找最短路徑(在有多解的情況下);

A*的搜索特性限制,如果用來輸出所有可行解,就沒有使用的意義了。

所以其實迷宮題並不適合用A*解,除非題目要求比較特殊(只有一條路),僅做參考。

采取

適用類型:找唯一的路徑

#include <iostream>
#include <queue>
using namespace std;

#define PAIR make_pair
//上下左右
int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} };
int sx = 0, sy = 0, ex = 4, ey = 4;
class Node {
public:
    int x, y, g, h;
    Node(int x, int y,int g){
        this->x = x;
        this->y = y;
        this->g = g;
        h = abs(ex - x) + abs(ey - y);
    }
    bool operator<(Node n)const {
        return n.g + n.h < g + h;
    }
};

int m[20][20];
int path[15][15][2];
int head = 0, tail = 1;
int cnt[255];

void BFS(int x, int y) {
    priority_queue<Node>q;
    //queue<pair<int, int>>q;
    q.push(Node(x, y, cnt[head]));

    while (!q.empty()) {
        x = q.top().x; y = q.top().y;
        q.pop();
        if (x == 0 && y == 0) {
            while (true) {
                cout << "(" << x << ", " << y << ")" << endl;
                if (x == 4 && y == 4)break;
                int from_x = path[x][y][0];
                int from_y = path[x][y][1];
                x = from_x; y = from_y;
            }
            return;
        }
        for (int i = 0; i < 4; i++) {
            int tox = x + direction[i][0], toy = y + direction[i][1];
            if (m[tox][toy] == 0 && (tox >= 0 && tox <= 4 && toy >= 0 && toy <= 4)) {
                cnt[tail++] = cnt[head] + 1;
                m[tox][toy] = 1;
                path[tox][toy][0] = x;
                path[tox][toy][1] = y;
                q.push(Node(tox, toy, cnt[head] + 1));
            }
        }
        head++;
    }
}
int main() {
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            cin >> m[i][j];
        }
    }
    m[4][4] = 0;
    BFS(4, 4);
    return 0;
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM