利用顺序栈走迷宫--数据结构栈的使用例题

从迷宫的入口走到迷宫的出口,一直是一个经典的程序设计问题。这里也是采用最简单的“穷举法”。即从入口处发顺着没有方向向前前进,若能走通则继续前进,否则换一个方向,所有方向都都走不通(不允许往来时的位置走,否则就会走两个各自间无限死循环)就退回到上一个格子,换方向继续尝试,直到走到出口,或者所有的地方都走到了仍然没有出口,表示此迷宫无解。
思路:使用一个二维数组存储地图,以0表示不可走的墙体,1表示可以走的通路。例如:

int a[10][10]={//迷宫地图,1为可以走,0为墙壁 
		{0,0,0,0,0,0,0,0,0,0},
		{0,1,1,0,1,1,1,0,1,0},
		{0,1,1,0,1,1,1,0,1,0},
		{0,1,1,1,1,0,0,1,1,0},
		{0,1,0,0,0,1,1,1,1,0},
		{0,1,1,1,0,1,1,1,1,0},
		{0,1,0,1,1,1,0,1,1,0},
		{0,1,0,0,0,1,0,0,1,0},
		{0,0,1,1,1,1,1,1,1,0},
		{0,0,0,0,0,0,0,0,0,0}
	};

用一个结构体存储表示位置坐标:

//数据元素结构体,两个整数表示横、纵坐标,x为纵坐标,y为横坐标 
typedef struct {
	int x;
	int y;
}Elem;

然后我们需要考虑如何模拟在迷宫中走。初步设想:构造一个存储上述类型结构体的栈,也就是说栈里面的元素都表示一个坐标,把下一步要走的坐标位置都入栈,当入栈的元素坐标是出口是也就是走出了迷宫。当此路不同需要退回上一个位置的时候,就是退栈操作。

//栈结构体,base,top指针都是指向上面结构体类型的指针 
typedef struct {
	Elem *base;
	Elem *top;
	int size;
}Stack;

然后就是需要的栈操作函数,由于迷宫求解问题有一个特性,需要判断当前位置下是不是无路可走。不能简单的四个方向都尝试一遍,四个方向都是墙才是无路可走,而是除了来时的路其他三个反向都是墙体的时候就是无路可走。看似这个想法是对的,实则不然,这个想法的错误之处我会在后面指出,先就按照这个有漏洞的想法设计我们的算法。
我的思路是判断当前位置是不是无路可走,而当前位置也就是栈顶元素,那就把它的横坐标或者纵坐标+1或者-1。来表示上下左右的位置坐标,要求至少有一个方向不是墙体同时还是不来的时候的路,就认为这个位置不是无路可走的位置。而来时的位置就是栈顶元素下面一个的元素啊。所以只需要获取栈顶下面的元素的坐标就能实现了:

void GetSecond(Stack *s,Elem *e){
	if(s->top==s->base){
		exit(1);
	}
	e->x=(s->top-2)->x;
	e->y=(s->top-2)->y;
}

然后走迷宫的过程代码为:

	for(int i=0;i<500;i++){
		Elem top,*topp=&top;second,*secondp=&second;
		GetTop(sp,topp);
		if(topp->x==outp->x&&topp->y==outp->y){
			printf("成功走出迷宫!\n");
			exit(1);
		}
		if(StackLength(sp)>1){
			GetSecond(sp,secondp);
		} 
		if(a[topp->x-1][topp->y]==1&&(topp->x-1!=secondp->x||topp->y!=secondp->y)){
			Elem next,*nextp=&next;
			nextp->x=topp->x-1;
			nextp->y=topp->y;
			Push(sp,nextp);
			step++;
			printf("%d:上移\n",step);
			continue;
		}
		if(a[topp->x][topp->y+1]==1&&(topp->x!=secondp->x||topp->y+1!=secondp->y)){
			Elem next,*nextp=&next;
			nextp->x=topp->x;
			nextp->y=topp->y+1;
			Push(sp,nextp);
			step++;
			printf("%d:右移\n",step);
			continue;
		}
		if(a[topp->x+1][topp->y]==1&&(topp->x+1!=secondp->x||topp->y!=secondp->y)){
			Elem next,*nextp=&next;
			nextp->x=topp->x+1;
			nextp->y=topp->y;
			Push(sp,nextp);
			step++;
			printf("%d:下移\n",step);
			continue;
		}
		if(a[topp->x][topp->y-1]==1&&(topp->x!=secondp->x||topp->y-1!=secondp->y)){
			Elem next,*nextp=&next;
			nextp->x=topp->x;
			nextp->y=topp->y-1;
			Push(sp,nextp);
			step++;
			printf("%d:左移\n",step);
			continue;
		}
		Elem pop,*popp=&pop;
		Pop(sp,popp);
		a[popp->x][popp->y]=0;
		step++;
		printf("%d:退格\n",step);
		if(popp->x==inp->x&&popp->y==inp->y){
			printf("此迷宫无解!\n");
			exit(1);
		}
	}
	printf("当前循环次数不够!"); 
	return 0; 
}

