写在前面:博主是一位普普通通的19届二本大学生,平时最大的爱好就是听听歌,逛逛B站。博主很喜欢的一句话
花开堪折直须折,莫待无花空折枝
:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事,做自己以后不会留有遗憾的事,做自己觉得有意义的事,不浪费这大好的青春年华。博主写博客目的是记录所学到的知识并方便自己复习,在记录知识的同时获得部分浏览量,得到更多人的认可,满足小小的成就感,同时在写博客的途中结交更多志同道合的朋友,让自己在技术的路上并不孤单。
目录:
1.图的基本概念
无向图有向图
完全有向图完全无向图
弧头和弧尾
入度和出度
(V1,V2) 和 <V1,V2>
集合 VR 的含义
路径和回路
权和网的含义
稀疏图和稠密图
连通图和强连通图
生成树生成森林
极大连通图和极小连通图
2.图的顺序存储结构(邻接矩阵)
3.图的邻接表存储结构
4.图的十字链表存储结构
1.图的基本概念
1.1无向图有向图
1.无向图:如果任意两个顶点之间的边都是无向边,那么该图称为无向图
2.有向图:如果任意两个顶点之间的边都是有向边,那么该图称为有向图
无向图:
有向图:
1.2完全有向图完全无向图
完全图:若图中各个顶点都与除自身外的其他顶点有关系,这样的无向图称为完全图,完全图分为完全有向图和完全无向图
完全无向图:
完全有向图:
在n个顶点的无向完全图中,有n(n-1)/2条边。在n个顶点的有向完全图中有n(n-1)条边
1.3弧头和弧尾
有向图中,无箭头一端的顶点通常被称为"初始点"或"弧尾",箭头直线的顶点被称为"终端点"或"弧头"。
1.4入度和出度
对于有向图中的一个顶点 V 来说,箭头指向 V 的弧的数量为 V 的入度(InDegree,记为 ID(V));箭头远离 V 的弧的数量为 V 的出度(OutDegree,记为OD(V))。某个顶点的度等于这个顶点的入度加出度
1.5(V1,V2) 和 <V1,V2>
无向图中描述两顶点(V1 和 V2)之间的关系可以用 (V1,V2) 来表示,而有向图中描述从 V1 到 V2 的"单向"关系用 <V1,V2> 来表示。
由于图存储结构中顶点之间的关系是用线来表示的,因此 (V1,V2) 还可以用来表示无向图中连接 V1 和 V2 的线,又称为边
;同样,<V1,V2> 也可用来表示有向图中从 V1 到 V2 带方向的线,又称为弧
。
1.6集合 VR 的含义
图中习惯用 VR 表示图中所有顶点之间关系的集合。例如,图 1 中无向图的集合 VR={(v1,v2),(v1,v4),(v1,v3),(v3,v4)},图 2 中有向图的集合 VR={<v1,v2>,<v1,v3>,<v3,v4>,<v4,v1>}。
1.7路径和回路
无论是无向图还是有向图,从一个顶点到另一顶点途径的所有顶点组成的序列(包含这两个顶点),称为一条路径。如果路径中第一个顶点和最后一个顶点相同,则此路径称为
回路
(或环
)。
并且,若路径中各顶点都不重复,此路径又被称为简单路径
;同样,若回路中的顶点互不重复,此回路被称为简单回路
(或简单环
)。
1.8权和网的含义
在某些实际场景中,图中的每条边(或弧)会赋予一个实数来表示一定的含义,这种与边(或弧)相匹配的实数被称为"权",而带权的图通常称为网。如图 3 所示,就是一个网结构如下:
1.9稀疏图和稠密图
稀疏图和稠密图:这两种图是相对存在的,即如果图中具有很少的边(或弧),此图就称为"稀疏图";反之,则称此图为"稠密图"。
稀疏和稠密的判断条件是:e<nlogn
,其中 e 表示图中边(或弧)的数量
,n 表示图中顶点的数量
。如果式子成立,则为稀疏图;反之为稠密图。
1.10连通图和强连通图
连通图:无向图中,如果任意两个顶点之间都能够连通。
强连通图:有向图中,若任意两个顶点 Vi 和 Vj,满足从 Vi 到 Vj 以及从 Vj 到 Vi 都连通,也就是都含有至少一条通路,则称此有向图为强连通图。
1.连通图:
若无向图不是连通图,但图中存储某个子图符合连通图的性质,则称该子图为连通分量。
注意:
1.由图中部分顶点和边构成的图为该图的一个子图,但这里的子图指的是图中"最大"的连通子图(也称"极大连通子图")
。
2.连通分量的提出是以"整个无向图不是连通图"为前提的,因为如果无向图是连通图,则其无法分解出多个最大连通子图,因为图中所有的顶点之间都是连通的。也就是说连通图只有一个连通分量就是他自己
这里有几个注意点:
- 在连通图中只有一个最大连通子图也就是自己本身
- 当无向图不是连通图的时候,有多个连通子图
- 连通分量=极大连通子图
如下左边是一个无向非连通图,右边是他的三个连通分量
2.强连通图:
若有向图本身不是强连通图,但其包含的最大连通子图具有强连通图的性质,则称该子图为强连通分量,如下的非强连通图就有两个强连通分量
1.11生成树生成森林
对
连通图
进行遍历,过程中所经过的边和顶点的组合可看做是一棵普通树,通常称为生成树。生成树也理解为包含所有顶点的极小连通子图(极小连通子图后边会讲)
如下左边的连通图对应右边的两种生成树
连通图中,由于任意两顶点之间可能含有多条通路,遍历连通图的方式有多种,往往一张连通图可能有多种不同的生成树与之对应
连通图中的生成树必须满足以下 2 个条件:
1.包含连通图中所有的顶点;
2.任意两顶点之间有且仅有一条通路;
因此,连通图的生成树具有这样的特征,即生成树中边的数量 = 顶点数 - 1
。
生成树是对应连通图来说的生成森林是对应非连通图来说的,我们知道,非连通图可分解为多个连通分量,而每个连通分量又各自对应多个生成树(至少是 1 棵),因此与整个非连通图相对应的,是由多棵生成树组成的生成森林
举个例子:
左边是连通图,右边是连通分量,那么它所对应的生成森林为:
1.12极大连通子图和极小连通子图
首先明确极大连通子图和极小连通子图都是在无向图中进行讨论的,极大连通子图上边已经讲了,我们看看极小连通子图
极小连通子图:一个子图删除任意一条边子图不再连通
2.图的顺序存储结构(邻接矩阵)
2.1顺序存储的概念
存储图中各顶点本身数据,使用一维数组就足够了;存储顶点之间的关系时,要记录每个顶点和其它所有顶点之间的关系,所以需要使用二维数组。所以我们要存储整张图的逻辑和物理信息就必须使用两个数组一维数组存储数据,二维数组存储节点之间的关系
图,包括无向图和有向图;网,是指带权的图,包括无向网和有向网。图和网的存储方式的不同,指的是:在使用二维数组存储图中顶点之间的关系时,如果顶点之间存在边或弧,在相应位置用 1 表示,反之用 0 表示;如果使用二维数组存储网中顶点之间的关系,顶点之间如果有边或者弧的存在,在数组的相应位置存储其
权值
;反之用∞
表示。
比如要存储下面的无向图和有向图(我们重点写二维数组):
对于A而言,我们的二维数组可以是(一维数组存储数据就不再):
例如,arcs[0][1] = 1 ,证明从 V1 到 V2 有弧存在。且通过二阶矩阵,可以很轻松得知各顶点的出度和入度,出度为该行非 0 值的和,入度为该列非 0 值的和。例如,V1 的出度为第一行两个 1 的和,为 2 ; V1 的入度为第一列中 1 的和,为 1 。所以 V1 的出度为 2 ,入度为 1 ,度为两者的和 3 。
对于B而言二维数组是:
每一行代表一个顶点,依次从 V1 到 V5 ,每一列也是如此。比如 arcs[0][1] = 1 ,表示 V1 和 V2 之间有边存在;而 arcs[0][2] = 0,说明 V1 和 V3 之间没有边。
再比如我们要存储下图有向网的时候:
那么其二维数组为:
2.2顺序存储的代码实现:
#define MaxInt 32767 //表示极大值∞
#define MVNum 100 //表示最大顶点数
typedef char VerTexType;//假设顶点的数据结构类型为char
typedef int ArcType;//假设权值类型为整形
typedef struct{
VerTexType vexs[MVNum];//顶点表
ArcType arcs[MVNum][MVNum];//邻接矩阵
int vexnum;//图的当前点数
int arcnum;//图的当前顶点数和边数
}AMGraph;
注意
∞
:表示计算机允许的大于边上所有权值的数
3.图的邻接表存储结构
3.1邻接表的存储概念
邻接点:在图中,如果两个点相互连通,即通过其中一个顶点,可直接找到另一个顶点,则称它们互为邻接点,邻接指的是图中顶点之间有边或者弧的存在
邻接表存储图的实现方式是,给图中的各个顶点独自建立一个链表,用节点存储该顶点,用链表中其他节点存储各自的邻接点。。邻接表既适用于存储无向图,也适用于存储有向图。
与此同时,为了便于管理这些链表,通常会将所有链表的头节点存储到数组中(也可以用链表存储)。也正因为各个链表的头节点存储的是各个顶点,因此各链表在存储临界点数据时,仅需存储该邻接顶点位于数组中的位置下标即可。啥意思呢,如下图:
拿顶点 V1 来说,与其相关的邻接点分别为 V2 和 V3,因此存储 V1 的链表中存储的是 V2 和 V3 在数组中的位置下标 1 和 2。
存储各顶点的节点(称为表头结点如上图的V1,V2)结构分为两部分,数据域和指针域。数据域用于存储顶点数据信息,指针域用于链接下一个节点,如图 :
对应上图的:
那么除了表头节点我们还有由表头节点引出来的链表,称为边表,若边表的节点存储的是图不是网,那么任仍然可以使用上边图示的存储结构,但是如果存储的是网,那么就可以使用下面的存储结构(adjvex存储与表头节点有关系的顶点的下标,next是连接的链表,info表示的是权重等信息):
对应上图的:
3.2邻接表的存储代码实现
#define MVNum 100 //表示最大顶点数
typedef int VerTexType//顶点数据的数据类型
typedef char OtherInfo;//权重等相关信息
typedef struct ArcNode{
int adjvex;//该边所指向的顶点的位置
struct ArcNode *nextarc;//指向下一条边的指针
OtherInfo info; //和边相关的信息如权重等
}ArcNode;
typedef struct VNode{
VerTexType data;
ArcNode *firstarc; //指向第一条依附该顶点的边的指针
}VNoded,AdjList[MVNum];
typedef struct{//邻接表
AdjList vertices;
int vexnum;//图当前的顶点数
int arcnum;//图当前的边数
};
3.3邻接表计算顶点的出度和入度
使用邻接表计算无向图中顶点的入度和出度会非常简单,只需从数组中找到该顶点然后统计此链表中节点的数量即可。
而使用邻接表存储有向图时,通常各个顶点的链表中存储的都是以该顶点为弧尾的邻接点,因此通过统计各顶点链表中的节点数量,只能计算出该顶点的出度,而无法计算该顶点的入度。
对于利用邻接表求某顶点的入度,有两种方式:
- 遍历整个邻接表中的节点,统计数据域与该顶点所在数组位置下标相同的节点数量,即为该顶点的入度;
- 建立一个逆邻接表,该表中的各顶点链表专门用于存储以此顶点为弧头的所有顶点在数组中的位置下标。
我们建立一个邻接表的逆邻接表,如下图:
那么他的逆邻接表:
注意一个图的邻接矩阵是唯一的但是邻接表不是唯一的,这是因为邻接表表示中,各边表结点的链接次序取决于建立邻接表的算法,以及边的输入次序
4.图的十字链表存储结构
4.1图的十字链表存储结构的概念
- 与邻接表不同,十字链表法仅适用于存储有向图和有向网。不仅如此,十字链表法还改善了邻接表计算图中顶点入度的问题
- 十字链表存储有向图(网)的方式与邻接表有一些相同,都以图(网)中各顶点为首元节点建立多条链表,同时为了便于管理,还将所有链表的首元节点存储到同一数组(或链表)中。
建立个各个链表中用于存储顶点的首元节点结构如图:
从上图 可以看出,首元节点中有一个数据域和两个指针域(分别用 firstin 和 firstout 表示):
- firstin 指针用于连接以当前顶点为弧头的其他顶点构成的链表;
- firstout 指针用于连接以当前顶点为弧尾的其他顶点构成的链表;
- data 用于存储该顶点中的数据;
由此可以看出,十字链表实质上就是为每个顶点建立两个链表,分别存储以该顶点为弧头的所有顶点和以该顶点为弧尾的所有顶点。
注意,存储图的十字链表中,各链表中首元节点与其他节点的结构并不相同,上图所示仅是十字链表中首元节点的结构,链表中其他普通节点的结构如图:
五个域分别表示:
- tailvex 用于存储以首元节点为弧尾的顶点位于数组中的位置下标;
- headvex 用于存储以首元节点为弧头的顶点位于数组中的位置下标;
- hlink 指针:用于链接下一个存储以首元节点为弧头的顶点的节点;
- tlink 指针:用于链接下一个存储以首元节点为弧尾的顶点的节点;
- info 指针:用于存储与该顶点相关的信息,例如量顶点之间的权值;
比如用存储下图的有向图:
十字链表法:
拿顶点 V1 来说,通过构建好的十字链表得知,以该顶点为弧头的顶点只有存储在数组中第 3 位置的 V4(因此该顶点的入度为 1),而以该顶点为弧尾的顶点有两个,分别为存储数组第 1 位置的 V2 和第 2 位置的 V3(因此该顶点的出度为 2)。
对于各个链表中节点来说,由于表示的都是该顶点的出度或者入度,因此没有先后次序之分。
4.1图的十字链表存储结构代码实现
#define MAX_VERTEX_NUM 20
#define InfoType int//图中弧包含信息的数据类型
#define VertexType int
typedef struct ArcBox{
int tailvex,headvex;//弧尾、弧头对应顶点在数组中的位置下标
struct ArcBox *hlik,*tlink;//分别指向弧头相同和弧尾相同的下一个弧
InfoType *info;//存储弧相关信息的指针
}ArcBox;
typedef struct VexNode{
VertexType data;//顶点的数据域
ArcBox *firstin,*firstout;//指向以该顶点为弧头和弧尾的链表首个结点
}VexNode;
typedef struct {
VexNode xlist[MAX_VERTEX_NUM];//存储顶点的一维数组
int vexnum,arcnum;//记录图的顶点数和弧数
}OLGraph;
5.图的邻接多重表的存储结构
5.1图的邻接多重表的存储结构概念
无向图的存储可以使用邻接表,但在实际使用时,如果想对图中某顶点进行实操(修改或删除),由于邻接表中存储该顶点的节点有两个,因此需要操作两个节点。
为了提高在无向图中操作顶点的效率,本节学习一种新的适用于存储无向图的方法——邻接多重表。
注意,邻接多重表仅适用于存储无向图或无向网。
邻接多重表存储无向图的方式,可看作是邻接表和十字链表的结合。同邻接表和十字链表存储图的方法相同,都是独自为图中各顶点建立一张链表,存储各顶点的节点作为各链表的首元节点,同时为了便于管理将各个首元节点存储到一个数组中。各首元节点结构如图 所示:
- data:存储此顶点的数据;
- firstedge:指针域,用于指向同该顶点有直接关联的存储其他顶点的节点。
从上图 可以看到,邻接多重表采用与邻接表相同的首元节点结构。但各链表中其他节点的结构与十字链表中相同,如下图 所示:
- mark:标志域,用于标记此节点是否被操作过,例如在对图中顶点做遍历操作时,为了防止多次操作同一节点,mark 域为 0 表示还未被遍历;mark 为 1 表示该节点已被遍历;
- ivex 和 jvex:数据域,分别存储图中各边两端的顶点所在数组中的位置下标;
- ilink:指针域,指向下一个存储与 ivex 有直接关联顶点的节点;
- jlink:指针域,指向下一个存储与 jvex 有直接关联顶点的节点;
- info:指针域,用于存储与该顶点有关的其他信息,比如无向网中各边的权;
例子:
从例子中,可直接找到与各顶点有直接关联的其他顶点。比如说,与顶点 V1 有关联的顶点为存储在数组下标 1 处的 V2 和数组下标 3 处的 V4,而与顶点 V2 有关联的顶点有 3 个,分别是 V1、V3 和 V5。
5.2图的邻接多重表的存储结构代码实现
#define MAX_VERTEX_NUM 20 //图中顶点的最大个数
#define InfoType int //边含有的信息域的数据类型
#define VertexType int //图顶点的数据类型
typedef enum {unvisited,visited}VisitIf; //边标志域
typedef struct EBox{
VisitIf mark; //标志域
int ivex,jvex; //边两边顶点在数组中的位置下标
struct EBox * ilink,*jlink; //分别指向与ivex、jvex相关的下一个边
InfoType *info; //边包含的其它的信息域的指针
}EBox;
typedef struct VexBox{
VertexType data; //顶点数据域
EBox * firstedge; //顶点相关的第一条边的指针域
}VexBox;
typedef struct {
VexBox adjmulist[MAX_VERTEX_NUM];//存储图中顶点的数组
int vexnum,degenum;//记录途中顶点个数和边个数的变量
}AMLGraph;