(待更新)数据结构第6章 图

图的基本概念、图的存储结构、图的遍历、图的应用

一、图的基本概念

连通、连通图——对应无向图
【连通图:无向图中任意两个顶点之间都有路径。】
强连通、强连通图——对应有向图
【强连通图:有向图中任意两个顶点之间都有两条方向相反的路经。】
注意区别于完全图(要求更严格):
【无向完全图:任意两个顶点之间都有边。】
【有向完全图:任意两个顶点之间都有方向相反的两条弧。】

n个顶点的无向图:
①若它是连通图(任意两个顶点之间都有路径),则它最少有n-1条边。不考虑重边的情况下,最多有C_{n}^{2}条边(无向图顶点数已确定,边数最多的情况为每两个顶点之间都有一条边)。
②若它是非连通图,则它最多只能有C_{n-1}^{2}条边。(孤立出一个点,并让剩下的n-1个点形成一个边数最多的的连通图(无向完全图:任意两个顶点之间都有边)。)
【证明② 边数不可能再比C_{n-1}^{2}大了,但凡比C_{n-1}^{2}大1,边数就绝对足够把那个孤立的点连进来,不再满足非连通图的条件。】

★★★仔细区分两种考题的重点所在:
①P202T11:已知一个无向图有n个顶点,若它是连通图,那么它的边数至少为多少?——至少为n-1。
②P202T7:已知一个无向图有n个顶点,要保证在任何情况下它都是连通的,那么至少需要多少条边?——至少需要1+C_{n-1}^{2}条边。

n个顶点的有向图:

①若它是强连通图(任意两个顶点之间都有两条方向相反的路经),则它最少有n条边(即形成一个回路时)。

n个顶点的连通图,其生成树(包含所有顶点的极小连通子图) 必定有n个顶点和n-1条边。
给连通图的生成树①加上一条边,一定会出现一个环(回路) ②去掉一条边,一定会导致该树不连通。

可以把之前学的树看作:不存在环(回路)、且连通的无向图。【不存在环的连通图】


二、图的存储结构

邻接矩阵  :一个二维数组,有向图的邻接矩阵是对称矩阵,存储有向图:每一行表示一个顶点的出度。一个图对应的邻接矩阵是唯一的。

邻接表  :一个一维数组+单链表,顶点表结点、边表结点。一个图对应的邻接表不是唯一的。有向图的邻接表不方便找入边,需要全部遍历一次。

优化:

十字链表-存储有向图

邻接多重表-存储无向图


三、图的遍历

自己选择一个起始顶点。不同的起始顶点,得到的BFS/DFS遍历序列肯定是不同的!

1、图的广度优先遍历 BFS(Breadth First Search),类似于树的层序遍历。需要一个辅助队列

空间复杂度:最坏情况,辅助队列⼤⼩为O\left (|V| \right )

基于邻接矩阵的广度优先遍历,序列唯一,时间复杂度:O\left (|V|^{2} \right )

基于邻接表的广度优先遍历,序列不唯一,时间复杂度:O\left ( |V|+|E| \right )

时间复杂度=访问各结点所需时间+探索各条边所需时间。需要结合具体的存储结构分析。

2、图的深度优先遍历 DFS(Depth First Search),类似于树的先根遍历。需要一个递归栈

空间复杂度:来⾃函数调⽤栈,最坏情况,递归深度为O\left (|V| \right );最好情况,O(1)

基于邻接矩阵的深度优先遍历,序列唯一,时间复杂度:O\left (|V|^{2} \right )

基于邻接表的深度优先遍历,序列不唯一,时间复杂度:O\left ( |V|+|E| \right )

BFS、DFS的代码实现:都需要一个visited[i]数组来标记哪些顶点被访问过,防止重复访问同一结点。初始化都为false。

对⽆向图进⾏一次BFS/DFS遍历,调⽤BFS/DFS函数的次数=连通分量数

特别的,对于连通图,只需调⽤1次BFS/DFS函数。

