《搜索算法——DFS、BFS、回溯》
目錄
- 深搜
- 200. 島嶼數量
- 695. 島嶼的最大面積
- 130. 被圍繞的區(qū)域
- 547. 省份數量
- 417. 太平洋大西洋水流問題
- 回溯
- 廣搜
- 111. 二叉樹的最小深度
- 752. 打開轉盤鎖
- 深搜與廣搜結合
- 934. 最短的橋
深搜
深搜DFS,在搜索到一個新節(jié)點時,立即對該新節(jié)點進行遍歷,需要用到棧實現,或者使用與棧等價的遞歸實現。
深搜也可以用來檢測環(huán)路:記錄每個遍歷過的節(jié)點的父節(jié)點,若有一個節(jié)點被再次遍歷且父節(jié)點不同,則說明有環(huán)。
有時我們可能會需要對已經搜索過的節(jié)點進行標記,以防止在遍歷時重復搜索某個節(jié)點,這種做法叫做狀態(tài)記錄或者記憶化。
200. 島嶼數量
class Solution { public:void dfs(vector<vector<char>>& grid,int m,int n,int i,int j){//如果越界或者為海域,退出if(i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') return;//標記為海域grid[i][j] = '0';dfs(grid,m,n,i-1,j);dfs(grid,m,n,i+1,j);dfs(grid,m,n,i,j-1);dfs(grid,m,n,i,j+1);return ;}int numIslands(vector<vector<char>>& grid) {int m = grid.size();int n = grid[0].size();int time = 0;for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){if(grid[i][j] == '1'){dfs(grid,m,n,i,j);time++;}}}return time;} };695. 島嶼的最大面積
一般來說,深搜可以分為主函數和輔助函數。
主函數用于遍歷所有的所有搜索位置,判斷是否可以開始搜索,如果可以即用輔助函數進行搜索。
輔助函數負責深搜的遞歸調用或者(棧)實現。
輔助函數還可以寫成這樣,我更傾向于這種寫法:
//輔助函數 int dfs(vector<vector<int>>& grid, int r, int c) {//如果該點越界或者為海域,退出遞歸if(r < 0 || r >= grid.size() || c < 0 || c >= grid[0].size() || grid[r][c] == 0) return 0;//否則說明該點為島嶼,面積+1,同時清除記錄grid[r][c] = 0;//返回以該點搜索起點的面積結果return 1 + dfs(grid,r-1,c) + dfs(grid,r,c+1) + dfs(grid,r+1,c) + dfs(grid,r,c-1); }130. 被圍繞的區(qū)域
class Solution { public:void dfs(vector<vector<char>>& board,int m,int n,int i,int j){//遞歸退出條件if(i < 0 || i >= m || j < 0 || j >= n || board[i][j] == 'X' || board[i][j] == 'A') return;//否則進行標記,然后繼續(xù)深搜board[i][j] = 'A';dfs(board,m,n,i-1,j);dfs(board,m,n,i+1,j);dfs(board,m,n,i,j-1);dfs(board,m,n,i,j+1);return;}void solve(vector<vector<char>>& board) {int m = board.size();int n = board[0].size();//用dfs將邊界的相連的0全部置為Afor(int i = 0; i < m ; i++){dfs(board,m,n,i,0);dfs(board,m,n,i,n-1);}for(int j = 0; j < n ; j++){dfs(board,m,n,0,j);dfs(board,m,n,m-1,j);}//進行覆蓋操作,只有為A的不進行覆蓋for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){if(board[i][j] == 'A') board[i][j] = 'O';else board[i][j] = 'X';}}return;} };547. 省份數量
分析:求無向圖中的連通域個數,輸入矩陣即為無向圖的鄰接矩陣
深搜思路:遍歷所有城市,對于每個城市,如果該城市沒有被訪問過,則從該城市開始深度優(yōu)先搜索,通過矩陣得到與該城市直接相連的城市有哪些,這些城市與該城市屬于同一個連通分量,然后對這些城市繼續(xù)深搜,直到同一個連通分量的所有城市都被訪問到,就可以得到一個省份。
class Solution { public:void dfs(vector<vector<int>>& isConnected,int i,vector<bool>& visited) {for(int j = 0; j < isConnected.size(); j++){//繼續(xù)遍歷與頂點相鄰的頂點,使用visited數組防止重復訪問if(isConnected[i][j] == 1 && visited[j] == false){//標記當前訪問過的頂點visited[j] = true;dfs(isConnected,j,visited);}}return;}int findCircleNum(vector<vector<int>>& isConnected) {int n = isConnected.size();int count = 0;//標識頂點是否被訪問vector<bool> visited(n,false);for(int i = 0; i < n; i++){//如果當前頂點沒有被訪問,說明是一個新的連通域,遍歷這個連通域且計數+1if(!visited[i]){dfs(isConnected,i,visited);count++;}}return count;} };417. 太平洋大西洋水流問題
1、邊界可以去往旁邊的一個海洋
也就是說邊界與海洋是連通的,所以需要從外往內擴散,獲得與邊界連通的區(qū)域(也就是高度相等或者高度比當前區(qū)域高的位置)
2、為了防止重復遍歷,添加visited數組
3、將連通的部分放到兩個子集中,一個是和太平洋連通的區(qū)域,一個是和大西洋連通的區(qū)域。然后對兩個求交集即可。
回溯
回溯之前,已經做過一些題目了,都是一些經典題目,直接放在這兒。
算法題復習(回溯)
廣搜
模板如下:
//計算從起點start到終點target的最短距離 int BFS(Node start,Node target) {queue<Node> q; //核心數據結構Set<Node> visited; //避免走回頭路q.push(start); //將起點加入隊列visited.insert(start);int steps = 0; //記錄擴散步數while(!q.empty()){//更新步數step++;int sz = q.size();//將當前隊列中的所有節(jié)點向四周擴散for(int i = 0; i < sz; i++){Node cur = q.front();q.pop();//判斷是否到達終點if(cur == target) return step;//將cur相鄰的節(jié)點加入隊列for(Node x: cur.adj()){//如果沒有遍歷過if(visited.find(x) == visited.end()){q.push(x);visited.insert(x);}}}} }cur.adj表示與cur相鄰的節(jié)點。visited主要是防止走回頭路,二叉樹結構沒有子節(jié)點到父節(jié)點的指針,所以不會走回頭路不需要visited。
不同于深度優(yōu)先搜索,廣搜是一層一層遍歷的,因此需要用到先入先出的隊列,常常用來處理最短路徑問題。
深搜和廣搜都可以處理可達性問題,即從一個節(jié)點開始是否能到達另一個節(jié)點。因為深搜可以利用遞歸快速實現,所以很多人習慣使用深搜。但是實際工程中,很少使用遞歸,容易產生棧溢出。
111. 二叉樹的最小深度
start:root根節(jié)點
target:最靠近根節(jié)點的的葉子節(jié)點
752. 打開轉盤鎖
計算從初始狀態(tài)“0000”撥出目標狀態(tài)的最少次數,如果永遠無法撥出,則返回-1.
列表 deadends 包含了一組死亡數字,一旦撥輪的數字和列表里的任何一個元素相同,這個鎖將會被永久鎖定,無法再被旋轉。
從0000開始,按照廣度的思想,轉一次有8種可能:
1000、9000、0100、0900、…
可以抽象成一幅圖,每個節(jié)點有8個相鄰的節(jié)點,求最短距離,此時可以用上BFS。
使用下面的初步代碼,是按照BFS來尋找的最小密碼組合,不過顯然是有問題的。
存在的問題如下:
1、會走回頭路,比如"0000"撥到"1000",但是等從隊列中拿出"1000",還會撥出"0000"
2、沒有對deadends進行處理,按道理來說,這些死亡密碼不能出現,遇到這些密碼的時候需要跳過。
修改后的代碼如下:
深搜與廣搜結合
934. 最短的橋
class Solution { public:vector<int> direction{-1,0,1,0,-1};void dfs(vector<vector<int>>& A,queue<pair<int,int>>& points,int m,int n,int i,int j){//如果越界了或者遍歷過了,不繼續(xù)向深處遍歷if(i < 0 || j < 0 || i >= m || j >= n || A[i][j] == 2) return;//如果是海域,插入隊列中if(A[i][j] == 0){points.push({i,j});return ;}A[i][j] = 2;//繼續(xù)深度遍歷dfs(A,points,m,n,i-1,j);dfs(A,points,m,n,i+1,j);dfs(A,points,m,n,i,j-1);dfs(A,points,m,n,i,j+1);return;} int shortestBridge(vector<vector<int>>& A) {int m = A.size();int n = A[0].size();//深度遍歷,尋找第一個島嶼int flag = 0;queue<pair<int,int>> points;for(int i = 0; i < m; i++){if(flag == 1) break;for(int j = 0; j < n; j++){//深度遍歷完一個島嶼后,立即退出if(A[i][j] == 1){flag = 1;dfs(A,points,m,n,i,j);break;}}}//開始廣度遍歷int level = 0;while(!points.empty()){int N = points.size();level++;while(N--){auto [r,c] = points.front();points.pop();//向四周廣度遍歷for(int k = 0; k < 4; k++){int x = r + direction[k];int y = c + direction[k+1];if(x >= 0 && x < m && y >= 0 && y < n){//如果還是第1個島嶼if(A[x][y] == 2) continue;if(A[x][y] == 1) return level;//如果還是0,則是繼續(xù)入隊列points.push({x,y});//將該坐標并入第1個島嶼A[x][y] = 2;}}}}return level;} };總結
以上是生活随笔為你收集整理的《搜索算法——DFS、BFS、回溯》的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《DBNotes:Join算法的前世今生
- 下一篇: 《LeetcodeHot100非困难题补