数据结构与算法分析~笔记7图(1)

7.1 图的基本概念

(Graph)是由有穷非空的顶点集合和顶点之间边的集合组成的,可表示为:G=(V,E)其中,G表示图,图中的数据元素通常叫做顶点(Vertex),V称为顶点的有穷非空集合,E是图G中顶点之间边的集合。
(1)若顶点v和w间的边没有方向,则称这条边为无向边,用(v,w)表示,此时的图称为无向图(Undigraph)。
(2)若顶点v和w间的边有方向,则称这条边为有向边(也称为(Arc)),用<v,w>表示,且称v为弧尾(Tail)或初始点(Initial Node),称w为弧头(Head)或终端点(Terminal Node),此时的图称为有向图(Digraph)。
如下图所示的图的示例,G1是一个无向图,G2是一个有向图。其中,G1的顶点集合为V={v1,v2,v3,v4,v5},边的集合为E={(v1,v2),(v1,v3),(v2,v4),(v2,v5),(v4,v5),(v3,v5)}。G2的顶点集合为V={v1,v2,v3,v4},边的集合为E={(v1,v2),(v1,v3),(v3,v4),(v4,v1)}。
在这里插入图片描述
在讨论图时,需要如下一些限制。
(1)不考虑顶点有直接与自身相连的边,即自环(Self Loop)。就是说不应有形如(x,x)或<x,x>的边。
(2)在无向图中,任意两个顶点之间不能有多条边直接相连。此图称为多重图(Multigraph)。
有关图的常用术语如下:
完全图(Complete Graph):在由n个顶点组成的无向图中,若有n(n-1)/2条边,则称之为无向完全图。以此类推,在由n个顶点组成的有向图中,若有n(n-1)条边,则称之为有向完全图。完全图中的边数达到最大。
(Weight):在某些图中,边或弧上具有与它相关的数据信息称之为权。在实际应用中,权值可以有某种含义。这种带权的图称为网络(Network)。分别称带权的有向图和带权的无向图为有向网无向网
邻接顶点(Adjacent Vertex):对于无向图G=(V,E),如果边(u,v)∈E,则称顶点u,v互为邻接点,即u,v相邻接。边(u,v)依附于顶点u和v,或者说边(u,v)与顶点u和v相关联。对于有向图而言,若弧<u,v>∈E,则称顶点u邻接到顶点v,顶点v邻接自顶点u,或者说弧<u,v>与顶点u,v相关联。
子图(Subgraph)设图G=(V,E)和G′=(V′,E′),若V′⊆V且E’⊆E,则称图G′是图G的子图。
(Degree):与顶点v关联的边的数目,称作v的度,记作deg(v)。在有向图中,顶点的度等于其入度与出度之和。其中,顶点v的入度(Indegree)是以顶点v为弧头的弧的数目,记作indeg(v);顶点v的出度(Outdegree)是以顶点v为弧尾的弧的数目,记作outdeg(v)。顶点v的度deg(v)=indeg(v)+outdeg(v)。。一般地,不论是有向图还是无向图,若图G中有n个顶点和e条边,则有:在这里插入图片描述

路径(Path):在图G=(V,E)中,若从顶点vi出发,沿一些边经过若干顶点vp1,vp2,…vpm到达顶点vj,则称顶点序列(vi,vp1,vp2,…,vpm,vj)为从顶点vi到顶点vj的一条路径。它经过的边(vi,vp1)、(vp1,vp2)、…、(vpm,vj)都属于E。如果G是一个有向图,则其路径也是有向的,顶点序列(vi,vp1,vp2,…,vpm,vj)满足它所经过的弧<vi,vp1>、vp1,vp2…、<vpm,vj>,都属于E。
路径长度(Path Length):一条路径上经过的边或弧的数目。
简单路径与回路(Simple Path & Cycle):若路径上各顶点v1,v2,…,vm均不重复,则称这样的路径为简单路径。若路径上第一个顶点v1与最后一顶点vm重合,则称这样的路径为回路或环。
连通图与连通分量(Connected Graph & Connected Commponent):在无向图中,若存在从顶点v1到顶点v2的路径,则称顶点v1与v2是连通的。如果图中任意一对顶点都是连通的,则称此图是连通图。非连通图的极大连通子图叫做连通分量。
强连通图与强连通分量(Strongly Connected Digraph&Strongly Connected Commponent):在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的路径,则称此图是强连通图。而非强连通图的极大强连通子图叫做强连通分量。
生成树(Spanning Tree):具有n个顶点的连通图G的生成树是包含G中全部顶点的一个极小连通子图。在生成树中添加任意一条属于原图中的边必定会产生回路或环,因为新添加的边使其所依附的两个顶点之间有了第二条路径;生成树中减少任意一条边必然会成为非连通图。因此,一棵具有n个顶点的生成树有且仅有n-1条边。
生成森林(Spanning Forest):非连通图的每个连通分量都可以得到一棵生成树,这些连通分量的生成树构成了的森林,称为生成森林。
稀疏图(Sparse Graph)和稠密图(Dense Graph):边数很少的图称为稀疏图,反之称为稠密图。稀疏和稠密本是模糊的概念,稀疏图和稠密图常常是相对而言的。

