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

问题描述

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

定义迷宫

迷宫大小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)

发布了36 篇原创文章 · 获赞 0 · 访问量 1766

猜你喜欢

转载自blog.csdn.net/Oneiro_qinyue/article/details/103434551