在上面的代码中我用了Elem second来存来时的位置,要求接下的路不可以是墙也不可以是来时的路。并且在退栈时候把被退栈的位置直接标记为墙,因为已近被退栈说明此路不通,直接标记为墙是没有问题的,接下来就不会在走到这条路上。这个算法在走55的迷宫时,没有任何问题,但是我尝试走一个1010的迷宫的出现问题了,主要是走进了下面的一个路线中。
无
从入口走到A这个位置的时候,按照我的算法会向上走,因为上面的位置既不是墙体又不是栈顶下面的元素,于是走成了一个环,无限循环。于是我意识到不时来时的位置不能走,而是走过的路统统不能再走,如果允许走已近走过的路的话,必然可以形成环,算法就陷入了死循环。这就是我说的上面的算法的漏洞之处。改进方法可以时写一个函数,接受一个Elem类型变量,再遍历栈,看看当前栈里面有没有和它相同的元素,如果有就说明这个位置已近走过不能再走了。但是这个方法的时间复杂度和空间复杂度是较高的。于是我联想到干脆把走过的路统统设置为墙体,即每入栈一个元素就把他设置为墙体。这样做看似不可以,你会想到我们在现实中是不可以的,我们在知道一条路走不通时徐娅回退,如果设置成了墙体就没办法回退了,但是在我们这里可以,因为我们走进一个没来过的位置和回退到上次呆的位置原理时不一样的。走入一个新的位置,也就是入栈的才要看它是不是墙体,而回退的时候只是出栈而已,自动把下面的一个元素排到栈顶,不需要考虑原来的位置是不是被我们设置成了墙体所以算法变成了这个样子:

for(int i=0;i<500;i++){
		Elem top,*topp=&top;//second,*secondp=&second;
		GetTop(sp,topp);
		//printf("\n%d\t%d",topp->x,topp->y);
		if(topp->x==outp->x&&topp->y==outp->y){
			printf("成功走出迷宫!\n");
			exit(1);
		}
		/*if(StackLength(sp)>1){
			GetSecond(sp,secondp);
			//printf("\n%d\t%d",secondp->x,secondp->y);
		} */
		if(a[topp->x-1][topp->y]==1/*&&(topp->x-1!=secondp->x||topp->y!=secondp->y)*/){
			Elem next,*nextp=&next;
			nextp->x=topp->x-1;
			nextp->y=topp->y;
			Push(sp,nextp);
			a[topp->x-1][topp->y]=0;
			step++;
			printf("%d:上移\n",step);
			continue;
		}
		if(a[topp->x][topp->y+1]==1/*&&(topp->x!=secondp->x||topp->y+1!=secondp->y)*/){
			Elem next,*nextp=&next;
			nextp->x=topp->x;
			nextp->y=topp->y+1;
			Push(sp,nextp);
			a[topp->x][topp->y+1]=0;
			step++;
			printf("%d:右移\n",step);
			continue;
		}
		if(a[topp->x+1][topp->y]==1/*&&(topp->x+1!=secondp->x||topp->y!=secondp->y)*/){
			Elem next,*nextp=&next;
			nextp->x=topp->x+1;
			nextp->y=topp->y;
			Push(sp,nextp);
			a[topp->x+1][topp->y]=0;
			step++;
			printf("%d:下移\n",step);
			continue;
		}
		if(a[topp->x][topp->y-1]==1/*&&(topp->x!=secondp->x||topp->y-1!=secondp->y)*/){
			Elem next,*nextp=&next;
			nextp->x=topp->x;
			nextp->y=topp->y-1;
			Push(sp,nextp);
			a[topp->x][topp->y-1]=0;
			step++;
			printf("%d:左移\n",step);
			continue;
		}
		Elem pop,*popp=&pop;
		Pop(sp,popp);
		//a[popp->x][popp->y]=0;出栈的元素必然如果栈,没必要再次置0 
		step++;
		printf("%d:退格\n",step);
		//printf("\n%d\t%d",popp->x,popp->y);
		if(popp->x==inp->x&&popp->y==inp->y){
			printf("此迷宫无解!\n");
			exit(1);
		}
	}
	printf("当前循环次数不够!"); 
	return 0; 
}

