之前发过的一篇文章也是关于栈的应用
https://blog.csdn.net/weixin_52771283/article/details/115506606
这是利用栈“后进先出”的特点实现有优先级的加减乘除四则运算。
想在这里介绍另外一个栈的应用的典型例子——迷宫求解。
首先说一下基本问题:
迷宫的表示
在写代码的时候我们可以用一个二维数组 int map[10][10] 表示一个8x8大小的迷宫(由于需要在迷宫的最外层加上一堵“墙”,定义数组的时侯行数和列数分别+2),并通过对数组的元素 map[x][y] 进行赋值,用于表示第 x 行第 y 列的格子是墙还是可通的路。为方便起见,我们规定 若map[x][y]==1,则其代表的格子是 墙 ,其值为 0 则是路。
也就是下面这样的迷宫(其中黑色表示墙,白色表示路)
我们可以定义这样的数组进行表示
int map[10][10] = {
{1,1,1,1,1,1,1,1,1,1},
{1,0,0,1,0,0,0,1,1,1},
{1,0,0,1,0,1,0,0,1,1},
{1,0,0,0,0,1,1,1,1,1},
{1,0,1,1,1,1,1,0,1,1},
{1,0,0,0,1,0,0,0,1,1},
{1,0,1,0,0,0,1,0,1,1},
{1,0,1,1,1,0,1,0,1,1},
{1,1,1,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1}
};
迷宫上格子的表示
由上面迷宫中墙与路的表示我们容易想到,在数组 map 中,每一个元素map[x][y]的 下标x与y 分别表示其所在位置的行号和列号,而map[x][y]的值又与其在迷宫上表示墙或路相联系。因此我们可以构造这样一个结构体:
typedef struct {
int x;
int y;
int di;
}site;
来表示一个点 (x,y),这个结构体中的变量 di用于表示迷宫探索过程中的方位,这个下面会讲。
为什么选用栈来处理迷宫问题
当我们在玩迷宫游戏时,如果不能够一次性的从迷宫入口走到出口,我们会自然而然地沿原路返回,走到离死胡同最近的分岔口,并选择另外一个方向继续走下去。这就很符合栈优先处理后进元素,即“后进先出”的特点,因此栈这种结构是处理迷宫问题的理想模型。
下面我们构建一个栈:
#define maxsize 100
typedef struct {
site route[maxsize];
int top;
}stack;
栈的基本操作
判断栈是否满或空
由于判断栈是否为满只需将栈顶指针 top 与 数组的最大下标 maxsize-1 的大小进行比较,故这里不再引入新的函数,只需将下式
(p->top) < maxsize-1
的真值进行判断,即可知道栈是否为满。
同理,判断栈是否为空只需判断下式
(p->top)>=0
的真值情况即可。
出栈
入栈操作的思路很简单
即如下
void push(stack* p, site nowsite) {
if ((p->top) < maxsize-1) { //判断栈是否为满
p->top++;
int i = p->top;
p->route[i] = nowsite;
}
else {
printf("stack is full\n");
}
}
出栈
与入栈的基本思路类似
即如下
void pop(stack* p){
if ((p->top) >= 0) { //判断栈是否为空
p->top--;
}
else {
printf("stack is blank\n");
}
}
迷宫的求解
有了上述基础之后,接下来的主要问题是迷宫该怎么走?怎么判断走到了死胡同然后退栈?退栈之后怎么改变方向继续走?这几个问题。
首先,在我们定义的地图(即二维数组)上首先确定两个点(x1,y1)表示入口,(x2,y2)表示出口。我们还需要定义一个点(x,y)来表示当前的位置以及定义 site nowsite,用nowsite这个变量来方便完成入栈的操作。
stack p; //定义栈p
p.top = -1; //初始化栈顶指针
int x1 = 1, y1 = 1, x2 = 8, y2 = 8; //定义(1,1)为入口(8,8)为出口
int x, y; //(x,y)为当前位置
site nowsite;
nowsite.x = x1; //将入口坐标赋值给nowsite的x,y,初始化用于表示方向的变量di为零,并将入口入栈
nowsite.y = y1;
nowsite.di = 0;
push(&p, nowsite);
map[x1][y1] = -1; //走过的格子从0变成-1,表示已走过
接下来就是如何走的问题
我们在定义结构体 site的时候,除了定义表示坐标的变量x,y之外,我们还定义了一个表示方向的的变量di,并在每一个nowsite入栈的时候,将其di初始化为0。
接下来我们就可以通过改变栈顶元素,即当前位置nowsite的di的值实现走的操作。如下:
p.route[p.top].di++;
switch (p.route[p.top].di)
{
case 1:
x = p.route[p.top].x - 1;
y = p.route[p.top].y;
break;
case 2:
x = p.route[p.top].x;
y = p.route[p.top].y+1;
break;
case 3:
x = p.route[p.top].x + 1;
y = p.route[p.top].y;
break;
case 4:
x = p.route[p.top].x ;
y = p.route[p.top].y-1;
break;
}
在这里,我们让栈顶元素的di值+1,并用di的值分别对应如何在nowsite的基础上进行上、下、左、右四个方位的移动,并用x,y记录移动后位置的坐标。
移动后的位置可能有三种状态,我们在上文已经提到用数组的数的值分别一一表示:
map[x][y] | 位置对应的状态 |
---|---|
1 | 墙 |
-1 | 走过的路 |
0 | 未走的路 |
那么通过这个map[x][y]的值,我们可以判断是否将经过上一步得到的新位置入栈,即
if (map[x][y] == 0) { //判断是否为未走过的路
nowsite.x = x; //新的坐标赋值给nowsite,并将其di初始化为0
nowsite.y = y;
nowsite.di = 0;
push(&p, nowsite);
map[x][y] = -1; //走过的路标记为-1
}
走到死胡同怎么办
首先是怎么判断走到了死胡同。在上面的代码中,我们用di的1、2、3、4分别表示要移动的四个方向,如果新位置满足map[x][y]==0,将其入栈,否则di+1,进行下一个方向的判断。
此时如果四个方向都走不通,容易想到当前位置的di应为5,因此我们可以用这个作为判断是否后退到上一个格子,即退栈的条件:
if (p.route[p.top].di == 5) {
pop(&p);
}
此时,栈顶元素代表后退之后的位置,在这个基础上继续进行di++,向另一个方向继续走。
结束循环的条件
1.迷宫无解
可以想到迷宫无解的情况,就是入口的四个方向都走不通后,栈中表示入口的元素出栈。即栈为空时,循环结束。
2.找到出口
即当前位置nowsite满足 nowsite.x == x2,nowsite.y ==y2,循环结束。
根据以上的思路整理出代码
while ( p.top>-1) { //判断栈是否为空,即是否无解
p.route[p.top].di++;
switch (p.route[p.top].di) //根据di的值移动当前位置
{
case 1:
x = p.route[p.top].x - 1;
y = p.route[p.top].y;
break;
case 2:
x = p.route[p.top].x;
y = p.route[p.top].y+1;
break;
case 3:
x = p.route[p.top].x + 1;
y = p.route[p.top].y;
break;
case 4:
x = p.route[p.top].x ;
y = p.route[p.top].y-1;
break;
}
if (map[x][y] == 0) { //若可走,入栈
nowsite.x = x;
nowsite.y = y;
nowsite.di = 0;
push(&p, nowsite);
if (x == x2 && y == y2) { //若当前位置为出口,循环结束
break;
}
map[x][y] = -1; //走过的路标记为-1
}
else if (p.route[p.top].di == 5) { //如果di==5,走到死胡同,出栈
pop(&p);
}
}
最后就是将路程打印出来,我们采用坐标的方式进行打印
if (p.top>-1) { //用栈是否为空判断迷宫是否有解
int i;
for (i = 0; i <= p.top; i++) {
printf(" ( %d , %d ) ", p.route[i].x, p.route[i].y);
if ((i + 1) % 5 == 0) {
printf("-->\n");
}
else if (p.route[i].x != x2 || p.route[i].y != y2) {
printf("-->");
}
}
}
else {
printf("no anwser\n");
}
最后执行的结果
即如下