在 深度優先搜索原理與實踐(java)文章介紹了深度優先搜索算法的理論和實踐。本文將介紹與其原理相似的廣度優先搜索算法。html
廣度優先搜索(也稱寬度優先搜索,縮寫 BFS,如下采用廣度來描述)是連通圖的一種遍歷算法這一算法也是不少重要的圖的算法的原型。Dijkstra 單源最短路徑算法和 Prim 最小生成樹算法都採用了和寬度優先搜索相似的思想。其別名又叫 BFS,屬於一種盲目搜尋法,目的是系統地展開並檢查圖中的全部節點,以找尋結果。換句話說,它並不考慮結果的可能位置,完全地搜索整張圖,直到找到結果爲止。基本過程,BFS 是從根節點開始,沿着樹(圖)的寬度遍歷樹(圖)的節點。若是全部節點均被訪問,則算法停止。通常用隊列數據結構來輔助實現 BFS 算法。java
對於下面的樹而言,BFS 方法首先從根節點1開始,其搜索節點順序是 1,2,3,4,5,6,7,8。算法
BFS 使用隊列 (queue) 來實施算法過程,隊列 (queue) 有着先進先出 FIFO (First Input First Output)的特性,數組
BFS 操做步驟以下:數據結構
把起始點放入 queue;框架
重複下述2步驟,直到 queue 爲空爲止:oop
下面結合一個圖 (graph) 的實例,說明 BFS 的工做過程和原理:
(1)將起始節點1放入隊列中,標記爲已遍歷:post
(2)從queue中取出隊列頭的節點1,找出與節點1鄰接的節點2,3,標記爲已遍歷,而後放入queue中。url
(3)從queue中取出隊列頭的節點2,找出與節點2鄰接的節點1,4,5,因爲節點1已遍歷,排除;標記4,5爲已遍歷,而後放入queue中。spa
(4)從queue中取出隊列頭的節點3,找出與節點3鄰接的節點1,6,7,因爲節點1已遍歷,排除;標記6,7爲已遍歷,而後放入queue中。
(5)從queue中取出隊列頭的節點4,找出與節點4鄰接的節點2,8,2屬於已遍歷點,排除;所以標記節點8爲已遍歷,而後放入queue中。
(6)從queue中取出隊列頭的節點5,找出與節點5鄰接的節點2,8,2,8均屬於已遍歷點,不做下一步操做。
(7)從queue中取出隊列頭的節點6,找出與節點6鄰接的節點3,8,9,3,8屬於已遍歷點,排除;所以標記節點9爲已遍歷,而後放入queue中。
(8)從queue中取出隊列頭的節點7,找出與節點7鄰接的節點3, 9,3,9屬於已遍歷點,不做下一步操做。
(9)從queue中取出隊列頭的節點8,找出與節點8鄰接的節點4,5,6,4,5,6屬於已遍歷點,不做下一步操做。
(10)從queue中取出隊列頭的節點9,找出與節點9鄰接的節點6,7,6,7屬於已遍歷點,不做下一步操做。
(11)queue 爲空,則遍歷結束
上面過程能夠用下面的代碼來表示:
private Map<String, Boolean> status = new HashMap<String, Boolean>(); private Queue<String> queue = new LinkedList<String>(); public void BFSSearch(String startPoint) { //1.把起始點放入queue; queue.add(startPoint); status.put(startPoint, false); bfsLoop(); } private void bfsLoop() { while(!queue.isEmpty()) { // 1) 從queue中取出隊列頭的點;更新狀態爲已經遍歷。 String currentQueueHeader = queue.poll(); //出隊 status.put(currentQueueHeader, true); System.out.println(currentQueueHeader); // 2) 找出與此點鄰接的且還沒有遍歷的點,進行標記,而後所有放入queue中。 List<String> neighborPoints = graph.get(currentQueueHeader); for (String poinit : neighborPoints) { if (!status.getOrDefault(poinit, false)) { //未被遍歷 if (queue.contains(poinit)) continue; queue.add(poinit); status.put(poinit, false); } } } }
其通用框架能夠歸納爲:
void bfs(起始點) { 將起始點放入隊列中; 標記起點訪問; while (若是隊列不爲空) { // 通常採用while ,固然也可使用遞歸 訪問隊列中隊首元素x; 刪除隊首元素; for (x 全部相鄰點) { if (該點未被訪問過且合法) { 將該點加入隊列末尾; if (該結點是目標狀態) { // 達到目標,提早結束終止循環 置 flag= true;
break; } } } } 隊列爲空,廣搜結束; }
下面來總結下寫出 BFS 算法規則:
經過這個 bfs 框架能夠看出該方法主要有如下幾個規律:
起點條件。從哪一個點開始訪問?是否每一個點都須要看成起點?第一次 bfs 調用相當重要。
循環參數。隊列不爲空。一個點的全部鄰接點都是在一個 while 裏面進行添加的,纔會進入
訪問標誌。爲了不重複訪問,須要對已經訪問過的節點加上標記,避免重複訪問。
講完了理論,下面開始進入實戰。
給你一個由 '1'(陸地)和 '0'(水)組成的的二維網格,請你計算網格中島嶼的數量。
島嶼老是被水包圍,而且每座島嶼只能由水平方向或豎直方向上相鄰的陸地鏈接造成。
此外,你能夠假設該網格的四條邊均被水包圍。
示例 1:
// 輸入: 11110 11010 11000 00000 // 輸出: 1
示例 2:
// 輸入: 11000 11000 00100 00011 // 輸出: 3
解釋: 每座島嶼只能由水平和/或豎直方向上相鄰的陸地鏈接而成。
題目解答以下:
class Solution { public int numIslands(char[][] grid) { if (grid == null || grid.length < 1 || grid[0].length<1) { return 0; } int num = 0; int nr = grid.length; int nc = grid[0].length;
// 每一個點均可能是起點 for (int x =0;x<nr;x++) { for (int y =0;y<nc;y++) { if (grid[x][y]=='1') { bfs(grid,x,y); num++; } } } return num; } // 對於 bfs 來講,只要隊列不爲空,就能夠一直走到頭, private void bfs(char[][] grid, int r, int c) { int nr = grid.length; int nc = grid[0].length;
// 隊列,用於保存鄰接點 Queue<Integer> neighbors = new LinkedList<>();
// 這裏能夠學下,對於二維能夠將座標轉化爲一個數字 neighbors.add(r * nc + c); while (!neighbors.isEmpty()) {
// 每次循環開始的時候,須要移出一個點 int id = neighbors.remove(); int row = id / nc; int col = id % nc;
// 四個鄰接點都是在一個while循環裏的 if (row - 1 >= 0 && grid[row-1][col] == '1') { neighbors.add((row-1) * nc + col); grid[row-1][col] = '0'; } if (row + 1 < nr && grid[row+1][col] == '1') { neighbors.add((row+1) * nc + col); grid[row+1][col] = '0'; } if (col - 1 >= 0 && grid[row][col-1] == '1') { neighbors.add(row * nc + col-1); grid[row][col-1] = '0'; } if (col + 1 < nc && grid[row][col+1] == '1') { neighbors.add(row * nc + col+1); grid[row][col+1] = '0'; } } } }
給定一個包含了一些 0 和 1 的非空二維數組 grid 。
一個 島嶼 是由一些相鄰的 1 (表明土地) 構成的組合,這裏的「相鄰」要求兩個 1 必須在水平或者豎直方向上相鄰。你能夠假設 grid 的四個邊緣都被 0(表明水)包圍着。
找到給定的二維數組中最大的島嶼面積。(若是沒有島嶼,則返回面積爲 0 。)
示例 1:
[[0,0,1,0,0,0,0,1,0,0,0,0,0], [0,0,0,0,0,0,0,1,1,1,0,0,0], [0,1,1,0,1,0,0,0,0,0,0,0,0], [0,1,0,0,1,1,0,0,1,0,1,0,0], [0,1,0,0,1,1,0,0,1,1,1,0,0], [0,0,0,0,0,0,0,0,0,0,1,0,0], [0,0,0,0,0,0,0,1,1,1,0,0,0], [0,0,0,0,0,0,0,1,1,0,0,0,0]]
對於上面這個給定矩陣應返回 6。注意答案不該該是 11 ,由於島嶼只能包含水平或垂直的四個方向的 1 。
示例 2:
[[0,0,0,0,0,0,0,0]]
對於上面這個給定的矩陣, 返回 0。
注意: 給定的矩陣grid 的長度和寬度都不超過 50。
這道題目和上面的很相似。題目解答以下:
class Solution { public int maxAreaOfIsland(int[][] grid) { if (grid == null || grid.length <1 || grid[0].length<1) { return 0; } int rx = grid.length; int cy = grid[0].length; int max = 0; for (int x =0; x< rx; x++) { for (int y= 0;y<cy; y++) { if (grid[x][y]==1) { int num = bfs(grid,x,y); max = Math.max(max, num); } } } return max; } private int bfs (int[][] grid, int x, int y){ int rx = grid.length; int cy = grid[0].length;
// 每次調用就是一個面積 int num = 1; grid[x][y] = 0; Queue<Integer> neQueue = new LinkedList<>();
// 這裏注意乘以的是col的長度 neQueue.add(x*cy + y);
// 隊列不爲空 while(!neQueue.isEmpty()) { int point = neQueue.remove(); int nx = point / cy; int ny = point % cy;
// 每個方向都要判斷邊界 if (nx - 1 >= 0 && grid[nx-1][ny] == 1) { neQueue.add((nx-1) * cy + ny); grid[nx-1][ny] = 0; num++; } if (nx + 1 < rx && grid[nx+1][ny] == 1) { neQueue.add((nx+1) * cy + ny); grid[nx+1][ny] = 0; num++; } if (ny - 1 >= 0 && grid[nx][ny-1] == 1) { neQueue.add(nx * cy + ny-1); grid[nx][ny-1] = 0; num++; } if (ny + 1 < cy && grid[nx][ny+1] == 1) { neQueue.add(nx * cy + ny+1); grid[nx][ny+1] = 0; num++; } } return num; } }