邻接表(Adjacency List) 是图的链式存储结构,我们会对图的每个顶点都创建一个单链表,每个单链表都存储与该顶点直接相连的顶点。
比如下面的一个无向图
可以发现,
- 顶点1与顶点4、顶点2直接相连
- 顶点2与顶点1、顶点3、顶点5直接相连
- 顶点3与顶点2、顶点4、顶点5直接相连
- 顶点4与顶点1、顶点3、顶点5直接相连
- 顶点5与顶点2、顶点3直接相连
所以该图的邻接表可以表示为:
1 -> 2 -> 4
2-> 1 -> 3 -> 5
3 -> 2 -> 4 -> 5
4 -> 1 -> 3 -> 5
5 -> 2 -> 3
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 1024 //最大顶点数
//边节点
struct ArcNode{
int adjvex; //改边指向的顶点的位置
ArcNode* next; //指向的下一个顶点
// int lowcost; //权值
};
//顶点
struct VNode{
ArcNode* firstArc; //指向的第一条依附于该顶点的指针
//书上还会有一个data域来存放顶点的名字,因为我这里的顶点都是记为从1到n,所以就省掉了
}vlist[maxn]; //vlist[i]表示第i条顶点的信息
int n,m; //顶点个数(编号从1-n),边的个数
/*
增加一条连接v1和v2的边
*/
void link(int v1,int v2){
//在v1表的表头插入v2
ArcNode* p1 = new ArcNode();
p1->adjvex = v2;
p1->next = vlist[v1].firstArc; //这两行是链表插入的常规操作
vlist[v1].firstArc = p1;
//同时也在v2的表头插入v1
ArcNode* p2 = new ArcNode();
p2->adjvex = v1;
p2->next = vlist[v2].firstArc; //这两行是链表插入的常规操作
vlist[v2].firstArc = p2;
}
/*
初始化
*/
void init(){
n = 5;
m = 6;
//以下n行初始化vlist
for(int i = 1;i<=n;++i){
vlist[i].firstArc = NULL;
}
//以下m行建图
link(1,2);
link(1,4);
link(2,3);
link(2,5);
link(3,4);
link(3,5);
}
/*
打印邻接表
*/
void printArc(){
for(int i = 1;i<=n;++i){
cout << i << " -> ";
ArcNode* an = vlist[i].firstArc;
while(an){
cout << an->adjvex << " -> ";
an = an->next;
}
cout << " ^" << endl;
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
init();
printArc();
return 0;
}
输出结果
思考
使用邻接表表示图的有以下优点:
- 便于增加和删除节点,只需要修改一下单链表即可
- 便于统计与这个顶点直接相连的边的数目,只需看单链表的大小即可
- 空间效率高,空间复杂度为O(m + n),更适合用于表示稀疏图
缺点:
- 不利于判断两个顶点之间是否有边,需要花费O(n)的时间复杂度扫描邻接表
- 不利于统计有向图顶点的度。对于无向图来说,顶点对应的链表的长度就是该顶点的度,但是对于有向图,链表的大小只能表示出度,而求入度较困难。