迷宫问题 邻接表存储结构 广度优先遍历(队列) 深度优先遍历(递归+非递归(栈))

问题描述

求迷宫图指定入口格子到出口格子的路径。最优解是最短的那条路径。

定义迷宫

迷宫大小M*N和最大值:

#define M 8
#define N 8
#define MaxSize 500

M*N加上外面一圈墙。0可走,1是障碍物。

int Maze[M+2][N+2]= {
  {1,1,1,1,1,1,1,1,1,1},
  {1,0,0,1,0,0,0,1,0,1},
  {1,0,0,1,0,0,0,1,0,1},
  {1,0,0,0,0,1,1,0,0,1},
  {1,0,1,1,1,0,0,0,0,1},
  {1,0,0,0,1,0,0,0,0,1},
  {1,0,1,0,0,0,1,0,0,1},
  {1,0,1,1,1,0,1,1,0,1},
  {1,1,0,0,0,0,0,0,0,1},
  {1,1,1,1,1,1,1,1,1,1} };

图的邻接表存储结构

邻接表中每个元素都是一个头结点(包括数据域data 第一个边结点指针*firstarc),每个元素都可能是别人的边结点(包括结点编号adjvex 下一结点指针域*nextarc以及权重weight)。

#迷宫图对应的邻接表:

定义邻接表:

//边结点类型//
typedef struct ANode {
	int i, j;
	struct ANode *nextarc;
}ArcNode;

//头结点类型//
typedef struct {
	ArcNode *firstarc;
}VNode;

//邻接表类型//
typedef struct {
	VNode adjlist[M+ 2][N+ 2];
}AdjGraph;

创建图的邻接表:

//建立迷宫数组对应的邻接表//
void CreateAdj(AdjGraph *&G, int A[M+2][N+2])
{
	ArcNode *p;//用来创建结点的工具指针
	G = (AdjGraph*)malloc(sizeof(AdjGraph));
	int i, j;//定义迭代器
	//给邻接表中所有头节点的指针域置初值//
	for (i = 0; i < M + 2; i++)                   
		for (j = 0; j < N + 2; j++)
			G->adjlist[i][j].firstarc = NULL;
	//检查迷宫中每个元素//
	for (i = 1; i <= M; i++)                   
		for (j = 1; j <= N; j++)
			if (Maze[i][j] == 0) {
				int di = 0;
				int i1, j1;
				while (di < 4)
				{
					switch (di)
					{
					case 0:
						i1 = i - 1;
						j1 = j;
						break;
					case 1:
						i1 = i;
						j1 = j + 1;
						break;
					case 2:
						i1 = i + 1;
						j1 = j;
						break;
					case 3:
						i1 = i, j1 = j - 1;
						break;
					}
					if (Maze[i1][j1] == 0) {
						p = (ArcNode*)malloc(sizeof(ArcNode));
						p->i = i1;
						p->j = j1;
						//放边结点的链表是头插法建立的//
						p->nextarc = G->adjlist[i][j].firstarc;   
						G->adjlist[i][j].firstarc = p;
					}
					di++;
				}
			}

}

图的遍历

#深度优先DFS(递归):类似二叉树先序遍历

定义迷宫格子和路径:

typedef struct
{	int i;                      //当前格子的行号
    int j;                      //当前格子的列号
    int di;
} Box;

typedef struct
{	Box data[MaxSize];
    int length;                
} PathType;         //path结构配合递归达到伪栈的目的         

定义访问标记数组和路径计数器:

int visited[M+2][N+2] = { 0 };
int count = 0;

DFS打印所有路径:

  • 我不在函数里面创建path而是作为参数传进去是因为递归。这里 path+递归 其实起到了栈的作用。

  • 每次调用先判断一次入口是否等于出口。再按条件递归或者结束过程返回上一层调用。

  • 用一个ArcNode标记当前的递归入口。

