要表示一个图G=(V,E),有两种标准的表示方法,即邻接表和邻接矩阵。这两种表示法既可用于有向图,也可用于无向图。通常采用邻接表表示法,因为用这种方法表示稀疏图(图中边数远小于点个数)比较紧凑。但当遇到稠密图(|E|接近于|V|^2)或必须很快判别两个给定顶点手否存在连接边时,通常采用邻接矩阵表示法,例如求最短路径算法中,就采用邻接矩阵表示。
图G=<V,E>的邻接表表示是由一个包含|V|个列表的数组Adj所组成,其中每个列表对应于V中的一个顶点。对于每一个u∈V,邻接表Adj[u]包含所有满足条件(u,v)∈E的顶点v。亦即,Adj[u]包含图G中所有和顶点u相邻的顶点。每个邻接表中的顶点一般以任意顺序存储。


如果G是一个有向图,则所有邻接表的长度之和为|E|,这是因为一条形如(u,v)的边是通过让v出现在Adj[u]中来表示的。如果G是一个无向图,则所有邻接表的长度之和为2|E|,因为如果(u,v)是一条无向边,那么u会出现在v的邻接表中,反之亦然。邻接表需要的存储空间为O(V+E)。
邻接表稍作变动,即可用来表示加权图,即每条边都有着相应权值的图,权值通常由加权函数w:E→R给出。例如,设G=<V,E>是一个加权函数为w的加权图。对每一条边(u,v)∈E,权值w(u,v)和顶点v一起存储在u的邻接表中。
邻接表C++实现:


1 #include <iostream>
2 #include <cstdio>
3 using namespace std;
4
5 #define maxn 100 //最大顶点个数
6 int n, m; //顶点数,边数
7
8 struct arcnode //边结点
9 {
10 int vertex; //与表头结点相邻的顶点编号
11 int weight = 0; //连接两顶点的边的权值
12 arcnode * next; //指向下一相邻接点
13 arcnode() {}
14 arcnode(int v,int w):vertex(v),weight(w),next(NULL) {}
15 arcnode(int v):vertex(v),next(NULL) {}
16 };
17
18 struct vernode //顶点结点,为每一条邻接表的表头结点
19 {
20 int vex; //当前定点编号
21 arcnode * firarc; //与该顶点相连的第一个顶点组成的边
22 }Ver[maxn];
23
24 void Init() //建立图的邻接表需要先初始化,建立顶点结点
25 {
26 for(int i = 1; i <= n; i++)
27 {
28 Ver[i].vex = i;
29 Ver[i].firarc = NULL;
30 }
31 }
32
33 void Insert(int a, int b, int w) //尾插法,插入以a为起点,b为终点,权为w的边,效率不如头插,但是可以去重边
34 {
35 arcnode * q = new arcnode(b, w);
36 if(Ver[a].firarc == NULL)
37 Ver[a].firarc = q;
38 else
39 {
40 arcnode * p = Ver[a].firarc;
41 if(p->vertex == b) //如果不要去重边,去掉这一段
42 {
43 if(p->weight < w)
44 p->weight = w;
45 return ;
46 }
47 while(p->next != NULL)
48 {
49 if(p->next->vertex == b) //如果不要去重边,去掉这一段
50 {
51 if(p->next->weight < w);
52 p->next->weight = w;
53 return ;
54 }
55 p = p->next;
56 }
57 p->next = q;
58 }
59 }
60 void Insert2(int a, int b, int w) //头插法,效率更高,但不能去重边
61 {
62 arcnode * q = new arcnode(b, w);
63 if(Ver[a].firarc == NULL)
64 Ver[a].firarc = q;
65 else
66 {
67 arcnode * p = Ver[a].firarc;
68 q->next = p;
69 Ver[a].firarc = q;
70 }
71 }
72
73 void Insert(int a, int b) //尾插法,插入以a为起点,b为终点,无权的边,效率不如头插,但是可以去重边
74 {
75 arcnode * q = new arcnode(b);
76 if(Ver[a].firarc == NULL)
77 Ver[a].firarc = q;
78 else
79 {
80 arcnode * p = Ver[a].firarc;
81 if(p->vertex == b) return; //去重边,如果不要去重边,去掉这一句
82 while(p->next != NULL)
83 {
84 if(p->next->vertex == b) //去重边,如果不要去重边,去掉这一句
85 return;
86 p = p->next;
87 }
88 p->next = q;
89 }
90 }
91 void Insert2(int a, int b) //头插法,效率跟高,但不能去重边
92 {
93 arcnode * q = new arcnode(b);
94 if(Ver[a].firarc == NULL)
95 Ver[a].firarc = q;
96 else
97 {
98 arcnode * p = Ver[a].firarc;
99 q->next = p;
100 Ver[a].firarc = q;
101 }
102 }
103 void Delete(int a, int b) //删除以a为起点,b为终点的边
104 {
105 arcnode * p = Ver[a].firarc;
106 if(p->vertex == b)
107 {
108 Ver[a].firarc = p->next;
109 delete p;
110 return ;
111 }
112 while(p->next != NULL)
113 if(p->next->vertex == b)
114 {
115 p->next = p->next->next;
116 delete p->next;
117 return ;
118 }
119 }
120
121 void Show() //打印图的邻接表(有权值)
122 {
123 for(int i = 1; i <= n; i++)
124 {
125 cout << Ver[i].vex;
126 arcnode * p = Ver[i].firarc;
127 while(p != NULL)
128 {
129 cout << "->(" << p->vertex << "," << p->weight << ")";
130 p = p->next;
131 }
132 cout << "->NULL" << endl;
133 }
134 }
135
136 void Show2() //打印图的邻接表(无权值)
137 {
138 for(int i = 1; i <= n; i++)
139 {
140 cout << Ver[i].vex;
141 arcnode * p = Ver[i].firarc;
142 while(p != NULL)
143 {
144 cout << "->" << p->vertex;
145 p = p->next;
146 }
147 cout << "->NULL" << endl;
148 }
149 }
150 int main()
151 {
152 int a, b, w;
153 cout << "Enter n and m:";
154 cin >> n >> m;
155 Init();
156 while(m--)
157 {
158 cin >> a >> b >> w; //输入起点、终点
159 Insert(a, b, w); //插入操作
160 Insert(b, a, w); //如果是无向图还需要反向插入
161 }
162 Show();
163 return 0;
164 }