不用写取栈顶下面元素的函数,也不需要遍历栈,只需要把走过的位置都设置为墙一切问题都迎刃而解了。下面给出完整的代码:

#include<stdio.h>
#include<stdlib.h>

//数据元素结构体,两个整数表示横、纵坐标,x为纵坐标,y为横坐标 
typedef struct {
	int x;
	int y;
}Elem;
//栈结构体,base,top指针都是指向上面结构体类型的指针 
typedef struct {
	Elem *base;
	Elem *top;
	int size;
}Stack;

int main(){
	//栈函数声明 
	void InitStack(Stack *);
	void DestroyStack(Stack *);
	void ClearStack(Stack *);
	int StackEmpty(Stack *);
	int StackLength(Stack *);
	void GetTop(Stack *,Elem *); 
	void Push(Stack *,Elem *);
	void Pop(Stack *,Elem *);
	void GetSecond(Stack *,Elem *);//第一次的构想,后来发现是用不上的函数 
	
	/*
	//栈函数测试代码 
	Stack s,*sp=&s;
	Elem a,*ap=&a,b,*bp=&b,c,*cp=&c;
	ap->x=1;
	ap->y=1;
	bp->x=2;
	bp->y=2;
	cp->x=3;
	cp->y=3;
	InitStack(sp);
	Push(sp,ap);
	Push(sp,bp);
	Push(sp,cp);
	Elem d,*dp=&d;
	GetSecond(sp,dp);
	printf("%d\t%d",dp->x,dp->y);*/
	//--------------------------------------------//
	Stack s,*sp=&s;//顺序栈 
	InitStack(sp);//栈初始化 
	int step=0;//step记录步数 
	int a[10][10]={//迷宫地图,1为可以走,0为墙壁 
		{0,0,0,0,0,0,0,0,0,0},
		{0,1,1,0,1,1,1,0,1,0},
		{0,1,1,0,1,1,1,0,1,0},
		{0,1,1,1,1,0,0,1,1,0},
		{0,1,0,0,0,1,1,1,1,0},
		{0,1,1,1,0,1,1,1,1,0},
		{0,1,0,1,1,1,0,1,1,0},
		{0,1,0,0,0,1,0,0,1,0},
		{0,0,1,1,1,1,1,1,1,0},
		{0,0,0,0,0,0,0,0,0,0}
	};
	//地图打印 
	for(int i=0;i<10;i++){
		for(int j=0;j<10;j++){
			if(a[i][j]==0){
				printf("0");
			}else{
				printf("1");
			}
		}
		printf("\n");
	}
	//in为出发位置,out为出口位置 
	Elem in={1,1},out={8,8},*inp=&in,*outp=&out;
	Push(sp,inp);//出发位置入栈 
	a[inp->x][inp->y]=0;//走过的位置标记为不可再走,因为如走过的路再走必然形成环 
	for(int i=0;i<500;i++){
		Elem top,*topp=&top;//second,*secondp=&second;
		GetTop(sp,topp);
		//printf("\n%d\t%d",topp->x,topp->y);
		if(topp->x==outp->x&&topp->y==outp->y){
			printf("成功走出迷宫!\n");
			exit(1);
		}
		/*if(StackLength(sp)>1){
			GetSecond(sp,secondp);
			//printf("\n%d\t%d",secondp->x,secondp->y);
		} */
		if(a[topp->x-1][topp->y]==1/*&&(topp->x-1!=secondp->x||topp->y!=secondp->y)*/){
			Elem next,*nextp=&next;
			nextp->x=topp->x-1;
			nextp->y=topp->y;
			Push(sp,nextp);
			a[topp->x-1][topp->y]=0;
			step++;
			printf("%d:上移\n",step);
			continue;
		}
		if(a[topp->x][topp->y+1]==1/*&&(topp->x!=secondp->x||topp->y+1!=secondp->y)*/){
			Elem next,*nextp=&next;
			nextp->x=topp->x;
			nextp->y=topp->y+1;
			Push(sp,nextp);
			a[topp->x][topp->y+1]=0;
			step++;
			printf("%d:右移\n",step);
			continue;
		}
		if(a[topp->x+1][topp->y]==1/*&&(topp->x+1!=secondp->x||topp->y!=secondp->y)*/){
			Elem next,*nextp=&next;
			nextp->x=topp->x+1;
			nextp->y=topp->y;
			Push(sp,nextp);
			a[topp->x+1][topp->y]=0;
			step++;
			printf("%d:下移\n",step);
			continue;
		}
		if(a[topp->x][topp->y-1]==1/*&&(topp->x!=secondp->x||topp->y-1!=secondp->y)*/){
			Elem next,*nextp=&next;
			nextp->x=topp->x;
			nextp->y=topp->y-1;
			Push(sp,nextp);
			a[topp->x][topp->y-1]=0;
			step++;
			printf("%d:左移\n",step);
			continue;
		}
		Elem pop,*popp=&pop;
		Pop(sp,popp);
		//a[popp->x][popp->y]=0;出栈的元素必然如果栈,没必要再次置0 
		step++;
		printf("%d:退格\n",step);
		//printf("\n%d\t%d",popp->x,popp->y);
		if(popp->x==inp->x&&popp->y==inp->y){
			printf("此迷宫无解!\n");
			exit(1);
		}
	}
	printf("当前循环次数不够!"); 
	return 0; 
}

