前言
本文是博主深感演算法方面的不足,作的一系列讀書筆記和原始碼分析。
原文地址:學習javascript資料結構與演算法(六)——圖,覺得有用的話可以給個star,謝謝啦。
作者:wengjq
1、 圖
圖是網路結構的抽象模型。圖是一組由邊連線的節點,任何二元關係都可以用圖來表示。
1.1、圖的相關概念
一個圖G = (V,E)由以下元素組成。
- V:一組頂點
- E:一組邊,連線V中的頂點
下圖表示一個圖:
由一條邊連線在一起的頂點稱為相鄰頂點。比如上圖的A和B是相鄰的,A和D是相鄰的,A和C是相鄰的,A和E不是相鄰的。一個頂點的度是其相鄰頂點的數量。比如,A和其他三個頂點相連線,因此,A的度為3;E和其他兩個頂點相連,因此E的度為2。路徑是頂點v1,v2,…,vk的一個連續序列,其中v[i]和v[i+1]是相鄰的。以上的圖為例,其中的路徑有ABEI和ACDG。
1.2、圖的表示
圖最常見的實現是鄰接矩陣。每個節點都和一個整數相關聯,該整數作為陣列的索引。這裡不作討論。另一種圖的表示方式是一種叫做鄰接表的動態資料結構。鄰接表由圖中每個頂點的相鄰頂點列表所組成。我們可以用陣列,連結串列,甚至是雜湊表或是字典來表示相鄰頂點列表。下面的示意圖展示了鄰接表的資料結構。我們後面也會用程式碼示例這種資料結構。
示例程式碼如下:
function Graph() {
var vertices = []; //儲存圖中所有的頂點名字
var adjList = new Dictionary();//用之前的一個字典來儲存鄰接表
this.addVertex = function(v){ //新增頂點
vertices.push(v);
adjList.set(v, []); //頂點為鍵,字典值為空陣列
};
this.addEdge = function(v, w){ //新增邊
adjList.get(v).push(w); //基於有向圖
adjList.get(w).push(v); //基於無向圖
};
this.toString = function(){
var s = ``;
for (var i=0; i<vertices.length; i++){
s += vertices[i] + ` -> `;
var neighbors = adjList.get(vertices[i]);
for (var j=0; j<neighbors.length; j++){
s += neighbors[j] + ` `;
}
s += `
`;
}
return s;
};
var initializeColor = function(){
var color = [];
for (var i=0; i<vertices.length; i++){
color[vertices[i]] = `white`;
}
return color;
};
}
//測試
var graph = new Graph();
var myVertices = [`A`,`B`,`C`,`D`,`E`,`F`,`G`,`H`,`I`];
for (var i=0; i<myVertices.length; i++){
graph.addVertex(myVertices[i]);
}
graph.addEdge(`A`, `B`);
graph.addEdge(`A`, `C`);
graph.addEdge(`A`, `D`);
graph.addEdge(`C`, `D`);
graph.addEdge(`C`, `G`);
graph.addEdge(`D`, `G`);
graph.addEdge(`D`, `H`);
graph.addEdge(`B`, `E`);
graph.addEdge(`B`, `F`);
graph.addEdge(`E`, `I`);
console.log(graph.toString());
結果如下:
A -> B C D
B -> A E F
C -> A D G
D -> A C G H
E -> B I
F -> B
G -> C D
H -> D
I -> E 複製程式碼
1.3、圖的遍歷
和樹的資料結構類似,我們可以訪問圖的所有節點。有兩種演算法可以對圖進行遍歷:廣度優先搜尋(Breadth-First Search,BFS)和深度優先搜尋(Depth-First Search,DFS)。圖遍歷可以用來尋找特定的頂點或尋找兩個頂點之間的路徑,檢查圖是否連通,檢查圖是否含有環等。
圖遍歷演算法的思想是必須追蹤每個第一次訪問的節點,並且追蹤有哪些節點還沒有被完全探索。對於兩種圖遍歷演算法,都需要明確指出第一個被訪問的頂點。完全探索一個頂點要求我們檢視該頂點的每一條邊。對應每一條邊所連線的沒有被訪問過的頂點,將其標註為被發現的,並將其加進待訪問頂點列表中。
為了保證演算法的效率,務必訪問每個頂點至多兩次。連通圖中每條邊和頂點都會被訪問到。當要標註已經訪問過的頂點時,我們用三種顏色來反映它們的狀態:
- 白色:表示該頂點還沒有被訪問。
- 灰色:表示該頂點被訪問過,但並未被探索過。
- 黑色:表示該頂點被訪問過且被完全搜尋過。
1.3.1、廣度優先搜尋(BFS)
廣度優先搜尋演算法會從指定的第一個頂點開始遍歷圖,先訪問其所有的相鄰點,就像一次訪問圖的一層。換句話說,就是先寬後深的訪問頂點。以下是從頂點v開始的廣度優先搜尋演算法所遵循的步驟。
- (1)建立一個佇列Q。
- (2)將v標註為被發現的(灰色),並將v入佇列Q。
- (3)如果Q非空,則執行以下步驟:
(a)將u從Q中出佇列; (b)將標註u為被發現的(灰色); (c)將u所有未被訪問過的鄰點(白色)入佇列; (d)將u標註為已被探索的(黑色);複製程式碼
示例程式碼如下:
var initializeColor = function(){
var color = [];
for (var i=0; i<vertices.length; i++){
color[vertices[i]] = `white`; //初始化所有的頂點都是白色
}
return color;
};
this.bfs = function(v, callback){
var color = initializeColor(),
queue = new Queue(); //建立一個佇列
queue.enqueue(v); //入佇列
while (!queue.isEmpty()){
var u = queue.dequeue(), //出佇列
neighbors = adjList.get(u); //鄰接表
color[u] = `grey`; //發現了但還未完成對其的搜素
for (var i=0; i<neighbors.length; i++){
var w = neighbors[i]; //頂點名
if (color[w] === `white`){
color[w] = `grey`; //發現了它
queue.enqueue(w); //入佇列迴圈
}
}
color[u] = `black`; //已搜尋過
if (callback) {
callback(u);
}
}
};
//測試如下:
function printNode(value){
console.log(`Visited vertex: ` + value);
}
graph.bfs(myVertices[0], printNode);
結果如下:
Visited vertex: A
Visited vertex: B
Visited vertex: C
Visited vertex: D
Visited vertex: E
Visited vertex: F
Visited vertex: G
Visited vertex: H
Visited vertex: I複製程式碼
1.3.2、深度優先搜尋(BFS)
深度優先搜尋演算法將會是從第一個指定的頂點開始遍歷圖,沿著路徑直到這條路徑最後一個頂點被訪問了,接著原路回退並探索下一條路徑。換句話說,它是先深度後廣度地訪問頂點。深度優先搜尋演算法不需要一個源頂點。要訪問頂點v,照如下的步驟做:
- (1)標註v為被發現的(灰色)。
- (2)對應v的所有未訪問的鄰點w。
(a)訪問頂點w。複製程式碼
- (3)標註v為已被探索的(黑色)。
如你所見,深度優先搜尋的步驟是遞迴的,這意味著深度優先搜尋演算法使用棧來儲存函式呼叫(由遞迴呼叫所建立的棧)。示例程式碼如下:
this.dfs = function(callback){
var color = initializeColor(); //前面的顏色陣列
for (var i=0; i<vertices.length; i++){
if (color[vertices[i]] === `white`){
dfsVisit(vertices[i], color, callback); //遞迴呼叫未被訪問過的頂點
}
}
};
var dfsVisit = function(u, color, callback){
color[u] = `grey`;
if (callback) {
callback(u);
}
var neighbors = adjList.get(u); //鄰接表
for (var i=0; i<neighbors.length; i++){
var w = neighbors[i];
if (color[w] === `white`){
dfsVisit(w, color, callback); //新增頂點w入棧
}
}
color[u] = `black`;
};
//測試如下:
function printNode(value){
console.log(`Visited vertex: ` + value);
}
graph.dfs(printNode);
結果如下:
Visited vertex: A
Visited vertex: B
Visited vertex: E
Visited vertex: I
Visited vertex: F
Visited vertex: C
Visited vertex: D
Visited vertex: G
Visited vertex: H複製程式碼
本文對你有幫助?歡迎掃碼加入前端學習小組微信群: