JS廣度優先查找無向無權圖兩點間最短路徑

廣度優先查找無向無權圖兩點間最短路徑,能夠將圖當作是以起點爲根節點的樹狀圖,每一層是上一層的子節點,一層一層的查找,直到找到目標節點爲止。node

起點爲0度,與之相鄰的節點爲1度,以此類推。數組

    // 廣度優先遍歷查找兩點間最短路徑
    breadthFindShortestPath(sourceId, targetId) {
        const { nodesKV } = this.chart.getStore();
        let visitedNodes = [];  // 出現過的節點列表
        let degreeNodes = [[sourceId]];  // 二維數組,每一個數組是每一度的節點列表。1度就是起點
        let degree = 0;  // 當前查找的度數
        let index = 0;  // 當前查找的當前度數節點數組中的索引
        let nodesParent = {};  // 記錄每一個節點的父節點是誰。廣度優先遍歷,每一個節點就只有一個父節點
        let pathArr = [];  // 最短路徑

        visitedNodes.push(sourceId);

        outer:
        while (degreeNodes[degree][index]) {

            degreeNodes[degree + 1] = degreeNodes[degree + 1] || [];  // 初始化下一度

            const node = nodesKV[degreeNodes[degree][index]];
            const neighborNodes = [...node.children || [], ...node.parents || []];

            for (let i = 0; i < neighborNodes.length; i++) {
                const id = neighborNodes[i];
                // 若是找到了,則退出
                if (id === targetId) {
                    nodesParent[id] = degreeNodes[degree][index];  // 記錄目標節點的父節點是誰
                    break outer;
                } else if (!visitedNodes.includes(id)) {  // 若是沒有找到,而且這個節點沒有訪問過,則把它添加到下一度中
                    visitedNodes.push(id);
                    degreeNodes[degree + 1].push(id);
                    nodesParent[id] = degreeNodes[degree][index];
                }
            }

            // 若是當前節點後面還有節點,則查找後一個節點
            if (degreeNodes[degree][index + 1]) {
                index++;
            } else {
                degree++;
                index = 0;
            }
        }

        // 經過目標節點的父節點,層層追溯找到起點,獲得最短路徑
        let nodeId;
        nodeId = targetId;
        while (nodeId) {
            pathArr.push(nodeId);
            // 當前節點有父節點,則將 nodeId 設置爲父節點的 id,繼續循環查找父節點
            if (nodesParent[nodeId]) {
                pathArr.push(nodesParent[nodeId]);
                nodeId = nodesParent[nodeId];  // nodeId 設置爲父節點的 id
            } else {  // 沒有父節點,則說明到了起點。nodeId 設爲 null,退出循環
                nodeId = null;
            }
        }

        return pathArr;
    }

上面代碼中,主要的數據結構有:數據結構

visitedNodes:一層層的查找,出現的節點馬上添加到這個數組中。當查找一個節點的相鄰節點時,若是相鄰節點是它的父節點或同一度的節點,那這個節點就已經在 visitedNodes 中了,不會將此節點標記爲這個節點的子節點。this

degreeNodes:數組中的每一個數組,就是0度至N度,每一度的節點列表。spa

nodesParent:查找節點時,會將當前節點標記爲相鄰節點的父節點(除了已經在 visitedNodes 中的,visitedNodes 中的節點都已有了父節點),每一個節點只有一個父節點。code

 

假設下圖中1號節點爲開始節點,15號節點爲目標節點:blog

 

 

 

狀況分析:索引

一、1號節點開始查找,找到相鄰節點2,3,4,5號,2,3,4,5號節點都沒在 visitedNodes 中,將它們添加到 visitedNodes 裏,而且將它們添加到 degreeNodes 中下一度的數組中。此時 visitedNodes 裏面就有1,2,3,4,5號節點,nodesParent 裏面,2,3,4,5號節點的父節點都是1號節點。get

二、1號節點後面沒有與之同度數的節點,degree 加1,index 重置爲0。it

三、2號節點開始查找,相鄰節點中有1,3,6,7,8號節點,圖中能夠看出1號節點和3號節點是它的父節點和同度數的節點,這兩個節點已經被添加到了 visitedNodes 中,則只將6,7,8號節點添加到 degreeNodes 中下一度的數組中。nodesParent 裏面,6,7,8號節點的父節點都設置爲2號節點。visitedNodes 中添加6,7,8號節點。

四、2號節點的相鄰節點遍歷完成後,判斷2號節點後面是否有相同度數的節點,degreeNodes[degree][index + 1] 發現不爲空,則 index++ 繼續循環查到當前度數的下一個節點的相鄰節點。

五、開始查找3號節點的相鄰節點,1,2,4,6,8,9號節點都是3號節點的相鄰節點,而1,2,4,6,8號節點都已在 visitedNodes 中,則只將9號節點的父節點設置爲3號節點。

六、同理,繼續判斷3號節點後是否有相同度數的節點,有4號節點,繼續查找,有5號節點,繼續查找。

七、當找到12號節點後,繼續查找5號節點後是否有相同度數的節點,degreeNodes[degree][index + 1]  的值爲 undefined 了,則 degree++, index = 0 繼續循環找下一度的節點。

八、經過6號節點的相鄰節點,找到了15號節點,此時退出循環,經過 nodesParent 獲得最短路徑 15-6-2-1。

固然,咱們也能從圖中看出1-3-6-15,1-3-9-15和1-5-9-15也是最短路徑,不過這不重要,找到一條便可。這也是爲何 nodesParent 裏面6號節點的父節點只設置2號而不用設置3號,一個節點只設置一個父節點,由於不管從哪一個父節點查找,路徑長度是同樣的。

相關文章
相關標籤/搜索