【洛谷】BFS 求解最短路:从马的遍历到迷宫问题的实战解析

文章目录


宽度优先搜索的过程中,每次都会从当前点向外扩展⼀层,所以会具有⼀个最短路的特性。因此,宽搜不仅能搜到所有的状态,⽽且还能找出起始状态距离某个状态的最⼩步数。

但是,前提条件是每次扩展的代价都为 1,或者都是相同的数。宽搜常常被⽤于解决边权相同的最短路问题。 宽度优先搜索适用于以下情况:

小编提醒一下,BFS不是递归,所以一般BFS函数不用传参。

马的遍历

题目描述

题目解析

先介绍一下象棋中马的走法,马是走日的对角线,如下图所示,一共有8中可能,我们枚举要把8种可能全部枚举一边,还是用方向向量表示偏移量:

本题用层序遍历,步数改变表示状态改变,步数相同表示同一层:

本题有两个可行性剪枝,第一个是数组越界剪枝,第二个是不能枚举已经枚举过的格子,所以一开始全为0的二维数组要初始化为-1,不让就无法区分格子为0是初识格子步数为0还是没有遍历枚举过,二维数组初始化为-1后-1就表示没有枚举过的格子。

代码

cpp 复制代码
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;

const int N = 410;

int n, m, x, y;
int a[N][N]; //存储步数

//方向向量
int dx[8] = {1, 2, 2, 1, -1, -2, -2, -1};
int dy[8] = {2, 1, -1, -2, -2, -1, 1, 2};

void bfs()
{
	//将a初始化为-1,避免混淆未递归格子的0和起始格子的0
	memset(a, -1, sizeof(a));
	a[x][y] = 0;
	queue<pair<int, int>> q;
	q.push({ x, y });
	while (q.size())
	{
		//取出队头元素,并将它弹出
		auto t = q.front();
		q.pop();
		//将队列中的pair对象转化为矩阵中的坐标信息
		int i = t.first;
		int j = t.second;
		//枚举8个位置
		for (int k = 0; k < 8; k++)
		{
			int x = i + dx[k];
			int y = j + dy[k];
			//可行性剪枝
			if (x < 1 || x > n || y < 1 || y > m)
				continue;
			if (a[x][y] != -1)
				continue;

			a[x][y] = a[i][j] + 1;
			q.push({ x, y });
		}
	}
}

int main()
{
	//初始化数据
	cin >> n >> m >> x >> y;
	bfs();
	//输出结果
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cout << a[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

kotori和迷宫

题目描述

题目解析

本题依旧用BFS枚举递归路径,本题需要两个棋盘,一个字符棋盘a存储迷宫,一个整型棋盘dict存储从起始位置到各个格子的步数。

当坐标越界或者走到墙或者走到已经走过的位置直接剪掉,除了上述的情况还剩两种情况,一种是走到-1格子,需要继续push该格子进队列然后后面继续上下左右枚举,一种是走到e,此时枚举结束,不用继续push该格子进队列。

本题需要注意一点,不能在bfs的时候统计结果:

cpp 复制代码
void bfs()
{
    memset(dict, -1, sizeof(dict));
    dict[x][y] = 0;
    queue<pair<int, int>> q;
    q.push({ x, y });
    while (q.size())
    {
        pair<int, int> t = q.front();
        q.pop();
        int i = t.first;
        int j = t.second;
        for (int k = 0; k < 4; k++)
        {
            int nx = i + dx[k];
            int ny = j + dy[k];
            //可行性剪枝
            if (nx < 1 || nx > n || ny < 1 || ny > m || a[nx][ny] == '*' || dict[nx][ny] != -1)
                continue;
            if (a[nx][ny] == 'e')
            {
                e++;
                b = min(b, dict[i][j] + 1);
                continue;
            }
            dict[nx][ny] = dict[i][j] + 1;
            q.push({ nx, ny });
        }
    }
}

如果在bfs的时候统计可能会有多条路径到达同一个出口,如下图所示,可选择的出口数量就会被多统计。

所以需要bfs搜索完后对dict数组进行统计。

代码

cpp 复制代码
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 35;

int n, m;
char a[N][N]; //存储原数据
int dict[N][N]; //存储步数信息
int x, y; //k的坐标
int e; //出口数
int b = 1000; //距离最近出口的步数

//方向向量
int dx[4] = { 0, 0, 1, -1 };
int dy[4] = { 1, -1, 0, 0 };

void bfs()
{
    memset(dict, -1, sizeof(dict));
    dict[x][y] = 0;
    queue<pair<int, int>> q;
    q.push({ x, y });
    while (q.size())
    {
        pair<int, int> t = q.front();
        q.pop();
        int i = t.first;
        int j = t.second;
        for (int k = 0; k < 4; k++)
        {
            int nx = i + dx[k];
            int ny = j + dy[k];
            //可行性剪枝
            if (nx < 1 || nx > n || ny < 1 || ny > m || a[nx][ny] == '*' || dict[nx][ny] != -1)
                continue;
            dict[nx][ny] = dict[i][j] + 1;
            if (a[nx][ny] != 'e')
            {
                //a[nx][ny]不为e,继续往后BFS
                q.push({ nx, ny });
            }
        }
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cin >> a[i][j];
            if (a[i][j] == 'k')
            {
                x = i;
                y = j;
            }
        }
    }
    bfs();
    //统计结果
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (a[i][j] == 'e' && dict[i][j] != -1)
            {
                e++;
                b = min(b, dict[i][j]);
            }
        }
    }
    //输出结果
    if (e)
    {
        cout << e << " " << b << endl;
    }
    else
    {
        cout << "-1" << endl;
    }

    return 0;
}