void InitStack(Stack *s){
	s->base=(Elem *)malloc(100*sizeof(Elem));
	if(!s->base){
		printf("地址申请失败!\n");
		exit(1);
	}
	s->top=s->base;
	s->size=100;
}

void DestroyStack(Stack *s){
	if(!s->base){
		exit(1);
	}
	free(s->base);
	s->base=NULL;
}

void ClearStack(Stack *s){
	s->top=s->base;
}

int StackEmpty(Stack *s){
	if(s->top==s->base){
		return 1;
	}else{
		return 0;
	}
}

int StackLength(Stack *s){
	return (s->top-s->base);
}

void GetTop(Stack *s,Elem *e){
	if(s->top==s->base){
		exit(1);
	}
	e->x=(s->top-1)->x;
	e->y=(s->top-1)->y;
}

void GetSecond(Stack *s,Elem *e){
	if(s->top==s->base){
		exit(1);
	}
	e->x=(s->top-2)->x;
	e->y=(s->top-2)->y;
}

void Push(Stack *s,Elem *e){
	if(s->top-s->base>=s->size){
		s->base=(Elem *)realloc(s->base,(100+10)*sizeof(Elem));
		if(!s->base){
			exit(1);
		}
		s->size+=10;
	}
	(s->top)->x=e->x;
	(s->top)->y=e->y;
	s->top++;
}

void Pop(Stack *s,Elem *e){
	if(s->top==s->base){
		exit(1);
	}
	e->x=(s->top-1)->x;
	e->y=(s->top-1)->y;
	s->top--;
}

运行结果:

0000000000
0110111010
0110111010
0111100110
0100011110
0111011110
0101110110
0100010010
0011111110
0000000000
1:右移
2:下移
3:下移
4:右移
5:右移
6:上移
7:上移
8:右移
9:右移
10:下移
11:左移
12:退格
13:退格
14:退格
15:退格
16:退格
17:退格
18:退格
19:退格
20:左移
21:上移
22:退格
23:下移
24:下移
25:右移
26:右移
27:下移
28:右移
29:右移
30:上移
31:上移
32:右移
33:右移
34:上移
35:右移
36:上移
37:上移
38:退格
39:退格
40:下移
41:下移
42:下移
43:下移
44:下移
成功走出迷宫!