7.2 图的存储结构

图的存储结构除了要存储图中各个顶点本身的信息外,还要存储各个顶点之间的关系(边或弧的信息)。常用的图的存储结构有邻接矩阵和邻接表。
邻接矩阵(Adjacency Matrix)存储结构是指用两个数组表示图。一个一维数组存储图中顶点(数据元素)的信息,一个二维数组存储图中顶点之间的关系(边或弧的信息)。
设图G=(V,E)包含n个顶点,则G的邻接矩阵是一个二维数组G.Edge[n][n]。
若G是一个无权图,则G的邻接矩阵定义为:
在这里插入图片描述
若G是一个网,则G的邻接矩阵定义为:
在这里插入图片描述
无权图采用邻接矩阵的表示下图所示,其中包括有向图G1和无向图G2的各自表示方法;
在这里插入图片描述
网采用邻接矩阵的表示下图所示:
在这里插入图片描述
图的邻接矩阵存储结构具有如下特点:
(1)无向图的邻接矩阵是对称的,采用压缩矩阵进行存储。
(2)有向图的邻接矩阵不一定对称,因此采用邻接矩阵存储具有n个顶点的有向图时,需要n的2次方个存储单元。
(3)无向图邻接矩阵的第i行(或第i列)中非零元素的个数,就是顶点i的度。
(4)有向图邻接矩阵的第i行中非零元素的个数,就是顶点i的出度,第i列中非零元素的个数,就是顶点i的入度。
(5)利用邻接矩阵可以较容易地确定图中两顶点之间是否有边。但若要确定图中一共有多少条边,则要逐行逐列进行检测,耗费的时间代价较大,这也是邻接矩阵存储结构的局限性。

图的邻接矩阵存储结构,其基本操作如下。
(1)构造函数用
构造函数建立一个无向图的邻接矩阵存储结构的算法步骤如下。
Step 1:确定所要创建无向图的顶点个数和边的个数。
Step 2:输入各个顶点的信息,并存储在一维数组vertex中。
Step 3:初始化邻接矩阵的信息。
Step 4:依次输入每一条边,并存储在邻接矩阵arc中。
• 依此输入边依附的两个顶点的序号i,j;
• 将邻接矩阵中第i行第j列的数据元素值置为1;
• 将邻接矩阵中第j行第i列的数据元素值置为1。
(2)顶点的增删
① 增加顶点:增加一个顶点,要在邻接矩阵中的相应位置插入一行一列,同时在存储顶点信息的一维数组中插入该顶点信息。
② 删除顶点:删除一个顶点,要将与它关联的边一起删除,即删除该顶点在邻接矩阵中对应的行与列,同时在存储顶点信息的一维数组中删除该顶点信息。
(3)边的增删
① 增加边:为现有的两个顶点之间增加一条边,只需将邻接矩阵中相应位置的数据元素置为1。
② 删除边:删除一条边只需将邻接矩阵中相应位置的数据元素置为0。
(4)深度优先遍历
深度优先遍历的过程:从图中某个初始顶点v出发,首先访问初始顶点v,然后选择一个与顶点v相邻且没有被访问过的顶点w作为初始顶点进行访问,再从w出发进行深度优先遍历,直到图中与当前顶点v邻接的所有顶点都被访问过为止。
(5)广度优先遍历
广度优先遍历的过程:从图中某个初始顶点v出发,首先访问初始顶点v,接着访问顶点v的所有未被访问过的邻接点v1,v2,…,vt,然后再按照v1,v2,…,vt的次序,访问每一个顶点的所有未被访问过的邻接点,依此类推,直到图中所有和初始顶点v有路径相通的顶点都被访问过为止。