对有向图进⾏一次BFS/DFS遍历,调⽤BFS/DFS函数的次数要具体问题具体分析:若起始顶点到其他各顶点都有路径,则只需调⽤1次BFS/DFS函数

特别的,对于强连通图,从任⼀结点出发都只需调⽤1次BFS/DFS函数。

      

3、生成树和生成森林

图的广度优先生成树:由⼴度优先遍历确定的树。由于邻接表的表示⽅式不唯⼀,因此基于邻接表的⼴度优先⽣成树也不唯⼀,可能有多棵

图的⼴度优先⽣成森林:对⾮连通图的⼴度优先遍历,可得到⼴度优先⽣成森林。

图的深度优先生成树,同理。


四、图的应用

1、最小生成树(最小代价树,对于带权连通⽆向图而言的,所有边的权值之和最小的生成树。)

【区别带权图带权树】:带权图的权值在上,带权树的权值在结点上。

【区别最小生成树(最小代价树)和哈夫曼树】:

哈夫曼树:最优二叉树,带权路径长度(WPL)最小的二叉树。

哈夫曼树不唯一,但WPL必然都是最小值。

回顾连通图的生成树:包含图中全部顶点的一个极小连通子图。若图中顶点数为n,则它的生成树含有n-1条边。对生成树而言,若砍去它的一条边,则会变成非连通图,若加上一条边则会形成一个回路。

类似:最⼩⽣成树可能有多个,但边的权值之和总是唯⼀且最⼩的。最⼩⽣成树的边数 = 顶点数 - 1。砍掉⼀条则不连通,增加⼀条边则会出现回路。

①Prim算法(普利姆)求最小生成树从某⼀个顶点开始构建⽣成树;每次将代价最⼩的新顶点纳⼊⽣成树。

时间复杂度:O\left (|V|^{2} \right )适合⽤于边稠密图。

代码实现:从V0开始,总共需要n-1轮处理。每轮循环2次:循环遍历所有个结点,找到lowCost最低的且还没加⼊的顶点。再次循环遍历,更新还没加⼊的各个顶点的lowCost值。

②Kruskal算法(卡鲁斯-卡尔)求最小生成树每次选择⼀条权值最⼩的边,使这条边的两头连通(原本已经连通的就不选)。

时间复杂度:O( |E|log2|E| )适合⽤于边稀疏图。

代码实现:初始,将各条边按权值排序,共执⾏|E|轮,每轮判断某条边两头是否已连通→两个顶点是否属于同⼀集合(利用并查集),每轮时间复杂度O(log2|E|)。

2、最短路径

单源最短路径:①BFS(广度优先遍历)求无权图的单源最短路径。起始顶点已确定。

②Dijkstra算法(迪杰斯特拉)求带权图(正值)(可以有环)的单源最短路径。起始顶点已确定。

时间复杂度:O\left (|V|^{2} \right )

3个数组:

final[ ]各顶点是否找到最短路径,初始化false;

dist[ ]最短路径长度,初始化dist[k]=arcs[0][k];

path[ ]前驱结点,和起始顶点v0相连的顶点path初始化为0,和起始顶点不相连的顶点path初始化 -1。

每一轮可以确定起始顶点到一个顶点的最短路径,共需n-1轮。

每轮循环2次:循环遍历剩余顶点,找到dist最小的顶点Vi,令final[i]=ture;再次循环遍历,检查这轮确定顶点的相邻顶点,更新dist[ ]和path[ ],每轮时间复杂度O(2n)。

【每到一个顶点的最短路径确定以后,都要更新dist[ ]和path[ ]。直到找不到final为false的结点,算法结束。】

Dijkstra算法不适⽤于有负权值的带权图。

所有顶点间最短路径:Floyd算法(弗洛伊德)求带权图(可以负值)(不可有带负边的环)所有顶点之间的最短路径。起始顶点不固定,可以是任意顶点。

......

3、有向无环图DAG描述表达式

......

4、拓扑排序

......

5、求关键路径