CatchThatCowS

题目描述

题目解析

本题大家可能最先想到用贪心解决,要走到一个比自己大的数先二倍,当超过目标数字再减,但是这个方法有时并不会得到最优结果,如下图所示。

所以本题需要将所有情况枚举出来,采用bfs。

本题的剪枝策略有三种,第一是越界剪枝,加一和二倍要小于等于上限1e5,减一要大于0,第二是枚举到已经枚举过的格子时要剪掉,具体时间依旧是把数组初始化为-1,格子数据为-1表示没有枚举过,注意本题有多组测试用例,所以每一组开始都需要初始化数组。第三时当枚举的数字等于目标数字时立即停止搜索,return返回,因为BFS 层序特性保证首次找到目标即为最短路径。

代码

cpp 复制代码
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;

const int N = 1e5 + 10;

int r = 1e5; //上限
int z, x, y;
int a[N]; //存储步数

void bfs()
{
	queue<int> q;

	q.push(x);
	a[x] = 0;
	while (q.size())
	{
		int t = q.front();
		q.pop();
		int i = t + 1;
		int j = t - 1;
		int k = 2 * t;
		//可行性剪枝
		if (i <= r && a[i] == -1)
		{
			a[i] = a[t] + 1;
			q.push(i);
		}
		if (j > 0 && a[j] == -1)
		{
			a[j] = a[t] + 1;
			q.push(j);
		}
		if (k <= r && a[k] == -1)
		{
			a[k] = a[t] + 1;
			q.push(k);
		}

		if (i == y || j == y || k == y)
		{
			return;
		}
	}
}

int main()
{
	cin >> z;
	while (z--)
	{
		//多组测试用例清空数据
		memset(a, -1, sizeof(a));
		cin >> x >> y;
		bfs();
		cout << a[y] << endl;
	}

	return 0;
}

以上就是小编分享的全部内容了,如果觉得不错还请留下免费的赞和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

相关推荐
Wect5 小时前
LeetCode 130. 被围绕的区域:两种解法详解(BFS/DFS)
前端·算法·typescript
NAGNIP16 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
颜酱1 天前
单调栈:从模板到实战
javascript·后端·算法
CoovallyAIHub1 天前
仿生学突破:SILD模型如何让无人机在电力线迷宫中发现“隐形威胁”
深度学习·算法·计算机视觉
CoovallyAIHub1 天前
从春晚机器人到零样本革命:YOLO26-Pose姿态估计实战指南
深度学习·算法·计算机视觉
CoovallyAIHub1 天前
Le-DETR:省80%预训练数据,这个实时检测Transformer刷新SOTA|Georgia Tech & 北交大
深度学习·算法·计算机视觉
CoovallyAIHub1 天前
强化学习凭什么比监督学习更聪明?RL的“聪明”并非来自算法,而是因为它学会了“挑食”
深度学习·算法·计算机视觉
CoovallyAIHub1 天前
YOLO-IOD深度解析:打破实时增量目标检测的三重知识冲突
深度学习·算法·计算机视觉
NAGNIP2 天前
轻松搞懂全连接神经网络结构!
人工智能·算法·面试
NAGNIP2 天前
一文搞懂激活函数!
算法·面试