--------------------------------
Process exited after 0.2662 seconds with return value 1
请按任意键继续. . .

或者修改一下迷宫的结构,让这个迷宫变成一个无解的迷宫再来运行:

0000000000
0110111010
0110111010
0111100110
0100011110
0111011110
0101110110
0100000000
0011111110
0000000000
1:右移
2:下移
3:下移
4:右移
5:右移
6:上移
7:上移
8:右移
9:右移
10:下移
11:左移
12:退格
13:退格
14:退格
15:退格
16:退格
17:退格
18:退格
19:退格
20:左移
21:上移
22:退格
23:下移
24:下移
25:右移
26:右移
27:下移
28:右移
29:右移
30:上移
31:上移
32:右移
33:右移
34:上移
35:右移
36:上移
37:上移
38:退格
39:退格
40:下移
41:下移
42:下移
43:左移
44:上移
45:左移
46:退格
47:退格
48:退格
49:退格
50:退格
51:退格
52:退格
53:退格
54:退格
55:退格
56:退格
57:退格
58:退格
59:退格
60:退格
61:退格
62:退格
63:下移
64:下移
65:退格
66:退格
67:退格
68:退格
69:退格
70:退格
71:退格
72:退格
73:退格
此迷宫无解!

--------------------------------
Process exited after 0.3026 seconds with return value 1
请按任意键继续. . .

这个算法判断迷宫误解时根据被弹出的元素是不是出发位置来判断的。因为如果这个迷宫无解的话,所有走过的路都会被设置为墙,终有一天必然会退回出发点,而出发点四周也都是被标记为墙,然后就会把出发位置弹栈,这时就说明从这个出发点走迷宫时是无解的。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,走迷宫游戏是一个非常有意思的课程设计,需要用到许多数据结构和算法。以下是一个简单的实现思路: 1. 将迷宫地图存储在一个二维数组中,其中 0 表示可以通过的路,1 表示墙壁。 2. 使用来实现迷宫的深度优先搜索,从起点开始,不断往前走,直到遇到墙壁或者到达终点。 3. 如果遇到墙壁,则将当前位置出,并回溯到上一个位置,继续搜索。 4. 如果到达终点,则输出路径并结束程序。 下面是一个简单的实现代码: ```python class Maze: def __init__(self, maze_map): self.maze_map = maze_map self.width = len(maze_map[0]) self.height = len(maze_map) def get_neighbors(self, pos): x, y = pos neighbors = [] if x > 0 and self.maze_map[y][x-1] == 0: neighbors.append((x-1, y)) if y > 0 and self.maze_map[y-1][x] == 0: neighbors.append((x, y-1)) if x < self.width-1 and self.maze_map[y][x+1] == 0: neighbors.append((x+1, y)) if y < self.height-1 and self.maze_map[y+1][x] == 0: neighbors.append((x, y+1)) return neighbors def find_path(self, start, end): stack = [start] visited = set() while stack: pos = stack[-1] if pos == end: return stack if pos not in visited: visited.add(pos) neighbors = self.get_neighbors(pos) for neighbor in neighbors: if neighbor not in visited: stack.append(neighbor) else: stack.pop() return None maze_map = [ [0, 1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 1, 0], [1, 1, 1, 1, 1, 0, 1, 0], [0, 0, 0, 0, 0, 0, 1, 0], [0, 1, 1, 1, 1, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0], ] maze = Maze(maze_map) start = (0, 0) end = (7, 6) path = maze.find_path(start, end) if path: print(path) else: print("No path found") ``` 这里我们使用了一个 Maze 类来存储迷宫地图和实现搜索算法。其中 get_neighbors 方法用来获取一个位置的所有邻居节点,find_path 方法用来实现深度优先搜索。我们首先将起点压入中,然后不断从中取出最后一个节点进行搜索,如果当前节点是终点,则返回路径;否则将当前节点的所有邻居节点压入中。如果当前节点没有邻居节点或者所有邻居节点都已经被访问过,则将当前节点出,回溯到上一个节点。 这个实现还比较简单,如果需要实现更高效的算法,可以考虑使用广度优先搜索或者A*算法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KOKO银角大王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值