廣度優先搜索的理解與實現

前言

有一個樹形無向圖,它描述了國、省、市、區之間的層級關係,此時咱們想找圖中的某一個結點,它位於圖中的第幾層,此時你應該怎麼作?javascript

本文將以圖文的形式,詳細講解廣度優先搜索,並用JavaScript將其實現,完成上面所描述的問題,歡迎各位感興趣的開發者閱讀本文。java

概念

廣度優先搜索是一種對圖進行搜索的算法。算法

假設咱們一開始位於某個結點(即起點),此時並不知道圖的總體結構,而咱們的目的是從起點開始順着邊搜索,直到到達指定頂點(即終點)。在此過程當中每走到一個頂點,就會判斷一次它是否爲終點。廣度優先搜索會優先從離起點近的頂點開始搜索。json

本文涉及到了圖與隊列,對此不瞭解的開發者,能夠閱讀個人另外兩篇文章: 圖的認識 & 棧與隊列數組

圖解示例

如圖所示,A爲起點,G爲終點。一開始咱們在起點A上,此時並不知道G在哪裏。 bash

  • 將能夠從A知道的三個頂點B、C、D設爲下一步的候補頂點
  • 從候補頂點中選出一個頂點。優先選擇最先稱爲候補的那個頂點,若是有多個頂點同時稱爲候補,那麼能夠隨意選擇其中一個。
  • 此處B、C、D同時稱爲候補,因此咱們隨機選擇了最左邊的頂點B。
  • 將起點移動至頂點B,將B變爲紅色,同時將已經搜索過的頂點變爲橙色。
  • 將能夠從B直達的兩個頂點E和F設爲候補頂點
  • 此時最先成爲候補頂點的是C和D,咱們選擇了左邊的頂點C。
  • 移動頂點到C上
  • 將能夠從C直達的頂點H設爲候補頂點
  • 重複上述操做,直到到達終點,或者全部的頂點都被遍歷爲止。
  • 此時,咱們的頂點到達了E,從A到E它的搜索順序爲:
A -> B
A -> C
A -> D
B -> E
複製代碼

  • 完成了A到I的搜索,如今頂點在J處
  • 到達終點G,搜索結束
# 從頂點A到終點G,搜索順序以下
A -> B
A -> C
A -> D
B -> E
B -> F
C -> H
D -> I
D -> J
E -> K
F
H -> G

複製代碼

廣度優先搜索的特徵爲從起點開始,由近及遠進行普遍的搜索。所以,目標頂點離起點越近,搜索結束得就越快。數據結構

用JS實現廣度優先搜索

正如圖解示例所述,廣度優先搜索會從一個頂點出發,普遍搜索它的子結點,將其子結點放進候選頂點中,判斷當前頂點是否爲終點,若是不是終點則按順序取出候選頂點中的數據執行上述操做,直至找到終點爲止。函數

操做候選結點時,咱們是按順序取出候選結點,符合了數據結構:隊列的特性(先進先出)post

所以,咱們須要先實現一個隊列用於存儲候選結點測試

  • 實現一個隊列,用於存放候選結點
/** * 實現一個基礎隊列 * @constructor */
const Queue = function () {
    // 使用數組初始化隊列
    let items = [];
    // 向隊列插入元素
    this.enqueue = function (elem) {
        items.push(elem);
    }
    // 從隊頭刪除元素
    this.dequeue = function () {
        return items.shift();
    }
    // 查看隊頭元素
    this.front = function () {
        return items[0];
    }
    // 判斷隊列是否爲空
    this.isEmpty = function () {
        return items.length ===0;
    }
    // 查看隊列長度
    this.size = function () {
        return items.length;
    }
    // 查看隊列中的元素
    this.print = function () {
        return items.toString();
    }
}
複製代碼
  • 聲明一個函數,參數爲:要查找的樹形圖,要查找的結點
  • 實例化一個隊列,聲明頂點到目標節點的深度變量並初始化爲0
  • 將樹加入隊列中
  • 遍歷隊列,直至隊列爲空或者找到目標結點
  • 每遍歷一次,頂點到目標結點的深度就+1
  • 遍歷隊列中的元素
  • 若是當前隊列中的元素等於目標元素,則返回當前深度
  • 若是不是,則判斷是否有下一層,將下一層的預選結點添加進隊列
  • 刪除遍歷過的結點

咱們將上述思路轉換爲代碼

/** * 廣度優先搜索 * @param tree 要查找的樹形圖 * @param target 要查找的結點 * @returns {number} 返回目標結點在樹中的深度 */
const breadthFirstSearch = function (tree,target) {
    // 實例化一個隊列
    let queue = new Queue();
    // 根節點到目標結點的深度
    let step = 0;
    // 入隊
    queue.enqueue(tree);
    // 遍歷隊列,直至隊列爲空,或者找到目標結點
    while (!queue.isEmpty()){
        step += 1;
        let len = queue.size();
        for (let i = 0; i < len; i++){
            // 獲取隊首元素
            let teamLeader = queue.front();
            // 若是是目標元素則返回當前深度
            if(target === teamLeader.value) return step;
            // 若是不是,將下一層結點添加進隊列
            if(teamLeader.children && teamLeader.children.length){
                teamLeader.children.map(item=>{
                   queue.enqueue(item);
                });
            }
            // 刪除遍歷過的結點
            queue.dequeue();
        }
    }
}
複製代碼

接下來,咱們用一個例子來測試下咱們編寫的廣度優先搜索函數

以下圖所示,是一個描述了國、省、市、區的對應關係的無向圖,咱們的問題是:從圖中找到天河區在第幾層。

  • 準備數據
// 咱們用json來描述上面的無向圖
const dataTree = {
    name:"國家",
    value:"中國",
    children:[
        {
            name:"省份",
            value:"廣東",
            children: [
                {
                    name:"城市",
                    value:"廣州",
                    children:[
                        {
                            name:"行政區",
                            value:"天河區",
                        },
                        /// 其餘部分省略 ////
                    ]
                },
                {
                    name:"城市",
                    value: "深圳",
                    children: [
                        {
                            name:"行政區",
                            value: "福田區"
                        },
                         /// 其餘部分省略 ////
                    ]
                }
            ]
        },
        {
            name:"省份",
            value:"陝西",
            children: [
                {
                    name:"城市",
                    value: "西安",
                    children: [
                         /// 其餘部分省略 ////
                    ]
                },
                {
                    name:"城市",
                    value: "商洛",
                    children: [
                         /// 其餘部分省略 ////
                    ]
                }
            ]
        }
    ]
}
複製代碼
  • 測試廣度優先搜索函數
let step = breadthFirstSearch(dataTree,"天河區");
console.log(`目標結點在圖中的第 ${step} 層`);
複製代碼

寫在最後

  • 文中使用的圖片源自《個人第一本算法書》,如若侵權,請評論區留言,做者當即刪除相關圖片。
  • 文中若有錯誤,歡迎在評論區指正,若是這篇文章幫到了你,歡迎點贊和關注😊
  • 本文首發於掘金,未經許可禁止轉載💌
相關文章
相關標籤/搜索