邻接表(Adjacency List)是图的一种链式存储结构。基本思想:邻接表只存储有关联的信息,对于图中存在的相邻顶点之间边的信息进行存储,而对于不相邻的顶点则不保留信息。设图G具有n个顶点,则用顶点数组表和边表(弧表)来表示图G。
顶点数组表:用于存储顶点vi的名或其他有关信息的数组,也称为数据域(Data)。该数组的大小为图中的顶点个数n。顶点数组表中的数据元素也称为表头结点,其形式如图(a)所示。
每个表头结点由2个域组成,其中:
• data:结点的数据域,用于保存结点的数据值(如顶点编号)。
• firstarc:结点的指针域,也称为链域,指向自该结点出发的第一条边(弧)的边(弧)结点。边表(弧表):图中每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边(对有向图是以顶点vi为尾的弧)。该单链表中的结点也称为边结点,其形式如图(b)所示。
在这里插入图片描述
每个边结点由3个域组成,其中:
adjvex:指示该边(弧)所指向的顶点在图中的位置(例如顶点在顶点数组表中的下标),也称为邻接点域。
nextarc:边(弧)结点的指针域,指向下一条边(弧)结点。
info:存储和边(弧)相关的信息,如权值等。若不是网,则info域可省去。

图的邻接表表示的图示,其中,图(a)和图(b)分别表示有向图G1和无向图G2及其各自的邻接表。
在这里插入图片描述
若无向图中有n个顶点、e条边,则它的邻接表需n个头结点和2e个表结点。显然,在[插图]的边稀疏的情况下,用邻接表表示图比用邻接矩阵节省存储空间。
在无向图的邻接表中,顶点vi的度恰恰为第i个单链表中的结点数;而在有向图中,第i个单链表中的结点数只是顶点vi的出度,为求顶点vi的入度,必须遍历整个邻接表,然后在所有单链表中查找邻接点的值为i的结点并计数求和。由此可见,对于用邻接表存储的有向图,求顶点vi的入度并不方便,需要扫描整个邻接表才能得到结果。因此,为了便于确定顶点的入度,可以建立有向图的逆邻接表,即为每个顶点vi建立一个所有以顶点vi为弧头的边链表。
这样求顶点vi的入度即是计算逆邻接表中第i个顶点的边链表中结点的个数。例如图所示即为上图(a)中G1的逆邻接表表示。
在这里插入图片描述
在建立邻接表或逆邻接表时,若输入的顶点信息为顶点的编号,则建立邻接表的时间复杂度为O(n+e);否则需要通过查找才能得到顶点在图中的位置,此时建立邻接表的时间复杂度为O(n·e)。
在邻接表上,容易找到任一顶点的第一个邻接点和下一个邻接点,但要判定任意两个顶点vi和vj之间是否有边或弧相连,则需要搜索第i个或第j个链表,相比之下。不如在邻接矩阵上操作方便。
用构造函数建立一个有向图的邻接表存储结构的算法步骤如下。
Step1:输入所要创建的有向图的顶点个数、边的个数及弧的相关信息。
Step 2:初始化顶点集。
Step 3:依次输入每一条边(或弧)的信息,构造表结点链表。

十字链表(Orthogonal List)是有向图的一种链式存储方法。十字链表可以看成是将邻接表与逆邻接表结合起来得到的一种新的链表。在十字链表中,对应于有向图中每一条弧有一个弧结点,对应于图中的每个顶点也有一个顶点结点,这些结点的结构如图所示。
在这里插入图片描述

