一道題看透DFS和BFS

2021-03-02 數據結構與算法

作者丨小小算法

來源丨小小算法(xiaoxiaosuanfa)

一道題看透DFS和BFS你知道 DFS 和 BFS 是哪幾個英文單詞的縮寫嗎?(手動滑稽)。DFS: Depth First Search (深度優先搜索)BFS: Breadth First Search (廣度優先搜索)DFS 和 BFS 都是常用來遍歷搜索樹或圖的算法。二叉樹中的前序、中序和後序遍歷都屬於DFS,層次遍歷屬於BFS。DFS常用遞歸和棧來實現,BFS常用隊列來實現。今天我們通過leetcode 1254. 統計封閉島嶼的數目(https://leetcode-cn.com/problems/number-of-closed-islands/) 來練習DFS和BFS.題目描述有一個二維矩陣 grid ,每個位置要麼是陸地(記號為 0 )要麼是水域(記號為 1 )。我們從一塊陸地出發,每次可以往上下左右 4 個方向相鄰區域走,能走到的所有陸地區域,我們將其稱為一座「島嶼」。如果一座島嶼 完全 由水域包圍,即陸地邊緣上下左右所有相鄰區域都是水域,那麼我們將其稱為 「封閉島嶼」。輸入:grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]]解釋:灰色區域的島嶼是封閉島嶼,因為這座島嶼完全被水域包圍(即被 1 區域包圍)。解題思路題目要我們求出封閉島嶼的個數,那我們可以先遍歷矩陣中的每一個位置,如果當前位置為0(陸地),我們就需要判斷該位置所在的島嶼是否為封閉島嶼。那如何找到該位置所在的島嶼呢?當然就是從該位置出發向四個方向遍歷了。這屬於遍歷一塊區域內的所有內容,使用DFS 和 BFS 都可以。

int closedIsland(vector<vector<int>>& grid) {
int ret = 0;
for (int i = 0; i < grid.size(); i++) {
for (int j = 0; j < grid[0].size(); j++) {
if (grid[i][j] == 0) {
ret += dfs(grid, i, j);
//或者使用BFS
//ret += bfs(grid, i, j);
}
}
}
return ret;
}

DFS從當前陸地開始出發,如果能走出邊界就說明該陸地所在島嶼不是封閉島嶼,返回封閉島嶼個數0

if (r < 0 || r >= grid.size() || r < 0 || c >= grid[0].size()) {
return 0;
}

如果碰到水域(值為1的點)就返回封閉島嶼個數1,表示該島嶼可能就是一個封閉島嶼

if (grid[r][c] == 1) {
return 1;
}

如果碰到陸地(值為0的點)就繼續向該陸地的四個方向遍歷,同時將該陸地標記為1,表示這個位置已經遍歷過了。DFS代碼如下

int dfs(vector<vector<int>>& grid, int r, int c) {
if (r < 0 || r >= grid.size() || r < 0 || c >= grid[0].size()) {
return 0;
}
if (grid[r][c] == 1) {
return 1;
}
grid[r][c] = 1;
int vr[] = {0, 1, 0, -1};
int vc[] = {1, 0, -1, 0};
int ret = 1;
for (int i = 0; i < 4; i++) {
ret = min(ret, dfs(grid, r+vr[i], c+vc[i]));
}
return ret;
}

這裡巧妙地使用了兩個向量vr和vc來模擬四個方向,最後只需要一個在for循環就可以向四個方向DFS了。當然手動寫四個dfs也是可以的,只是這樣顯得不夠優雅,如果加上斜對角線有八個方向可以走呢,寫八遍dfs就顯得很累贅了。

dfs(grid, r, c+1);
dfs(grid, r+1, c);
dfs(grid, r-1, c);
dfs(grid, r, c-1);

為什麼要使用min()呢?因為只要有一個返回0,就說明有一個方向走出了邊界,那該位置所在島嶼就不是封閉島嶼,即使其它三個方向都返回1.聰明的你可能會問,既然有一個方向返回了0,那為什麼還要繼續走把其它方向走完呢?也就是說為什麼不在for循環中加個判斷呢

for (int i = 0; i < 4; i++) {
ret = min(ret, dfs(grid, r+vr[i], c+vc[i]));
if (ret == 0) {
return 0;
}
}

其實這樣是會出錯的,因為當我們選擇了一個陸地,我們就必須要把該陸地所在的島嶼上的所有位置遍歷完,如果你沒遍歷完就結束,就相當於把一塊島嶼分成了幾個幾塊島嶼,如果原先的這塊島嶼是封閉島嶼,你這樣遍歷就可能會得到幾塊封閉島嶼。得到的結果是大於答案的。BFSBFS是用隊列來實現的,我們先將當前陸地的位置加入到隊列中,然後取出當前位置,並將它標記為1,表示它已經遍歷過了,最後將它四個方向也為陸地的位置加入到隊列中,一直循環,直到隊列為空。在循環的過程中我們需要判斷是否走出了邊界,如果走出了邊界就說明該位置所在的島嶼不是封閉島嶼。BFS代碼如下

int bfs(vector<vector<int>>& grid, int r, int c) {
int ret = 1;
queue<vector<int>> q;
q.push({r, c});
while (!q.empty()) {
vector<int> pos(q.front());
q.pop();
grid[pos[0]][pos[1]] = 1;
int vr[] = {0, 1, 0, -1};
int vc[] = {1, 0, -1, 0};
for (int i = 0; i < 4; i++) {
int curr = pos[0] + vr[i];
int curc = pos[1] + vc[i];
if (curr < 0 || curr >= grid.size() || curc < 0 || curc >= grid[0].size()) {
    ret = 0;
    continue;
}
if (grid[curr][curc] == 0) {
q.push({curr, curc});
}
}
}
return ret;
}

另一種思路其實我們還可以直接從邊界的陸地開始DFS或BFS遍歷,只要邊界陸地能遍歷到的地方就不是封閉島嶼,同時我們也要將遍歷過得點置為1,表示該位置已經遍歷過。最後,裡面為0的位置都是屬於封閉島嶼的陸地了。考慮到篇幅就不貼代碼了,感興的可以去leetcode 1254 題,看小小算法的題解哦,裡面有全部代碼。(https://leetcode-cn.com/problems/number-of-closed-islands/solution/yi-ti-kan-tou-dfs-he-dfs-by-xiao-xiao-suan-fa/)總結這就是這篇文章的全部內容了,希望大家看完之後有所收穫,然後找個時間去leetcode上同時使用DFS和BFS把這道題AC了。這道題雖然不怎麼難,但只要真正理解了這道題,其它DFS和BFS題也就大同小異了。

相關焦點

  • 圖文詳解 DFS 和 BFS
    來源:碼海作者:碼海深度優先遍歷(Depth First Search, 簡稱 DFS) 與廣度優先遍歷(Breath First Search)是圖論中兩種非常重要的算法,生產上廣泛用於拓撲排序,尋路(走迷宮),搜尋引擎,爬蟲等,也頻繁出現在 leetcode,高頻面試題中。
  • 圖文詳解 DFS 算法 和 BFS 算法
    前言 深度優先遍歷(Depth First Search, 簡稱 DFS) 與廣度優先遍歷(Breath First Search)是圖論中兩種非常重要的算法,生產上廣泛用於拓撲排序,尋路(走迷宮),搜尋引擎,爬蟲等,也頻繁出現在 leetcode,高頻面試題中。
  • BFS+DFS終結遊戲題目
    示例:輸入:board = [[1,2,3],[4,0,5]]輸出:1解釋:交換 0 和 5 ,1 步完成解決這道題比較關鍵的幾點:0與周圍位置交換後得到一個新的二維矩陣下面開始正文,本題的結構這樣,以[[1,2,3],[4,0,5]]為例:        123405   123045 123450 103425 ...
  • 從頭開始複習算法之讓你徹底搞清楚BFS和DFS
    關於BFS和DFS,這是我們在面試的時候經常會遇到的兩個基礎算法,為什麼說基礎呢?因為它理解了之後才10行左右的代碼,你說基礎不基礎?一、 BFS BFS,全稱:Breadth First Search。中文翻譯為廣度優先搜索或者是寬度優先搜索,具體是怎麼回事兒呢?
  • 萬字詳述 | 全開源:python寫小遊戲+AI強化學習與傳統DFS/BFS控制分別實現
    我的源碼見:https://github.com/PiperLiu/Amazing-Brick-DFS-and-DRL/blob/master/dfs_play.pyfinal_s_a_list = []def dfs_forward(root_state, show=False): # 最後需要返回的就是這個(狀態、動作)
  • 聽說這題套個BFS模板就可以 AC?
    ., C_k 組成:相鄰單元格 C*i 和 C*{i+1} 在八個方向之一上連通(此時,C*i 和 C*{i+1} 不同且共享邊或角)C_1 位於 (0, 0)(即,值為 grid[0][0])C_k 位於 (N-1, N-1)(即,值為 grid[N-1][n-1])如果 C_i 位於 (r, c),則 grid[r][c] 為空(即,grid[r][c]
  • LeetCode 例題精講 | 13 BFS 的使用場景:層序遍歷、最短路徑問題
    DFS 遍歷使用遞歸:void dfs(TreeNode root) {    if (root == null) {        return;    }    dfs(root.left);    dfs
  • 有關樹遍歷的javascript實現【前端-總結-leetcode算法題】
    在這裡插入圖片描述前言二月的第一天,總結一下近段時間刷的有關樹遍歷的leetcode算法題,希望寫完這篇文章的我和看完文章的你都有些收穫吧。全篇主要講的是有關樹的遍歷,使用前端javascript語言實現。當然有關樹的操作還有很多需要的深入理解和總結的。今天就暫時先把樹遍歷理解全吧。
  • 一道 LeetCode 題帶我們深入二進位表示、搜索策略和剪枝
    回到問題我相信關於二進位表示法的使用和原理,大家應該都了解了,但是本題當中元素是可以多次出現的,二進位表示法看起來並不頂用,我們怎麼解決這個問題呢?難道這麼大的篇幅就白寫了?當然不會白寫,針對這種情況也有辦法。其實很簡單,因為題目當中規定所有的元素都是正數,那麼對於每一個元素而言,我們最多取的數量是有限的。
  • 幾乎刷完了力扣所有的樹題,我發現了這些東西...
    這其實就屬於構造二叉樹的內容,這個類型目前力扣就這一道題。這道題如果你徹底理解 BFS,那麼就難不倒你。還有一種是給你描述一種場景,讓你構造一個符合條件的二叉樹。這種題和上面的沒啥區別,套路簡直不要太像,比如 654. 最大二叉樹,我就不多說了,大家通過這道題練習一下就知道了。
  • 【每日一題】(33題)面試官:你對圖論了解多少?(三)
    (三)關注「松寶寫代碼」,精選好文,每日一題前兩期的圖論已經介紹完了,今天我們介紹第三期。今天帶來的是Overstars的圖論總結,ACM打比賽,他的方向是思維數論圖論。下文是他和他隊友四年刷圖論的總結。其實本身就是個筆記,以後方便打板子的。
  • 507,BFS和DFS解二叉樹的層序遍歷 II
    (即按從葉子節點所在層到根節點所在的層,逐層從左向右遍歷)例如:給定二叉樹 [3,9,20,null,null,15,7],返回其自底向上的層序遍歷為:這題類似於二叉樹的BFS列印,就是一層一層的列印,一般情況下對於二叉樹我們都是從上往下列印,但這題是從下往上列印。
  • 算法小專欄開篇:DFS之Fibonaci數列
    從今天開始會給大家介紹一些我在學習軟體算法時期的一些題目或者筆記總結,本身我也不是計軟班科的,也不是打ACM的,所以也不要指望我來給你講解Leetcode或者Linkcode了,遇到Hard題在下實在也是愛莫能助
  • Java數據結構和算法(三)圖的深度優先遍歷算法,附代碼(DFS)
    所謂圖的遍歷,即是對結點的訪問,一個圖會有很多的結點,那麼如何遍歷這些結點,需要特定的策略,一般有兩種訪問策略深度遍歷優先和廣度遍歷優先。
  • [每日一題]LCA問題解法匯總
    分別求出 left 分支 的LCA 和 right 分支的LCA2. 然後比較 LCA(left) 和 LCA(right) 的值    a. 如果 LCA(left) 為空,那麼返回 LCA(right)    b. 如果 LCA(right) 為空, 那麼返回 LCA(left)    c.
  • 香港dfs環球免稅店攻略
    香港dfs環球免稅店位於香港九龍尖沙咀地段的兩間DFS Galleria,即是該集團極具代表性的大型綜合免稅商場。徜徉於店內Bally、Burberry、Cartier、Celine、Christian Dior、Fendi、Prada、Omega、Longine等品牌招搖的最新款包袋、皮具、時裝、飾品、化妝品、香水、手錶、珠寶等關於DFS開卡,只要按照淘寶賣家的要求準確提供相關信息和名片,然後大大方方去開卡就沒有問題。有個建議就是,大家印名片時不要用那種很薄的紙,讓人一看就是假的。
  • ​LeetCode刷題實戰91:解碼方法
    所以,為了提高大家的算法能力,這個公眾號後續每天帶大家做一道算法題,題目就從LeetCode上面選 !,比如12可以理解成12這個字符也就是L,也可以理解成1和2兩個字符拼接而成。這道題看起來沒有頭緒,但是當我們仔細分析一下其中的情況,其實並不複雜。首先對於字符串當中的每一位來說,只有0到9這10種可能性。我們逐一來考慮,首先如果是0,由於我們的A映射的是1,所以0隻能和前面一個數字湊成10或者是20,如果前一位不是1或者是2,那麼說明這個字符串是非法的,也就是沒有辦法還原。如果某一位是1-9當中的數字,它都可以單獨成為一個字母。
  • ​LeetCode刷題實戰93:復原IP位址
    所以,為了提高大家的算法能力,這個公眾號後續每天帶大家做一道算法題,題目就從LeetCode上面選 !例如:"0.1.2.201" 和 "192.168.1.1" 是 有效的 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 無效的 IP 地址。