【力扣】島嶼數量(體會一下dfs和bfs思路的實質)

SaTsuki26681534發表於2024-03-20

題目描述

image
注意,需要求的是島嶼的數量,而不是島嶼的總面積,
這道題很考驗對dfs思路的理解,而不是簡單地套用模版。
可以用dfs和bfs兩種方法做。

深度優先搜尋版本

首先看程式碼:

class Solution {
private:
    int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四個方向
    void dfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
        for (int i = 0; i < 4; i++) {
            int nextx = x + dir[i][0];
            int nexty = y + dir[i][1];
            if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 越界了,直接跳過
            if (!visited[nextx][nexty] && grid[nextx][nexty] == '1') { // 沒有訪問過的 同時 是陸地的

                visited[nextx][nexty] = true; 
                dfs(grid, visited, nextx, nexty);
            } 
        }
    }
public:
    int numIslands(vector<vector<char>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<bool>> visited = vector<vector<bool>>(n, vector<bool>(m, false)); 

        int result = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (!visited[i][j] && grid[i][j] == '1') { 
                    visited[i][j] = true;
                    result++; // 遇到沒訪問過的陸地,+1
                    dfs(grid, visited, i, j); // 將與其連結的陸地都標記上 true
                }
            }
        }
        return result;
    }
};

這個程式碼的思路是:在numIslands函式里,透過迴圈每次找到一個沒有被訪問過的島嶼。在新島嶼上找到一個起始點後,用dfs對這個島嶼上所有的陸地點標記為visited,然後再回到numIslands函式里尋找下一個新島嶼(這裡就能看出dfs操作的作用)。
但是看程式碼後會發現,這個dfs和一般的dfs程式碼很不一樣,因為在開頭沒有終止條件。理解這裡就需要對dfs或者回溯演算法的執行過程細節熟悉。
在前面學習的回溯演算法裡,需要注意,return語句並不是為了“停止遍歷”,而是為了“在合適的位置停止遍歷”,其實就算不加return語句,許多回溯函式在遍歷所有可能結果後也會自動停止並回溯,因為執行到邊界後,程式不會進入for迴圈而是直接執行到函式結尾,這個過程其實和return ;的效果是一樣的,只有在:遍歷已經到達盡頭,但是仍然會進入下一層遞迴的情況下,才有可能造成死迴圈。所以控制遞迴呼叫的位置才是避免死迴圈的關鍵。
所以在這個程式碼裡,由於dfs函式只會遍歷陸地方塊,所以不需要return語句,它自然地就會遍歷一個島嶼的所有陸地然後結束。

廣度優先搜尋版本

根據上面的演算法,廣度優先版本的思路就很容易想到,也是在numIslands裡尋找新的島嶼,在bfs函式里遍歷整個島嶼的所有陸地。
程式碼如下:

class Solution {
private:
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四個方向
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
    queue<pair<int, int>> que;
    que.push({x, y});
    visited[x][y] = true; // 只要加入佇列,立刻標記
    while(!que.empty()) {
        pair<int ,int> cur = que.front(); que.pop();
        int curx = cur.first;
        int cury = cur.second;
        for (int i = 0; i < 4; i++) {
            int nextx = curx + dir[i][0];
            int nexty = cury + dir[i][1];
            if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 越界了,直接跳過
            if (!visited[nextx][nexty] && grid[nextx][nexty] == '1') {
                que.push({nextx, nexty});
                visited[nextx][nexty] = true; // 只要加入佇列立刻標記
            }
        }
    }
}
public:
    int numIslands(vector<vector<char>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<bool>> visited = vector<vector<bool>>(n, vector<bool>(m, false));

        int result = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (!visited[i][j] && grid[i][j] == '1') {
                    result++; // 遇到沒訪問過的陸地,+1
                    bfs(grid, visited, i, j); // 將與其連結的陸地都標記上 true
                }
            }
        }
        return result;
    }
};

可以看出,bfs中沒有用到遞迴,而是透過迴圈實現的。
透過que.push({nextx, nexty});語句,每次將後續結點入隊,直到que.empty(),即沒有任何後繼結點,也就是所有結點都被遍歷過,這時結束迴圈。

一種可能出現的錯誤:
image

相關文章