栈的应用——迷宫求解问题

之前发过的一篇文章也是关于栈的应用

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

的真值情况即可。

出栈

入栈操作的思路很简单

判断栈知否满
栈不为满
栈顶指针top+1
元素入栈
栈满
返回stack is full表示栈满

即如下

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");
	}
}
出栈

与入栈的基本思路类似

判断栈知否空
栈不为空
栈顶指针top-1
栈空
则返回stack is blank

即如下

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");
		}

最后执行的结果
在这里插入图片描述
即如下
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值