BFS解最短路问题

广度优先搜索,广度优先,深度次要

与DFS不同是,BFS不再嵌套循环的思想,而是采用辅助队列来辅助实现广度与深度上的推进。

目录

例题

算法的代码逻辑

使用队列的原因

重点:建系、方向向量、mark数组

建两个队列 

此路入队的判定条件

完整代码


例题

此题是一道十分经典且基础的BFS求最短路径问题

算法的代码逻辑

算法的代码骨架:循环+辅助队列 

算法的代码逻辑

BFS模版较为固定,只要弄懂其中的逻辑,相似题目代码的写法大差不差

起点入队,循环开始。,每从队中取出一个元素,然后对该元素在图中上下左右四个方向的相邻元素进行检查,若该条路能走,令该元素入队,等它成为队头后,令其出队,再对其上下左右四个方向的相邻元素进行检查。若该路不能走,直接pass掉,不令该元素入队。如此循环往复,直到终点元素出队,BFs结束,break掉循环

注意的是,此处的元素入队是指当前元素的x,y坐标的值入队,具体请看下列详情。

使用队列的原因

利用队列先进先出的特点。当前深度的所有节点均检查完并出队列之后,再去检查下一层的节点,再出队列。

重点:建系、方向向量、mark数组

BFS解决图的最短路径问题,往往会涉及到建系与方向向量。

建系作用:确定当前节点在图中的具体位置,一般可用(x,y)来表示当前位置。

单位方向向量作用:利用向量的坐标运算来获取当前节点上下左右相邻元素的位置

(x-1,y)(x,y+1)(x+1,y)(x,y-1)

mark数组:

0表示未走过,1表示已走过。

作用(重点):

1.记录该点有没有走过,防止已经出队的节点重复入队,同时可以在一定程度上进行剪枝优化(即令一些已经走远路的DFS路线搜索中途截断,节约时间)。

2.记录从起点到该点最短路径为多少。

在所有能够到达该点的路径中,最先到达该点的路径是最短的。

也正因如此,从起点出发到达终点的若干路径中,最短路径是——从起点开始,每次走的路都是到达某点最短的一条路径,直到到达终点。(其中隐隐嵌套了积分的思想


int map[110][110];

//注意:此处不能直接使用cout计数器来统计最短步数,因为代码在执行时会不可避免地出现cout重复++
int mark[110][110] = { 0 };
int x[4] = {-1,0,1,0};//模拟x方向上的方向向量
int y[4] = {0,1,0,-1};//模拟y方向上的方向向量

建两个队列的原因

由于c语言的局限性,队列的元素只能是一个值,而不能像cpp那般是一对值,因此必须要建两个辅助队列。

一个队列存坐标x值,一个队列存坐标y值



	//队列难点:
	//1. 封装一个函数来创建一个队列,结构体指针来指代一个队列
	//2.在主函数中,直接利用结构体来声明一个队列,可用结构体变量来指代一个队列

	pQueue queuex = creatAndIniteQueue();
	pQueue queuey = creatAndIniteQueue();
	pushElement(queuex,0);
	pushElement(queuey,0);

此路入队的判定条件

1.该路能走,即坐标系中该点对应的map元素值为0;

2.该路未被走过,即坐标系中该点对应的mark元素值为0;


	while (isEmpty(queuex)!=1 ){
		int dx = popElement(queuex);
		int dy = popElement(queuey);
		for (int i = 0; i < 4;++i) {
			int nex = dx + x[i], ney = dy + y[i];
			if (nex == n-1 && ney == m-1) {
				int ans = mark[dx][dy] + 1;
				printf("%d",ans);
				return 0;
			}

			if ( map[nex][ney] == 0 && mark[nex][ney] == 0 && nex>= 0 && ney >=0 
				&& nex <= n-1 && ney <= m-1 
				)
			{
				//程序进到这里说明此路可通
				mark[nex][ney] = 1;
				//每次走的路都是相邻的路,因此每次只走一步
				mark[nex][ney] = mark[dx][dy] + 1;
				pushElement(queuex,nex);
				pushElement(queuey,ney);
			}

		}

	}

完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

//c语言并没有现成的队列使用,在比赛中需要手撸一个队列出来

typedef struct {
	int* nums;
	int head;
	int tail;
	int capacity;
}Queue,* pQueue;

//这里创建队列空间出错了: 函数内成功给临时变量pqe创建了空间,但出函数后,这个pqe就销毁了,并且更为严重的是,pqe指向的空间未被销毁,这导致了该空间没有指针指向,纯纯的空间浪费
//改错:函数体创建了该空间后,一定要记得将该地址返回给主调函数,让这块空间能够被外部接收
pQueue creatAndIniteQueue() {
	pQueue pqe;
	pqe = (pQueue)malloc(sizeof(Queue));
	pqe->head = 0;//头指针代表队头
	pqe->tail = 0;//尾指针代表队尾
	pqe->capacity = 200;
	pqe->nums = (int*)malloc(sizeof(int) * pqe->capacity);
	
	return pqe;
	
	
}

void pushElement(pQueue pqe,int target) {
	//
	if ((pqe->tail + 1)%pqe->capacity == pqe->head) {
		printf("队列已满!!");
		return;
	}

	pqe->nums[pqe->tail] = target;

	pqe->tail = (pqe->tail + 1) % pqe->capacity;

}

int popElement(pQueue pqe) {
	if (pqe->tail == pqe->head) {
		printf("队列已空!");
		return;
	}

	int tmp = pqe->nums[pqe->head];

	pqe->head = (pqe->head + 1) % pqe->capacity;
	return tmp;
}

int isEmpty(pQueue pqe) {
	if (pqe->tail == pqe->head) 
		return 1;
	return 0;
}



int map[110][110];

//注意:此处不能直接使用cout计数器来统计最短步数,因为代码在执行时会不可避免地出现cout重复++
int mark[110][110] = { 0 };
int x[4] = {-1,0,1,0};//模拟x方向上的方向向量
int y[4] = {0,1,0,-1};//模拟y方向上的方向向量




int main() {
	int n = 0, m = 0;
	scanf("%d %d",&n,&m);
	for (int i = 0; i < n;++i) {
		for (int j = 0; j < m;++j ) {
			scanf("%d",&map[n][m]);
		}
	}

	//队列难点:
	//1. 封装一个函数来创建一个队列,结构体指针来指代一个队列
	//2.在主函数中,直接利用结构体来声明一个队列,可用结构体变量来指代一个队列

	pQueue queuex = creatAndIniteQueue();
	pQueue queuey = creatAndIniteQueue();
	pushElement(queuex,0);
	pushElement(queuey,0);

	
	//判定条件:1.该路能走,即元素为0 2.该路未被走过,即mark中该处值为0;
	while (isEmpty(queuex)!=1 ){
		int dx = popElement(queuex);
		int dy = popElement(queuey);
		for (int i = 0; i < 4;++i) {
			int nex = dx + x[i], ney = dy + y[i];
			if (nex == n-1 && ney == m-1) {
				int ans = mark[dx][dy] + 1;
				printf("%d",ans);
				return 0;
			}

			if ( map[nex][ney] == 0 && mark[nex][ney] == 0 && nex>= 0 && ney >=0 
				&& nex <= n-1 && ney <= m-1 
				)
			{
				//程序进到这里说明此路可通
				mark[nex][ney] = 1;
				//每次走的路都是相邻的路,因此每次只走一步
				mark[nex][ney] = mark[dx][dy] + 1;
				pushElement(queuex,nex);
				pushElement(queuey,ney);
			}

		}

	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值