迷宫问题是典型的回溯问题,还用到我们比较熟悉的栈结构。敏姐给的书中的算法是这样的:
设定当前位置的初始值为入口位置;
do{
若当前位置可通,
则 { 将当前位置插入栈顶; //纳入路径
若该位置是出口位置,则结束; //求得路径存放在栈中
否则切换当前位置的东邻方块为新的当前位置;
}
否则,若栈不空且栈顶位置尚有其他方向未经探索,
则设定新的当前位置为沿顺时针方向旋转找到的栈顶位置的下一相邻块;
若栈不空单栈顶位置的四周均不可通,
则 { 删去栈顶位置; //从路径中删去该通道块
若栈不空,则重新测试新的栈顶位置,
直至找到一个可通的相邻块或出栈至栈空;
}
}while(栈不空);
在这里面还有许多小的细节需要处理:
1 将迷宫以二维数组的方式存储起来,当数组的元素值为0的时候表示可以通过,当元素值为1的时候表示不能通过。这样可以简化操作。
2 定义压栈的数据结点,因为这些结点不仅要保存当前的位置,还要保存方向。
3为了简化,本文中把栈中已有的点和不能通过的点都赋值为1.
敏姐的书中的关于方向调整的算法我不是太理解,这里我给出了自己的算法,就是x与y坐标同时进行调整,围绕栈顶点的顺时针进行筛选。
//迷宫问题的求解
#include "stdafx.h"
#include <vector>
#include <iterator>
#include <iostream>
using namespace std;
//定义点类,然后定义了一个复制构造函数和一个比较函数
class Point{
public:
Point():x(0),y(0) {}
Point(int m,int n) {x=m;y=n;}
Point& operator=(const Point &t_Point)
{
if (this==&t_Point) return *this;
x = t_Point.x;
y = t_Point.y;
return *this;
}
public:
int x; //x coordinate
int y; //y coordinate
};
//重载比较函数,判断两个点是否相等
bool operator ==(const Point &first_Point,const Point &second_Point)
{
return (first_Point.x== second_Point.x && first_Point.y == second_Point.y);
}
bool operator != (const Point &first_Point,const Point &second_Point)
{
return !(first_Point==second_Point);
}
//定义压栈的数据结构,包括坐标和方向
typedef struct SelemType{
Point pos; // 坐标位置
int direction; //从此通道块走向下一通道块的“方向”
}SelemType;
int MazePath(int maze[][7], Point start,Point end){
//使用向量作为栈的实现
vector<SelemType> t_vSElemType;
Point curpos = start; //将当前位置设为入口位置
do{
if (0==maze[curpos.x][curpos.y]) //这个位置在迷宫中是可通过的
{
maze[curpos.x][curpos.y]=1; //留下标记,表示此点已经处理过,包括经过了,和不能经过
//初始化将要压栈的点
SelemType t_elemPot;
t_elemPot.direction=1;
t_elemPot.pos=curpos;
t_vSElemType.push_back(t_elemPot);
if (curpos== end)
{
cout<<"成功"<<endl;
//遍历向量,将路径的点输出
vector<SelemType>::const_iterator it=t_vSElemType.begin();
for (;it!=t_vSElemType.end();++it)
{
cout<<(*it).pos.x<<','<<(*it).pos.y<<endl;
}
return 0;
}
++curpos.y; //初始方向向右
}
else{
//如果所有的方向都是不行的,那么这个点就应该出栈,将栈顶的位置点赋给现在的位置点
if ( t_vSElemType.back().direction==4&&!t_vSElemType.empty())
{
maze[curpos.x][curpos.y]=1;
//
//这里把当前坐标复制为即将要出栈的点
//结合出栈后栈顶点的方向,即可判断下一个curpos的值,这个在switch里面可以体现
//
curpos=t_vSElemType.back().pos;
t_vSElemType.pop_back();
if (t_vSElemType.empty()) return 1; //栈为空,查找路径失败
}
if (t_vSElemType.back().direction<4){ //如果当前点是不通的,则调整方向
switch (t_vSElemType.back().direction){
case 1: ++curpos.x; --curpos.y; break; //如果初试的方向是向右,那么下一个方向是向下,注意原来的y左边已经改变,这时要改回来
case 2: --curpos.x; --curpos.y; break; //如果方向是向下,那么下一个方向是向左
case 3: ++curpos.y; --curpos.x; break; //如果初始方向向左,那么下一个方向向上
}//switch
++t_vSElemType.back().direction; //方向要加1
}//if
}
}while(!t_vSElemType.empty());
cout<<"失败"<<endl;
return 1;
}
int _tmain(int argc, _TCHAR* argv[])
{
int mazeArr[7][7]={
1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 1, 0, 1, 1,
1, 0, 1, 1, 0, 1, 1,
1, 0, 0, 0, 0, 0, 1,
1, 1, 1, 0, 1, 1, 1,
1, 1, 1, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1
};
Point start(1,1);
Point end(5,5);
MazePath(mazeArr,start,end);
return 0;
}
程序运行结果截图:
附严蔚敏书中实现的代码:
#include "stdafx.h"
#include<iostream>
using namespace std;
struct PosType /* 迷宫坐标位置类型 */
{
int x; /* 行值 */
int y; /* 列值 */
};
#define MAXLENGTH 25 /* 设迷宫的最大行列为25 */
typedef int MazeType[MAXLENGTH][MAXLENGTH]; /* 迷宫数组[行][列] */
/* 全局变量 */
MazeType m; /* 迷宫数组 */
int curstep=1; /* 当前足迹,初值为1 */
int a[MAXLENGTH];
int b[MAXLENGTH];
int c[MAXLENGTH];
struct SElemType/* 栈的元素类型 */
{
int ord; /* 通道块在路径上的”序号” */
PosType seat; /* 通道块在迷宫中的”坐标位置” */
int di; /* 从此通道块走向下一通道块的”方向”(0~3表示东~北) */
};
#define STACK_INIT_SIZE 10 /* 存储空间初始分配量 */
#define STACKINCREMENT 2 /* 存储空间分配增量 */
struct SqStack //SqStack
{
SElemType *base; /* 在栈构造之前和销毁之后,base的值为NULL */
SElemType *top; /* 栈顶指针 */
int stacksize; /* 当前已分配的存储空间,以元素为单位 */
}; /* 顺序栈 */
bool Pass(PosType b)
{ /* 当迷宫m的b点的序号为1(可通过路径),return true, 否则,return false。 */
if(m[b.x][b.y]==1)
return true;
else
return false;
}
void FootPrint(PosType a)
{ /* 使迷宫m的a点的序号变为足迹(curstep) */
m[a.x][a.y]=curstep;
}
PosType NextPos(PosType c,int di)
{ /* 根据当前位置及移动方向,返回下一位置 */
PosType direc[4]={{0,1},{1,0},{0,-1},{-1,0}}; /* {行增量,列增量} */
/* 移动方向,依次为东南西北 */
c.x+=direc[di].x;
c.y+=direc[di].y;
return c;
}
void MarkPrint(PosType b)
{ /* 使迷宫m的b点的序号变为-1(不能通过的路径) */
m[b.x][b.y]=-1;
}
bool InitStack(SqStack *S)
{ /* 构造一个空栈S */
(*S).base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
if(!(*S).base)
exit(1); /* 存储分配失败 */
(*S).top=(*S).base;
(*S).stacksize=STACK_INIT_SIZE;
return true;
}
bool Push(SqStack *S,SElemType e)
{ /* 插入元素e为新的栈顶元素 */
if((*S).top-(*S).base>=(*S).stacksize) /* 栈满,追加存储空间 */
{
(*S).base=(SElemType *)realloc((*S).base,((*S).stacksize+STACKINCREMENT)*sizeof(SElemType));
if(!(*S).base)
exit(1); /* 存储分配失败 */
(*S).top=(*S).base+(*S).stacksize;
(*S).stacksize+=STACKINCREMENT;
}
*((*S).top)++=e;
return true;
}
bool StackEmpty(SqStack S)
{ /* 若栈S为空栈,则返回TRUE,否则返回FALSE */
if(S.top==S.base)
return true;
else
return false;
}
bool Pop(SqStack *S,SElemType *e)
{ /* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
if((*S).top==(*S).base)
return false;
*e=*--(*S).top;
return true;
}
bool MazePath(PosType start,PosType end) /* 算法3.3 */
{ /* 若迷宫maze中存在从入口start到出口end的通道,则求得一条 */
/* 存放在栈中(从栈底到栈顶),并返回TRUE;否则返回FALSE */
SqStack S;
PosType curpos;
SElemType e;
InitStack(&S);
curpos=start;
do
{
if(Pass(curpos))
{ /* 当前位置可以通过,即是未曾走到过的通道块 */
FootPrint(curpos); /* 留下足迹 */
e.ord=curstep;
a[curstep]=e.seat.x=curpos.x;
b[curstep]=e.seat.y=curpos.y;
c[curstep]=e.di=0;
Push(&S,e); /* 入栈当前位置及状态 */
curstep++; /* 足迹加1 */
if(curpos.x==end.x&&curpos.y==end.y) /* 到达终点(出口) */
return true;
curpos=NextPos(curpos,e.di);
}
else
{ /* 当前位置不能通过 */
if(!StackEmpty(S))
{
Pop(&S,&e); /* 退栈到前一位置 */
curstep--;
while((e.di==3) && (!StackEmpty(S))) /* 前一位置处于最后一个方向(北) */
{
MarkPrint(e.seat); /* 留下不能通过的标记(-1) */
Pop(&S,&e); /* 退回一步 */
curstep--;
}
if(e.di<3) /* 没到最后一个方向(北) */
{
e.di++; /* 换下一个方向探索 */
Push(&S,e);
a[curstep]=e.seat.x=curpos.x;
b[curstep]=e.seat.y=curpos.y;
c[curstep]=e.di;
curstep++;
curpos=NextPos(e.seat,e.di); /* 设定当前位置是该新方向上的相邻块 */
}
}
}
}while(!StackEmpty(S));
return false;
}
void Print(int x,int y)
{ /* 输出迷宫的解 */
int i,j;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
cout<<'/t'<<m[i][j];
cout<<endl;
}
}
//stack.h
void main()
{
PosType begin,end;
int i,j,x,y,x1,y1;
cout<<"请输入迷宫的行数,列数(包括外墙):";
cin>>x>>y;
for(i=0;i<x;i++) /* 定义周边值为0(同墙) */
{
m[0][i]=1; /* 行周边 */
m[x-1][i]=1;
}
for(j=1;j<y-1;j++)
{
m[j][0]=1; /* 列周边 */
m[j][y-1]=1;
}
for(i=1;i<x-1;i++)
for(j=1;j<y-1;j++)
m[i][j]=1; /* 定义通道初值为1 */
cout<<"请输入迷宫内墙单元数:";
cin>>j;
cout<<"请依次输入迷宫内墙每个单元的行数,列数:"<<endl;
for(i=1;i<=j;i++)
{
cin>>x1>>y1;
m[x1][y1]=0; /* 定义墙的值为0 */
}
cout<<"迷宫结构如下:"<<endl;
Print(x,y);
cout<<"请输入起点的行数,列数:";
cin>>begin.x>>begin.y;
cout<<"请输入终点的行数,列数:";
cin>>end.x>>end.y;
if(MazePath(begin,end)) /* 求得一条通路 */
{
cout<<"此迷宫从入口到出口的一条路径如下:"<<endl;
Print(x,y); /* 输出此通路 */
for(i=1;i<curstep;i++)
cout<<a[i]<<" "<<b[i]<<" "<<c[i]<<endl;
}
else
cout<<"此迷宫没有从入口到出口的路径"<<endl;
}