BFS算法框架

BFS算法框架

  BFS的核心思想,就是把一些问题抽象成图,从一个节点开始,向四周扩散。一般来说,写BFS都是用[队列]这个数据结构,每次将一个节点周围的节点加入到队尾。

  BFS相对于DFS的最主要区别是:BFS找到的路径一定是最短的,但代价就是空间复杂度比DFS大很多。本文从两道经典的BFS题目来讲解。

先举例⼀下 BFS 出现的常⻅场景好吧, 问题的本质就是让你在⼀幅「图」 中找到从起点 start 到终点 target 的最近距离, 这个例⼦听起来很枯燥, 但是 BFS 算法问题其实都是在⼲这个事⼉, 把枯燥的本质搞清楚了, 再去欣赏各种问题的包装才能胸有成竹嘛。

  • ⽐如⾛迷宫, 有的格⼦是围墙不能⾛, 从起点到终点的最短距离是多少?如果这个迷宫带「传送门」 可以瞬间传送呢?

  • ⽐如说两个单词, 要求你通过某些替换, 把其中⼀个变成另⼀个, 每次只能替换⼀个字符, 最少要替换⼏次?

  • ⽐如说连连看游戏, 两个⽅块消除的条件不仅仅是图案相同, 还得保证两个⽅块之间的最短连线不能多于两个拐点。 你玩连连看, 点击两个坐标, 游戏是如何判断它俩的最短连线有⼏个拐点的?

  这些问题都没啥奇技淫巧, 本质上就是⼀幅「图」 , 让你从⼀个起点, ⾛到终点, 问最短路径。 这就是 BFS 的本质, 框架搞清楚了直接默写就好。

BFS框架:

int BFS(Node start, Node target){
    // 核心数据结果:队列
    queue<Node> q;
    // 标记集合,避免走回头路
    set<Node> visited;
    // 首先将起点加入队尾
    q.push(start);
    // 记录扩散的次数(其实就是要求的路径长度)
    int step = 0;
    while(!q.empty()){
        int sz = q.size();
        // 将当前队列中的所有节点向其”周围(图就是邻接点,二叉树就是子节点)“扩散
        for(int i = 0; i < sz; i++){
            // 获取队首元素并将其出列
            Node cur = q.front();
            q.pop();
            // 划重点,这里判断是否到达终点
            if(cur is target)
                return step;
            
            // 将当前节点cur的所有相邻节点加入队尾
            for(Node p : cur.adj()){
                if(p not in visited){
                    // 加入队尾
                    q.push(p);
                    // 标记
                    visited.add(p);
                }
            }
        }
        // 划重点,更新步数在这里
        step++;
    }
}
其中,cur.adj()表示节点cur的相邻节点(图即邻接点,树即子节点)。

经典例题一:二叉树的最小高度(leetcode.111)

  怎么套到 BFS 的框架⾥呢?⾸先明确⼀下起点 start 和终点 target 是什么,怎么判断到达了终点?显然起点就是 root 根节点, 终点就是最靠近根节点的那个「叶⼦节点」嘛, 叶⼦节点就是两个⼦节点都是 null 的节点:

if(cur->left == nullprt && cur->right == nullptr)
	//到达叶子结点,返回
  那么,按照BFS框架稍加改造:
#include <queue>

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int minDepth(TreeNode* root) {
        if(root == nullptr)
            return 0;
        queue<TreeNode*> q;
        // 将起点加入到队尾
        q.push(root);
        int depth = 1;
        while(!q.empty()){
            int sz = q.size();
            // 将当前队列中的所有节点向四周扩散
            for(int i = 0; i < sz; i++){
                // 每次取出队首节点
                TreeNode* cur = q.front();
                q.pop();
                // 这里判断是否到达终点
                if(cur->left == nullptr && cur->right == nullptr)
                    return depth;
                // 将cur的相邻节点加入队尾
                if(cur->left != nullptr)
                    q.push(cur->left);
                if(cur->right != nullptr)
                    q.push(cur->right);
            }
            // 划重点,更新步数在这里
            depth++;
        }
        return depth;
    }
};

经典例题二:打开转盘锁(leetcode.752)

直接上代码:

#include <string>
#include <queue>

class Solution {
public:
    // 将s[i]向上拨
    string plusOne(string s, int i){
        if(s[i] =='9')
            s[i] = '0';
        else 
            s[i] = s[i] + 1;
        return s;
    }

    // 将s[i]向下拨
    string minusOne(string s, int i){
        if(s[i] == '0')
            s[i] = '9';
        else
            s[i] = s[i] - 1;
        return s;
    }  

    int BFS(vector<string> &deadends, string target){
        queue<string> q;
        set<string> visited;
        set<string> deads;
        for(int i = 0; i < deadends.size(); i++)
            deads.insert(deadends[i]);
        int step = 0;
        q.push("0000");
        visited.insert("0000");
        while(!q.empty()){
            int sz = q.size();
            for(int i = 0; i < sz; i++){
                string cur = q.front();
                q.pop();
                // 跳过死锁
                if(deads.find(cur) != deads.end())
                    continue;
                if(cur == target){
                    printf("%d
", step);
                    return step;
                }
                // 将该结点的所有子节点加入队尾
                for(int i = 0; i < 4; i++){
                    string adjPlus = plusOne(cur, i);
                    string adjMinus = minusOne(cur, i);
                    if(visited.find(adjPlus) == visited.end()){
                        q.push(adjPlus);
                        visited.insert(adjPlus);
                    }
                    if(visited.find(adjMinus) == visited.end()){
                        q.push(adjMinus);
                        visited.insert(adjMinus);
                    }
                }
            }
            step++;
        }
        return -1;
    }

    int openLock(vector<string>& deadends, string target) {
        return BFS(deadends, target);
    }
};
原文地址:https://www.cnblogs.com/flyingrun/p/13567847.html