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\n", 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);
    }
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM