基本概念
图(graph) 是由顶点集合和顶点间的二元关系集合(即边的集合或弧的集合)组成的数据结构,通常可以用
G
(
V
,
E
)
G(V,E)
G(V,E)表示
顶点集合(vertext set) 用
V
(
G
)
V(G)
V(G) 表示,其中元素称为顶点(vertex),用
u
、
v
u、v
u、v 等符号表示。
边的集合(edge set) 用
E
(
G
)
E(G)
E(G) 表示,其中元素称为边(edge),用
e
e
e 等符号表示
图的阶(order):顶点的个数,通常用
n
n
n 表示
边数(size): 边的个数,通常用
m
m
m 表示
无向图
图中所有的边都没有方向性,这种图称为无向图(undirected graph).
每个元素
(
u
,
v
)
(u,v)
(u,v)为一对顶点构成的无序对(用圆括号括起来),便是与顶点
u
u
u 和
v
v
v 相关联的一条无向边(undirected edge),这条边没有特定的方向。
E
(
G
1
)
=
{
(
1
,
2
)
,
(
1
,
3
)
,
(
2
,
3
)
,
(
2
,
4
)
,
(
2
,
5
)
,
(
2
,
6
)
,
(
3
,
4
)
,
(
3
,
5
)
,
(
4
,
5
)
}
E(G_1)=\{ (1,2),(1,3),(2,3),(2,4),(2,5),(2,6),(3,4),(3,5),(4,5) \}
E(G1)={(1,2),(1,3),(2,3),(2,4),(2,5),(2,6),(3,4),(3,5),(4,5)}
有向图
图中所有边都是有方向性的,这种图称为有向图,有向图的边也可以称为弧(arc)
度数
顶点的度数(degree):一个顶点
u
u
u的度数是与它相关联的边的数目,记作
d
e
g
(
u
)
deg(u)
deg(u)
顶点的出度(outdegree):是以
u
u
u 为起始的顶点的有向边(即从顶点
u
u
u 出发的有向边)的数目,记作
o
d
(
u
)
od(u)
od(u)
顶点的入度(indegree):是以
u
u
u 为终点的顶点的有向边(即从顶点
u
u
u 进入的有向边)的数目,记作
i
d
(
u
)
id(u)
id(u)
d
e
g
(
u
)
=
o
d
(
u
)
+
i
d
(
u
)
deg(u)=od(u)+id(u)
deg(u)=od(u)+id(u)
定理:在无向图和有向图中,所有顶点度数总和,等于边数的两倍
m
=
1
2
{
∑
i
=
1
n
d
e
g
(
u
i
)
}
m=\frac{1}{2}\{ \sum_{i=1}^ndeg(u_i)\}
m=21{i=1∑ndeg(ui)}
偶点(even vertex):度数为偶数的顶点称为偶点
奇点(odd vertex):度数为奇数的顶点称为奇点
推论:每个图都有偶数个奇点
孤立顶点(isolated vertex):度数为0的顶点,称为孤立顶点。孤立顶点不与其他任何顶点邻接。
叶(leaf):度数为1的顶点,称为叶顶点,也称端点(end vertex), 其他顶点称为非叶顶点
图的最小度(minimum degree):图所有顶点最小的度数,记为
δ
(
G
)
\delta(G)
δ(G)
图的最大度(maximum degree):图所有顶点的最大的度数,记为
Δ
(
G
)
\Delta(G)
Δ(G)
Havel-Hakimi 定理(判断是否可图)
由非负整数组成的非增序列
s
:
d
1
,
d
2
,
…
,
d
n
(
n
≥
2
,
d
1
≥
1
)
s:d_1,d_2,\ldots,d_n(n\geq2,d_1\geq1)
s:d1,d2,…,dn(n≥2,d1≥1)是可图的,当且仅当序列
s
1
:
d
2
−
1
,
d
3
−
1
,
…
,
d
d
1
+
1
−
1
,
d
d
1
+
2
,
…
,
d
n
s_1:d_2-1,d_3-1,\ldots,d_{d1+1}-1,d_{d1+2},\ldots,d_n
s1:d2−1,d3−1,…,dd1+1−1,dd1+2,…,dn
是可图的。序列
s
1
s_1
s1 中有
n
−
1
n-1
n−1 个非负整数,
s
s
s 序列中
d
1
d_1
d1后的前
d
1
d_1
d1 个度数(即
d
2
d_2
d2 ~
d
d
1
+
1
d_{d1+1}
dd1+1 减1 后构成
s
1
s1
s1 中前
d
1
d_1
d1 个数
eg:判断 s : 5 , 4 , 3 , 3 , 2 , 2 , 2 , 1 , 1 , 1 s:5,4,3,3,2,2,2,1,1,1 s:5,4,3,3,2,2,2,1,1,1 是否可图?
- 删除序列 s 的首项5, 对其后的5项每项减1,得到 3,2,2,1,1,2,1,1,1, 重新排序 3,2,2,2,1,1,1,1,1
- 继续删除序列的首项3,对其后的3项每项减1,得到:1,1,1,1,1,1,1,1
- 如下图推导,可判断序列可图
eg 判断序列
s
:
7
,
7
,
4
,
3
,
3
,
3
,
2
,
1
s: 7,7,4,3,3,3,2,1
s:7,7,4,3,3,3,2,1 是否可图的。
- 删除序列 s 的首项7,对其后面的7项每项减1,得到 6,3,2,2,2,1,0
- 删除首项6,对其后面6项每项减1,得到 2,1,1,1,0,-1
出现负数,序列是不可图的
邻接矩阵
邻接矩阵(adjacency matrix):一个表示各顶点之间关系的二维矩阵
E
d
g
e
[
i
]
[
j
]
=
{
1
,
<
i
,
j
>
∈
E
,
或
(
i
,
j
)
∈
E
0
Edge[i][j] = \begin{cases} 1 ,<i,j>\in E, 或(i,j) \in E\\ 0\\ \end{cases}
Edge[i][j]={1,<i,j>∈E,或(i,j)∈E0
无向图 的邻接矩阵是沿主对角线 对称 的
有向图 的邻接矩阵 不一定 沿主对角线对称
无法用邻接矩阵存储: 图中存在自身环(self loop, 连接某个顶点自身的边) 和 重边 (multiple edge, 多条边的起点一样,终点也一样,亦称平行边,parallel edge)
通过邻接矩阵求无向图的度数
d
e
g
(
i
)
=
∑
j
=
0
n
−
1
E
d
g
e
[
i
]
[
j
]
=
∑
j
=
0
n
−
1
E
d
g
e
[
j
]
[
i
]
deg(i)=\sum_{j=0}^{n-1}Edge[i][j]=\sum_{j=0}^{n-1}Edge[j][i]
deg(i)=j=0∑n−1Edge[i][j]=j=0∑n−1Edge[j][i]
邻接矩阵第
i
i
i 行所有元素之和,表示顶点
i
i
i 的度数,或者 第
i
i
i 列所有元素之和,表示顶点
i
i
i 的度数
通过邻接矩阵求有向图的出度与入度
出度:第
i
i
i 行 所有元素之和
o
d
(
i
)
=
∑
j
=
0
n
−
1
E
d
g
e
[
i
]
[
j
]
od(i)=\sum_{j=0}^{n-1}Edge[i][j]
od(i)=j=0∑n−1Edge[i][j]
入度:第
i
i
i 列 所有元素之和
i
d
(
i
)
=
∑
j
=
0
n
−
1
E
d
g
e
[
j
]
[
i
]
id(i)=\sum_{j=0}^{n-1}Edge[j][i]
id(i)=j=0∑n−1Edge[j][i]
邻接表
邻接表(adjacency list): 把从同一个顶点发出的边链接在同一个称为 边链表 的单链表中
边结点: 边链表中的每个结点代表一条边,有两个域
- 该边终点的序号
- 指向下一个边结点的指针
顶点数组:用于存储顶点信息,每个元素有两个成员
3. 一个成员用于存储顶点信息
4. 一个成员为该顶点的边链表的表头指针,指向该顶点的边链表
出边表:在邻接表每个顶点的边链表中,各边结点所表示的边都是从该顶点发出的边。
出边表的出度 : 统计出边表每个顶点的边链表中边结点的个数
逆邻接表(入边表):把进入同一个顶点的边链接在同一个边链表中。
入边表的入度 : 统计入边表每个顶点的边链表中边结点的个数
使用 出边表 表示有向图的邻接表
使用 入边表 表示有向图的邻接表
无向图 如下,使用 邻接表 表示
eg1 用邻接矩阵存储有向图,并输出各顶点的出入度
输入 第一行为顶点个数及边数,第二行开始为边的关系
7 9
1 2
2 3
2 5
2 6
3 5
4 3
5 2
5 4
6 7
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAXN 100
int Edge[MAXN][MAXN];
int main(){
int a,b;
int i,j,count;
int n,m; // 顶点个数、边数
int u,v; // 边的起点和终点
int od,id; // 顶点的出度、入度
FILE *file = fopen("1_1.txt","r");
if(!file){
printf("Fail to open file....\n");
return;
}
memset(Edge,0,sizeof(Edge));
count = 0;
while(1){
if(EOF == fscanf(file,"%d %d",&a,&b))
break;
count = count + 1;
if(count==1){
n=a;
m=b;
}
if (count != 1){
u=a;
v=b;
Edge[u-1][v-1]=1;
}
}
fclose(file);
printf("%d %d\n",n,m);
// 打印邻接矩阵
for(i=0;i<n;i++){
for(j=0;j<n;j++){
printf("%d,",Edge[i][j]);
}
printf("\n");
}
// 求各顶点的出度
printf("\n各顶点的出度为:\n");
for(i=0; i<n; i++){
od = 0;
for(j=0;j<n;j++){
od = od + Edge[i][j]; // 累加第i行
}
printf("%d ",od);
}
printf("\n");
printf("各顶点的入度为:\n");
// 求各顶点的入度
for(i=0;i<n;i++){
id = 0;
for(j=0;j<n;j++)
id =id + Edge[j][i]; //累计第i列
printf("%d ",id);
}
printf("\n");
return 0;
}
eg2 判断是否可图
输入: 测试组数,每组输入顶点个数,及顶点的度数
输出: 邻接矩阵
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 15
struct vertex{
int degree; //顶点的度
int index; //顶点的序号
}v[N];
int cmp(const void *a,const void *b)
{
return (((struct vertex*)b)->degree) -( ((struct vertex*)a)->degree);
}
int main(){
int r,k,p,q; // 循环遍历
int i,j; // 顶点序号
int d1; // 对剩下序列排列后的第1个顶点(度数最大的顶点)的度数
int T,n; // 测试数据的个数,顶点的个数
int Edge[N][N],flag; // 邻接矩阵,是否存在合理相邻关系的标记
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(i=0;i<n;i++){
scanf("%d",&v[i].degree);
v[i].index=i; // 按输入顺序给每个湖泊(顶点)编号
}
memset(Edge,0,sizeof(Edge));
flag=1;
for(k=0;k<n&&flag;k++){
qsort(v+k,n-k,sizeof(struct vertex),cmp); // 对数组进行快速排序,从大到小
i=v[k].index;
d1=v[k].degree;
if(d1>n-k-1)
flag = 0;
for(r=1;r<=d1&&flag;r++){
j=v[k+r].index;
if(v[k+r].degree<=0) flag=0;
v[k+r].degree--;
Edge[i][j]=Edge[j][i]=1;
}
}
if(flag){
puts("Yes");
for(p=0;p<n;p++){
for(q=0;q<n;q++){
if(q) printf(" ");
printf("%d ",Edge[p][q]);
}
puts(" ");
}
}
else
puts("No");
if(T) puts("");
}
return 0;
}
在这里插入图片描述
eg3 用邻接表存储有向图,求各顶点出度与入读
输入:第一行为顶点个数及边数,第二行开始为边的关系
输出:第一行为顶点的出度,第二行为顶点的入度
测试数据1
7 9
1 2
2 3
2 5
2 6
3 5
4 3
5 2
5 4
6 7
测试数据2:
4 7
1 4
2 1
2 2
2 3
2 3
4 2
4 3
#include<stdio.h>
#include<stdlib.h>
#define MAXN 100
struct ArcNode{ //边节点
int adjvex; // 有向边的另一个邻接点的序号
struct ArcNode *nextarc; // 指向下一个边结点的指针
};
struct VNode{ //顶点
struct ArcNode *head1; // 出边表的表头指针
struct ArcNode *head2; // 入边表的表头指针
};
struct LGraph{ // 图的邻接表存储结构
struct VNode vertexs[MAXN]; //顶点数组,每个顶点为一个结构体
int vexnum,arcnum; //顶点数和边数
}; //图(邻接表存储)
struct LGraph lg;
void CreateLG(){ // 采用邻接表存储表示,构造有向图 G
int i=0,a,b;
int v1,v2;
struct ArcNode *pi;
for(i=0;i<lg.vexnum;i++){
lg.vertexs[i].head1=lg.vertexs[i].head2=NULL;
}
FILE *file = fopen("1_3_demo.txt","r");
if(!file){
printf("创建图期间 Fail to open file....\n");
return 0;
}
int count = 0;
while(1){
if(EOF == fscanf(file,"%d %d",&a,&b))
break;
count = count+1;
if(count!=1){
v1=a;
v2=b;
v1--;
v2--;
pi = (struct ArcNode*)calloc(2,sizeof(struct ArcNode));
pi->adjvex = v2;
pi->nextarc = lg.vertexs[v1].head1;
lg.vertexs[v1].head1 = pi;
pi = (struct ArcNode*)calloc(2,sizeof(struct ArcNode));
pi->nextarc = v1;
pi->nextarc = lg.vertexs[v2].head2;
lg.vertexs[v2].head2 = pi;
}
}
fclose(file);
}
// 释放图G邻接表各顶点的边链表中的所有节点所占用的存储空间
void DeleteLG(){
int i;
struct ArcNode *pi;
for(i=0;i<lg.vexnum;i++){
pi = lg.vertexs[i].head1;
while(pi!=NULL){
lg.vertexs[i].head1 = pi->nextarc;
free(pi);
pi = lg.vertexs[i].head1;
}
pi = lg.vertexs[i].head2;
while(pi!=NULL){
lg.vertexs[i].head2 = pi->nextarc;
free(pi);
pi = lg.vertexs[i].head2;
}
}
}
int main(){
int i,a,b;
int id,od;
struct ArcNode *pi;
FILE *file = fopen("1_3_demo.txt","r");
if(!file){
printf("创建图期间 Fail to open file....\n");
return 0;
}
int count = 0;
while(1){
if(EOF == fscanf(file,"%d %d",&a,&b))
break;
count = count + 1;
if(count==1){
lg.vexnum=a;
lg.arcnum=b;
}
}
fclose(file);
printf("文件中图的顶点个数及边数读取完成...\n");
CreateLG();
for(i=0;i<lg.vexnum;i++){
od=0;
pi=lg.vertexs[i].head1;
while(pi!=NULL){
od++;
pi=pi->nextarc;
}
if(i==0) printf("%d ",od);
else
printf("%d ",od);
}
printf("\n");
for(i=0;i<lg.vexnum;i++){
id=0;
pi=lg.vertexs[i].head2;
while(pi!=NULL){
id++;
pi = pi->nextarc;
}
if(i==0)
printf("%d ",id);
else
printf("%d ",id);
}
printf("\n");
DeleteLG();
return 0;
}
核心代码计算机详细过程
pi = (struct ArcNode*)calloc(2,sizeof(struct ArcNode));
pi->adjvex = v2;
pi->nextarc = lg.vertexs[v1].head1;
lg.vertexs[v1].head1 = pi;