基本概念
图(graph) 是由顶点集合和顶点间的二元关系集合(即边的集合或弧的集合)组成的数据结构,通常可以用 G(V,E)G(V,E)G(V,E)表示
顶点集合(vertext set) 用 V(G)V(G)V(G) 表示,其中元素称为顶点(vertex),用 u、vu、vu、v 等符号表示。
边的集合(edge set) 用 E(G)E(G)E(G) 表示,其中元素称为边(edge),用 eee 等符号表示
图的阶(order):顶点的个数,通常用 nnn 表示
边数(size): 边的个数,通常用 mmm 表示
无向图
图中所有的边都没有方向性,这种图称为无向图(undirected graph).
每个元素(u,v)(u,v)(u,v)为一对顶点构成的无序对(用圆括号括起来),便是与顶点 uuu 和 vvv 相关联的一条无向边(undirected edge),这条边没有特定的方向。
E(G1)={(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):一个顶点uuu的度数是与它相关联的边的数目,记作 deg(u)deg(u)deg(u)
顶点的出度(outdegree):是以 uuu 为起始的顶点的有向边(即从顶点 uuu 出发的有向边)的数目,记作 od(u)od(u)od(u)
顶点的入度(indegree):是以 uuu 为终点的顶点的有向边(即从顶点 uuu 进入的有向边)的数目,记作 id(u)id(u)id(u)
deg(u)=od(u)+id(u)deg(u)=od(u)+id(u)deg(u)=od(u)+id(u)
定理:在无向图和有向图中,所有顶点度数总和,等于边数的两倍
m=12{∑i=1ndeg(ui)}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:d1,d2,…,dn(n≥2,d1≥1)s:d_1,d_2,\ldots,d_n(n\geq2,d_1\geq1)s:d1,d2,…,dn(n≥2,d1≥1)是可图的,当且仅当序列
s1:d2−1,d3−1,…,dd1+1−1,dd1+2,…,dns_1:d_2-1,d_3-1,\ldots,d_{d1+1}-1,d_{d1+2},\ldots,d_ns1:d2−1,d3−1,…,dd1+1−1,dd1+2,…,dn
是可图的。序列 s1s_1s1 中有 n−1n-1n−1 个非负整数,sss 序列中 d1d_1d1后的前 d1d_1d1 个度数(即d2d_2d2 ~ dd1+1d_{d1+1}dd1+1 减1 后构成 s1s1s1 中前 d1d_1d1 个数
eg:判断 s:5,4,3,3,2,2,2,1,1,1s:5,4,3,3,2,2,2,1,1,1s: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,1s: 7,7,4,3,3,3,2,1s: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):一个表示各顶点之间关系的二维矩阵
Edge[i][j]={1,<i,j>∈E,或(i,j)∈E0Edge[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)
通过邻接矩阵求无向图的度数
deg(i)=∑j=0n−1Edge[i][j]=∑j=0n−1Edge[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]
邻接矩阵第 iii 行所有元素之和,表示顶点 iii 的度数,或者 第iii 列所有元素之和,表示顶点iii 的度数
通过邻接矩阵求有向图的出度与入度
出度:第 iii 行 所有元素之和
od(i)=∑j=0n−1Edge[i][j]od(i)=\sum_{j=0}^{n-1}Edge[i][j]od(i)=j=0∑n−1Edge[i][j]
入度:第 iii 列 所有元素之和
id(i)=∑j=0n−1Edge[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;



本文探讨了图的基本概念,包括无向图和有向图的定义,顶点、边的度量(度数、出度、入度),以及图的性质如阶、边数和奇偶点。还介绍了邻接矩阵和邻接表的表示方法,以及Havel-Hakimi定理用于判断可图性。通过实例演示了如何计算度数、判断图的性质和使用邻接表进行操作。
5万+

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



