簡單說,就是有多個節點(vertex),且彼此有些連接線(edge)的資料結構,以下都是 graph :
並且 graph 種類還能分為有向 & 無向兩種 :
Graph 的應用還蠻多的,例如 :
以下介紹兩種常見 Graph 的表示方式 :
用一個二維陣列表示,例如以下圖片是無向的 graph,1、2 兩個節點之間有 edge,所以 [1][2] 和 [2][1] 都是 1。
如果是有向的圖,陣列不會是對稱的數字,也就是說 [1][2] 是 1,但 [2][1] 不一定是 1。
會先以一個一維陣列列出所有的節點,再以 Linked list 表示所有與該節點相連的節點。例如節點 1 相連的節點有 2、3、4。
圖形的走訪主要分成兩種-BFS(廣度優先搜尋) & DFS(深度優先搜尋),Tree 和 Graph 都可以執行這兩個演算法。
從特定節點開始找,假設該特定節點有 3 個鄰居節點,那就會一一將 3 個鄰居節點都找過,接著才往其中一個鄰居節點的其他節點查找。
要思考的幾個點如下:
接著將上面的想法轉換成程式碼就如下,不過只是有關於 BFS 的片段的程式碼,包含整個 Graph 和相關操作函式的完整程式碼可以參考 javascript-data-structure/Graph。
breadthFirst(start) {
const queue = [start];
const result = [];
const visited = {};
let currentVertex;
visited[start] = true;
while (queue.length) {
currentVertex = queue.shift();
result.push(currentVertex);
this.adjacencyList[currentVertex].forEach((neighbor) => {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.push(neighbor);
}
});
}
return result;
}
此演算法時間複雜度為 O(v + e),v 是節點數量,e 是邊的數量。
從特定節點開始找,假設該特定節點有 3 個鄰居節點,那不會 3 個鄰居節點都找過,而是選擇其中一個鄰居節點查找後,再從該鄰居節點繼續往其鄰居節點查找。
可以有兩種做法,一種有用遞迴一種沒有,這裡使用遞迴的方式來作示範。
要思考的幾個點如下:
adjacencyList 就像下圖左邊的物件,記錄著各節點的鄰居節點,而右邊的物件代表走訪過的節點,走過就紀錄 true。
接著將上面的想法轉換成程式碼就如下:
depthFirstRecursive(start) {
const result = [];
const visited = {};
const adjacencyList = this.adjacencyList;
(function dfs(vertex) {
if (!vertex) return null;
visited[vertex] = true;
result.push(vertex);
adjacencyList[vertex].forEach((neighbor) => {
if (!visited[neighbor]) {
return dfs(neighbor);
}
});
})(start);
return result;
}