用邻接表和广度优先搜索解决图类迷宫问题

题目:

从开始位置(start)找出到出口(end)的通路,并从start到end输出;输出路径的长度。
采用下图迷宫
可以将节点进行编号,从1开始编号,编号次序从上到下,从左到右;保存每条边,根据两个节点之间是否有边,来决定是否能够走通;
将边保存在文件中,用俩个端点来表示一条边;
每个节点的分支数不一样,要求根据分支数决定遍历的方向数,不能用恒定的遍历方向数。
补充:找到从start到end的一条最短的路径;当迷宫有多个出口应该如何找到最近的出口;

下图:起点2,终点17
在这里插入图片描述
注意编号是特定的,要修改的话就必须修改邻接表

如标题所述如果你要用这种方法就必须做好心理准备,这种操作虽然高效,内存利用率更高,但这实在不好操作,好了,话不多说,让我们看看要用什么知识吧~

  • 邻接表
    邻接表可以看作两部分,一部分结构体数组,另一部分是链表,具体结构是邻接表每一项可以看作一个结点与相邻节点的组成链表

    如图所示:
    在这里插入图片描述
    解释一下,因为上图是无向图,所以如果1,2节点有路径,用邻接表就可以表示为1->2,2->1,最后一个节点用^表示。

下面代码是创建邻接表的过程,解释一下:
比如4->5->3->^这条路径,首先4是首顶点,我将它与其他首顶点一起存入数组AdjList中,并把AdjList的数据类型设置为顶点类型,再创建一个次点表,最后创建一个以首顶点为头,一层一层连接次点的邻接链表,这就相当于每一条路径都有对应的链表,至此邻接表创建完成。

图例:
在这里插入图片描述

/*创建邻接表*/
typedef struct Node { //次点表
	int adjvex;
	struct Node* next;
} EdgeNode;

typedef struct vertex { //首顶点表
	int vertex;
	EdgeNode* edgenext;
} VertexNode;

typedef VertexNode AdjList[poi];//首顶点表数组

typedef struct {
	AdjList adjlist;
	int n, e;//n为顶点数,e为边数
} AdjMatrix; //建立邻接表结构体

void CreateAdja(AdjMatrix *G) { //建立邻接表G
	EdgeNode *edge_node;//用来表示边链表

	printf("Please input the number of vertexs and edges(there is space in the middle):");
	scanf("%d%d", &G->n, &G->e);//输入顶点和边的数量

	/*读取文件*/
	printf("Reading file,please wait......\n");
	FILE *fp;
	fp = fopen("demo.txt", "r");

	if (fp == NULL)//判断文件是否为空
		printf("Fail to open the file, because it is null or its address is invalid!\n");

	if (fp != NULL) {//如果文件不为空
		for (int k = 0; k < G->n; ++k) { //建立顶点表
			G->adjlist[k].vertex = k + 1;
			G->adjlist[k].edgenext = NULL;
		}
		int i = 0, j = 0;
		while (fscanf(fp, "%d", &i) != EOF) { //读取顶点和边节点
			fscanf(fp, "%d", &j);
			edge_node = (EdgeNode*)malloc(sizeof(struct Node));
			edge_node->adjvex = j;//边表赋值
			edge_node->next = G->adjlist[i - 1].edgenext;
			G->adjlist[i - 1].edgenext = edge_node;

			edge_node = (EdgeNode*)malloc(sizeof(struct Node));
			edge_node->adjvex = i;//双向连接,因为是无向表,所以要双向建表
			edge_node->next = G->adjlist[j - 1].edgenext;
			G->adjlist[j - 1].edgenext = edge_node;
		}
		printf("Congratulations, adjacency is successfully created!\n");
		//无向邻接表成功建立
	}
}
  • 广度优先搜索
    广度优先搜索可以看作剥洋葱,一层层的寻找最短路径,这里广度优先就不展开解释,具体说明思路:
    首先我们需要一个容器(这里我使用的是队列),这个容器用来装下所有节点,如下:
    从起点2开始,与它相邻的是3,所以把3写入2的邻格,与3相邻的是1,2,4,6,7,8,因为2已经在容器内,所以我们依次填入1,4,6,7,8(填入顺序无影响)。
    在这里插入图片描述
    重复操作,直到终点进入容器,此时我们可以用通过记录每条路径,找到从终点到起点的路径(这里注意因为使用容器的缘故,我们找到的路径是逆序的),而且因为广度优先搜索的原因,这是最短路径之一。
    最后我使用栈,将顶点逆序输出得到从起点到终点的一条最短路径。
