1. 程式人生 > >資料結構與演算法之 佇列和廣度優先搜尋(BFS)

資料結構與演算法之 佇列和廣度優先搜尋(BFS)

 佇列和 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
}
  1. 如程式碼所示,在每一輪中,佇列中的結點是等待處理的結點
  2. 在每個更外一層的 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
}

有兩種情況你不需要使用雜湊集:

  1. 你完全確定沒有迴圈,例如,在樹遍歷中;
  2. 你確實希望多次將結點新增到佇列中。