在弧结点中有5个域。其中,
tailvex域:弧尾结点,即弧尾在顶点表中的下标。
headvex域:弧头结点,即弧头在顶点表中的下标。
hlink链域:指向弧头相同的下一条弧。
tlink链域:指向弧尾相同的下一条弧。
Info域:存储该弧的相关信息。
弧头相同的弧在同一链表上,弧尾相同的弧也在同一链表上。链表的头结点即为顶点结点,它由3个域组成。其中,
data域:存储和该顶点相关的信息。
firstin链域:指向以该顶点为弧头的第一个弧结点。
firstout链域:指向以该顶点为弧尾的第一个弧结点。
若将有向图的邻接矩阵看成是稀疏矩阵的话,则十字链表也可以看成是邻接矩阵的链式存储结构。
在图(b)所示的十字链表存储结构中,弧结点所在的链表是非循环链表,结点之间相对位置自然形成,不一定按顶点序号有序排列,表头结点即为顶点结点,它们之间的关系不是链接,而是顺序存储。
在这里插入图片描述
邻接多重表(Adjacency Multilist)是无向图的一种链式存储方式。
用邻接表存储无向图,任一条边的两个顶点分别在以该边所依附的两个顶点的边表中,这种重复存储给图的某些操作带来不便。
邻接多重表的存储结构和十字链表类似,在邻接多重表中,每一条边用一个边结点表示,其结构如图(a)所示。
在这里插入图片描述
其中,mark:标志域,可以标记该边是否被搜索过。
ivex、jvex:与某该边依附的两个顶点在顶点表中的下标。
ilink、jlink:指针域,分别指向下一条依附于顶点ivex和jvex的边。
info:存储和边相关的各种信息。
每一个顶点也用一个顶点结点表示,其结构如图(b)所示。
其中,data:存储和该点相关的信息。firstedge:指示第一条依附于该顶点的边。

下图给出了一个无向图的邻接多重表的存储示意图。在邻接多重表中,所有依附于同一顶点的边结点串联在同一链表中,由于每条边依附于两个顶点,因此每个边结点同时链接在两个链表中。除了在边结点中增加一个标识域外,邻接多重表所需的存储量和邻接表相同。在邻接多重表上,各种基本操作的实现亦和邻接表相似。
在这里插入图片描述

7.3 图的遍历

图的遍历(Graph Traversal)是指给定一个图G和其中任意一个顶点v0,从v0出发,沿着图中各边访遍图中所有顶点,且每个顶点仅被访问一次。
图的遍历是求解图的连通性问题、拓扑排序和求关键路径等算法的基础,通过遍历可以找出某个顶点所在的极大连通子图,也可以消除图中的所有回路等。
图的遍历比树的遍历要复杂得多。因为图的任一顶点都可能和其余顶点相邻接。为了避免重复访问,需要利用一个标志数组visited[0…n-1]记录顶点是否已被访问过。在开始遍历之前,将该数组的所有数据元素全部置为“假”或者“零”;在遍历的过程中,顶点vi一旦被访问,就立即将visited[i]置为“真”或者被访问时的次序号。
另外,对于非连通图来说,从一个顶点出发,每次遍历只能遍访其中的一个连通分量,还需要考虑如何选取下一个出发点以访问图中其余的连通分量。
与树结构类似,图的遍历算法也有很多种。图的遍历通常有深度优先搜索和广度优先搜索两种方式,这两种方式既适用于无向图,也适用于有向图,以下以无向图为例讨论。

深度优先搜索(Depth_First Search,DFS)遍历类似于树的先根遍历,是树的先根遍历的推广。
深度优先搜索是个不断探查和回溯的过程。假设初始状态是图中所有顶点未曾被访问,则深度优先搜索可从图中的某个顶点v出发,作为当前顶点。访问此顶点,并设置该顶点的访问标志,接着从v的未被访问的邻接点中找出一个作为下一步探查的当前顶点。倘若当前顶点的所有邻接点都被访问过,则退回一步,将前一步访问的顶点重新取出,作为当前探查顶点。重复上述过程,直至图中的最初指定起点的所有邻接顶点都被访问到,此时连通图中所有顶点也必然都被访问过了。
图(a)给出了一个深度优先搜索遍历图的实例。从顶点A出发做深度优先搜索,可以遍历该连通图的所有顶点。各顶点旁边的数字是各顶点被访问的次序,这个访问次序与树的先根遍历次序类似。图(b)给出了在深度优先搜索的过程中所有访问过的顶点和经过的边,它们构成一个连通的无环图,也就是树,称之为原图的深度优先搜索生成树(DFS Tree),简称DFS树。既然遍历覆盖了图中的所有n个顶点,故DFS树包含n-1条边。
在这里插入图片描述
广度优先搜索(Breadth_First Search,BFS)遍历类似于树的按层次遍历的过程。假设从图中某顶点v出发作为当前顶点,在访问了v之后设置访问标志。
接着依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直至图中所有已被访问的顶点的邻接点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问过的顶点作为起始点,重复上述过程,直至图中所有顶点都被访问到为止。
图给出了一个进行广度优先搜索的无向连通图的实例。该图的广度优先访问顺序为v1,v2,v12,v11,v3,v6,v7,v10,v4,v5,v8,v9,经过广度优先搜索得到的广度优先生成树(BFS Tree),简称BFS树。BFS树由遍历时访问过的n个顶点和所经历的n-1条边组成。
在这里插入图片描述
广度优先搜索不是一个递归的过程,其算法也不是递归的。为了实现逐层访问,算法中使用了一个队列,以记忆正在访问的这一层和上一层的顶点,以便于对下一层进行访问。另外,与深度优先搜索过程一样,为避免重复访问,需要一个标志数组visited[],对访问过的顶点进行标记。

