廣度優先查找無向無權圖兩點間最短路徑,能夠將圖當作是以起點爲根節點的樹狀圖,每一層是上一層的子節點,一層一層的查找,直到找到目標節點爲止。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號,一個節點只設置一個父節點,由於不管從哪一個父節點查找,路徑長度是同樣的。