【洛谷】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;
}

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

相关推荐
老鼠只爱大米6 小时前
LeetCode经典算法面试题 #46:全排列(回溯、交换、剪枝等五种实现方案详细解析)
算法·leetcode·剪枝·回溯·全排列·stj算法
Dovis(誓平步青云)6 小时前
《滑动窗口算法:从 “暴力遍历” 到 “线性高效” 的思维跃迁》
运维·服务器·数据库·算法
_OP_CHEN6 小时前
【算法基础篇】(五十七)线性代数之矩阵乘法从入门到实战:手撕模板 + 真题详解
线性代数·算法·矩阵·蓝桥杯·c/c++·矩阵乘法·acm/icpc
天天爱吃肉82186 小时前
【跨界封神|周杰伦×王传福(陶晶莹主持):音乐创作与新能源NVH测试,底层逻辑竟完全同源!(新人必看入行指南)】
python·嵌入式硬件·算法·汽车
im_AMBER6 小时前
Leetcode 114 链表中的下一个更大节点 | 删除排序链表中的重复元素 II
算法·leetcode
xhbaitxl6 小时前
算法学习day38-动态规划
学习·算法·动态规划
多恩Stone7 小时前
【3D AICG 系列-6】OmniPart 训练流程梳理
人工智能·pytorch·算法·3d·aigc
历程里程碑7 小时前
普通数组----轮转数组
java·数据结构·c++·算法·spring·leetcode·eclipse
pp起床7 小时前
贪心算法 | part02
算法·leetcode·贪心算法