前言
本文介绍一种经典算法——回溯法,可作为迷宫问题的一种解法,以下是本篇文章正文内容,包括算法简介、算法应用(迷宫问题)、算法流程和C++代码实现。
一、回溯法简介
回溯法(Backtracking)是枚举法的一种,可以找出所有或者一部分的一般性算法,且有效避免枚举不对的解。当发现某个解的方向不准确时,就不再继续往下进行,而是回溯到上一层,减少算法运行时间,俗称“走不通就回头换路走”。特点是在搜索过程中寻找问题的解,一旦发现不满足条件便回溯,继续搜索其他路径,提高效率。
二、算法应用(迷宫问题)
1.问题描述
迷宫问题是回溯法的一种应用。迷宫问题的描述为:假设主体(人、动物或者飞行器)放在一个迷宫地图入口处,迷宫中有许多墙,使得大多数的路径都被挡住而无法行进。主体可以通过遍历所有可能到出口的路径来到达出口。当主体走错路时需要将走错的路径记录下来,避免下次走重复的路径,直到找到出口。主体需遵从如下三个原则:
- 一次步进只能走一格;
- 遇到路径堵塞后,退后直到找到另一条路径可行;
- 走过的路径记录下来,不会再走第二次。
2.解题思路
首先创建一个迷宫图,比如用二维数组人为定义MAZE[row][col],MAZE[i][j]=1时表示有墙无法通过,MAZE[i][j]=0时表示可行,假设MAZE[1][1]为入口,MAZE[8][10]为出口,创建如下初始迷宫图:
图1 初始迷宫图
当主体在迷宫中前行时,有东南西北(即右下左上)四个方向可以选择,如下图所示:
图2 方向示意图
视情况而定,并不是所有位置都可以上下左右前进,只能走MAZE[i][j]=0的地方。通过链表来记录走过的位置,并将其标记为2,把这个位置的信息放入堆栈,再进行下个方向的选择。若走到死胡同且未到达终点,则退回到上一个岔路口选择另一个方向继续走。由于每次新加入的位置处于堆栈的顶端,因此堆栈顶端指针所指向的方格编号便是当前主体所在的位置。如此重复直到走到迷宫出口为止。
本文提到的迷宫只是一个简易版的迷宫,对更复杂的迷宫问题,可基于该思路进行拓展或采取其他的算法。
三、算法流程
基于第二部分所讲的迷宫问题解题思路,其算法逻辑为:开始,输入初始入口位置;当前位置x和y均小于出口的x和y时,继续前进;判断东南西北可行的方向,并将步进的位置信息放入移动路径的堆栈中;检查是否到达出口;若路径堵塞,退后一步(即从堆栈中弹出一个位置)再检查是否有其他路径可走;当前位置x或y大于出口的x和y时,遍历结束,输出结果。
算法流程图如下:
图3 回溯法解迷宫问题-算法流程图
四、C++代码实现
#include < iostream > // 二维数组中[x][y],x表示行,y表示列 #define EAST MAZE[x][y+1] // 东方向 #define WEST MAZE[x][y-1] // 西方向 #define SOUTH MAZE[x+1][y] // 南方向 #define NORTH MAZE[x-1][y] // 北方向 using namespace std; const int ExitX = 8; // 出口x坐标 const int ExitY = 10; // 出口y坐标 struct list { int x, y; struct list *next; }; typedef struct list node; typedef node* link; int MAZE[10][12] = { 1,1,1,1,1,1,1,1,1,1,1,1, // 定义迷宫 1,0,0,0,1,1,1,1,1,1,1,1, 1,1,1,0,1,1,0,0,0,0,1,1, 1,1,1,0,1,1,0,1,1,0,1,1, 1,1,1,0,0,0,0,1,1,0,1,1, 1,1,1,0,1,1,0,1,1,0,1,1, 1,1,1,0,1,1,0,1,1,0,1,1, 1,1,1,0,1,1,0,1,1,0,1,1, 1,1,1,0,0,0,0,0,1,0,0,1, 1,1,1,1,1,1,1,1,1,1,1,1 }; link push(link path, int x, int y); link pop(link path, int *x, int *y); int chkExit(int x, int y, int ex, int ey); link push(link path, int x, int y) { link newnode; newnode = new node; if (!newnode) { cout << "Error:内存分配失败!" << endl; return NULL; } newnode->x = x; newnode->y = y; newnode->next = path; path = newnode; return path; } link pop(link path, int *x, int *y) { link top; if (path != NULL) { top = path; path = path->next; *x = top->x; *y = top->y; delete top; return path; } else *x -= 1; return path; } int chkExit(int x, int y, int ex, int ey) { if ((x == ex) && (y = ey)) { if (NORTH == 1 || SOUTH == 1 || WEST == 1 || EAST == 2) return 1; if (NORTH == 1 || SOUTH == 1 || WEST == 2 || EAST == 1) return 1; if (NORTH == 1 || SOUTH == 2 || WEST == 1 || EAST == 1) return 1; if (NORTH == 2 || SOUTH == 1 || WEST == 1 || EAST == 1) return 1; } return 0; } int main(void) { cout << endl << endl; int i, j; link path = NULL; int x = 1; // 入口x坐标 int y = 1; // 入口y坐标 cout << " "<<"迷宫图(0的位置可走,1的位置为墙):\n" << endl; // 显示地图 for (i = 0; i < 10; i++) { for (j = 0; j < 12; j++) cout << " " << MAZE[i][j] << " "; cout << endl; } cout << endl << endl; // 开始走迷宫 while (x <= ExitX && y <= ExitY) { MAZE[x][y] = 2; if (NORTH == 0) { x -= 1; path = push(path, x, y); } else if (SOUTH == 0) { x += 1; path = push(path, x, y); } else if (WEST == 0) { y -= 1; path = push(path, x, y); } else if (EAST == 0) { y += 1; path = push(path, x, y); } else if (chkExit(x, y, ExitX, ExitY) == 1) break; else { MAZE[x][y] = 2; path = pop(path, &x, &y); } } cout << "迷宫完成图(0的位置未走,1的位置为墙,2的位置已走):\n" << endl; // 显示地图 for (i = 0; i < 10; i++) { for (j = 0; j < 12; j++) cout << " " << MAZE[i][j] << " "; cout << endl; } return 0; }
效果图:
图4 效果图
五、拓展
在2019年“华为杯”第十六届中国研究生数学建模竞赛中,F题目名称是《多约束条件下智能飞行器航迹快速规划》,该问题类似于一个复杂的迷宫问题,有诸多约束条件限制,要求寻找一条符合约束条件的最优路径。当时我们队伍就是基于约束条件和迷宫算法的思维,设计了新式的迷宫算法,该算法简单来说就是:遍历所有可行的路径,在达到节点时进行判断,如果该节点到终点的直线距离加走过的路程已经小于之前找到过的可行最短路径距离,则该节点往后的路径不用走了直接pass,正因如此,算法的最优求解过程可以快速收敛,计算后期可能还没跑到一半就已经知道这条路径符不符合要求了。其他队伍进行计算有的高达数小时,而我们的算法只需要半分钟。
第三问是概率方面的做的不太好,再加上比赛后期通宵了一晚上精力不佳,录数据的时候犯了粗心错,只拿了国三有些可惜,原本6个答案对了5个,粗心写错了2个。。。
该建模论文资料分享给大家:建模-飞行器航迹最优规划.pdf-算法与数据结构文档类资源-CSDN下载
一起学习努力,加油。论文内含matlab代码,仅供参考,参数命名有些乱,望海涵。
总结
以上就是本文所讲的内容,简单介绍了回溯法以及该方法的一个实际应用——迷宫问题,包含了迷宫实例的解题逻辑和C++代码实现。
如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!