void DFSprintAllPath(AdjGraph *G, int xi,int yi,int xe,int ye,PathType path) {
	
	//进path//
	path.data[path.length].i = xi;
	path.data[path.length].j = yi;
	path.length++;

	visited[xi][yi] = -1;
	//找到出口打印路径(找到的出口在path的尾)//
	if (xi == xe && yi == ye)
	{
		printf("  第%d条路径: ", ++count);
		for (int k = 0; k < path.length; k++)
			printf("(%d,%d) ", path.data[k].i, path.data[k].j);
		printf("\n");
	}
	//DFS部分//
	ArcNode *p;
	p = G->adjlist[xi][yi].firstarc;//p指向顶点的第一个邻接点
	while (p != NULL) {
		if (visited[p->i][p->j] == 0)
			DFSfindAllPath(G, p->i, p->j, xe, ye,path);
		//返回到上一层后指向下一ArcNode//
		p = p->nextarc;
	}
	//走到这里说明此路不通//
	//最后调用dfs的格子设置成没走过,返回上一层调用,相当于退栈//
	visited[xi][yi] = 0;
}

测试一下:

int main() {
	AdjGraph *G;
	CreateAdj(G, Maze);
	printf("所有的迷宫路径:\n");
	PathType path;
	path.length = 0;
	DFSprintAllPath(G, 1, 1, 8, 8,path);
	return 0;
}
用栈实现非递归的DFS求解迷宫问题:

顺序栈结构的定义包含一个data数组和top栈顶指针(int类型,存放栈顶元素在data数组中的下标)。

进栈push(top++),退栈pop(top–)。

格子和栈的定义

//格子和栈//
typedef struct {
	int i;
	int j;
	int di; //下一相邻可走方位的方位号
}Box;

typedef struct {
	Box data[MaxSize];
	int top; //栈顶指针
}StType; //顺序栈类型

具体求解过程:

//求解路径为:(xi,yi)->(xe,ye)//
void MazePath(int xi, int yi, int xe, int ye) {
	//定义指示当前格子的变量//
	int i, j, di;

	//声明栈st并初始化栈顶指针//
	StType st;
	st.top = -1;

	//声明放最短路径的栈//
	StType minpath;
	int minlen=MaxSize;

	//用来数找到几条路径的计数器//
	int count=0;

	//入口格子进栈//
	st.top++;
	st.data[st.top].i = xi;
	st.data[st.top].j = yi;
	st.data[st.top].di = -1;//刚进栈的格子方向置为-1表示尚未试探周围

	Maze[xi][yi] = -1;

	//栈不为空时循环//
	while (st.top > -1)
	{
		//取栈顶格子(最后进栈的那个格子)//
		i = st.data[st.top].i;
		j = st.data[st.top].j;
		di = st.data[st.top].di;

		//找到出口,输出路径//
		if (i == xe && j == ye)
		{
			//输出这条路径//
			printf("第%d条:\n",++count);
			for (int k = 0; k <= st.top; k++)
			{
				printf("(%d,%d)", st.data[k].i, st.data[k].j);
				if ((k + 1) % 5 == 0)
					printf("\n");
			}
			printf("\n");

			//更新最短路径栈//
			if (st.top + 1 < minlen) {
				minpath.top = -1;//初始化最短路径栈顶指针
				for (int k = 0; k <= st.top; k++) {
					minpath.top++;
					minpath.data[k] = st.data[k];
				}
				minlen = st.top + 1;
			}

			//回溯//
			//出口退栈//
			Maze[st.data[st.top].i][st.data[st.top].j] = 0;
			st.top--;
			//当前格子指向出口前一个格子//
			i = st.data[st.top].i;
			j = st.data[st.top].j;
			di = st.data[st.top].di;

		}

		//查找(i,j,di)格子的下一个可走格子//
		bool find = false;//下一个格子可不可以走的flag
		while (di < 4 && !find)
		{
			//先把当前格子换到di方向上那个格子上//
			di++;
			switch (di)
			{
			case 0:
				i = st.data[st.top].i - 1;
				j = st.data[st.top].j;
				break;
			case 1:
				i = st.data[st.top].i;
				j = st.data[st.top].j + 1;
				break;
			case 2:
				i = st.data[st.top].i + 1;
				j = st.data[st.top].j;
				break;
			case 3:
				i = st.data[st.top].i;
				j = st.data[st.top].j - 1;
				break;
			}
			//再检查这个格子可不可以走(排除走过的和有障碍物的)//
			if (Maze[i][j] == 0)
				find = true;
		}

		if (find)//下一个格子可以走
		{
			st.data[st.top].di = di;//修改原栈顶元素的di值
			st.top++;//下一个可走格子进栈
			st.data[st.top].i = i;
			st.data[st.top].j = j;
			st.data[st.top].di = -1;//刚进栈的格子方向置为-1表示尚未试探周围
			Maze[i][j] = -1;//避免重复走到格子
		}
		else {
			//当前格子没有可走的下一个格子则设置成其他格子可走并退栈//
			Maze[st.data[st.top].i][st.data[st.top].j] = 0;
			st.top--;
		}
	}
	//输出最短路径//
	printf("最短路径:长度%d\n",minlen);
	for (int k = 0; k <= minpath.top; k++)
	{
		printf("\t(%d,%d)", minpath.data[k].i, minpath.data[k].j);
		if ((k + 1) % 5 == 0)
			printf("\n");
	}
	printf("\n");
}

