存圖的三種方式


合適的存圖方式往往能事半功倍,這里介紹三種方式:鄰接、鄰接表、鏈式前向星。

鄰接矩陣

1)存圖思想

用一個矩陣來記錄一個圖,矩陣第 i 行第 j 列的值就表示頂點 i 到頂點 j 的權值

2  代碼實現

 1 #include<stdio.h>
 2 #include<string>
 3 const int V = 1000;  //最大頂點數
 4 int mat[V][V];
 5 
 6 int main()
 7 {
 8     //初始化操作
 9     //假設權值為0表示沒有該邊
10     memset(mat, 0, sizeof(mat));  
11 
12     //增加邊
13     //增加頂點i到頂點j的權值為w的邊
14     mat[i][j] = w;
15 
16     //刪除邊
17     mat[i][j] = 0;
18 
19     //查詢邊
20     mat[i][j]
21 }

3)優點

a.簡單易學

b.對已確定的邊進行操作效率高:對已確定的邊(兩頂點已知),進行增加、刪除(或更改權值)、查詢,都是O(1)的時間復雜度

4)缺點

a.過高的空間復雜度:這幾乎是一個致命的缺點,對於頂點數V,鄰接矩陣存圖的空間復雜度高達O(V * V),頂點數高於10000就不用考慮了。對於稀疏矩陣,太浪費內存了

鄰接表

1)存圖思想

對每個頂點,使用不定長的鏈表來存儲從該點出發的所有邊的情況,第 i 個鏈表的第 j 個值就是頂點 i 到頂點 j 的權值。

2)代碼實現

 1 #include<stdio.h>
 2 #include<vector>
 3 #include<string>
 4 using namespace std;
 5 
 6 const int V = 100000;
 7 vector<int>e[V];  //定義了V個鏈表
 8 
 9 int main()
10 {
11     int i,j,k;
12     //鄰接鏈表的初始化
13     //將起點為"i "的鏈表清空
14     e[i].clear();
15 
16     //增加邊
17     //增加頂點"i"到頂點"j"的邊
18     e[i].push_back(j);
19 
20     //查詢邊值
21     //查詢以"i"為起點的第"j"條邊
22     e[i][j];
23 
24     //尋找權值為k的邊
25     for (int j = 0; j < (int)e[i].size(); j++)
26     {
27         if (e[i][j] == k)
28         {
29             //do something
30         }
31     }
32 
33 }

3)優點

a.較為簡單易學:數組轉鏈表,定長轉不定長

b.內存利用率較高:對於頂點數V、邊數E,空間復雜度為O(V + E),能較好的處理稀疏圖

c.對不確定的邊操作比較方便:比如,要遍歷從某點出發的某條邊,不會像鄰接矩陣一樣可能遍歷到不存在的邊

4)缺點

a.重邊不好處理:判重比較麻煩,還要遍歷已有的邊,不能直接判斷;一般情況下使用鄰接表存圖是會存儲重邊的,不會做重邊的判斷

b.對確定邊效率不高:比如對於給定i->j的邊要進行查詢或修改等操作只有通過遍歷這種方式找到

鏈式前向星

1)存圖思想

主要的數據結構是邊數組(存儲邊的數組),當然想要完美表示圖,光有一個邊集數組還不夠,還要有一個數組每一個點的第一條邊。同時,每一條邊都需要存儲接下來一條邊的“指針”

2)代碼實現

 1 #include<stdio.h>
 2 #include<string>
 3 const int V = 100000;
 4 const int E = 100000;
 5 
 6 //邊結構體的定義
 7 struct Edge
 8 {
 9     int to;        //該邊的另一個頂點
10     int w;        //該邊的權值
11     int next;  //下一條邊的數組下標
12 };
13 
14 //head[i]表示頂點i的第一條邊的數組下標,"-1"表示頂點i沒有邊
15 int head[V];
16 Edge edge[E];
17 
18 
19 //增加邊
20 //新增加邊"a -> b",該邊的權值為w,數組下標為"id"
21 inline void AddEdge(int a, int b, int w, int id)
22 {
23     edge[id].to = b;
24     edge[id].w = w;
25     edge[id].next = head[a];  // 新增的邊要成為頂點`a`的第一條邊,而不是最后一條邊
26     head[a] = id;              //類似於鏈表的頭插法,最后一條邊指向"-1"
27     return;
28 }
29 int main()
30 {
31     int a,b,w;
32     //鏈式前向星的初始化
33     //只需要初始化頂點數組就可以了
34     memset(head, -1, sizeof(head));
35 
36     //遍歷從a出發的所有邊
37     for (int i = head[a]; i != -1; i = edge[i].next)
38     {
39         //do something
40         if(edge[i].to == b)  //找到邊a -> b
41         if(edge[i].w == w)   //找到權值為w的邊
42     }
43 }

3)優點

a.內存利用率高:相比vector實現的鄰接表而言,可以准確開辟最多邊數的內存,不像vector實現的鄰接表有爆內存的風險

b..對不確定的邊操作比較方便:和鄰接表一樣不會遍歷到不存在的邊

4)缺點

a.操作復雜一點:要求比較熟練,不熟練寫起來比較慢

b.重邊不好處理:這點與鄰接表一樣,只有通過遍歷判重

c.對確定邊操作效率不高:也與鄰接表一樣,不能通過兩點馬上確定邊,只能遍歷查找

總結

對於鄰接矩陣,由於內存消耗局限性的,幾乎只能用於簡單的圖論題

鄰接表存圖是最為常見的一種方法,絕大部分采用C++STL中的vector實現,一般情況下大部分圖論題目都能使用該存圖方式。

而鏈式前向星是一種較好的用來代替鄰接表的數據結構,在鄰接表存圖不能使用時可以使用,幾乎可以用於全部圖論題目。如果熟練掌握,可以作為主要的存圖方法。

參考鏈接:izqt.github.io/2015/07/21/ACM圖論之存圖方式

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM