*注:如果还没有学过 结构体 或者 栈/队列/优先队列 的同学请仔细看完我的总结哦~

一、广度优先搜索

1. 意义

广度优先搜索(Breadth-first search,简称BFS),别名宽度优先搜索,是一种搜索算法。但是相较于深度优先搜索(Depth-first search,简称DFS),对于某些题目,时间复杂度更低。举个例子,对于走迷宫哪条路径最短,BFS 找来很多人一起走,只要出现有人走到终点的情况,那么这条路径必定是最短的,而 DFS 不能。

2. 分析

假如有这样一个迷宫:

12345
1*
2**
3
4***
5*
步数位置存活
0(1,1)1
1(2,1)1
2(3,1)2
3(4,1)/(3,2)1 / 1
4(3,3)/(5,1)2 / 1
5(2,3)(3,4)/(5,2)

3. 基本程序

首先我们要理解为什么要用队列,这是因为在每次人没有存活的时候就要删除一开始的元素,那么用队列会更方便。

结构体队列的定义方法:

#include <queue>
struct Node
{
    int x;
    bool flag;
    char c;
}; // 记得写分号
queue <Node> q;
q.push({1, 0, 'c'}); // 入队
auto [a, b, c] = q.front(); // 获取队首元素
cout << a << " " << b << " " << c; // 输出: 1 0 'c'

伪代码如下:

void bfs()
{
    vis[1][1] = 1; // 起点标记
    q.push({1, 1, 0}); // 起点入队
    
    while (!q.empty) // 非空时
    {
        // 获取队首元素并出队
        
        // 判断是否为终点
        // 是:结束
        
        // 遍历四个方向
            // 获得新的x,y坐标,存为tx,ty
            // 判断能否走(边界,障碍,访问)
            // 能走:tx,ty,step+1入队
    }
}

二、例题

1. 最短路径模板

#include <iostream>
#include <queue>
using namespace std;

struct Node
{
    int x, y; // 当前坐标
    int step; // 当前步数
};

int n;
int a[105][105];
int sx, sy, ex, ey;
queue <Node> q; // 结构体队列
bool vis[105][105];
int dx[5] = {-1, 0, 0, 1};
int dy[5] = {0, -1, 1, 0};

void bfs()
{
    vis[sx][sy] = 1; // 起点标记
    q.push({sx, sy, 0}); // 起点入队
    
    while (!q.empty()) // 非空时
    {
        // 获取队首元素并出队
        auto [fx, fy, fstep] = q.front();
        q.pop();
        
        // 判断是否为终点
        if (fx==ex && fy==ey)
        {
            cout << fstep;
            return;
        }
        
        // 遍历四个方向
        for (int i = 0; i <= 3; i++)
        {
            int tx = fx+dx[i];
            int ty = fy+dy[i];
            
            if (a[tx][ty]==0 && tx>=1 && tx<=n && ty>=1 && ty<=n && vis[tx][ty]==0)
            {
                vis[tx][ty] = 1;
                q.push({tx, ty, fstep+1});
            }
        }
    }
}

int main()
{
    // 输入
    cin >> n;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> a[i][j];
    cin >> sx >> sy >> ex >> ey;
    
    // bfs
    bfs();
    return 0;
}

2. 跳跃的马

2.1 审题

根据国际象棋规则,马只能走日字格。现在给定你一个 n × m n\times m n×m 的矩阵,已知小马在矩阵的 (x, y),请你给出一个步数矩阵,计算每个格子到达的步数(不能到达就填充 -1)。文件:horse.cpp要求行末尾没有空格

输入:

3 3 1 1

输出:

0 3 2
3 -1 1
2 1 4

2.2 思路

明确日子格的偏移量!

x x x 偏移量 y y y 偏移量
-2-1
-1-2
1-2
2-1
-21
-12
12
21

2.3 参考答案

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;

struct Node
{
    int x, y;
    int step;
};

int n, m;
int x, y;
int a[105][105];
int dx[10] = {-2, -1, 1, 2, -2, -1, 1, 2};
int dy[10] = {-1, -2, -2, -1, 1, 2, 2, 1};
queue <Node> q;