#广度优先BFS(非递归 队列):类似二叉树层次遍历

队列

顺序队,有队头指针和队尾指针,进队rear+1,出队front+1。可能发生假溢出(当rear=MaxSize-1的时候前面是空的)。
用环形队列解决假溢出的问题:

队空条件:rear==front;
队满条件:(rear+1) % MaxSize == front;
任何时候队中有MaxSize-1个元素

进队出队都变成循环+1:
进:rear=(rear+1)%MaxSize;
出:front=(front+1)%MaxSize;

如果用队列找最优解
就用顺序队,不需要循环队列

因为如果用环形队列,出队以后原先格子的位置可能被后面进队的格子覆盖。
当然只要数组足够大就不会出现问题。

下面开始BFS求解迷宫问题

定义队列用的格子:

typedef struct {
	int i;
	int j;
	int pre;//标记前一个格子的位置
}QBox;//用于队列的格子

BFS打印最短路径:

void BFSprintShortPath(AdjGraph *G, int xi, int yi, int xe, int ye) {
	
	QBox qu[500];
	int front = -1, rear = -1;

	//入口进队//
	rear++;
	qu[rear].i = xi;
	qu[rear].j = yi;
	qu[rear].pre = front;
	visited[qu[rear].i][qu[rear].j] = -1;

	//队不空时循环//
	while (front != rear) {

		//出队一个//
		front++; 

		//队头在出口时打印路径//
		if (qu[front].i == xe && qu[front].j == ye) {
			printf("最短路径(逆向)为:\n");
			int i = front;
			while (qu[i].pre != -1) {
				printf("(%d,%d)", qu[i].i,qu[i].j);
				i = qu[i].pre;
			}
			printf("(%d,%d)", qu[i].i, qu[i].j);
			return;
		}

		ArcNode *p = G->adjlist[qu[front].i][qu[front].j].firstarc;
		while (p != NULL) {
			if (visited[p->i][p->j] == 0) {
				visited[p->i][p->j] = -1;
				rear++;
				qu[rear].i = p->i;
				qu[rear].j = p->j ;
				qu[rear].pre = front;
			}
			p = p->nextarc;
		}

	}
}

测试一下:

int main() {
	AdjGraph *G;
	CreateAdj(G, Maze);
	BFSprintShortPath(G,1,1,8,8);
	return 0;
}

小结

