迷宫问题(MazePath)的求解——利用回溯法(backtracking)
1. 迷宫问题的提法
- 迷宫问题是典型的图的搜索问题。
- 假设一个迷宫,只有一个入口和一个出口。如果从迷宫的入口到达出口,途中不出现行进方向错误,则得到一条最佳路线。
- 为此,用一个二维数组maze[m][p]来表示迷宫。
(1)当数组元素maze[i][j]=1 (0≤i≤m-1,1≤j≤p-1),表示该位置是墙壁,不能通行。
(2)当数组元素maze[i][j]=0 (0≤i≤m-1,1≤j≤p-1),表示该位置是通路,可以通行。
2. 回溯法的概念
2.1 回溯法的定义
- 回溯法(backtracking)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。
2.2 回溯法的思想
- 回溯法将问题的候选解按某种顺序逐一枚举和检验。
(1)当发现当前的候选解不可能是解时,就放弃它而选择下一个候选解。
(2)如果当前的候选解除了不满足问题规模要求外,其他所有要求都已满足,则扩大当前候选解的规模继续试探。。
(3)如果当前的候选解满足了包括问题规模在内的所有要求,则这个候选解将成为问题的一个解。
- 注:
(1)当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
(2)扩大当前候选解的规模并继续试探的过程叫做向前试探。
(3)用回溯法求解问题时常常使用递归方法进行试探,或使用栈帮助向前试探和回溯。
3. 用回溯法求解迷宫问题的算法原理
- 在求解迷宫问题的过程中,当沿某一条路径一步步走向出口但发现进入死胡同走不通时,就回溯一步或多步,寻找其他可走的路径,这就是回溯。
- 人在迷宫中任一时刻的位置可用数组行下标i和列下标j表示。从maze[i][j]出发,可能的前进方向有8个,按顺时针方向为N([i-1][j]),NE([i-1][j+1]),E([i][j+1]),SE([i+1][j+1]),S([i+1][j]),SW([i+1][j-1]),W([i][j-1]),NW([i-1][j-1])。
- 设位置[i][j]标记为X,它实际是一系列交通路口。X周围有8个前进方向,分别代表8个前进位置。如果某一方向是0值,表示该方向有路可通,否则表示该方向已堵死。为了有效地选择下一位置,可以将从位置[i][j]出发可能的前进方向预先定义在一个表内。
(1)可能的前进方向示意图:
(2)前进方向表move:
Move[q].dir move[q].a move[q].b “N” -1 0 “NE” -1 1 “E” 0 1 “SE” 1 1 “S” 1 0 “SW” 1 -1 “W” 0 -1 “NW” -1 -1
4. 利用递归求解迷宫问题
4.1 迷宫的初始化及前进方向表的定义
- 文件:MazeConfig.h
#pragma once #include <iostream> #include <windows.h> using namespace std; //网格的结构定义 struct GridType { int x;//网格的x坐标 int y;//网格的y坐标 }; //位置在直角坐标下的偏移量的结构定义 struct MoveTable { int a;//x方向上的偏移 int b;//y方向上的偏移 char *dir;//移动的方向 }; const int m = 14;//迷宫的行数 const int p = 17;//迷宫的列数 const int pathmark = 6;//迷宫通路网格标识值 static int mark[m][p];//访问标记数组 GridType entry = {1, 0};//迷宫入口网格坐标 GridType exitus = {m-2, p-1};//迷宫出口网格坐标 //各个方向的偏移表定义 MoveTable moveTable[8] = { {-1, 0, "N"}, {-1, 1, "NE"}, {0, 1, "E"}, {1, 1, "SE"}, {1, 0, "S"}, {1, -1, "SW"}, {0, -1, "W"}, {-1, -1, "NW"} }; //初始化迷宫 int Maze[m][p] = { { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1 }, { 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, { 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1 }, { 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1 }, { 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1 }, { 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1 }, { 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1 }, { 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1 }, { 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1 }, { 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } }; //初始化访问标记数组 void init_mark() { for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { mark[i][j] = 0; } } } //打印迷宫 void print_maze() { cout << "======>MazePath" << endl; for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { if (Maze[i][j] == pathmark) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); } else { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY); } cout << Maze[i][j] << " "; } cout << endl; } }
4.2 迷宫问题的递归求解算法实现
- 文件:SeekPath.h
#pragma once #include "MazeConfig.h" //从迷宫某一位置[i][j]开始,寻找通向出口的一条路径。 //如果找到,则函数返回1。 //如果没找到,则函数返回0。 int SeekPath(GridType curGrid) { if ((curGrid.x == exitus.x) && (curGrid.y == exitus.y)) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); cout << "======>SeekPath Success" << endl; return 1;//已到达出口,函数返回1。 } GridType nextGrid;//下一个网格的位置 for (int i = 0; i < 8; i++)//依次按每一个方向寻找通向出口的路径 { nextGrid.x = curGrid.x + moveTable[i].a; nextGrid.y = curGrid.y + moveTable[i].b; if ((Maze[nextGrid.x][nextGrid.y] == 0) && (mark[nextGrid.x][nextGrid.y] == 0)) { //下一位置可通,试探该方向 mark[nextGrid.x][nextGrid.y] = 1;//标记为已访问过 if (SeekPath(nextGrid) != 0)//从此位置递归试探 { //cout << "(" << nextGrid.x << "," << nextGrid.y << ")," << "Direction:" << moveTable[i].dir << ", "; Maze[nextGrid.x][nextGrid.y] = pathmark; return 1;//试探成功,逆向输出路径坐标 } } //回溯,换一个方向再试探通向出口的路径。 } if ((curGrid.x == entry.x) && (curGrid.y == entry.y)) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED); cout << "======>SeekPath Fail" << endl; } return 0;//无可通路到出口,函数返回0。 }
4.3 主函数(main函数)的实现
- 文件:main.cpp
#include "SeekPath.h" int main() { print_maze(); init_mark(); if (SeekPath(entry) != 0) { //cout << "(" << entry.x << "," << entry.y << ")" << endl; Maze[entry.x][entry.y] = pathmark; print_maze(); } SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY); system("pause"); return 0; }
4.4 迷宫问题求解结果
- 控制台输出,迷宫通路是绿色高亮显示的路径。
5. 利用栈求解迷宫问题
5.1 链表结点结构的定义
- 文件:LinkNode.h
#ifndef LINK_NODE_H_ #define LINK_NODE_H_ #include <iostream> #include <string> #include <strstream> using namespace std; template <class T> struct LinkNode //链表结点类的定义 { T data; //数据域 LinkNode<T> *link; //指针域——后继指针 //仅初始化指针成员的构造函数 LinkNode(LinkNode<T>* ptr = NULL){ link = ptr; } //初始化数据与指针成员的构造函数 LinkNode(const T& value, LinkNode<T>* ptr = NULL){ data = value; link = ptr; } }; #endif /* LINK_NODE_H_ */
5.2 链式栈的类定义及其操作的实现
- 文件:LinkedStack.h
#ifndef LINKED_STACK_H_ #define LINKED_STACK_H_ #include "LinkNode.h" #include "Stack.h" template <class T> class LinkedStack { public: LinkedStack(); //构造函数 ~LinkedStack(); //析构函数 public: void Push(const T& x) ; //新元素x进栈 bool Pop(T& x); //栈顶元素出栈,并将该元素的值保存至x LinkNode<T>* getTop() const; //获取栈顶结点 bool IsEmpty() const; //判断栈是否为空 void MakeEmpty(); //清空栈的内容 private: LinkNode<T> *top; //栈顶指针,即链头指针 }; //构造函数 template <class T> LinkedStack<T>::LinkedStack() : top(NULL) { cout << "$ 执行构造函数" << endl; } //析构函数 template <class T> LinkedStack<T>::~LinkedStack() { cout << "$ 执行析构函数" << endl; MakeEmpty(); } //新元素x进栈 template <class T> void LinkedStack<T>::Push(const T& x) { LinkNode<T> *newNode = new LinkNode<T>(x); newNode->link = top; top = newNode; } //栈顶元素出栈,并将该元素的值保存至x template <class T> bool LinkedStack<T>::Pop(T& x) { if (true == IsEmpty()) { return false; } LinkNode<T> *curNode = top; top = top->link; x = curNode->data; delete curNode; return true; } //获取栈顶结点 template <class T> LinkNode<T>* LinkedStack<T>::getTop() const { return top; } //判断栈是否为空 template <class T> bool LinkedStack<T>::IsEmpty() const { return (NULL == top) ? true : false; } //清空栈的内容 template <class T> void LinkedStack<T>::MakeEmpty() { LinkNode<T> *curNode = NULL; while (NULL != top) //当链表不为空时,删去链表中所有结点 { curNode = top; //保存被删结点 top = curNode->link; //被删结点的下一个结点成为头结点 delete curNode; //从链表上摘下被删结点 } } #endif /* LINKED_STACK_H_ */
5.3 迷宫的初始化及前进方向表的定义
- 文件:MazeConfig.h
#ifndef MAZECONFIG_H_ #define MAZECONFIG_H_ #include <iostream> #include <windows.h> using namespace std; //网格的结构定义 struct GridType { int x;//网格的x坐标 int y;//网格的y坐标 }; //位置在直角坐标下的偏移量的结构定义 struct MoveTable { int a;//x方向上的偏移 int b;//y方向上的偏移 char *dir;//移动的方向 }; const int m = 14;//迷宫的行数 const int p = 17;//迷宫的列数 const int pathmark = 6;//迷宫通路网格标识值 static int mark[m][p];//访问标记数组 GridType entry = { 1, 0 };//迷宫入口网格坐标 GridType exitus = { m - 2, p - 1 };//迷宫出口网格坐标 //各个方向的偏移表定义 MoveTable moveTable[8] = { { -1, 0, "N" }, { -1, 1, "NE" }, { 0, 1, "E" }, { 1, 1, "SE" }, { 1, 0, "S" }, { 1, -1, "SW" }, { 0, -1, "W" }, { -1, -1, "NW" } }; //初始化迷宫 int Maze[m][p] = { { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1 }, { 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, { 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1 }, { 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1 }, { 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1 }, { 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1 }, { 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1 }, { 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1 }, { 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1 }, { 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } }; //初始化访问标记数组 void init_mark() { for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { mark[i][j] = 0; } } } //打印迷宫 void print_maze() { cout << "======>MazePath" << endl; for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { if (Maze[i][j] == pathmark) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); } else { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY); } cout << Maze[i][j] << " "; } cout << endl; } } #endif /* MAZECONFIG_H_ */
5.4 迷宫问题的非递归求解算法实现
- 文件:SeekPath.h
#ifndef SEEKPATH_H_ #define SEEKPATH_H_ #include "MazeConfig.h" #include "LinkedStack.h" //从迷宫某一位置[i][j]开始,寻找通向出口[m][p]的一条路径。 template <class T> void SeekPath(LinkedStack<T>* st, GridType curGrid) { GridType nextGrid;//下一个网格的位置 st->Push(curGrid); while (st->IsEmpty() == false) { st->Pop(curGrid); for (int d = 0; d < 8; d++) { nextGrid.x = curGrid.x + moveTable[d].a; nextGrid.y = curGrid.y + moveTable[d].b; if ((nextGrid.x == exitus.x) && (nextGrid.y == exitus.y)) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); cout << "======>SeekPath Success" << endl; st->Push(curGrid); st->Push(nextGrid); return; } if ((Maze[nextGrid.x][nextGrid.y] == 0) && (mark[nextGrid.x][nextGrid.y] == 0)) { mark[nextGrid.x][nextGrid.y] = 1;//标记为已访问过 st->Push(curGrid); curGrid = nextGrid; d = 0; } } } SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED); cout << "======>SeekPath Fail" << endl; } template <class T> void MarkPath(LinkedStack<T>* st) { LinkNode<T> *curNode = st->getTop(); while (NULL != curNode) { GridType item = curNode->data; Maze[item.x][item.y] = pathmark; curNode = curNode->link; } } #endif /* SEEKPATH_H_ */
5.5 主函数(main函数)的实现
- 文件:main.cpp
#include "SeekPath.h" int main(int argc, char* argv[]) { print_maze(); init_mark(); LinkedStack<GridType> *linkedStack = new LinkedStack<GridType>; SeekPath(linkedStack, entry); MarkPath(linkedStack); print_maze(); SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY); delete linkedStack; linkedStack = NULL; system("pause"); return 0; }
5.6 迷宫问题求解结果
- 控制台输出,迷宫通路是绿色高亮显示的路径。
参考文献:
[1]《数据结构(用面向对象方法与C++语言描述)(第2版)》殷人昆——第三章
[2]?百度搜索关键字:迷宫问题、回溯法、试探法
时间: 2024-11-07 16:14:33