廣度優先搜索一層一層地進行遍歷,每層遍歷都以上一層遍歷的結果作爲起點,遍歷一個距離能訪問到的所有節點。需要注意的是,遍歷過的節點不能再次被遍歷。
每一層遍歷的節點都與根節點距離相同。設 di 表示第 i 個節點與根節點的距離,推導出一個結論:對於先遍歷的節點 i 與後遍歷的節點 j,有 di <= dj。利用這個結論,可以求解最短路徑等 最優解 問題:第一次遍歷到目的節點,其所經過的路徑爲最短路徑。應該注意的是,使用 BFS 只能求解無權圖的最短路徑,無權圖是指從一個節點到另一個節點的代價都記爲 1。
在程序實現 BFS 時需要考慮以下問題:
隊列:用來存儲每一輪遍歷得到的節點;
標記:對於遍歷過的節點,應該將它標記,防止重複遍歷。
1.計算在網格中從原點到特定點的最短路徑長度
/*
* 計算在網格中從原點到特定點的最短路徑長度
* 在一個 N × N 的方形網格中,每個單元格有兩種狀態:空(0)或者阻塞(1)。
* */
public int shortestPathBinaryMatrix(int[][] grid) {
int direction[][] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
int n = grid.length;
Queue<Pair<Integer, Integer>> queue = new LinkedList<>();
if (grid[0][0] == 0) {
if (n == 1) return 1;
queue.add(new Pair<>(0, 0));
}
int path = 0;
while (!queue.isEmpty()) {
int size = queue.size();
path++;
while (size-- != 0) {//把每層的節點遍歷完
Pair<Integer, Integer> curElem = queue.poll();
int x = curElem.getKey();
int y = curElem.getValue();
for (int i = 0; i < 8; i++) {
int curx = direction[i][0] + x, cury = direction[i][1] + y;
if (curx < 0 || curx >= n || cury < 0 || cury >= n || grid[curx][cury] == 1) continue;
if (curx == n - 1 && cury == n - 1) return path + 1;
queue.add(new Pair<>(curx, cury));
grid[curx][cury] = 1;//標記一下已走過
}
}
}
return -1;
}
2. 組成整數的最小平方數數量
/*
* 題目:完全平方數
* 描述:給定正整數 n,找到若干個完全平方數(比如 1, 4, 9, 16, ...)使得它們的和等於 n。
* 你需要讓組成和的完全平方數的個數最少。
* 分析:
* 可以將每個整數看成圖中的一個節點,如果兩個整數之差爲一個平方數,那麼這兩個整數所在的節點就有一條邊。
* 要求解最小的平方數數量,就是求解從節點 n 到節點 0 的最短路徑。
* 本題也可以用動態規劃求解
* */
public int numSquares(int n) {
if (n == 0) return 0;
List<Integer> squares = squareWithinN(n);
Queue<Integer> queue = new LinkedList<>();
queue.add(n);
int book[] = new int[n];
int path = 0;
while (!queue.isEmpty()) {
int size = queue.size();
path++;
while (size-- != 0) {
int cur = queue.poll();
for (Integer square : squares) {
int newNode = cur - square;
if (newNode < 0) break;
if (newNode == 0) return path;
if (book[newNode] == 1) continue;
queue.add(newNode);
book[newNode] = 1;
}
}
}
return -1;
}
//求n以內的平方數
public List<Integer> squareWithinN(int n) {
List<Integer> squares = new LinkedList<>();
for (int a = 1; a <= Math.sqrt(n); a++) {
squares.add(a * a);
}
return squares;
}
3. 最短單詞路徑
/*
* 題目:給定兩個單詞(beginWord 和 endWord)和一個字典,找到從 beginWord 到 endWord 的最短轉換序列的長度。
* 轉換需遵循如下規則:
* 每次轉換隻能改變一個字母
* 轉換過程中的中間單詞必須是字典中的單詞。
* 說明:
* 如果不存在這樣的轉換序列,返回 0。所有單詞具有相同的長度。所有單詞只由小寫字母組成。字典中不存在重複的單詞。你可以假設 beginWord 和 endWord 是非空的,且二者不相同。
*
* */
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
int n = wordList.size();
/*判斷endWord是否在字典中,並找到所在位置*/
int endWordIndex = -1;
for (int i = 0; i < n; i++) {
String word = wordList.get(i);
if (word.equals(endWord)) {
endWordIndex = i;
break;
}
}
if (endWordIndex == -1) return 0;
/*建立一個連通圖,graph[i]中存儲着第i個詞可轉化爲字典中其它哪些詞*/
wordList.add(beginWord);
n = wordList.size();
List<Integer> gragh[] = new List[n];
for (int i = 0; i < n; i++) {
List<Integer> curlist = new LinkedList<>();
String curWord = wordList.get(i);
for (int j = 0; j < i; j++) {
String otherWord = wordList.get(j);
if (isConnect(curWord, otherWord)) {
curlist.add(j);
gragh[j].add(i);
}
}
gragh[i] = curlist;
}
/*廣度優先搜索*/
return findLadderLength(gragh, wordList, endWordIndex);
}
public int findLadderLength(List<Integer>[] gragh, List<String> wordList, int endWordIndex) {
Queue<Integer> queue = new LinkedList<>();
int n = gragh.length;
boolean book[] = new boolean[n];
queue.add(n - 1);
int path = 1;
while (!queue.isEmpty()) {
int size = queue.size();
path++;
while (size-- != 0) {
int index = queue.poll();
List<Integer> linkit = gragh[index];
for (int curlinkIndex : linkit) {
if (curlinkIndex == endWordIndex) return path;
if (book[curlinkIndex]) continue;
queue.add(curlinkIndex);
book[curlinkIndex] = true;
}
}
}
return 0;
}
/*判斷s1和s2是否可以互相轉化*/
public boolean isConnect(String s1, String s2) {
int differ = 0;
for (int i = 0; i < s1.length(); i++) {
if (s1.charAt(i) != s2.charAt(i)) {
differ++;
}
}
return differ == 1;
}