***迷宫:
栈是只能在一端进行添加元素和删除元素的表,分顺序栈和链式栈。程序里变量命名应该长一些,做到见名知意。提高程序阅读性。本文用栈的方法存储路径中每一步,栈中每个元素是结构体变量,包含每一步的横坐标,纵坐标和方向,以0 1 2 3 分别表示向右,向下,向左,向上。
程序里注释详细,话不多说,上代码:
struct StepInfo {
int rank;
int colomn;
int direction;
};
struct StepsStack {
StepInfo steps[MAXSIZE];
int indexTop = -1;
}stepsStack;
bool findPath() { //路径找到返回true,否则返回false
StepInfo pathNow,pathNext;//定义两个变量,当前路径和下一路径
//当前路径合适则入栈,作为栈顶
pathNow.rank = 1; //路径起点坐标(1,1),终点坐标(8,8)
pathNow.colomn = 1;
pathNow.direction = -1; //为了循环方便设的初始值,仅此一次
stepsStack.indexTop++; //首地址入栈
stepsStack.steps[stepsStack.indexTop] = pathNow;
while (stepsStack.indexTop != -1) {
pathNow = stepsStack.steps[stepsStack.indexTop];
stepsStack.indexTop--;//在循环中,该步表示的路径进入死胡同,
// 所以退栈,并修正前进方向
pathNow.direction++;
while (pathNow.direction <= 3) { //进路径循环加方向选择循环
switch (pathNow.direction) {
case 0: // 向右, 向下向右优先
pathNext.rank = pathNow.rank ; pathNext.colomn = pathNow.colomn + 1 ;
break;
case 1 : //向下
pathNext.rank = pathNow.rank + 1 ; pathNext.colomn = pathNow.colomn ;
break;
case 2 : // 向左
pathNext.rank = pathNow.rank ; pathNext.colomn = pathNow.colomn - 1 ;
break;
case 3 : //向上
pathNext.rank = pathNow.rank - 1 ; pathNext.colomn = pathNow.colomn ;
break;
}
if (pathNext.rank == 8 && pathNext.colomn == 8) { //若下个点是终点
stepsStack.indexTop++; //则入栈该点信息
stepsStack.steps[stepsStack.indexTop] = pathNow;//终点不入栈
return true; //只要找到一条路径,程序就提前结束
}
else if (maze[pathNext.rank][pathNext.colomn] == ' ') {//下个点是空格
stepsStack.indexTop++; //说明这一步是合适的,坐标和方向入栈
stepsStack.steps[stepsStack.indexTop] = pathNow;
maze[pathNow.rank][pathNow.colomn] = 'n';//no,改为非空格
//对于下一步来说,该点不可返回,防止死循环
pathNow = pathNext;
pathNow.direction = 0;//pathNext始终在pathNow的前面,易于循环处理
}
else
pathNow.direction++;//下一点既非终点,也非空格,继续调整方向
} //到这里,说明该点的4个方向都验证结束,且都不合适,没有找到终点,退栈,
} //返回上一个点
return false; //如果上面的循环彻底结束,说明始终没有找到终点
}
void modifyMaze() { //为输出迷宫做的准备
int i, j;
for(i=0;i<10;i++) // 原坐标中标识'n'的都改为空格
for (j = 0; j < 10; j++)
if (maze[i][j] == 'n')
maze[i][j] = ' ';
while (stepsStack.indexTop != 0) { //栈中保存路径用斜杠表示
i = stepsStack.steps[stepsStack.indexTop].rank;
j = stepsStack.steps[stepsStack.indexTop].colomn;
maze[i][j] = '/';
stepsStack.indexTop--;
}
maze[1][1] = 'I'; maze[8][8] = 'O';
}
运行结果如下:
(2)用栈的方法输出所有路径。栈方法是以深度优先的方法。当输出一条完整路径后,程序不能return终止。再一个要注意的方面是凡是退栈的时候,统统都把栈顶‘n’恢复为空格再退栈,以供以后的路径使用。因为这些路径肯定共用一些空格。那么要问:改成空格后,不会造成路径死循环和路径重复么?我觉得程序做到查询路径不重不漏的原因是,direction++的方向是一成不变的,每次退栈还会direction加1,恢复为空格的路径,已经落在了direction后面,direction不会再指向该空格了。除非栈顶指针再次前进在下一路径下找到该空格。所以有大块空白空格聚集的地方会产生很多分叉,很多路径。代码如下:
struct StepInfo {
int rank;
int colomn;
int direction; // 0right 1down 2left 3up
};
struct StepsStack {
StepInfo steps[MAXSIZE];
int indexTop = -1;
}stepsStack;
struct AllPathLinkNode {
StepsStack stepsStack;
AllPathLinkNode * pt;
}*allPathLink; //全局变量,要在输出迷宫的函数里使用链表里的数据
void findPath() { //每找到一条路径,就把整个栈的数据作
allPathLink = new AllPathLinkNode; //为另一个链式栈的节点,保存起来,最后再扫描
allPathLink->pt = NULL; // 整个链表输出所有路径
int rankNow, colomNow, rankNext, colomNext;
stepsStack.indexTop++; //存入路径首地址(1,1) ,终点(8,8)
stepsStack.steps[stepsStack.indexTop].rank = 1;
stepsStack.steps[stepsStack.indexTop].colomn = 1;
stepsStack.steps[stepsStack.indexTop].direction = -1;
maze[1][1] = 'n'; // 凡是入栈的空格都改为'n',not不能走的意思
while (stepsStack.indexTop > -1) { //起始值为0,大于-1
rankNow = stepsStack.steps[stepsStack.indexTop].rank;//同上例,不再重复注释
colomNow = stepsStack.steps[stepsStack.indexTop].colomn;
stepsStack.steps[stepsStack.indexTop].direction++;//以此确保不会重复路径
while (stepsStack.steps[stepsStack.indexTop].direction < 4) {
switch (stepsStack.steps[stepsStack.indexTop].direction) {
case 0 : rankNext = rankNow; colomNext = colomNow + 1; break;
case 1 : rankNext = rankNow + 1; colomNext = colomNow; break;
case 2 : rankNext = rankNow; colomNext = colomNow - 1; break;
case 3 : rankNext = rankNow - 1; colomNext = colomNow; break;
}
if (rankNext == 8 && colomNext == 8) { //找到了一条路径,进行保存
AllPathLinkNode* ptNew = new AllPathLinkNode;
ptNew->stepsStack = stepsStack;
ptNew->pt = allPathLink->pt;
allPathLink->pt = ptNew;//头插法建立节点
maze[rankNow][colomNow] = 'n'; //重点,依旧让direction加一,该节点作为
stepsStack.steps[stepsStack.indexTop].direction++;//下一路径的一部分
}
else if (maze[rankNext][colomNext] == ' ') {
maze[rankNext][colomNext] = 'n';//rank colomn
stepsStack.indexTop++;
stepsStack.steps[stepsStack.indexTop].rank = rankNext;
stepsStack.steps[stepsStack.indexTop].colomn = colomNext;
rankNow = rankNext;
colomNow = colomNext; //rankNow,colomnNow始终指向栈顶节点的数据
stepsStack.steps[stepsStack.indexTop].direction = 0;
} //新节点从0开始找路径
else //非空格,也非终点
stepsStack.steps[stepsStack.indexTop].direction++;
}
maze[rankNow][colomNow] = ' ';//该节点作为栈顶节点,其4个方向都验证过,
stepsStack.indexTop--; //不合适,退栈
}
printLink(); //至此,循环结束,得到了所有路径,可以输出了。栈里应该为空
//indexTop = -1
}
测试结果如下:
另一测试结果,可见栈顶出栈时必须把出栈的’n’改为空格
(3)递归方法求迷宫
递归,函数直接或者间接调用自身的方式叫做递归。难在确认递归体。本例中巧妙的是设置了函数
findPath(int rankIn, int colomIn, int rankOut, int colomOut)见名知意,从入口坐标如(1,1)到出口
坐标(8,8)。设想如果我们已经从(1,1)到了(2,1),坐标(2,1)也是空格,非障碍,并把从(1,1)到(2,1)的路径信息保存在栈中。那么如果从(2,1)到(8,8)的路径找到了,整个从(1,1)到(8,8)的路径也找到了,结合栈中存储的已走过的路径信息,即求解函数findPath(2,1,8,8),如此类推,如果(3,1)也是空格,那么求解出了findPath(3,1,8,8)就求解出了整个问题,直到findPath(8,8,8,8),然后递归开始回溯。当然伴随函数调用的还需要传递保存已走过路径的栈和一个保存所有n条路径信息的链表,每找到一条完整路径,就为链表增加一个节点。
后两者采用c++的引用传递,相当于地址传递,非值传递,因为没有必要重建栈和链表。
代码如下:
struct StepInfo {
int rank;
int colomn;
}; //保存每一步的坐标信息
struct PathSequenStack {
StepInfo steps[MAXSIZE];
int stepTotal; // 该栈保存了一个完整路径,从(1,1)到(8,8)
};
struct PathSaveLinkNode {
PathSequenStack pathSequStack;
PathSaveLinkNode* pt; //该链表用于保存所有的路径信息,前面的一个栈作为一个节点
};
//该顺序栈和链表均在递归调用时采用了引用传递,看来递归调用
void findPath(int rankIn, int colomIn, int rankOut, int colomOut,//不限制引用传递
PathSequenStack &pathSequStack, PathSaveLinkNode*& pathSaveLink) {
int direction; //0 1 2 3
int rankNext, colomNext;
if (rankIn != rankOut || colomIn != colomOut) {//
pathSequStack.stepTotal++; //已在主函数main()中初始化该栈,stepTotal初始值-1
pathSequStack.steps[pathSequStack.stepTotal].rank = rankIn;//即数组steps[0]
pathSequStack.steps[pathSequStack.stepTotal].colomn = colomIn;
maze[rankIn][colomIn] = 'n';
for (direction = 0; direction < 4; direction++) {
switch (direction) {
case 0 : rankNext = rankIn; colomNext = colomIn + 1; break;
case 1 : rankNext = rankIn + 1; colomNext = colomIn; break;
case 2 : rankNext = rankIn; colomNext = colomIn - 1; break;
case 3 : rankNext = rankIn - 1; colomNext = colomIn; break;
} //每次fingPanth(,)调用都要判断起点是否等于终点。若相等则保存
//栈信息到链表;否则,入栈起点信息,
if (maze[rankNext][colomNext] == ' ')
findPath(rankNext,colomNext,rankOut,colomOut,//若起点相邻的
pathSequStack,pathSaveLink); //下一个点也是空格,则
} //以相邻点为起点,再次调用本函数结果是起点离终点越来越近
pathSequStack.stepTotal--; //如果栈顶起点是空格,但相邻点没有空格,
maze[rankIn][colomIn] = ' '; //都是障碍,则恢复起点为空格,
} //以便别的路径可用该点,并退栈,路径开始返离起点(1,1)越来越近
else if (rankIn == rankOut && colomIn == colomOut) {
PathSaveLinkNode* ptNew = new PathSaveLinkNode;//栈的出栈入栈应该与函数
ptNew->pathSequStack = pathSequStack; //的递归调用同步,始终
//保证本次函数执行时起点在栈顶,若起点相邻的都是障碍,
//调用结束回溯前,应弹出栈顶的起点,并恢复为空格
ptNew->pt = pathSaveLink->pt; // 所以,所有路径找完后,栈应为空栈
pathSaveLink->pt = ptNew; //头插法,因为这样可以少用一个指针
}
}
代码测试结果:
(4)队列
队列是双端操作表,一端只进行插入,另一端只进行读取和删除。队列是广度优先的算法。从起点开始,对于每一个可以走的空格,计算其相邻的可以走的空格,都存入队列,当然终点也必须以空格表示。再从另一端依次读取队列中的每个数值,直到读取到终点坐标。存入队列的结构体元素,除了包含每一步的横纵坐标,还要包括产生这一步的前一步在队列中的下标,以便于从终点到起点依次读取每一步的坐标信息。队列法得到的是最短路径,第一个到达终点的路线。
疑问:队列法可以得到所有路径么?我觉得是不可能。因为队列里没有回溯,把‘n’改成空格,即使改,我们又回溯到哪一步为止?才不重不漏呢?
代码如下:
struct PathInfo {
int rank;
int colomn;
int indexPrior;
};
struct SequenceQueue {
PathInfo pathInfo[MAXSIZE];
int indexHead;
int indexTail;
}sequenQueue;
bool findPath(int inRank, int inColomn, int outRank, int outColomn) {
int rankNow, colomnNow, rankNext, colomnNext,i;
sequenQueue.indexHead = sequenQueue.indexTail = -1;//initial queue
sequenQueue.indexTail++;
sequenQueue.pathInfo[sequenQueue.indexTail].rank = inRank;
sequenQueue.pathInfo[sequenQueue.indexTail].colomn = inColomn;
sequenQueue.pathInfo[sequenQueue.indexTail].indexPrior = -1;//input start dot
maze[inRank][inColomn] = 'n';
while (sequenQueue.indexHead <= sequenQueue.indexTail) {
sequenQueue.indexHead++;
rankNow = sequenQueue.pathInfo[sequenQueue.indexHead].rank;
colomnNow = sequenQueue.pathInfo[sequenQueue.indexHead].colomn;
if (rankNow == outRank && colomnNow == outColomn)
return true; //因为队列采用了全局变量,另一个打印函数里可以直接采用其值
//输出路径
for ( i = 0; i < 4; i++) {
switch (i) {
case 0 : rankNext = rankNow ; colomnNext = colomnNow + 1; break;
case 1 : rankNext = rankNow + 1; colomnNext = colomnNow ; break;
case 2 : rankNext = rankNow ; colomnNext = colomnNow - 1; break;
case 3 : rankNext = rankNow - 1; colomnNext = colomnNow ; break;
}
if (maze[rankNext][colomnNext] == ' ') {
sequenQueue.indexTail++; //erter queue
sequenQueue.pathInfo[sequenQueue.indexTail].rank = rankNext;
sequenQueue.pathInfo[sequenQueue.indexTail].colomn = colomnNext;
sequenQueue.pathInfo[sequenQueue.indexTail].indexPrior =
sequenQueue.indexHead;
maze[rankNext][colomnNext] = 'n';
}
}
}
return false;
}
void modifyMaze() {
int i, j, sequIndex;
for(i=0;i<10;i++)
for (j = 0; j < 10; j++)
if (maze[i][j] == 'n')
maze[i][j] = ' ';
for (sequIndex = sequenQueue.pathInfo[sequenQueue.indexHead].indexPrior;
sequIndex > 0;
sequIndex = sequenQueue.pathInfo[sequIndex].indexPrior) {
i = sequenQueue.pathInfo[sequIndex].rank;
j = sequenQueue.pathInfo[sequIndex].colomn;
maze[i][j] = '/';
cout << '(' << i << ',' << j << ')' << "<-";
}
cout << endl;
maze[1][1] = 'I';
maze[8][8] = 'O';
}
测试结果如下:
修改迷宫后,如下:
。
谢谢,下一篇,写写八皇后问题。都是关于栈,队列。递归的