無權重最短路徑問題:廣度優先搜尋 & Java 實現
阿新 • • 發佈:2018-12-16
一、什麼是圖
通俗的說,圖就是由 頂點(Vertex) 和 邊(edge) 組成的。一般來說又分為 有向圖 和 無向圖,即頂點到頂點的邊是否是有方向區分的。
二、廣度優先搜尋
1. 概念
廣度優先搜尋(Breadth First Search)通常用於指出是否存在一條路徑由頂點 A 到達頂點 B,如果有,則找出頂點 A 到頂點 B 的最短路徑。 注意,這裡的最短路徑是沒有權重概念的,即可以認為任一邊的長度均為 1。
2. 步驟
首先,我們從頂點 A 出發,找出它的所有直接鄰居點(可以想象為一度人脈),檢查這裡面是否存在頂點 B。 然後,假設上述 “一度人脈” 中不存在頂點 B,這將鄰居點的鄰居點(可以想象為二度人脈)也加入到搜尋隊列中,並檢查其中是否存在頂點 B。 如上,我們依次擴大到 “三度人脈”、“四度人脈” … 直到找到頂點 B 為止。 如果找完了都找不到頂點 B 的話,則說明圖中不存在頂點 A 到頂點 B 的路徑。
注意: 找過的點務必不要再去檢查,否則可能導致無限迴圈!
3. 執行時間
廣度優先搜尋的執行時間為 O(頂點數 + 邊數),通常記為 O(V + E)。
三、例項分析
1. 如下給定一個圖,求 “頂點0” 到 “頂點8” 的最短路徑
2. 思路分析
- 首先我們可以想到,頂點是一類擁有共同特性的物件,因此我們可以建立一個 Vertex 類來表示圖中的頂點。它應該包含
自己的id
,有哪些鄰居
等基本特性。 - 其次為了程式碼實現和記錄搜尋路徑,Vertex 類還需要一個
是否被檢查過
和它的上一個頂點是誰
這兩個屬性。 - 然後,我們選擇 佇列 作為我們的資料結構,我們先將起始點的直接鄰居全部新增到佇列中去,依次從隊首取出元素比較,如果不是我們要找的點這將其鄰居(這裡就是類似第二度人脈了)新增到隊尾。
- 對於檢查過的點,我們選擇跳過,避免無限迴圈。
- 我們只需要重複上述過程,直到找到終點或是佇列為空時結束。
3. 程式碼實現
首先建立一個 Vertex 類,如下:
import java.util.LinkedList;
public class Vertex {
private int id; // 頂點的標識
private LinkedList<Vertex> neighbors; // 這個頂點的鄰居頂點
private boolean isSerched; // 這個頂點是否被搜尋過了
private Vertex predecessor; // 這個頂點的前驅,用來記錄路徑的
public Vertex(int id) {
this.id = id;
}
public void addNeighbor(Vertex... vertexes) {
this.neighbors = new LinkedList<>();
for (Vertex vertex : vertexes) {
this.neighbors.add(vertex);
}
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public LinkedList<Vertex> getNeighbors() {
return neighbors;
}
public void setNeighbors(LinkedList<Vertex> neighbors) {
this.neighbors = neighbors;
}
public boolean isSerched() {
return isSerched;
}
public void setSerched(boolean isSerched) {
this.isSerched = isSerched;
}
public Vertex getPredecessor() {
return predecessor;
}
public void setPredecessor(Vertex predecessor) {
this.predecessor = predecessor;
}
}
然後,建立一個場景類,並實現和呼叫 BFS 方法:
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
// init data (對照上面題目給的圖)
Vertex[] vertexes = new Vertex[9];
for (int i = 0; i < vertexes.length; i++) {
vertexes[i] = new Vertex(i);
}
vertexes[0].addNeighbor(vertexes[1], vertexes[3]);
vertexes[1].addNeighbor(vertexes[0], vertexes[2]);
vertexes[2].addNeighbor(vertexes[1], vertexes[4], vertexes[5]);
vertexes[3].addNeighbor(vertexes[0], vertexes[5], vertexes[6]);
vertexes[4].addNeighbor(vertexes[2], vertexes[8]);
vertexes[5].addNeighbor(vertexes[2], vertexes[3], vertexes[6], vertexes[8]);
vertexes[6].addNeighbor(vertexes[3], vertexes[5], vertexes[7]);
vertexes[7].addNeighbor(vertexes[6], vertexes[8]);
vertexes[8].addNeighbor(vertexes[4], vertexes[5], vertexes[7]);
// start search
Vertex start = vertexes[0];
Vertex end = vertexes[8];
boolean hasPath = bfs(start, end);
if (hasPath) {
// print path
Vertex predecessor = end;
do {
System.out.print(predecessor.getId() + " -> ");
predecessor = predecessor.getPredecessor();
} while (predecessor != null);
} else {
System.out.println("不存在該起點到該終點的路徑");
}
}
public static boolean bfs(Vertex start, Vertex end) {
LinkedList<Vertex> queue = new LinkedList<>();
queue.addLast(start); // 新增到隊尾
while (!queue.isEmpty()) {
Vertex vertex = queue.pollFirst(); // 從隊首取出一個元素
// 如果這個頂點是否已經檢索過了,則跳過
if (vertex.isSerched()) {
continue;
}
if (vertex == end) {
// 如果到了終點了就可以返回了
return true;
} else {
// 如果還不是終點,這把該頂點可以到達的鄰居全部新增到佇列末端
for (Vertex neighbor : vertex.getNeighbors()) {
if (neighbor.getPredecessor() == null && neighbor != start) {
neighbor.setPredecessor(vertex);
}
queue.addLast(neighbor);
}
}
vertex.setSerched(true);
}
return false;
}
}
執行結果:
8 -> 5 -> 3 -> 0 ->