資料結構與演算法之 佇列和廣度優先搜尋(BFS)
阿新 • • 發佈:2018-12-12
佇列和 BFS
廣度優先搜尋(BFS)的一個常見應用是找出從根結點到目標結點的最短路徑。在本文中,我們提供了一個示例來解釋在 BFS 演算法中是如何逐步應用佇列的。
洞悉
1. 結點的處理順序是什麼?
在第一輪中,我們處理根結點。在第二輪中,我們處理根結點旁邊的結點;在第三輪中,我們處理距根結點兩步的結點;等等等等。
與樹的層序遍歷類似,越是接近根結點的結點將越早地遍歷
。
如果在第 k 輪中將結點 X
新增到佇列中,則根結點與 X
之間的最短路徑的長度恰好是 k
。也就是說,第一次找到目標結點時,你已經處於最短路徑中。
2. 佇列的入隊和出隊順序是什麼?
如上面的動畫所示,我們首先將根結點排入佇列。然後在每一輪中,我們逐個處理已經在佇列中的結點,並將所有鄰居新增到佇列中。值得注意的是,新新增的節點不會
結點的處理順序與它們新增
到佇列的順序是完全相同的順序
,即先進先出(FIFO)。這就是我們在 BFS 中使用佇列的原因。
廣度優先搜尋 - 模板
之前,我們已經介紹了使用 BFS 的兩個主要方案:遍歷
或找出最短路徑
。通常,這發生在樹或圖中。正如我們在章節描述中提到的,BFS 也可以用於更抽象的場景中。
在本文中,我們將為你提供一個模板。然後,我們在本文後提供一些習題供你練習。
在特定問題中執行 BFS 之前確定結點和邊緣非常重要。通常,結點將是實際結點或是狀態,而邊緣將是實際邊緣或可能的轉換。
模板 I
在這裡,我們為你提供虛擬碼作為模板:
/** * Return the length of the shortest path between root and target node. */ int BFS(Node root, Node target) { Queue<Node> queue; // store all nodes which are waiting to be processed int step = 0; // number of steps neeeded from root to current node // initialize add root to queue; // BFS while (queue is not empty) { step = step + 1; // iterate the nodes which are already in the queue int size = queue.size(); for (int i = 0; i < size; ++i) { Node cur = the first node in queue; return step if cur is target; for (Node next : the neighbors of cur) { add next to queue; } remove the first node from queue; } } return -1; // there is no path from root to target }
- 如程式碼所示,在每一輪中,佇列中的結點是
等待處理的結點
。 - 在每個更外一層的
while
迴圈之後,我們距離根結點更遠一步
。變數step
指示從根結點到我們正在訪問的當前結點的距離。
模板 II
有時,確保我們永遠不會訪問一個結點兩次
很重要。否則,我們可能陷入無限迴圈。如果是這樣,我們可以在上面的程式碼中新增一個雜湊集來解決這個問題。這是修改後的虛擬碼:
/**
* Return the length of the shortest path between root and target node.
*/
int BFS(Node root, Node target) {
Queue<Node> queue; // store all nodes which are waiting to be processed
Set<Node> used; // store all the used nodes
int step = 0; // number of steps neeeded from root to current node
// initialize
add root to queue;
add root to used;
// BFS
while (queue is not empty) {
step = step + 1;
// iterate the nodes which are already in the queue
int size = queue.size();
for (int i = 0; i < size; ++i) {
Node cur = the first node in queue;
return step if cur is target;
for (Node next : the neighbors of cur) {
if (next is not in used) {
add next to queue;
add next to used;
}
}
remove the first node from queue;
}
}
return -1; // there is no path from root to target
}
有兩種情況你不需要使用雜湊集:
- 你完全確定沒有迴圈,例如,在樹遍歷中;
- 你確實希望多次將結點新增到佇列中。