📚博客主页:Zhui_Yi_
🔍:上期回顾:JAVA面向对象(上)
❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️
🎇追当今朝天骄,忆顾往昔豪杰。
一、图的概念
1.图的定义
图:Graph=(V,E)
V:顶点(数据元素)的有穷非空集合;
E:边的有穷集合
2.图的分类
图又分为有向图和无向图。
无向图是:每条边都是无方向的
有向图是:每条边都是有方向的
其中开始的位置我们称为弧尾,结束的位置我们称为弧头。
3.图的术语
子图
设有两个图G=(V,{E})、G1=(V1,{E1}),若V1包含于 V,E1 包含于 E,则称 G1是G的子图。
例:(b)、© 是 (a) 的子图
完全图
任意两个点都有一条边相连
也分为有向和无向。
而对于无向完全图来说,如果有n个顶点,那么有多少条边嘞?
对于第n个点来说,它连接了n-1条边
对于第n-1个点来说,它连接了n-1-1条边
对于第n-2个点来说,它连接了n-1-1-1条边
…
那么即:
而对于有向完全图来说,如果有n个顶点,那么有多少条边嘞?
它是无向完全图的2倍,即:n(n-1)
稀疏图
有很少边或弧的图。
稠密图
有较多边或弧的图。
权与网
图中边或弧所具有的相关数称为权。表明从一个顶点到另一个顶点的距离或耗费。带权的图称为网。
4.边
邻接
有边/弧相连的两个顶点之间的关系 存在(vi, vj),则称vi和vj互为邻接点;存在<vi, vj>,则称vi邻接到vj,vj邻接于vi。
vi称为始点或弧尾,vj称为终点或弧头。
关联(依附)
边/弧与顶点之间的关系。存在(vi, vj)/ <vi, vj>, 则称该边/弧关联于vi和vj
对于边<0,1>,称顶点0“邻接到”顶点1,顶点1“邻接于”顶点0,弧<0,1>与顶点0和1“相关联”。
5.点
顶点的度
与该顶点相关联的边的数目,记为TD(v)
在有向图中, 顶点的度等于该顶点的入度与出度之和。
顶点 v 的入度是以 v 为终点的有向边的条数, 记作 ID(v) 顶点 v 的出度是以 v 为始点的有向边的条数, 记作OD(v)
那么当有向图中仅1个顶点的入度为0,其余顶点的入度均为1,此时是何形状?
答:是树!而且是一棵有向树!
关于结点度的重要结论:
若一个图(有向/无向)中有n个顶点和e条边,且每个顶点的度为di (0≤i≤n-1 )则
即图中所有顶点的度之和等于边数的两倍。
6.路径
路径:接续的边构成的顶点序列。
路径长度:路径上边或弧的数目/权值之和。
回路(环):第一个顶点和最后一个顶点相同的路径。
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径。
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。
7.连通图
图G=( V, E)中,若对任意两个顶点v、u,无向图从v到u都是连通的,则称G是连通图;有向图从v到u和从u到v都存在路径则称G是强连通图。
连通分量
无向图G的极大连通子图称为G的连通分量。
极大连通子图意思是:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不再连通。
极小连通图
极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图不再连通。
其他概念
生成树:包含无向图G 所有顶点的极小连通子图。
生成森林:对非连通图,由各个连通分量的生成树的集合。
结论
如果G1是一个有n个顶点的连通无向图,则G1最多有多少条边?最少有多少条边?
为完全无向图时边最多→n(n-1)/2,
为树时边数最少→n-1
如果G2是一个有n个顶点的强连通有向图,则G2最多有多少条边?最少有多少条边?
为完全有向图时边最多 →n(n-1)
为树时边数最少(有弧指向根 →)n
二、图的存储
引入
在前面我们学过的一些知识中,线性表有唯一的前驱和后继,树中有唯一的双亲和多个后继,但在图里,一个点可能有好多前趋和后继。
图中任意两个顶点之间可能存在联系,因此不能以数据元素在内存中的物理位置表示元素之间的关系(内存中物理位置是线性的)
此时还是有两种存储方式:顺序存储结构和链式存储结构
其中我们重点掌握:邻接矩阵和邻接表
数组(邻接矩阵)表示法
我们首先考虑一下,我们会在图中存储哪些信息嘞?
图的定义中包含了顶点和边,所以我们就要存储这两个信息,即:
建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间关系)。
设图 A = (V, E) 有 n 个顶点,则图的邻接矩阵是一个二维数组 A.Edge[n][n],如果图不带权,定义为:
无向图的邻接矩阵表示法
如图,则我们要建立5*5大小的数组,即:
那么我们该如何表示它们之间的关系呢?
有关系的话为1,无关系的话为0,即:
我们再看一下这个图片,有什么规律吗?
关于对角线对称,这很容易理解,在无向图中,一个顶点与另一个顶点有关系的话,那么这个顶点既有出度,也有入度。
那么既然是对称矩阵了,我们是不是可以压缩存储。
那么除了这个,我们还能知道些什么呢?
每个顶点的行或列相加即为这个顶点的度。
特别:完全图的邻接矩阵中,对角元素为0,其余1。
空间复杂度即为:O(n2)。
有向图的邻接矩阵表示法
我们可以建立如下矩阵:
植入关系后:
注意一下:
在有向图的邻接矩阵中, 第i行含义:以结点vi为尾的弧(即出度边); 第i列含义:以结点vi为头的弧(即入度边)。
而规律如下:
分析1:有向图的邻接矩阵可能是不对称的。
分析2:顶点的出度=第i行元素之和
顶点的入度=第i列元素之和
顶点的度=第i行元素之和+第i列元素之和
网(即有权图)的邻接矩阵表示法
无穷大为自己定义。
对于这个网而言,我们该如何存储?
如下图:
邻接矩阵的存储表示(以网为例)
首先我们先设置两个值
#define MaxInt 32767 //表示极大值,即∞
#define MVNum 100 //最大顶点数
然后呢,我们要存储点和边,即我们需要定义两个数组,一个点集,一个边集,在此之前,我们先定义一下其他的:
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType; //假设边的权值类型为整型
然后我们再定义结构体:
typedef struct{
VerTexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的当前点数和边数
}AMGraph;
对于这个图,你认为改存哪些信息?
四个顶点,五条边,以及五个权值。
即
即整个算法思想为:
(1)输入总顶点数和总边数。
(2)依次输入点的信息存入顶点表中。
(3)初始化邻接矩阵,使每个权值初始化为极大值。
(4)构造邻接矩阵。
我们此时定义:
AMGraph &G
故我们要先输入总点数和总边数:
cin>>G.vexnum>>G.arcnum; //输入总顶点数,总边数
然后再输入顶点的信息:
for(i = 0; i<G.vexnum; ++i)
cin>>G.vexs[i]; //依次输入点的信息
然后再初始化邻接矩阵,使权值为最大值(若无权值,则将其初始化为0):
for(i = 0; i<G.vexnum;++i) //初始化邻接矩阵,边的权值均置为极大值
for(j = 0; j<G.vexnum;++j)
G.arcs[i][j] = MaxInt; //有权值
//G.arcs[i][j] = 0;//无权值
即
最后再构造邻接矩阵:
那么此时就会有一个问题,我们将500赋值给A到B的这条边?
那么我们是不是需要先找到A和B,然后再将A和B转化成相对应的位置信息,那么我们该如何找到A和B呢?
故我们需要从点集中进行遍历,返回A和B的位置对应的下标:
int LocateVex(MGraph G,VertexType u)
{//存在则返回u在顶点表中的下标;否则返回-1
int i;
for(i=0;i<G.vexnum;++i)
if(u==G.vexs[i])
return i;
return -1;
}
故构造邻接矩阵:
for(k = 0; k<G.arcnum;++k){ //构造邻接矩阵
cin>>v1>>v2>>w; //无权值的时候,这步骤不需要 //输入一条边依附的顶点及权值
i = LocateVex(G, v1); j = LocateVex(G, v2); //确定v1和v2在G中的位置
G.arcs[i][j] = w; //边<v1, v2>的权值置为w
//G.arcs[i][j] =1;//无权值的时候
G.arcs[j][i] = G.arcs[i][j]; //置<v1, v2>的对称边<v2, v1>的权值为w
}
完整代码如下:
bool CreateUDN(AMGraph &G){
//采用邻接矩阵表示法,创建无向网G
cin>>G.vexnum>>G.arcnum; //输入总顶点数,总边数
for(i = 0; i<G.vexnum; ++i)
cin>>G.vexs[i]; //依次输入点的信息
for(i = 0; i<G.vexnum;++i) //初始化邻接矩阵,边的权值均置为极大值
for(j = 0; j<G.vexnum;++j)
G.arcs[i][j] = MaxInt;
for(k = 0; k<G.arcnum;++k){ //构造邻接矩阵
cin>>v1>>v2>>w; //输入一条边依附的顶点及权值
i = LocateVex(G, v1); j = LocateVex(G, v2); //确定v1和v2在G中的位置
G.arcs[i][j] = w; //边<v1, v2>的权值置为w
G.arcs[j][i] = G.arcs[i][j]; //置<v1, v2>的对称边<v2, v1>的权值为w
}//for
return true;
}
总结
优点:容易实现图的操作,如:求某顶点的度、判断顶点之间是否有边、找顶点的邻接点等等。
缺点:n个顶点需要n*n个单元存储边;空间效率为O(n2)。 对稀疏图而言尤其浪费空间。
邻接表(链式)表示法
既然是链式,那么我们可以和单链表结合在一起:
存储顶点信息:
每个单链表有一个头结点(设为2个域),存vi信息;
每个单链表的头结点另外用顺序存储结构存储。
顺序存储,是这样吗:
A |
---|
B |
C |
D |
顶点是存下来了,那么它指向的那个顶点嘞?
我们可以在搞一个域,用来存储第一个指向的结点即:
那么存储完点了,该如何存储边呢?
存储边的信息
每个顶点vi 建立一个单链表,把与vi有关联的边的信息链接起来,每个结点设为3个域。
存储表示
我们首先先看一下这个图片,我们是应该先存储黄色部分,还是蓝色部分?
当然是黄色部分,因为我们蓝色部分要指向黄色部分,如果黄色部分未声明的话,那么肯定会悬空。
即:
typedef struct ArcNode{ //边结点
int adjvex; //该边所指向的顶点的位置
struct ArcNode * nextarc; //指向下一条边的指针
OtherInfo info; //和边相关的信息
}ArcNode;
然后我们就要声明蓝色部分的:
蓝色部分包含什么?
一个结点和一个指针:
typedef struct VNode{
VerTexType data; //顶点信息
ArcNode * firstarc; //指向第一条依附该顶点的边的指针
}VNode; //AdjList表示邻接表类型
此时是不是声明完了?
当然没有啊,我们还没存储顶点数和边数呢!
typedef struct{
VNode v[MVNum]; //邻接表
int vexnum, arcnum; //图的当前顶点数和边数
}ALGraph;
整体代码如下:
#define MVNum 100 //最大顶点数
typedef struct ArcNode{ //边结点
int adjvex; //该边所指向的顶点的位置
struct ArcNode * nextarc; //指向下一条边的指针
OtherInfo info; //和边相关的信息
}ArcNode;
typedef struct VNode{
VerTexType data; //顶点信息
ArcNode * firstarc; //指向第一条依附该顶点的边的指针
}VNode; //AdjList表示邻接表类型
typedef struct{
VNode v[MVNum]; //邻接表
int vexnum, arcnum; //图的当前顶点数和边数
}ALGraph;
无向图的邻接表表示
我们首先看这个图片,有几个顶点?
5个,我们把它们存到顺序表中:
然后我们再根据每一个结点与其他结点相连来扩充:
我们看上表,能发现什么?邻接表是否唯一?
当然不唯一,你不知道每个结点后的顺序排列如何。比如,v1后面可以改为:1,3
空间效率为O(n+2e)其中e为边数。
有向图的邻接表表示
空间效率为O(n+e)
出度OD(Vi)=单链出边表中链接的结点数
入度ID(Vi)=整个链表中邻接点域为Vi的结点个数
度:TD(Vi)=OD+ID
网的邻接表表示
对于带权值的网,可以在边表定义中增加一个数据域存储权值。
总结
由于后面的知识有点遗忘,而且我太久没写东西了,就先发着一部分,抱歉,各位!!!