迷宫问题
题目:给一个二维列表,表示迷宫(0表示通道,1表示围墙)。给出算法,求一条走出迷宫的路径。
解决思路:
- 在一个迷宫节点(x,y)上,可以进行四个方向的探查:maze[x-1][y], maze[x+1][y], maze[x][y-1], maze[x][y+1]
- 思路:从一个节点开始,任意找下一个能走的点,当找不到能走的点时,退回上一个点寻找是否有其他方向的点。
- 方法:创建一个空栈,首先将入口位置进栈。当栈不空时循环:获取栈顶元素,寻找下一个可走的相邻方块,如果找不到可走的相邻方块,说明当前位置是死胡同,进行回溯(就是讲当前位置出栈,看前面的点是否还有别的出路)
代码
# include <iostream>
# include <vector>
# include <stack>
# include <string>
# include <sstream>
# include <iterator>
using namespace std;
struct PathUnit //三元组结构
{
int x; //坐标x
int y; //坐标y
int direct; //方位特征值。0东,1南,2西,3北
};
class MyMaze
{
vector<vector<int > > vecMaze; //迷宫描述矩阵
vector <vector<int> > vecMark; //迷宫标志矩阵
public:
MyMaze(int m, int n) :vecMaze(m + 2, vector<int>(n + 2)), vecMark(m + 2, vector<int>(n + 2))
{
for (int i = 0; i < m + 2; i++)
{
fill(vecMaze[i].begin(),vecMaze[i].end(),1);
}
}
void Initial(string strMaze)//根据字符串建立迷宫矩阵
{
istringstream in(strMaze);
for (int i = 1; i < vecMaze.size() - 1; i++)
{
for (int j = 1; j < vecMaze[0].size()-1; j++)
{
in >> vecMaze[i][j];
}
}
}
void showMaze() //显示迷宫矩阵
{
for (int i = 0; i < vecMaze.size(); i++)
{
copy(vecMaze[i].begin(),vecMaze[i].end(),ostream_iterator<int>(cout,"\t"));
cout << endl;
}
}
void GetNextPath(const PathUnit& cur, PathUnit& next) //根据当前位置走向,获得下一结点
{
next = cur;
next.direct = 0;
switch (cur.direct)
{
case 0:
next.x += 1;
break;
case 1:
next.y += 1;
break;
case 2:
next.x -= 1;
break;
case 3:
next.y -= 1;
break;
}
}
bool GetOuter(const PathUnit &u) //当前结点是否是出口结点
{
int out_x = vecMaze[0].size() - 2;
int out_y = vecMaze.size() - 2;
bool bRet = ((u.x==out_x && u.y == out_y)? true:false);
return bRet;
}
void GetPath() //迷宫算法函数
{
stack <PathUnit> st;
PathUnit unit;
unit.x = 1;
unit.y = 1;
unit.direct = 0;
st.push(unit);
bool bOver = false;
while (!st.empty())
{
PathUnit cur = st.top(); //获得栈顶3元组,并出栈
st.pop();
PathUnit next;
while (cur.direct <= 3) //若有方位可走
{
GetNextPath(cur,next); //获得下一节点三元组
if (GetOuter(next)) //若是出口节点
{
st.push(cur); //当前节点入栈
st.push(next); //下一节点入栈
bOver = true; //置结束标志
break; //退出循环
}
if (!vecMaze[next.y][next.x] && !vecMark[next.y][next.x]) //合法移动且从未走过
{
vecMark[next.y][next.x] = 1; //标志矩阵置1,下次不能再走了
st.push(cur); //当前节点入栈
cur = next; //当前节点等于下一节点,从下一节点继续跟踪
}
else//换当前节点方向,继续跟踪
{
cur.direct++;
}
} //while(cur.direct <= 3)
if (bOver)
break;
} //while(!st.empty())
if (bOver)
{
while (!st.empty())
{
PathUnit& u = st.top();
cout << "(" << u.x << "," << u.y << "," << u.direct << ")" << endl;
st.pop();
}
}
else
{
cout << "there is no path" << endl;
}
}
};
int main()
{
MyMaze m(6, 6);
string s = "";
s = s + "0 0 1 0 1 1 " + "0 1 0 0 1 1 " + "0 0 0 1 1 1 " + "1 0 1 1 1 1 " + "1 0 0 0 0 1 " + "1 1 1 1 0 0 "; //每个字串后面有空格
m.Initial(s);
cout << "显示迷宫矩阵:" << endl;
m.showMaze();
cout << "显示3元组表示结点路径" << endl;
m.GetPath();
return 0;
}
运行结果
回溯法与深度优先遍历
1、相同点:
回溯法在实现上也是遵循深度优先的,即一步一步往前探索,而不像广度优先那样,由近及远一片一片地扫。
2、不同点
(1)访问序
深度优先遍历:目的是“遍历”,本质是无序的。也就是说访问次序不重要,重要的是都被访问过了。可以参见题Surrounded Regions,深度优先只需要把从边界起始的'O'全部访问到即可。因此在实现上,只需要对于每个位置记录是否被visited就足够了。
回溯法:目的是“求解过程”,本质是有序的。也就是说必须每一步都是要求的次序。可以参见题Word Search,需要以要求的序进行深度优先探索,必须每一步都符合要求。因此在实现上,不能使用visited记录,因为同样的内容不同的序访问就会造成不同的结果,而不是仅仅“是否被访问过”这么简单。要使用访问状态来记录,也就是对于每个点记录已经访问过的邻居方向,回溯之后从新的未访问过的方向去访问邻居。至于这点点之前有没有被访问过并不重要,重要的是没有以当前的序进行访问。
(2)访问次数
深度优先遍历:已经访问过的节点不再访问,所有点仅访问一次。
回溯法:已经访问过的点可能再次访问,也可能存在没有被访问过的点。
回溯法可以求解所有可能的解。而广度优先利用分支限界的思想,求解最优化问题,广度优先求图中两点最短路径,要求是不带权或者每条边的权值相等。如果带权就只能使用迪杰斯特拉算法。