/*创建队列*/
typedef struct Queue {
	int vertex_number ;//顶点
	int number ;//边点
} queue;

queue q[poi + 1];//全局变量队列数组

/*创建栈*/
typedef struct Stack {
	int top;
	int path[poi + 1];//路径数组
} stack;

void stack_push(stack* s, int i) {
	s->path[s->top++] = i;
}//压栈

int stack_pop(stack* s) {
	int j = s->path[--s->top];
	return j;
}//出栈

int main() {
	AdjMatrix *demo = (AdjMatrix*)malloc(sizeof(AdjMatrix));//分配邻接表空间
	CreateAdja(demo);//创建邻接表
	int start, end;
	int flag = 0;

	stack* sta = (stack*)malloc(sizeof(struct Stack));//给栈分配空间
	sta->top = 0;//栈置空

	printf("Please input start and end, there is space in the middle: ");
	scanf("%d%d", &start, &end);

	int	book[poi + 1] = {0}; //标记数组用来标记是否进入队列,没进队列设为0,进入队列设为1
	int i = start, j = 0, count = 0;//i为起始点,j表示遍历的动态点,count用来控制队列数组的移动
	int path_length[poi + 1] = {0};//存储路径长度的数组

	/*使用队列模拟广度优先搜索找到路径*/
	while (demo->adjlist[start-1].edgenext!=NULL && demo->adjlist[end-1].edgenext!=NULL && count < demo->n) {//首先判断输入的起始点和结束点有邻边
		if (book[demo->adjlist[i - 1].vertex] == 0) {//遍历顶点
			q[j].vertex_number = demo->adjlist[i - 1].vertex;
			q[j].number = i;
			if (q[j].number != start) {
				path_length[q[j].number] = path_length[q[j].vertex_number] + 1;
			}
			book[i] = 1;//标记该点已遍历过
			j++;
		}

		EdgeNode* demo1 = demo->adjlist[i - 1].edgenext;//让节点指向边节点

		while (demo1) { //遍历边结点
			if (book[demo1->adjvex] == 0) { //判断是否已经入队
				q[j].vertex_number = demo->adjlist[i - 1].vertex;
				q[j].number = demo1->adjvex;
				path_length[q[j].number] = path_length[q[j].vertex_number] + 1;
				book[demo1->adjvex] = 1;//标记该点已遍历过

				if (q[j].number == end) {//找到出口,结束遍历
					flag = 1;
					break;
				}
				j++;
			}
			demo1 = demo1 -> next;//边链表遍历
		}
		if (flag == 1) //找到出口,标志变为1
			break;

		i = q[count++].number;
	}

	/*用栈逆序输出路径*/
	if (flag == 1) {
		printf("Successfully , you have found a path:\n");
		int t = j ;
		for (int m = 0 ; m <= path_length[q[j].number] ; ++m) {//循环结束条件为出口到起点距离遍历完成
			stack_push(sta, q[t].number);//把经过的点压栈(这里是逆序,所以刚开始为出口顶点)
			for (int i = 0 ; i < j ; ++i)
				if (q[i].number == q[t].vertex_number)//寻找出口节点的顶点,并定位顶点,再找顶点的顶点,迭代寻找,找到起点
					t = i;
		}

		while (sta->top > 1) {//输出路径
			printf("%d->", stack_pop(sta));
		}
		printf("%d\n", stack_pop(sta));

		printf("Length of the path is:");//打印路径长度
		printf(" %d", path_length[q[j].number]);
	}
	else//如果标志flag!=1表示未找到通路
		printf("There is no path to connect two vertexs!");
	free(demo);//释放邻接表内存
	free(sta);//释放逆序栈内存
	return 0;
}

兄弟们,完整代码可以去我的代码仓复制粘贴(dogge):

https://gitcode.net/flag_wkx/bupt

创作不易,谢谢点赞

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值