1)AOE网——在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销(如完成活动所需的时间),称之为⽤边表示活动的⽹络,简称AOE⽹(Activity On Edge NetWork)。

在AOE⽹中仅有⼀个⼊度为0的顶点,称为开始顶点(源点),它表示整个⼯程的开始;

仅有⼀个出度为0的顶点,称为结束顶点(汇点),它表示整个⼯程的结束。

2)关键路径

从源点到汇点的有向路径可能有多条,所有路径中,具有最⼤路径⻓度的路径称为关键路径,⽽把关键路径上的活动称为关键活动完成整个⼯程的最短时间就是关键路径的⻓度,若关键活动不能按时完成,则整个⼯程的完成时间就会延⻓】

顶点:事件,一瞬间。边:活动,持续一段时间。

最早/最迟:事件【发生】时间,活动【开始】时间。

①事件vk的最早发生时间ve(k)——决定了所有从事件vk开始的活动能够开工的最早时间。

②事件vk的最迟发生时间vl(k)——在不推迟整个⼯程完成的前提下,该事件最迟必须发生的时间。

③活动ai的最早开始时间e(i)——该活动弧的起点所表示的事件的最早发⽣时间

④活动ai的最迟开始时间l(i)——该活动弧的终点所表示事件的最迟发⽣时间该活动所需时间之差。

⑤活动ai的时间余量d(i)=l(i)-e(i)——在不增加完成整个工程所需总时间的情况下,活动ai可以拖延的时间

★d(i)=0即l(i)=e(i)的活动ai是关键活动,由关键活动组成的路径就是关键路径。

求关键路径的步骤:①=③,②推出④,由④-③得⑤,找⑤=0。

①求所有事件的最早发⽣时间ve( )。

按拓扑排序序列,依次求各个顶点的ve(k): 使用拓扑排序序列可以使此过程有条不紊的进行。

(1)开始顶点(源点)的最早发生时间=0;

(2)事件最早发生时间=前驱结点(事件)最早发生时间+边(活动)时间有多个前驱结点(事件)的事件最早时间取max{前驱结点(事件)最早发生时间+边(活动)时间}      在同一行里计算 

②求所有事件的最迟发⽣时间vl( )。

按逆拓扑排序序列,依次求各个顶点的vl(k): 

(1)结束顶点(汇点)的最迟发生时间=结束顶点(汇点)的最早发生时间;

(2)事件最迟发生时间=后继事件(结点)最迟发生时间-活动(边)时间有多个后继结点(事件)的事件最迟时间取min{后继结点(事件)最迟发生时间-边(活动)时间}      在同一行里计算

③求所有活动的最早开始时间e( )

③由①推得。e(i)=ve(k)。

(1)活动最早开始时间=活动(边)的前一个事件(结点)的最早发生时间

④求所有活动的最迟开始时间l( )

④由②推得。l(i) =vl(j) -Weight(vk,vj)。

(1)活动最迟开始时间=后一事件(结点)最迟发生时间-活动(边)时间

⑤求所有活动的时间余量d( )

⑤由④-③得到d(i) =l(i) -e(i)。


例子:

求①: 求②:

由①推③:  由②推④:

由④-③推⑤:

由d=0的活动推出:关键活动:a2、a5、a7。

由关键活动组成关键路径:关键路径:V1—>V3—>V4—>V6。


3)关键活动、关键路径的特性

关键活动时间增加,则整个⼯程⼯期增⻓。

缩短关键活动的时间,可以缩短整个⼯程⼯期。但是当缩短到⼀定程度时,关键活动可能会变成非关键活动

并非关键路径越压缩,完成工程时间就越短。原因:过度压缩后该路径不再是关键路径。】

缩短活动a3→

★可能有多条关键路径,只缩短一条关键路径上的关键活动时间不能缩短工期只有加快那些包括在所有关键路径上的关键活动才能达到缩短⼯期的⽬的。

猜你喜欢

转载自blog.csdn.net/m0_65207522/article/details/127410574