深度优先搜索解决迷宫问题

Posted C语言进阶学习

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度优先搜索解决迷宫问题相关的知识,希望对你有一定的参考价值。


学习难,

难在于当你处于学习过程时对自己的克制,

 编程亦是如此。


算法简介

 
深度优先搜索(Depth First Search,简称DFS)算法是图论中的经典算法,是一种针对树和图的遍历算法。其实在之前的文章《 》中,我们已经接触过了,只是当时并没有提到。DFS算法的特点是,给定一个根结点和指定其优先搜索的方向,它就一直朝着这个方向访问子结点,直到访问完毕或者找到指定的结点,然后才返回上一个结点搜索另一个方向。用一句话概括就是“不撞南墙不回头”。下面请先看一个访问二叉树的例子:
/* * 前序遍历 */static void pre_order_tree(tree_p node) {
if ( node == NULL ) {
return; } printf("%d ", node->value); pre_order_tree(node->left); pre_order_tree(node->right);}
这段代码来自于《 》,在该代码中,“南墙”是当下一个结点为空,指定的方向是左子树。
DFS算法可以采用递归或者非递归的方式来实现,当深度较小时可以采用递归方式,当深度较大时递归的方式就不合适了,因为可能会耗尽系统分配给该进程的栈而导致进程崩溃。


迷宫问题

 

有一二维数组定义如下:

int maze[MAX_ROW][MAX_COL] = { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, };
其中0代表可以走,1代表墙壁,不能走,而且只能横着走或者竖着走,不能斜着走。请编程找出从左上角到右下角的路线。


解决思路

 
采用深度优先搜索算法来解决该问题,除了地图边界,每一个点都有上、下、左和右四个方向可以走,当然还需要判断这个方向的下一个点是否为墙壁。每访问一个点,就对该点做一个标记,防止重复走甚至无限循环。关键代码如下:
/** * @brief 深度优先搜索算法获得迷宫路径 * @param entrance 入口坐标 * @param exit 出口坐标 * @param maze 迷宫(二维数组,1表示墙,0表示路) */void get_maze_path(maze_t const entrance, maze_t const exit, int maze[MAX_ROW][MAX_COL]) {
maze_t m; maze_t path[MAX_ROW][MAX_COL];
init_maze_path(path); set_explored(entrance, maze); push(entrance); while (!is_empty()) {
m = pop(); /** * 出口 */ if (m.x == exit.x && m.y == exit.y) {
break; } /** * 分别依次搜索当前点的上、下、左、右四个方向 */ if (m.y-1 >= 0 && maze[m.x][m.y - 1] == 0) {
explore((maze_t) { m.x, m.y - 1 }, m, maze, path); } if (m.y + 1 <= COL_BORDER && maze[m.x][m.y + 1] == 0) {
explore((maze_t) { m.x, m.y + 1 }, m, maze, path); } if (m.x - 1 >= 0 && maze[m.x - 1][m.y] == 0) {
explore((maze_t) { m.x - 1, m.y }, m, maze, path); } if (m.x + 1 <= ROW_BORDER && maze[m.x + 1][m.y] == 0) {
explore((maze_t) { m.x + 1, m.y }, m, maze, path); }#ifdef _DEBUG_ print_maze_debug(maze);#endif } if (m.x == exit.x && m.y == exit.y) { /** * 由于保存的路径是从出口到入口,因此将其入栈后再打印。 */ clear_stack(); do {
push(m); m = path[m.x][m.y]; } while (path[m.x][m.y].x != -1); push(m); while (!is_empty()) {
m = pop(); printf("( %d, %d )\n", m.x, m.y); } } else {
printf("No path\n"); }}

从代码中可以看出,我们使用了“栈”来辅助该算法,因为这次没有采用递归,所以随便手写一个栈来保存当前访问到的点。在这里我们设置的方向顺序是上、下、左、右,并且当找到出口后就结束了,并没有继续找第二条可走的路径,有兴趣的读者可以尝试以下找出所有路径。运行截图如下:

图中,2表示已经走过的点,在第10次搜索时结束。我们可以看到右上部分的一些点并没有搜索就结束了,这是因为优先搜索的方向设置问题,右方向是最后才搜索的,而且程序一旦找到出口就结束,所以有一些地方并没有搜索。如果我们优先搜索右边的点,情况就不一样了,如下图:

深度优先搜索解决迷宫问题


花里胡哨的版本

 
为了让读者更形象地理解,作者还特意编写了一个带有用户界面(基于控制台)的版本,效果如下:

深度优先搜索解决迷宫问题

该版本的核心代码没有改动,只是增加了UI界面的代码,为了使程序在使用更方便,该版本的可以在无UI和有UI之间随意切换,只需要修改“config.h”中的一行代码即可,如下:
/** * 调试宏定义 */#define _DEBUG_#undef _DEBUG_
/** * 使用界面 */#define _USE_UI_//#undef _USE_UI_

如果想要UI,则定义宏“_USE_UI_”,如不需要UI,取消定义“_USE_UI_”宏即可,同时还提供了Debug模式,开启Debug模式,可以查看每一次搜索后地图的情况,但Debug模式只能在无UI下使用。演示视频中读者可能会觉得刷新频率太快,当然这个也可以在“config.h”中修改,单位是毫秒,如下:

/** * 使用UI时,刷新UI时间 */#ifdef _USE_UI_#define REFRESH_TIMES ((unsigned int)100)#endif


总结

 
本文用二维数组来充当迷宫地图,使用深度优先搜索算法查询路径,在记录路径时只能逆序存放,如果直接打印路径,那么坐标是从出口到入口,而非入口到出口的顺序。这是因为在搜索当前点时才存上一点的坐标,有点类似 中的单向链表,所以是逆序,本文并没有对其进行优化,而是先依次将这些点的坐标入栈,然后再出栈打印,这样就能倒过来了。
可见,有什么样的数据结构就决定了可以用什么样的算法。那为什么不再建一个数组来保存每个点的后继呢?从DFS算法的过程可以看出,虽然每个点的前趋只有一个,后继却不止一 个,如果我们为每个点只保存一个后继,则无法保证这个后继指向正确的路线。由此可见,有什么样的算法就决定了可以用什么样的数据结构。设计算法和设计数据结构这两件工作是紧密联系的。
最后,就是源码环节了,作者已经将代码放到百度网盘中,需要的童鞋可复制链接或在公众号回复“深度优先搜索算法”获取。

链接:https://pan.baidu.com/s/1R4R_nsZ8CZj6E9i2h9iC2Q

提取码:aoqk



如果你有想学或者正在学习C语言的好友,长按二维码图即可分享。







也可以长按二维码图添加作者微信交流。



以上是关于深度优先搜索解决迷宫问题的主要内容,如果未能解决你的问题,请参考以下文章

广度优先搜索解决迷宫问题

深度优先搜索

[算法与数据结构] 走迷宫问题(广度与深度优先搜索)

算法浅谈——走迷宫问题与广度优先搜索

算法:深度优先搜索之迷宫寻径

走出迷宫————深度优先和广度优先搜索