void bfs()
{
    a[x][y] = 0; // 起点标记
    q.push({x, y, 0}); // 起点入队
    
    while (!q.empty()) // 非空时
    {
        // 获取队首元素并出队
        auto [fx, fy, fstep] = q.front();
        q.pop();
        
        // 遍历八个方向
        for (int i = 0; i <= 7; i++)
        {
            int tx = fx+dx[i];
            int ty = fy+dy[i];
            
            if (tx>=1 && tx<=n && ty>=1 && ty<=m && a[tx][ty]==-1)
            {
                a[tx][ty] = fstep+1;
                q.push({tx, ty, fstep+1});
            }
        }
    }
}

int main()
{
    freopen("horse.in", "r", stdin);
    freopen("horse.out", "w", stdout);
    
    // 输入
    cin >> n >> m >> x >> y;
    memset(a, -1, sizeof(a));
    
    // bfs
    bfs();
    
    // 输出
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cout << a[i][j] << " \n"[j==m];
        }
    }
    
    fclose(stdin);
    fclose(stdout);
    return 0;
}

3. 今天就上 200 200 200

3.1 审题

题目描述

《今天就上 200 200 200 层》是一款考验玩家策略的益智类游戏,游戏中需要操控 yls 上到摩天大楼的第 200 200 200 层,摩天大楼的每一层都有一个光圈,光圈上会显示一个数字 k i k_i ki,光圈有 2 2 2 个箭头,一个向上,一个向下,上下的层数等于光圈上的数字,但注意,并不是所有的光圈都是有效的,例如 3 3 3 3 3 3 1 1 1 2 2 2 5 5 5 代表 k i k_i ki k 1 = 3 k_1 = 3 k1=3 k 2 = 3 k_2 = 3 k2=3…),如果 yls 当前在 1 1 1 层,选择"上"的箭头可以到达 4 4 4 层,选择"下"的箭头是不起作用的,因为没有 − 2 -2 2 层。
假设 yls 在 x x x 层,想要到 y y y 层,至少需要选择几次光圈?

输入描述

输入共 2 2 2 行。
第一行为三个用空格隔开的正整数,表示 n n n(表示摩天大楼的层数) , x , y ,x,y ,x,y 1 ≤ n ≤ 200 1 \le n \le 200 1n200 1 ≤ x , y ≤ n 1 \le x, y \le n 1x,yn)。
第二行为 n n n 个用空格隔开的非负整数,表示 k i k_i ki 1 ≤ k i ≤ n 1 \le k_i \le n 1kin)。

输出描述

一行,即最少按键次数,若无法到达,则输出 − 1 -1 1

样例1

输入

5 1 5
3 3 1 2 5

输出

3

3.2 参考答案

#include <iostream>
#include <queue>
using namespace std;

struct Node
{
    int f;
    int step;
};

int n;
int s, e;
int k[205];
bool vis[205];
queue <Node> q;

void bfs()
{
    vis[s] = 1; // 起点标记
    q.push({s, 0}); // 起点入队
    
    while (!q.empty())
    {
        // 获取队首元素并出队
        auto [ff, fstep] = q.front();
        q.pop();
        
        // 判断是否为终点
        if (ff == e)
        {
            cout << fstep;
            return;
        }
        
        // 往上走
        int up = ff+k[ff];
        if (up<=n && vis[up]==0)
        {
            vis[up] = 1;
            q.push({up, fstep+1});
        }
        
        // 往下走
        int down = ff-k[ff];
        if (down>=1 && vis[down]==0)
        {
            vis[down] = 1;
            q.push({down, fstep+1});
        }
    }
    cout << -1;
    return;
}

int main()
{
    // 输入
    cin >> n >> s >> e;
    for (int i = 1; i <= n; i++)
        cin >> k[i];
    
    // bfs
    bfs();
	return 0;
}

三、DFS VS. BFS

BFS 适用于求源点和目标结点距离近的情况,例如求最少步数;
DFS 适用于求解一个任意符合方案中的一种或者遍历所有情况,例如能否到达、求所有路径数、全排列。

对于不同的题目,二者的优势也各不相同,在今天的三道例题中都推荐用时间复杂度更低的 BFS。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