图是由顶点集合和边集合组成的,考虑怎么把这两样东西存储在计算机内存中
用两个数组来表示图。
设图G有n
个顶点,则邻接矩阵arc
是一个n
× n
的方阵
(vi, vj)
∈E,arc[i][j]
= 1arc[i][j]
= 0arc[0][0]
,arc[1][1]
,arc[2][2]
,arc[3][3]
都是0
同时这个邻接矩阵是一个对称矩阵。
v1
到v3
有一条边,arc[1][3]
= 1;v3
到v1
也有一条边,arc[3][1]
= 1arc[i][j]
= arc[j][i]
vi
和vj
之间的边(vi, vj)
是否存在,判断arc[i][j]
是否为1
vi
的度,对矩阵第i
行(或第i
列)求和,v1
的度即为1+0+1+1=3vi
的所有邻接点,扫描矩阵第i
行,若arc[i][j]
为1
即为邻接点有向图讲究入度和出度,所以对应的邻接矩阵不是对称矩阵。
v0
到v3
有弧,arc[0][3]
=1v3
带v0
没有弧,arc[3][0]
= 0vi
到vj
的弧
是否存在,判断arc[i][j]
是否为1
vi
的度,对矩阵第i
行求和,再对第i
列求和,两次求和结果相加得到结果。vi
的所有邻接点,扫描矩阵第i
行和第i
列,若为1
即为邻接点每条边上带有权重的图叫做网,这些权重需要一并存储。
(vi, vj)
∈E或
∈E,arc[i][j]
=Wij
i
=j
,arc[i][j]
= 0arc[i][j]
= ∞typedef int VertexType; //顶点类型, 假定为int
typedef char ArcType; //边的类型, 假定为char
typedef struct _mgraph {
VertexType* vexs; //顶点数一维组
ArcType** arc; //邻接矩阵
int num_vexs; //顶点数目
}mgraph;
void initGraph(mgraph** g)
{
int i;
*g = (mgraph*)malloc(sizeof(mgraph));
printf("输入顶点数\n");
scanf("%d", &(*g)->num_vexs);
(*g)->vexs = (VertexType*)malloc(sizeof(VertexType) * (*g)->num_vexs);
(*g)->arc = (ArcType**)malloc(sizeof(ArcType*) * (*g)->num_vexs);
for (i = 0; i < (*g)->num_vexs; i++) {
(*g)->arc[i] = (ArcType*)malloc(sizeof(ArcType) * (*g)->num_vexs);
}
}
void createGraph(mgraph* g)
{
int i, j;
printf("从0开始按照编号顺序输入顶点值\n");
for (int i = 0; i < g->num_vexs; i++) {
scanf("%d", &g->vexs[i]);
}
getchar();
printf("输入邻接矩阵, 矩阵元素之间不要有空格, 用#代替无穷大\n");
for (i = 0; i < g->num_vexs; i++) {
for (j = 0; j < g->num_vexs; j++) {
scanf("%c", &g->arc[i][j]);
}
getchar();
}
}
已知无向图邻接矩阵是一个对称矩阵,只需要存储上三角或下三角即可。以存储下三角为例。
对于一个n*n
的矩阵,压缩后元素个数为
( n + 1 ) n 2 \frac {(n+1)n} 2 2(n+1)n
现在要在压缩后的矩阵中访问arc[i][j]
,对应的下标是
( i + 1 ) i 2 + j \frac {(i+1)i} 2+j 2(i+1)i+j
以arc[3][1]
为例,上面i
行总共有
( 1 + 3 ) 3 2 = 6 个 元 素 \frac {(1+3)3} 2 = 6个元素 2(1+3)3=6个元素
再加上j
,得到下标为7
下三角的元素满足 i >= j
,如果要访问上三角部分,交换i
和j
即可
图的邻接表存储方法将顺序存储结构和链式存储结构相结合
i
的所有邻接点串起来i
的元素表示顶点i
的表头节点。vi
到vj
的弧
是否存在,在下标为i
的位置进入链表查找是否有j
vi
的度,计算对应链表的结点数vi
的所有邻接点,将对应链表逐个输出和无向图不同的是,有向图的边是有方向的,将入度和出度分开存储。
在链表节点中增加权重数据域
typedef int VertexType;//顶点类型 假定为int
typedef char ArcType; //边的类型 假定为char
typedef struct _arcnode {
int adjvex; //邻接点下标
int weight; //权重
struct _arcnode* next;
}arcnode;
typedef struct _vertexnode {
VertexType data; //存储顶点信息
arcnode* head; //指向邻接表表头的指针
}vertexnode;
typedef struct _Lgraph {
vertexnode* arr; //顶点数组
int num_vexs; //顶点个数
}lgraph;
void initGraph(lgraph** g)
{
int n;
*g = (lgraph*)malloc(sizeof(lgraph));
printf("输入顶点数\n");
scanf("%d", &n);
(*g)->num_vexs = n;
(*g)->arr = (vertexnode*)malloc(sizeof(vertexnode) * n);
}
void createGraph(lgraph* g)
{
int i;
arcnode* p, * tail;
printf("从0开始按照编号顺序输入顶点值\n");
for (i = 0; i < g->num_vexs; i++) {
scanf("%d", &g->arr[i].data);
}
getchar();
printf("从0开始按照编号顺序输入邻接表, 所以输入数据都不要用空格分开, 输入#表示当前结点输入完毕\n");
for (i = 0; i < g->num_vexs; i++) {
char adj, weight;
p = (arcnode*)malloc(sizeof(arcnode));
g->arr[i].head = tail = p;
while ((adj = getchar()) != '#' && (weight = getchar()) != '#') {
p = (arcnode*)malloc(sizeof(arcnode));
p->adjvex = adj - '0';
p->weight = weight - '0';
tail->next = p;
tail = p;
}
tail->next = NULL;
getchar();
}
}
n
个顶点和e
条边的无向图,其邻接表有n
个vertexnode
和2e
个arcnode
为了兼顾出度和入度的问题,十字邻接表将邻接表和逆邻接表结合起来
typedef int VertexType;
typedef struct _arcnode {
int tailvex, headvex; //tail为起点, head为终点
struct _arcnode* headlink, * taillink; //headlink指向下一条入边, taillink指向下一条出边
}arcnode;
typedef struct _vexnode {
VertexType data; //顶点数据域
arcnode* firstin, * firstout; //firstin入边链表头指针, firstout出边链表头指针
}vexnode;
firstout
出发,沿着taillink
链走,找到<0,3>
firstin
出发,沿着headlink
链走,找到<1,0>
,<2,0>
邻接多重表用于存储无向图。
在无向图的邻接表存储方法中,每条边(vi, vj)
用两个边结点表示。在操作图时带来不便,例如删除某条边时,需要花时间在整个表中找到这两个节点。
typedef int VertexType;
typedef struct _arcnode {
int ivex;
struct _arcnode* ilink;
int jvex;
struct _arcnode* jlink;
}arcnode;
typedef struct _vexnode {
VertexType data;
arcnode* firstedge;
}vexnode;
找到依附于顶点0
的所有边
0
的firstedge
出发,找到边(v0, v1)
,idex
为0
,顺着ilink
找到下一条边(v3, v0)
,jdex
为0
,顺着jlink
往下找(v0, v2)
,idex
为0
,ilink
为NULL
,结束如果要删掉(v0, v2)
,只需将图中的⑤和⑩删掉即可。
用一个一维数组来存储弧的起点、终点和权重信息。边集数组不适合对顶点相关的操作,它比较适合对边进行处理的操作。
起点 | 终点 | 权重 |
---|
可视化的数据结构
https://visualgo.net/zh/graphds