通常圖分爲有向圖和無向圖。
頂點的度是指和該頂點相連的邊的條數。特變的對於有向圖,頂點的出邊條數成爲出度,頂點的蠕變條數成爲入度。頂點和邊均可以由一些屬性,稱爲點權和邊權。c++
圖可使用兩種存儲方式:鄰接矩陣和鄰接表。算法
設圖G(V,E)的頂點編號爲0-N-1,那麼能夠令二維數組G[N][N]的兩維分別表示圖的頂點標號,及若是G[i][j]的值爲1,表示i和j之間有變。這個二維矩陣被稱爲鄰接矩陣。而且若是存在邊權可讓G[N][N]中存放邊權。
無向圖的鄰接矩陣是對稱矩陣。數組
設圖G(V,E)的頂點標號爲0,1,… ,N-1,每一個頂點都有可能有若干條出邊,若是把同一個頂點的全部出邊放在一個列表中,那麼N個頂點就會有N個列表(沒有出邊,對應空表)。這N個列表被稱爲圖的鄰接表。記做Adj[N]。
鄰接表實現可使用變長數組vector,開一個vector數組Adj[N], N是頂點個數,每一個頂點對應一個變長數組,存儲其出邊。
以下所示:
<Vector<int> Adj[N];>
若是節點還有權值,咱們能夠定義一個結構體:函數
Struct Node{ Int v; Int w; };
而後vector鄰接表中的元素類型就是Node型的。
<Vector<Node> Adj[N];>
測試
是指對圖的全部頂點按必定順序進行訪問,遍歷方法通常有兩種:深度優先搜索(DFS)和廣度優先搜索(BFS)。優化
DFS的具體實現,首先介紹兩個概念:
連通份量。在無向圖中,若是兩個頂點之間能夠相互到達(能夠是經過必定路徑間接到達),那麼就稱這兩個頂點連通。若是圖G(V,E)的任意兩個頂點都連通,則通圖G爲連通圖,不然稱G爲非連通圖,且稱其中的極大連通子圖爲連通份量。
強連通份量。在有向圖中,若是倆ing個頂點能夠各自經過一條有向路徑到達另外一個頂點,就稱這兩個頂點強連通。若是圖G(V,E)的任意兩個頂點都強連通,則稱圖G爲強連通圖;不然稱G爲非強連通圖,且稱其中的極大強連通子圖爲強連通份量。
若是要遍歷一個圖就要對全部的連通塊進行遍歷,若是已知的圖是連通圖,則只須要一次DFS遍歷就能夠完成。
DFS的僞代碼:(可使用臨界矩陣和鄰接表實現)spa
DFS(u){//訪問頂點u vis[u] = true; //設置u爲已訪問 for(從u出發能到達的全部頂點v){ //枚舉從u出發能夠到達的全部頂點v if(vis[v] == false){ DFS(v); } } DFSTrave(G){ //遍歷圖 for(G 的全部頂點u) //對G的全部頂點u if vis[u] == false //若是u未被訪問 DFS(u); //訪問u所在的連通塊 }
廣度優先搜索以「廣度」做爲關鍵詞,每次以擴散的方式向外訪問頂點。和樹的遍歷同樣,使用BFS遍歷圖須要使用一個隊列,經過反覆取出隊首頂點,將該頂點可到達的不曾加入過隊列的頂點所有入隊,(而不是未被訪問)直到隊列爲空時遍歷結束。
能夠查看下面的僞代碼,根據思路可使用鄰接表和臨界矩陣進行實現。code
BFS(u){ //遍歷u所在的連通塊 queue q;//定義隊列q 將u入隊; inq[u] = true; while(q 非空){ 取出隊首元素u進行訪問; for(從u出發可達到的全部頂點v) if( inq[v] == false) { //若是v不曾加入過隊列 將v入隊; inq[v] = true; } } } BFSTrave(G){ for(G 的全部頂點u) if(inq[u] == false){ //若是u不曾加入過隊列 BFS(u); //遍歷u所在的連通塊 } }
是在一個給定的無向圖G(V,E)中求一棵樹T,使這棵樹擁有圖G中的全部頂點,且全部邊都來自圖G中,而且知足整棵樹的邊權之和最小。
最小生成樹有三個性質須要掌握:
1. 最小生成樹是樹,所以其邊數等於頂點數減一,且樹內必定不會有環。
2. 對給定的圖G(V,E),其最小生成樹能夠不惟一,但其邊權之和必定是惟一的。
3. 因爲最小生成樹是在無向圖上生成的,所以其根節點能夠是這棵樹上的任意一個結點。通常爲了輸出惟一,會指定一個結點做爲根節點。
經常使用的算法有:Prim(普利姆算法)和 Kruskal算法(克魯斯卡爾算法)排序
僞代碼以下,其時間複雜度爲O(V^2),若是圖用鄰接表實現,可使用堆優化即便用優先級隊列將複雜度下降爲O(VlogV + E):隊列
G爲圖,S是以及加入圖中的頂點集,數組d爲頂點與集合S的最短距離 Prim(G,d[]){ 初始化G[],d[],d[1] = 0; for(循環n次) { u = 使d[u]最小的還未被訪問的頂點的標號; 記錄u已被訪問; for(從 u 出發能到達的全部頂點v){ if(v 未被訪問 && 以u爲中介點使得v與集合S的最短距離d[v]更優){ 將G[u][v]賦值給d[v]; } } } }
僞代碼以下,其時間負責度主要在拍於函數上,是O(ElogE),其中E是圖的邊數。
int kruskal(){ 令最小生成樹的邊權之和爲ans,最小生成樹的當前邊數Num_edge; 將全部邊按照邊權從小到達排序; for(從小到大枚舉全部邊) { if(當前測試邊的兩個端點在不一樣的連通塊中){ //判斷是否在一個聯通塊中可使用並查集 將該測試邊加入最小生成樹; ans += 測試邊的邊權; 最小生成樹的當前邊數num_edge+1; 當前邊數num_edge 等於定點數減一時結束循環; } } return ans; }
從上面的時間複雜度分析可知,Prim 算法的時間複雜度與V相關,適合稠密圖(頂點少邊多),而kruskal算法的實際複雜度與E的數目有關,適合稀疏圖(頂點多,邊少)。
參考內容:《算法筆記》 胡凡 曾磊主編