文章目录
一. 图的相关概念和术语
概念
- 在图形结构中,结点之间的关系可以是任意的,图中任意个数元素之间都可能相关。
- 任何复杂的图都是由顶点和边(弧)构成的。
- 采用形式化的定义,图G由两个集合V和E组成,记为G=(V,E),其中V是顶点的有限集合,记为V(G),E是连接V中两个不同顶点的边(弧)的有限集合,记为E(G).
- 对于含有n个顶点的图,通常用字母或自然数来唯一标识图中顶点(顶点的编号)。
如在所给的图中,顶点可以使用字符Vi来表示,也可以用数字i(0<=i<=n-1)来表示第i个顶点Vi的编号。
无向图
- 对于一个图G,若边集E(G)为无向边,则称该图为无向图。如下图。
- 在一个无向图中,若存在一条边 (i,j),则称顶点i,j为该边的两个顶点,并称它们互为邻接点(或者相邻点)。如上图,0和1互为邻接点。
有向图
- 对于一个图G,若边集E(G)为有向边,则称该图为有向图。如下图。
- 在一个有向图中,若存在一条边(弧)<i,j>,则称此边(弧)是顶点(弧尾)i 的一条出边,同时也是顶点(弧头)j 的一条入边,称顶点 i 和 j 分别是此边的起始顶点(简称为起点)和终止顶点(简称为终点)。
如上图,对于边<0,1>,该边是顶点0的出边,顶点1是入边,同时,顶点0称为起点,顶点1称为终点。 - 对于边<0,1>,则称顶点0“邻接到”顶点1,顶点1“邻接自”顶点0,弧<0,1>与顶点0和1“相关联”。
度,入度和出度
- 顶点v的度记为D(v)。
- 对于无向图,每个顶点v的度定义为和v顶点相关联的边的数目。
- 对于有向图,顶点的度分为入度和出度。
- 入度是以该顶点为弧头(终点)的入边数目。
- 出度是以该顶点为弧尾(起点)的出边数目。
- 该顶点的度D(v)=其出度ID(v)+其入度OD(v)。
- 若一个图中(无论有向图还是无向图)中有n个顶点和e条边,每个顶点的度为Di(0<=i<=n-1),则有:
即一个图中所有顶点的度之和=边数的两倍。
子图
- 设有两个图G=(V,E)和G’=(V’,E’),若V’是V的子集,且E’是E的子集,则称G’是G的子图。
- 注:对于一个图G=(V,E),V’是V的子集,且E’是E的子集。而(V’,E’)可能不是一个图,所以由V的子集和E的子集并非一定构成G的子图。
完全无向图和完全有向图
- 对于无向图,若具有n(n-1)/2条边,则称之为完全无向图。如左图。
- 对于有向图,若具有n(n-1)条边,则称之为完全有向图。如右图。
稀疏图和稠密图
边数较少(边数e<<log2(n),其中n为顶点数)的图称为稀疏图,边数较多的称为稠密图。
简单路径,回路(环)
- 若一条路径的顶点序列中顶点不重复出现,则该路径为简单路径。
- 若一条路径上的开始点和结束点为同一个顶点,则称该路径为回路(环)。
- 除开始点和结束点相同外,其余顶点不重复出现的回路称为简单回路(简单环)。
连通,连通图和连通分量(无向图)
- 在无向图G中,若从顶点 i 到顶点 j 有路径,则称顶点 i 和 j 是连通的。
- 若G中任意两个顶点都是连通的,则称无向图G为连通图,否则为非连通图。
- 无向图G中极大连通子图称为G的连通分量。
强连通图和强联通分量(有向图)
- 在有向图G中,若任意两个顶点 i 和 j 都是连通的,即从顶点 i 到 j 和从顶点 j 到 i 都存在路径,则称该图是强连通图。
- 有向图G中极大强联通子图称为G的强联通分量。
权和网
在一个图中,每条边可以标上具有某种含义的数值,该数值称为该边的权,边上带权的图称为带权图,也称为网。一般规定所有边的权均为非负数。
二. 图的存储结构—邻接矩阵
邻接矩阵是表示顶点之间相邻关系的矩阵。设G=(V,E)是具有n个顶点的图,顶点编号依次为0,1,…,n-1,则G的邻接矩阵是具有如下定义的n阶方阵A。
若G是不带权的图:
若G是带权图或网:
Wij 为边(i,j)或<i,j>的权
无向图
- 无向图的矩阵一定是一个对称矩阵。
- 每一行或每一列代表相应顶点的度。
有向图
- 有向图的矩阵不一定是一个对称矩阵。
- 每一行代表相应顶点的出度。
- 每一列代表相应顶点的入度
有权图(网)
- 有向图时不一定是一个对称矩阵。
- 入度和出度与有向图的计算方式一样。
图的邻接矩阵的特点
- 对于n个顶点e条边的图采用邻接矩阵存储时占据存储空间为O(n*n),与边数e无关(不考虑压缩存储),特别适合存储稠密图。
- 任何图的邻接矩阵表示是唯一的。
- 图采用邻接矩阵存储时判断两个顶点 i 和 j 之间是否有边十分容易。
图的邻接矩阵类型声明
#define INF INT_MAX //用整型最大值代替∞
#define MAXVEX 30 //图中最大顶点个数
typedef char VertexType[4]; //定义VertexType为字符串类型
typedef struct vertex
{
int adjvex; //顶点编号
VertexType data; //顶点的信息
}VType; //顶点类型
typedef struct graph
{
int n,el //n为实际顶点数,e为实际边数
VType vexs[MAXVEX]; //顶点集合
int edges[MAXVEX][MAXVEX]; //边的集合
}MGraph; //图的邻接矩阵类型
三. 图的存储结构—邻接表
- 邻接表是图的一种链式存储结构。
- 在邻接表中,对图中每个顶点建立一个单链表,把该顶点的所有相邻点串起来。
- 所有的头指针构成一个数组,称为表头结点数组,用adjlist表示,第 i 个单链表adjlist[ i ] 中的结点表示依附于顶点 i 的边,也就是说头指针数组元素的下标与顶点编号一致。
- 表头结点的设置:
- 单链表中的每个结点由3个域组成:
顶点域adjvex : 用以指示该相邻点在头结点数组中的下标
权值域weight : 存放对应边的权值
指针域naxtarc : 用以指向依附于顶点i的下一条边所对应的结点
- 对于不带权的图,weight域可以不设置;对于带权图,weight域设置为相应边的权值
无向图
每个单链表的长度就是相应顶点的度。
有向图
每个单链表的长度表示相应点的出度大小。
对所有的单链表进行遍历可以计算出相应点的入度大小。
有向图的逆邻接表
在有向图中求入度大小很麻烦,因此建立一个有向图的逆邻接表,即对每个顶点Vi建立一个链接以Vi为头的弧的表。
每条单链表的长度为相应点的入度大小。
图的邻接表的特点
- 对于n个顶点e条边的图采用邻接矩阵存储时占据存储空间为O(n+e),与边数e有关,特别适合存储稀疏图。
- 任何图的邻接矩阵表示不一定是唯一的。这是因为邻接表的每个单链表中,各结点的顺序是任意的。
- 图采用邻接表存储时查找一个顶点 的所有相邻顶点十分容易。
图的邻接表存储结构的类型声明
typedef char VertexType[10]; //VertexType为字符串类型
typedef struct edgenode
{
int adjvex; //相邻点序号
int weight; //边的权值
struct edgenode *nextatc; // 下一条边的顶点
}ArcNode; //每个顶点建立的单链表中边结点的类型
typedef struct vexnode
{
VertexType data; //存放一个顶点的信息
ArcNode *firstarc; //指向第一条边结点
}VHeadNode; //单链表的头结点类型
typedef struct
{
int n,e; //n为实际顶点数,e为实际边数
VHeafNode adjlist[MAXVEX];//单链表头结点数组
}ALGraph; //图的邻接表类型
四. 邻接矩阵的相关算法
建立图的邻接矩阵算法
void CreateGraph(MGraph &g, int a[][MAXVEX],int n, int e)
{
int i,j;
g.n=n;
g.e=e;
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
g.edges[i][j]=A[i][j];
}
}
}
求无向图顶定度算法
int Degree(MGraph g, int v)
{
int i,d=0;
if(v<0 || v>=g.n)
return -1; //顶点编号错误返回-1
for(i=0;i<g.n;i++)
{
if(g.edges[v][i]>0 && g.edges[v][i]<INF)
d++; //统计第v行既不为0也不为∞的边数即度
}
return d;
}
求有向图顶点度算法
int Degree(MGraph g, int v)
{
int i,d1=0,d2=0,d;
if(v<0 || v>=g.n)
return -1; //顶点编号错误返回-1
for(i=0;i<g.n;i++)
{
if(g.edges[v][i]>0 && g.edges[v][i]<INF)
d1++; //统计第v行既不为0也不为∞的边数即出度
}
for(i=0;i<g.n;i++)
{
if(g.edges[i][v]>0 && g.edges[i][v]<INF)
d2++; //统计第v列既不为0也不为∞的边数即入度
}
d=d1+d2;
return d;
}
五. 邻接表的相关算法
建立图的邻接表算法
基本思路:
先创建邻接表头结点数组,并置所有头结点的firstarc为NULL。
遍历邻接矩阵数组A,当A[i][j]不等0且不等∞时,说明有一条从 i 到 j 的边,建立一个结点p,并置其adjvex域为j,其weight域为A[i][j],将p结点插入到顶点i的单链表头部
void CreateGraph(ALGraph *&G, int A[][MAXVEX],int n, int e)
{
int i,j;
ArcNode *p;
G=(ALGraph *)malloc(sizeof(ALGraph));
G->n=n;
G->e=e;
for(i=0;i<G->n;i++) //邻接表中所有头结点的指针域置空
G->adjlist[i].firstarc=NULL;
for(i=0;i<G->n;i++) //检查A中每个元素
{
for(j=G->n-1;j>=0;j--)
{
if(A[i][j]>0 && A[i][j]<INF)//存在一条边
{
p=(ArcNode *)malloc(sizeof(ArcNode))//创建结点p
p->adjvex=j;
p->weight=A[i][j];
p->nextarc=G->adjlist.firstarc; //头插法插入p
G->adhlist.firstarc=p;
}
}
}
}
求无向图顶定度算法
int Degree(ALGraph *G, int v)
{
int d=0;
ArcNode *p;
if(v<0 || v>=G->n)
return -1; //顶点编号错误返回-1
p=G->adjlist.firstarc;
while(p!=NULL)//统计v顶点的单链表中边结点个数即度
{
d++;
p=p->nextarc;
}
return d;
}
求有向图顶点度算法
int Degree(ALGraph *G, int v)
{
int i,d1=0,d2=0,d;
ArcNode *p;
if(v<0 || v>=G->n)
return -1; //顶点编号错误返回-1
p=G->adjlist.firstarc;
while(p!=NULL)//统计v顶点的单链表中边结点个数即出度
{
d1++;
p=p->nextarc;
}
for(i=0;i<G->n;i++)//统计边结点中adjvex为v的个数即入度
{
p=G->adjlist.firstarc;
while(p!=NULL)
{
d2++;
p=p->nextarc;
}
}
d=d1+d2;
return d;
}