圖和散列表、二叉樹同樣,是一種非線性數據結構。如圖1所示,圖由一系列頂點以及鏈接頂點的邊構成。由一條邊鏈接在一塊兒的頂點成爲相鄰頂點,好比A和B、A和D是相鄰的,而A和E不是相鄰的。一個頂點相鄰頂點的數量叫做度,好比A的度爲三、D的度爲4。路徑指相鄰頂點的一個連續序列,如ABEI、ACDG;簡單路徑指不包含重複頂點的路徑(除環外),如ADG;環指首尾頂點相同的路徑,如ADCA,環也屬於簡單路徑。若是圖中每兩個頂點之間都有路徑相連,則稱該圖是連通的。javascript
如圖2,若是圖中的邊具備方向,稱該圖爲有向圖。若是圖中的邊是雙向的,則該圖是強連通的,例如圖3中的C和D是強連通的。圖也能夠是加權的,例如圖3中的每條邊都有權值。java
圖能夠用來解決計算機中的不少問題,好比搜索圖中的一個特定頂點或搜索一條特定邊,尋找圖中的一條路徑(從一個頂點到另外一個頂點) ,尋找兩個頂點之間的最短路徑,以及環檢測。算法
圖的表示方式有多種,沒有絕對正確的表示方式,採用哪一種方式取決於圖的類型和待解決的問題。這裏介紹三種方式:鄰接矩陣、鄰接表、關聯矩陣。segmentfault
鄰接矩陣用一個二維數組來表示圖中頂點的鏈接狀況;若是索引爲i的節點和索引爲j的節點鏈接,則array[i][j] === 1,不然array[i][j] === 0,如圖4。鄰接矩陣的缺點是,若是圖不是強連通的,矩陣中就會出現不少0,從而計算機須要浪費存儲空間來表示根本不存在的邊。例如,即便某一頂點只有一個相鄰頂點,也須要一整行來表示該頂點的鏈接狀況,數組
鄰接表由圖中每一個頂點的相鄰頂點的列表所組成,如圖5。只要能表示一對多的數據結構,均可以用來描述鄰接表,好比多維列表(數組)、鏈表、散列表、字典等。
在大多數狀況下,鄰接表是更好的選擇,但鄰接矩陣也有其優勢,好比要判斷頂點A和B是否相鄰,鄰接矩陣比鄰接表要快。數據結構
圖5測試
在關聯矩陣表示的圖中,矩陣的行表示頂點,列表示邊。如圖6所示,咱們使用二維數組來表示二者之間的連通性,若是頂點A是邊E的入射點,則array[A][E] === 1;不然,array[A][E] === 0。
關聯矩陣一般用於邊的數量比頂點少的狀況下,以節省空間和內存。如圖6,頂點數是5,邊的數量是6,用鄰接矩陣表示圖須要的空間是5*5=25,而使用關聯矩陣表示圖須要的空間是5*6=30。this
圖6spa
首先首先聲明類的骨架:3d
function Graph() { var vertices = []; var adjList = new Dictionary(); }
其中Dictionary類的實現參考以前的文章字典。
咱們使用數組 vertices 來存儲圖中全部頂點的名字,以及字典 adjList 來存儲鄰接表。字典將會使用頂點的名字做爲鍵,鄰接頂點列表做爲值。
接下來實現向圖中添加頂點的方法:
this.addVertex = function(v){ vertices.push(v); adjList.set(v, []); };
該方法接受頂點 v 做爲參數。咱們將該頂點添加到頂點列表 vertices 中,而且在鄰接表中,設置頂點 v 做爲鍵對應的字典值爲一個空數組。
接着實現一個點到另外一個點的鏈接:
this.addEdge = function(v, w){ adjList.get(v).push(w); adjList.get(w).push(v); };
這個方法接受兩個頂點做爲參數。首先,咱們經過將 w 加入到 v 的鄰接表中,添加了一條自頂
點 v 到頂點 w 的邊。若是是有向圖,則只須要該方法的第一行代碼就好了。咱們這裏要實現無向圖,咱們須要添加一條自 w 向 v 的邊,即該方法的第二行代碼。
使用該圖類進行簡單的測試:
var graph = new Graph(); var myVertices = ['A','B','C','D']; for (var i=0; i<myVertices.length; i++){ graph.addVertex(myVertices[i]); // 添加圖的頂點 } // 添加圖的邊 graph.addEdge('A', 'B'); graph.addEdge('A', 'C'); graph.addEdge('B', 'D'); graph.addEdge('C', 'D');
上述代碼生成圖對應的鄰接表爲:
A -> B C
B -> A D
C -> A D
D -> B C
有兩種算法能夠對圖進行遍歷:廣度優先搜索(Breadth-First Search,BFS)和深度優先搜索(Depth-First Search,DFS)。圖遍歷能夠用來尋找特定的頂點或尋找兩個頂點之間的路徑,檢查圖是否連通,檢查圖是否含有環等。
圖遍歷算法的思路是追蹤每一個第一次訪問的節點,而且追蹤有哪些節點尚未被徹底探索。對於兩種圖遍歷算法,都須要明確指出第一個被訪問的頂點。
徹底探索一個頂點須要查看該頂點的每一條邊。對於每一條邊所鏈接的沒有被訪問過的頂點,將其標註爲被發現的,並將其加進待訪問頂點列表中。
咱們用三種狀態來反映頂點的狀態:
由於只有這三種狀態,初始狀態是白色,所以每一個頂點至多訪問兩次,這樣作可以保證算法的效率。
廣度優先搜索算法和深度優先搜索算法基本上是相同的,只是待訪問頂點列表的數據結構不一樣。
廣度優先搜索算法:數據結構是隊列。經過將頂點存入隊列中,最早入隊列的頂點先被探索。
深度優先搜索算法:數據結構是棧。經過將頂點存入棧中,沿着路徑探索頂點,存在新的相鄰頂點就去訪問。