一文弄懂BFS【广度优先搜索(Breadth-First Search)】

在这里插入图片描述

BFS,全名为广度优先搜索(Breadth-First Search),是一种用于图或树的遍历或搜索的算法。它的主要思想是由节点自身开始向它的邻居节点新进展开搜索,因此也常被形象地称为“层序遍历”。

BFS 基本思想

BFS 工作原理是,从开始节点开始,在访问节点的邻居节点之前,我们先访问其它节点。换句话说,我们旧基于当前层次来遍历节点,然后移至下一层再遍历节点。BFS 是以一种队列(Queue)结构的方式运行的,首先我们有一个包含开始节点的队列,然后:

- 我们从队列的前端取出一个节点
- 为了防止回溯和重复访问,我们会标记取出的节点为已访问
- 针对取出的节点,把尚未访问过的邻居节点全部加入队列
- 我们重复以上步骤,直至队列为空

通过以上步骤,你将会发现,你在访问节点的时候,首先访问的是距离开始节点最近的节点(层次最低的节点),然后层次逐渐提升,这就是 BFS 的特性。

BFS 伪代码模板

BFS 主要应用于树和图结构的遍历,因此伪代码也大致分为对应的两类(以下都是基于未标记图进行的操作):

  • 树的广度优先搜索
function BFS(root) {
    initialize queue;
    queue.push(root);
    while(!queue.empty()) {
       node = queue.front();
       queue.pop();
       process(node); //处理节点
       nodes = generate_related_nodes(node); //获取与节点相关的未访问过的子节点
       queue.push(nodes);
    }
}
  • 图的广度优先搜索
function BFS(graph, start) {
    initialize queue and visited set;
    queue.push(start);
    visited.insert(start);
    while(!queue.empty()) {
        node = queue.front();
        queue.pop();
        process(node); //处理节点
        nodes = generate_related_nodes(node); //获取与节点相关的邻居节点
        for(node in nodes) {
            if(node not in visited) { //如果邻居节点尚未访问过
              queue.push(node);
              visited.add(node);
            }
        }
    }
}

接下来举出六个BFS经典问题,详细介绍和解题思路:

  1. 二叉树的层次遍历

在这个问题中,我们需要通过 BFS 遍历二叉树的每一层,以二维数组的形式返回结果。

vector<vector<int>> levelOrder(TreeNode* root) {
  if (!root) return {};
  vector<vector<int>> result;
  queue<TreeNode*> q;
  q.push(root);
  while (!q.empty()) {
    vector<int> one_level;
    for (int i = q.size(); i > 0; i--) {
      TreeNode* node = q.front();
      q.pop();
      one_level.push_back(node->val);
      if (node->left) q.push(node->left);
      if (node->right) q.push(node->right);
    }
    result.push_back(one_level);
  }
  return result;
}
  1. 岛屿的个数

给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。

解题思路主要是遍历二维数组,当遇到 ‘1’ 时,通过 BFS 搜索并 ‘感染’ 周围的 ‘1’,最后计算 ‘1’ 的个数。

int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

void bfs(vector<vector<char>>& grid, int x, int y, int row, int col) {
  queue<pair<int, int>> q;
  q.push({x, y});
  grid[x][y] = '0';  // 把 '1' 感染为 '0'
  while (!q.empty()) {
    auto [r, c] = q.front();
    q.pop();
    for (int i = 0; i < 4; ++i) {
      int nx = r + dx[i], ny = c + dy[i];
      if (nx >= 0 && nx < row && ny >= 0 && ny < col && grid[nx][ny] == '1') {
        q.push({nx, ny});
        grid[nx][ny] = '0';
      }
    }
  }
}

int numIslands(vector<vector<char>>& grid) {
  int row = grid.size();
  if (row == 0) {
    return 0;
  }
  int col = grid[0].size();
  int res = 0;
  for (int i = 0; i < row; ++i) {
    for (int j = 0; j < col; ++j) {
      if (grid[i][j] == '1') {
        bfs(grid, i, j, row, col);
        ++res;  // 计算岛屿数量
      }
    }
  }
  return res;
}
  1. 打开密码锁的最少步数

给你一个初始为 ‘0000’ 的四位密码,你可以每次对密码的任意一位做上下旋转:旋转一次可以将该位的数字增加 1 或减少 1 。求出最少的旋转次数,使得密码等于 target 。

int openLock(vector<string>& deadends, string target) {
  unordered_set<string> dead(deadends.begin(), deadends.end());
  if (dead.count("0000")) return -1;
  if (target == "0000") return 0;

  unordered_map<string, int> dist{{"0000", 0}};
  queue<string> q;
  q.push("0000");

  while (!q.empty()) {
    string t = q.front(); q.pop();
    for (int i = 0; i < 4; i++) {
      for (int j = -1; j <= 1; j += 2) {
        string str = t;
        str[i] = (str[i] - '0' + j + 10) % 10 + '0';
        if (str == target) return dist[t] + 1;
        if (!dead.count(str) && !dist.count(str)) {
          dist[str] = dist[t] + 1;
          q.push(str);
        }
      }
    }
  }
  return -1;
}
  1. 图中两点间最短路径长度

给定一个无向图,求从起点 s 到终点 t,最短路径长度是多少。

vector<unordered_set<int>> g; // 图
unordered_map<int, int> dist; // 从起点到每个点的距离

int bfs(int s, int t) {
  queue<int> q;
  q.push(s);
  dist[s] = 0;
  while (!q.empty()) {
    int x = q.front(); q.pop();
    for (int y : g[x]) {
      if (!dist.count(y)) {
        dist[y] = dist[x] + 1;
        q.push(y);
      }
    }
  }
  return dist[t];
}
  1. 找到最近的医院

给定一个二维的 0-1 矩阵,1 表示医院,0 表示型房屋。求每个房屋距离最近医院的距离。

vector<vector<int>> dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};

vector<vector<int>> findNearestHospital(vector<vector<int>>& grid) {
  int m = grid.size(), n = grid[0].size();
  vector<vector<int>> dist(m, vector<int>(n, INT_MAX));
  queue<pair<int, int>> q;// BFS 队列

  // 先把所有的医院节点放进队列,然后开始 BFS
  for(int i = 0; i < m; i++) {
      for(int j = 0; j < n; j++) {
        if(grid[i][j] == 1) {
            dist[i][j] = 0;
            q.push({i, j});
        }
      }
  }

  while(!q.empty()) {
    auto cur = q.front(); q.pop();
    for(auto dir : dirs) {
      int nx = cur.first + dir[0], ny = cur.second + dir[1];
      if(nx >= 0 && ny >= 0 && nx < m && ny < n) {
          if(dist[nx][ny] > dist[cur.first][cur.second] + 1) {
              dist[nx][ny] = dist[cur.first][cur.second] + 1;
              q.push({nx, ny});
          }
      }
    }
  }
  return dist;
}
  1. 最小基因变异

给定两个基因序列 start 和 end,一个基因库表 bank,求出把 start 变到 end 所需要的最小变异次数。一次基因变化代表改变一个字母。

int minMutation(string start, string end, vector<string>& bank) {
  unordered_set<string> dict(bank.begin(), bank.end());
  if (!dict.count(end)) return -1;
  unordered_map<string, int> dist{{start, 0}};
  queue<string> q;
  q.push(start);
  while (!q.empty()) {
    string gene = q.front(); q.pop();
    if (gene == end) return dist[gene];
    for (int i = 0; i < gene.size(); i++) {
      string newGene = gene;
      for (char c : "ACGT") {
        newGene[i] = c;
        if (dict.count(newGene) && !dist.count(newGene)) {
          dist[newGene] = dist[gene] + 1;
          q.push(newGene);
        }
      }
    }
  }
  return -1;
}

如果你想更深入地了解人工智能的其他方面,比如机器学习、深度学习、自然语言处理等等,也可以点击这个链接,我按照如下图所示的学习路线为大家整理了100多G的学习资源,基本涵盖了人工智能学习的所有内容,包括了目前人工智能领域最新顶会论文合集和丰富详细的项目实战资料,可以帮助你入门和进阶。

链接: 人工智能交流群【最新顶会与项目实战】(点击跳转)

在这里插入图片描述

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RRRRRoyal

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值