深度优先算法适合找所有路径,邻接表表示总时间为O(n+e)
广度优先算法适合找最短路径,邻接表表示总时间为O(n+e)

  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线性表 某软件公司大约有30名员工,每名员工有姓名、工号、职务等属性,每年都有员工离职和入职。 把所有员工按照顺序存储结构建立一个线性表,建立离职和入职函数,当有员工离职或入职时,修改线性表,并且打印最新的员工名单。 约瑟夫(Josephus)环问题:编号为1,2,3,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数的上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一人开始重新从1报数,如此下去,直到所有人全部出列为止。 建立n个人的单循环链表存储结构,运行结束后,输出依次出队的人的序号。 队列 某商场有一个100个车位的停车场,当车位未满时,等待的车辆可以进入并计时;当车位已满时,必须有车辆离开,等待的车辆才能进入;当车辆离开时计算停留的的时间,并且按照每小时1元收费。 汽车的输入信息格式可以是(进入/离开,车牌号,进入/离开时间),要求可以随时显示停车场内的车辆信息以及收费历史记录。 某银行营业厅共有6个营业窗口,设有排队系统广播叫号,该银行的业务分为公积金、银行卡、理财卡等三种。公积金业务指定1号窗口,银行卡业务指定2、3、4号窗口,理财卡业务指定5、6号窗口。但如果5、6号窗口全忙,而2、3、4号窗口有空闲时,理财卡业务也可以在空闲的2、3、4号窗口之一办理。 客户领号、业务完成可以作为输入信息,要求可以随时显示6个营业窗口的状态。 5、4阶斐波那契序列如下:f0=f1=f2=0, f3=1,…,fi=fi-1+fi-2+fi-3+fi-4, 利用容量为k=4的循环队列,构造序列的前n+1项(f0, f1 , f2 ,… fn ),要求满足fn ≤200而fn+1 >200。 6、八皇后问题:设8皇后问题的解为 (x1, x2, x3, …,x8), 约束条件为:在8x8的棋盘上,其中任意两个xi 和xj不能位于棋盘的同行、同列及同对角线。要求用一位数组进行存储,输出所有可能的排列。 7、迷宫求解:用二维矩阵表示迷宫,自动生成或者直接输入迷宫的格局,确定迷宫是否能走通,如果能走通,输出行走路线。 8、英国人格思里于1852年提出四色问题(four colour problem,亦称四色猜想),即在为一平面或一球面的地着色时,假定每一个国家在地上是一个连通域,并且有相邻边界线的两个国家必须用不同的颜色,问是否只要四种颜色就可完成着色。现在给定一张地,要求对这张地上的国家用不超过四种的颜色进行染色。 要求建立地的邻接矩阵存储结构,输入国家的个数和相邻情况,输出每个国家的颜色代码。 9、以下问题要求统一在一个大程序里解决。 从原四则表达式求得后缀式,后缀表达式求值,从原四则表达式求得中缀表达式,从原四则表达式求得前缀表达式,前缀表达式求值。 数组与广义表 鞍点问题: 若矩阵A中的某一元素A[i,j]是第i行中的最小值,而又是第j列中的最大值,则称A[i,j]是矩阵A中的一个鞍点。写出一个可以确定鞍点位置的程序。 稀疏矩阵转置: 输入稀疏矩阵中每个元素的行号、列号、值,建立稀疏矩阵的三元组存储结构,并将此矩阵转置,显示转置前后的三元组结构。 用头尾链表存储表示法建立广义表,输出广义表,求广义表的表头、广义表的表尾和广义表的深度。 树和二叉树 以下问题要求统一在一个大程序里解决。 按先序遍历的扩展序列建立二叉树的存储结构 二叉树先序、中序、后序遍历递归算法 二叉树中序遍历非递归算法 二叉树层次遍历非递归算法 求二叉树的深度(后序遍历) 建立树的存储结构 求树的深度 输入任意的一个网,用普里姆(Prim)算法构造最小生成树。 要求建立存储结构邻接表或邻接矩阵),输入任意的一个,显示的深度优先搜索遍历路径。 要求建立存储结构邻接表或邻接矩阵),输入任意的一个,显示的广度优先搜索遍历路径。 查找 设计一个读入一串整数构成一颗二叉排序树的程序,从二叉排序树中删除一个结点,使该二叉树仍保持二叉排序树的特性。 24、设定哈希函数 H(key) = key MOD 11 ( 表长=11 ),输入一组关键字序列,根据线性探测再散列解决冲突的方法建立哈希表的存储结构,显示哈希表,任意输入关键字,判断是否在哈希表中。 排序 以下问题要求统一在一个大程序里解决。 25、折半插入排序 26、冒泡排序 27、快速排序 28、简单选择排序 29、归并排序 30、堆排序

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值