邻接表表示法也有潜在的不足之处,即如果要确定图中边(u,v)是否存在,只能在顶点u邻接表Adj[u]中搜索v,除此之外没有其他更快的办法。这一不足可通过图的邻接矩阵表示法来弥补,但要(在渐进意义下)以占用更多的存储空间为代价。
在图G=(V,E)的临界矩阵表示法中,假定各顶点按某种任意的方式编号为1,2,···,|V|,那么G的邻接矩阵为一个|V|*|V|的矩阵A=(a[i][j]),它满足:

观察无向图的邻接矩阵会发现,它是沿主对角线对称的。在一个无向图中,(u,v)和(v,u)表示同一条边,故无向图的邻接矩阵A的转置矩阵就是它本真。在某些应用中,可以只存储邻接矩阵的对角线以及对角线以上的部分,这样一来,图所占用的存储空间几乎可以减少一半。
邻接矩阵也可以用来表示加权图。例如,如果G=<V,E>是一个加权图,其权值函数为w,对于边(u,v)∈E,其权值w(u,v)就可以简单地存储在邻接矩阵的第u行第v列的元素中。如果边不存在,则可以在矩阵的相应元素中存一个NIL值,在很多问题中,对这样的元素赋0或∞会更为方便些。

邻接矩阵C++实现:


1 #include <iostream>
2 #include <cstdio>
3 using namespace std;
4
5 #define maxn 100
6 #define INF 1xffffff //预定于的最大值
7 int n, m; //顶点数、边数
8 int g[maxn][maxn]; //邻接矩阵表示
9
10 void Init()
11 {
12 for(int i = 1; i <= n; i++)
13 for(int j = 1; j <= n; j++)
14 g[i][j] = 0; //讲所有顶点度数置零,若为带权图,则置为INF
15 }
16 void Show() //打印邻接矩阵
17 {
18 for(int i = 1; i <= n; i++)
19 {
20 for(int j = 1; j <= n; j++)
21 cout << g[i][j] << " ";
22 cout << endl;
23 }
24 }
25 int main()
26 {
27 int a, b;
28 cout << "Enter n and m:";
29 cin >> n >> m;
30 while(m--)
31 {
32 cin >> a >> b; //输入为边的始点、终点,若有权,还需输入权w
33 g[a][b] = 1; //a、b间存在边,将g[a][b]置1,若有权,则将其置为权值
34 g[b][a] = 1; //对于无向图,还要插入边(b,a)
35 }
36 Show();
37 return 0;
38 }

(文章以及相关代码参考算法导论编写)
本文详细介绍了图数据结构的两种主要表示方法:邻接表和邻接矩阵,包括它们的适用场景、存储方式及优缺点。通过C++代码示例展示了如何实现这两种表示方法,适用于稀疏图和稠密图的不同需求。
6492

被折叠的 条评论
为什么被折叠?