当无向图为非连通图时,利用深度优先搜索算法或广度优先搜索算法,无法遍历图的所有顶点,而只能遍历到该顶点所在最大连通子图的所有顶点,这些顶点构成一个连通分量(Connected Component)。
在无向图每一个连通分量中,分别从某个顶点出发进行一次遍历,就可以遍历到无向图的所有连通分量。
在无向图每一个连通分量中,分别从某个顶点出发进行一次遍历,就可以遍历到无向图的所有连通分量。
图(a)给出了一个非连通无向图,其对应的邻接表如图(b)所示。对它进行深度优先搜索,则需要三次调用DFS过程:第一次从顶点A出发,第二次从顶点H出发,第三次从顶点K出发,最后遍历得到原图的3个连通分量,即原图的3个极大连通子图,如图(c)所示。
在这里插入图片描述
对于非连通的无向图,每个连通分量中的所有顶点集合和用某种方式遍历它时所走过的边的集合,构成了一棵生成树,这是一个极小连通子图。所有连通分量的生成树组成了非连通图的生成森林。
在无向连通图G中,顶点v被称作一个关节点(Articulation Point),当且仅当删去v以及依附于v的所有边之后,G将被分割成至少两个连通分量。
一个没有关节点的连通图称为重连通图(Biconnected Graph)。在重连通图中,任何一对顶点之间至少存在有两条路径,在删去某个顶点及与该顶点相关联的边后,也不破坏图的连通性。
在这里插入图片描述

一般地,若一个连通图G不重连通,它必然包括多个重连通分量。G的每一个重连通分量都是一个极大连通子图。不难验证,同一无向连通图中,任何两个重连通分量最多只可能有一个公共顶点,同一条边也不可能同时处在多个重连通分量中。因此,图G的重连通分量事实上把G的边划分到互不相交的边的子集中。
任一连通图本身就是一个重连通分量。为了找出无向连通图G的各个重连通分量,可以利用DFS树。图(a)给出上图(a)所示的连通图从顶点v4出发进行深度优先搜素得到根为v4的DFS树。为了更直观地描述树形结构,将此生成树改成如图(b)所示的树形形状,并用虚线画出了几条虽然属于图G,但不属于生成树的边。
在这里插入图片描述
不难证明:对于任意两个顶点u和v,若在DFS树中u是v的祖先,则必有visited[u] <=visited[v],也就是说,祖先的深度优先数必然小于其子孙。上图(b)中的虚线,对应于未被DFS树采用的边,称作回边(Back Edge)。

由深度优先生成树可得出两类关节点的特性。
(1)若生成树的根有两棵或两棵以上的子树,则此根顶点必为关节点。
(2)若生成树中某个非叶子顶点v,其某棵子树的根和子树中其他结点均没有指向v的祖先的回边,则v为关节点。
因此,DFS树的根是关节点的充要条件是,它至少有两棵子树。另外,任一非根顶点u不是关节点的充要条件是,它的每一个子女w(如果存在的话)都可以沿着某条路径(包括绕过它的子孙)通往u的某一祖先,而且u不属于这条路径(生成树中不存在任何回路,故这样的一条路径上,必然含有至少一条回边)。
要想消除关节点,建立重连通图,只需加入少量边,使所有关节点的子孙都有回边指向它的祖先即可。

  • 42
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值