zoukankan      html  css  js  c++  java
  • DFS和回溯法实现走迷宫问题

    今天记录一下用DFS和回溯法实现走迷宫问题,输出一个迷宫中从起点到终点的所有可能的路径。

    迷宫我们用一个二维的字符数组表示,其中的0表示路,1表示墙。 为了方便起见,我们从txt文件中读入这个数组,txt文件中的内容如下所示:

    接下来我们写一下从文件中读入这个数组的代码: 

     1 vector initMaze(string fileName = "maze.txt")
     2 {
     3     ifstream fin;
     4     fin.open(fileName);
     5     if (!fin)
     6     {
     7         cout << "open '" << fileName << "' failed!" << endl;
     8         return {};
     9     }
    10 
    11     vector res;
    12     string line;
    13     while (getline(fin, line))
    14     {
    15         trim(line);
    16         res.push_back(line);
    17     }
    18     return res;
    19 }

    由于文件中的字符之间可能存在空格,于是我们写一个函数过滤掉这些空格,有一说一,C++在很多细节处理方面是远没有python方便的

     1 void trim(string &s)
     2 {
     3     int index = 0;
     4     if (!s.empty())
     5     {
     6         while ((index = s.find(' ', index)) != string::npos)
     7         {
     8             s.erase(index, 1);
     9         }
    10     }
    11 }


    然后我们准备好要用到的数据结构,由于其中很多地方都要用到坐标,所以我们写一个坐标的结构体

     1 typedef struct pos
     2 {
     3     int x;
     4     int y;
     5     pos(int x, int y) : x(x), y(y) {}
     6     pos() : x(-1), y(-1) {}
     7     pos(int v[2]) : x(v[0]), y(v[1]) {}
     8     pos operator+(pos p)
     9     {
    10         pos tmp = pos(this->x + p.x, this->y + p.y);
    11         return tmp;
    12     }
    13     pos operator+(int v[2])
    14     {
    15         pos tmp = pos(this->x + v[0], this->y + v[1]);
    16         return tmp;
    17     }
    18     pos operator-(pos p)
    19     {
    20         pos tmp = pos(this->x - p.x, this->y - p.y);
    21         return tmp;
    22     }
    23     pos operator-(int v[2])
    24     {
    25         pos tmp = pos(this->x - v[0], this->y - v[1]);
    26         return tmp;
    27     }
    28     bool operator==(pos p) { return this->x == p.x && this->y == p.y; }
    29     bool operator==(int v[2]) { return this->x == v[0] && this->y == v[1]; }
    30 } pos;

    其中我们主要是定义了一些构造函数和重载了一些运算符。其实C++中的结构体和类基本用法是一样的,唯一的区别就是class默认是private,struct默认是public。

    接下来我们写一下走迷宫过程的dfs函数

     1 int dfs(stack &s, pos now, pos end, vector<vector> &flag, vector &maze, vector<vector> &path)
     2 {
     3     if (now == end)
     4     {
     5         copyStack2Vector(s, path);
     6         return 1;
     7     }
     8     else
     9     {
    10         flag[now.x][now.y] = 1;                             //标记当前位置走过
    11         int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; //上、下、左、右
    12         for (int i = 0; i < 4; i++)
    13         {
    14             pos next = now + dir[i];
    15             if (!judge(next, end, flag, maze))
    16                 continue; //如果下一个节点不合法, 那么直接继续判断下一个可能的位置
    17             s.push(next);
    18             if (dfs(s, next, end, flag, maze, path))
    19             {
    20                 s.pop();
    21             }
    22         }
    23         flag[now.x][now.y] = 0;
    24         s.pop(); //说明这边走不通了,把栈顶的位置弹出
    25         return 0;
    26     }
    27 }

    这里需要详细解释一下,首先我们看一下递归函数的参数

    stack& s, pos now, pos end, vector<vector>& flag, vector& maze, vector<vector>& path

    由于走迷宫过程中可能走到某一步的时候发现无法继续前进了,那么我们只能往回退一步(回溯)。那么我们就需要用一种数据结构去记录我们走过的路径,很明显,栈是符合我们要求的(后进先出)。所以这里的 stack& s 就是记录目前我们走过的路径。

    pos now 是我们当前的位置,我们每次往前走一步就要更新这个变量。

    pos end 是最后的终点位置,始终保持不变。

    vector<vector>& flag是用来记录哪些位置是我们走过的,这个主要是为了避免我们又走到当前栈里面存在的位置上。所以一旦一个位置走过,我们就在这个数组里面标记一下。

    vector& maze是我们的迷宫矩阵,里面的字符只能是0或者1,flag的维度和maze的维度相同。

    vector<vector>& path 是用来记录我们找到的路径,每当我们走到终点的时候,我们就把栈里面的所有位置组成一条路径,再添加到path中。

    然后我们再解释一下其中的代码。 既然是递归形式的代码,那么第一步自然是先写好边界条件,就好比n皇后问题一样,第一步是确定递归到什么时候结束,这里明显是当我们到了终点就结束。然后把此时栈里面的路径添加到结果path中。 每次进来,先把当前位置标记为已经在当前的路径当中,然后接下来就是试探性往上下左右某一个方向走一步,所以我们需要判断下一个位置是否可以走,这里是用judge函数判断的,其定义如下:

    1 bool judge(pos p, pos end, vector<vector> &flag, vector &maze)
    2 {
    3     return p.x >= 0 && p.x <= end.x && p.y >= 0 && p.y <= end.y && maze[p.x][p.y] != '1' && flag[p.x][p.y] == 0; //没有越界,没有走过,有路
    4 }

    如果当前位置没有在迷宫外边,并且当前位置的迷宫中标记为0,同时flag数组没有标记过该位置,也就是还没在当前已经走过的路径中,所以是可以走的 回到dfs函数,如果当前该位置不可以走,那么就去判断下一个位置,一旦可以走,就把下一个位置添加到栈中,然后从下一个位置开始递归。 如果递归返回的是1,说明成功过一次,那么就弹出当前栈顶位置,去判断下一个位置。 如果当前位置的周围的4个位置都走不通,那么就说明当前位置是不能走的,于是我们把当前位置从路径中去掉,也就是最后面的

    flag[now.x][now.y] = 0;
    s.pop();//说明这边走不通了,把栈顶的位置弹出
    return 0;

    针对我们上面那个迷宫,程序输出了740种走法,如下所示:

    其中@表示起始位置,$表示终点位置,中间的+ - |字符表示路径。


    最后,完整程序如下: 

      1 // Maze.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
      2 //
      3 
      4 #include <iostream>
      5 #include <fstream>
      6 #include <vector>
      7 #include <string>
      8 #include <stack>
      9 
     10 using namespace std;
     11 
     12 typedef struct pos {
     13     int x;
     14     int y;
     15 
     16     pos(int x, int y) : x(x), y(y){}
     17     pos() : x(-1), y(-1) {}
     18     pos(int v[2]) : x(v[0]), y(v[1]) {}
     19 
     20     pos operator+(pos p) {
     21         pos tmp = pos(this->x + p.x, this->y + p.y);
     22         return tmp;
     23     }
     24 
     25     pos operator+(int v[2]) {
     26         pos tmp = pos(this->x + v[0], this->y + v[1]);
     27         return tmp;
     28     }
     29 
     30     pos operator-(pos p) {
     31         pos tmp = pos(this->x - p.x, this->y - p.y);
     32         return tmp;
     33     }
     34 
     35     pos operator-(int v[2]) {
     36         pos tmp = pos(this->x - v[0], this->y - v[1]);
     37         return tmp;
     38     }
     39 
     40     bool operator==(pos p) {
     41         return this->x == p.x && this->y == p.y;
     42     }
     43 
     44     bool operator==(int v[2]) {
     45         return this->x == v[0] && this->y == v[1];
     46     }
     47 }pos;
     48 
     49 void trim(string& s)
     50 {
     51     int index = 0;
     52     if (!s.empty()) {
     53         while ((index = s.find(' ', index)) != string::npos) {
     54             s.erase(index, 1);
     55         }
     56     }
     57 }
     58 
     59 vector<string> initMaze(string fileName = "maze.txt") {
     60     ifstream fin;
     61     fin.open(fileName);
     62     if (!fin) {
     63         cout << "open '" << fileName << "' failed!" << endl;
     64         return {};
     65     }
     66     vector<string> res;
     67 
     68     string line;
     69     while (getline(fin, line)) {
     70         trim(line);
     71         res.push_back(line);
     72     }
     73 
     74     return res;
     75 }
     76 
     77 bool judge(pos p, pos end, vector<vector<int>>& flag, vector<string>& maze) {
     78     return p.x >= 0 && p.x <= end.x && p.y >= 0 && p.y <= end.y &&
     79         maze[p.x][p.y] != '1' && flag[p.x][p.y] == 0;//没有越界,没有走过,有路
     80 }
     81 
     82 void copyStack2Vector(stack<pos> s, vector<vector<pos>>& path) {
     83     stack<pos> s2 = s;
     84     stack<pos> s3;
     85     vector<pos> vec;
     86     while (!s2.empty()) {
     87         pos p = s2.top();
     88         s2.pop();
     89         s3.push(p);
     90     }
     91 
     92     while (!s3.empty()) {
     93         pos p = s3.top();
     94         s3.pop();
     95         vec.push_back(p);
     96     }
     97 
     98     path.push_back(vec);
     99 }
    100 
    101 int dfs(stack<pos>& s, pos now, pos end, vector<vector<int>>& flag, vector<string>& maze, vector<vector<pos>>& path) {
    102     if (now == end) {
    103         copyStack2Vector(s, path);
    104         return 1;
    105     }
    106     else {
    107         flag[now.x][now.y] = 1;//标记当前位置走过
    108 
    109         int dir[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };//上、下、左、右
    110         for (int i = 0; i < 4; i++) {
    111             pos next = now + dir[i];
    112             if (!judge(next, end, flag, maze)) 
    113                 continue;//如果下一个节点不合法, 那么直接继续判断下一个可能的位置
    114             
    115             s.push(next);
    116 
    117             if (dfs(s, next, end, flag, maze, path)) {
    118                 s.pop();
    119             }
    120         }
    121 
    122         flag[now.x][now.y] = 0;
    123         s.pop();//说明这边走不通了,把栈顶的位置弹出
    124         
    125         return 0;
    126     }
    127 }
    128 
    129 void printMaze(vector<string>& maze) {
    130     for (auto e : maze) cout << e << endl;
    131     cout << endl;
    132 }
    133 
    134 void printMaze(vector<vector<unsigned char>>& maze) {
    135     for (auto vec : maze) {
    136         for (auto e : vec)
    137             cout << e << " ";
    138         cout << endl;
    139     }
    140     cout << endl;
    141 }
    142 
    143 int checkDirection(pos a, pos b) {
    144     int dir[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };//上、下、左、右
    145     for (int i = 0; i < 4; i++) {
    146         if (a + dir[i] == b) {
    147             return i;
    148         }
    149     }
    150 }
    151 
    152 unsigned char printPath(pos pre, pos now, pos next, pos end) {
    153     int dir[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };//上、下、左、右
    154     if (now == pos(0, 0)) {
    155         return '@';
    156     }
    157     else if (now == end) {
    158         return '$';
    159     }
    160     else {
    161         int pre_now = checkDirection(pre, now), now_next = checkDirection(next, now);
    162         if ((pre_now == 2 && now_next == 1) || (pre_now == 1 && now_next == 2))
    163             return '+';//Left Down
    164         else if ((pre_now == 0 && now_next == 3) || (pre_now == 3 && now_next == 0))
    165             return '+';//Right Up
    166         else if ((pre_now == 2 && now_next == 0) || (pre_now == 0 && now_next == 2))
    167             return '+';//Left Up
    168         else if ((pre_now == 3 && now_next == 1) || (pre_now == 1 && now_next == 3))
    169             return '+';//Left Up
    170         else if ((pre_now == 0 && now_next == 1) || (pre_now == 1 && now_next == 0))
    171             return '|';//Down Up
    172         else if ((pre_now == 2 && now_next == 3) || (pre_now == 3 && now_next == 2))
    173             return '-';//Left Right
    174         else
    175             return '$';
    176     }
    177 
    178     return '$';
    179 }
    180 
    181 int main()
    182 {
    183     //std::cout << "Hello World!
    ";
    184     vector<string> maze = initMaze("maze.txt");
    185     if (maze.empty()) return 0;
    186 
    187     int rows = maze.size(), cols = maze[0].size();
    188     vector<vector<int>> flag(rows, vector<int>(cols, 0));
    189     printMaze(maze);
    190 
    191     pos start = pos(0, 0), end = pos(rows-1, cols-1);
    192 
    193     stack<pos> s;
    194     s.push(start);
    195     
    196     vector<vector<pos>> path;
    197     dfs(s, start, end, flag, maze, path);
    198 
    199     if (!path.empty()) {
    200         for (auto vec : path) {
    201             int path_len = vec.size();
    202             cout << "path length: " << path_len << endl;
    203             vector<vector<unsigned char>> maze_tmp(rows, vector<unsigned char>(cols, '0'));
    204             for (int i = 0; i < rows; i++)
    205                 for (int j = 0; j < cols; j++)
    206                     maze_tmp[i][j] = maze[i][j];
    207             
    208             for (int i = 0; i < path_len; i++) {
    209                 pos now = vec[i];
    210                 pos pre = (i == 0) ? now : vec[i - 1];
    211                 pos next = (i == path_len - 1) ? now : vec[i + 1];
    212                 maze_tmp[now.x][now.y] = printPath(pre, now, next, end);
    213             }
    214 
    215             printMaze(maze_tmp);
    216         }
    217 
    218         cout << "there exists " << path.size() << " paths for this maze" << endl;
    219     }
    220     else {
    221         cout << "there is no path for this maze" << endl;
    222     }
    223 }
  • 相关阅读:
    flask 需要下载的包
    flask知识点
    移动端网页实现(用百分比进行定位)
    js中的preventDefault
    网页重构面试笔试题
    J2EE课程设计的购物车代码(水平有限,仅供参考)
    JavaScript实现对象克隆函数clone( )的程序及分析
    WEB技术书籍推荐
    2016 Tianjin University Software Testing (lab2)
    Mac下安装npm 、node、ionic和cordova
  • 原文地址:https://www.cnblogs.com/njuxjx/p/14020118.html
Copyright © 2011-2022 走看看