王卓数据结构与算法图
Posted 生命是有光的
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了王卓数据结构与算法图相关的知识,希望对你有一定的参考价值。
✍、目录脑图
1、图
1.1、图的定义
图:G=(V,E) Graph=(Vertex,Edge)
- V:顶点(数据元素)的有穷非空集合
- E:边的有穷集合
1.2、图的术语
无向图:每条边都是无方向的
有向图:每条边都是有方向的
完全图:任意两个点都有一条边相连
无向完全图:n个顶点,任意两个顶点都要有边。所以n个顶点,共有 Cn2 = n(n-1)/2 条边。
有向完全图:n个顶点,任意两个顶点都要有两条边。所以n个顶点,共有 2Cn2 = n(n-1) 条边。
稀疏图:有很少或弧(有向图的边也称作弧)的图
稠密图:有较多边或弧的图
网:边/弧带权的图
邻接:有边/弧相连的两个顶点之间的关系。
- 存在(Vi,Vj) ,则称Vi和Vj互为邻接点
- 存在<Vi,Vj> , 则称Vi邻接到Vj,Vj邻接于Vi
关联(依附):边/弧与顶点之间的关系。
顶点的度:与该顶点相关联的边的数目
- 有向图中,顶点的度等于该顶点的入度和出度之和。
- 顶点v的入度是以v为终点的有向边的条数
- 顶点v的出度是以v为始点的有向边的条数
路径:接续的边构成的顶点序列。
路径长度:路径上边或弧的数目/权值之和。
回路(环):第一个顶点和最后一个顶点相同的路径。
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径。
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。
连通图:无向图中称为连通图,有向图中称为强连通图
- 在无向图G=(V,{E})中,若对任何两个顶点v、u都存在从v到u的路径,则称G为连通图
- 在有向图G=(V,{E})中,若对任何两个顶点v、u都存在从v到u的路径,则称G为强连通图
子图:设有两个图G=(V,{E})、G1=(V1,{E1}),若V1∈V,E1∈E,则称G1是G的子图
连通分量:无向图中叫连通分量,有向图中叫强连通分量。
- 无向图G的极大连通子图称为G的连通分量。
- 有向图G的极大连通子图称为G的强连通分量。
- 极大连通子图的意思是:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不再连通。
极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边该子图不再连通。
生成树:包含无向图G的所有顶点的极小连通子图。
生成森林:对非连通图,由各个连通分量的生成树的集合。
1.3、图的存储结构
1.3.1、邻接矩阵(数组)表示法
建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间关系)
- 设图A=(V,E)有n个顶点,则
i | 0 | 1 | 2 | … | n-1 |
---|---|---|---|---|---|
Vexs[i] | V1 | V2 | V3 | … | Vn |
- 图的邻接矩阵是一个二维数组A.arcs[n] [n],定义为:
A . a r c s [ i ] [ i ] = { 1 , 如 果 < i , j > ∈ E 或 者 ( i , j ) ∈ E 0 , 否 则 A.arcs[i][i]=\\begin{cases} 1, 如果<i,j>∈E 或者(i,j)∈E\\\\ 0, 否则\\end{cases} A.arcs[i][i]={1,如果<i,j>∈E或者(i,j)∈E0,否则
上述解释为:如果两个顶点之间存在边或者弧,那么二维数组就为1,如果两个顶点之间不存在边或者弧,那么二维数组就为0
1.3.1.1、无向图的邻接矩阵表示法
- 无向图的邻接矩阵是对称的。
- 顶点 i 的度 = 第i行(列)中 1 的个数。
- 特别的,完全图(任意两个顶点都有边)的邻接矩阵中,对角元素为0,其余均为1。
1.3.1.2、有向图的邻接矩阵表示法
在有向图的邻接矩阵中:
- 第i行含义:以结点Vi为尾的弧(即出度边)
- 第i列含义:以结点Vi为头的弧(即出度边)
分析:
- 有向图的邻接矩阵可能是不对称的。
- 顶点的出度 = 第 i 行元素之和
- 顶点的入度 = 第 i 列元素之和
- 顶点的度 = 第i行元素之和+第i列元素之和
1.3.1.3、网(有权图)的邻接矩阵表示法
A . a r c s [ i ] [ i ] = { W i j , < V i , V j > 或 ( V i , V j ) ∈ V R ∞ , 无 边 ( 弧 ) A.arcs[i][i]=\\begin{cases} Wij, <Vi,Vj>或(Vi,Vj)∈VR\\\\ ∞, 无边(弧)\\end{cases} A.arcs[i][i]={Wij,<Vi,Vj>或(Vi,Vj)∈VR∞,无边(弧)
1.3.2、邻接矩阵优缺点
邻接矩阵的优点:
- 直观、简单、好理解
- 方便检查任意一对顶点间是否存在边
- 方便找任一顶点的所有"邻接点"(有边直接相连的顶点)
- 方便计算任一顶点的"度"(从该点发出的边数为"出度",指向该点的边数为"入度")
- 无向图:对应行(或列)非0元素的个数
- 有向图:对应行非0元素的个数是"出度",对应列非0元素的个数是"入度"
缺点:
- 不便于删除和增加顶点
- 浪费空间-存稀疏图(点很多而边很少),有大量无效元素
- 对稠密图(特别是完全图)还是很合算的
- 浪费时间-统计稀疏图中一共有多少条边
1.3.3、邻接表(链式)表示法
1.3.3.1、无向图的邻接表
3表示V3,1表示V2。表示V1链接了V4,V1链接了V2
顶点:
- 按编号顺序将顶点数据存储在一维数组中。
关联同一顶点的边(以顶点为尾的弧):
- 用线性链表存储
无向图的邻接表特点:
-
邻接表不唯一
-
若无向图中有n个顶点、e条边,则其邻接表需n个头结点和2e个表结点。适宜存储稀疏图。
-
无向图中顶点Vi的度为第 i 个单链表中的结点数。
1.3.3.2、有向图的邻接表
只记录顶点发出的弧:邻接表
特点:找出度易,找入度难。
- 顶点Vi的出度为第i个单链表中的结点个数
- 顶点Vi的入度为整个单链表中邻接点域值是 i-1 的结点个数。
我们也可以只记录顶点进入的弧:逆邻接表
- 顶点Vi的入度为第i个单链表中的结点个数。
- 顶点Vi的出度为整个单链表中邻接点域值是 i-1 的结点个数
1.3.4、邻接表特点
-
方便找任一顶点的所有"邻接点"
-
节约稀疏图的空间
- 需要N个头指针+2E个结点(每个结点至少2个域)
-
方便计算任一顶点的"度"?
- 对无向图:是的
- 对有向图:只能计算"出度",需要构造"逆邻接表"(存指向自己的边)来方便计算入度
-
不方便检查任意一对顶点间是否存在边
1.3.5、邻接矩阵与邻接表的关系
联系:邻接表中每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数。
区别:
- 对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)。
- 邻接矩阵的空间复杂度为O(n2),而邻接表的空间复杂度为O(n+e)
用途:邻接矩阵多用于稠密图,邻接表多用于稀疏图。
1.3.6、十字链表
十字链表是有向图的另一种链式存储结构。我们也可以把它看成是将有向图的邻接表和逆邻接表结合起来形成的一种链表。
有向图中的每一条弧对应十字链表中的一个弧结点,同时有向图中的每个顶点在十字链表中对应有一个结点,叫做顶点结点。
顶点结点:存储顶点数据、第一条出度边、第一条入度边
弧结点:
如上图,
第一条出度边,a->b(0->1), a-c(0->2)
第一条入度边,c->a(2->0), d->a(3->0)
1.3.7、邻接多重表
十字链表和邻接多重表初听挺难理解的,这里直接听视频讲解:https://www.bilibili.com/video/BV1nJ411V7bd?p=116
之后理解了再更新笔记记录!
1.4、图的遍历
从已给的连通图中某一顶点出发,沿着一些边访遍图中的所有的顶点,且使每个顶点仅被访问依次,就叫做图的遍历,它是图的基本运算。
图中可能存在回路,且图的任一顶点都可能与其他顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。那么怎么样避免重复访问呢?
解决思路:设置辅助数组 visited[n],用来标记每个被访问过的顶点。
- 初始状态 visited[i] 为0
- 顶点i被访问,改visited[i]为1,防止被多次访问
1.4.1、深度优先搜索DFS
Depth_First Search DFS (一条道走到黑)
方法如下:
- 在访问图中某一起始顶点 v 后,由 v 出发,访问它的任一邻接顶点W1
- 再从 W1 出发,访问与 W1 邻接但还未被访问过的顶点W2
- 然后再从 W2 出发,进行类似的访问
- 如此进行下去,直至到达所有的邻接顶点都被访问过的顶点u为止
- 接着,退回一步,退到前一次刚访问过的顶点,看是否还有其他没有被访问的邻接顶点
- 如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问
- 如果没有,那就再退回一步进行搜索。重复上述过程,直到连通图中的所有顶点都被访问过为止。
连通图的深度优先遍历类似于树的先根遍历
1.4.2、广度优先搜索BFS
Breadth_First Search BFS
方法如下:
从图的某一顶点出发,首先依次访问该结点的所有邻接点Vi1,Vi2,Vi3…Vin ,再按这些顶点被访问的先后次序依次访问与它们想邻接的所有未被访问的顶点。重复此过程,直至所有顶点均被访问为止。
- 连通图的广度遍历
- 非连通图的广度遍历
1.5、图的应用
1.5.1、最小生成树
生成树:所有顶点均由边链接在一起,但不存在回路的图。(将所有顶点都保持连通,边数最少,没有回路)
一个图可以有许多棵不同的生成树
所有生成树具有以下共同特点:
-
生成树的顶点个数与图的顶点个数相同
-
生成树是图的极小连通子图,去掉一条边则非连通
-
一个有n个顶点的连通图的生成树有 n-1 条边(含有n个顶点,n-1条边的图不一定是生成树。)
-
在生成树中再加一条边必然形成回路
-
生成树中任意两个顶点间的路径是唯一的。
1.5.1.1、无向图的生成树
顶点以及蓝色路径为生成树
设图 G=(V,E)是个连通图,当从图任一顶点出发遍历图G时,将边集E(G)分成两个集合T(G)和B(G)。其中T(G)是遍历图时所经过的边的集合,B(G)是遍历图时未经过的边的集合。显然,G1(V,T)是图G的极小连通子图。即子图G1是连通图G的生成树。
1.5.1.2、网的最小生成树
给定一个无向网,在该网中的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树,也叫最小代价生成树。
1.5.2、构造最小生成树
构造最小生成树的算法很多,其中多数算法都利用了 MST 的性质。
MST性质:设N=(V,E)是一个连通网,U是顶点集V的一个非空子集。若边(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。
1.5.2.1、Prim算法
算法思想:
-
设N=(V,E)是连通网,TE是N上最小生成树中边的集合
-
初始令 U={u0},(u0∈V),TE={}。
-
在所有 u ∈ U,v∈V-U的边(u,v)∈E中,找一条代价最小的边(u0,v0)
-
将(u0,v0)并入集合TE,同时v0并入U0
-
重复上述操作直至U=V为止,则T=(V,TE)为N的最小生成树。
例如:
我们要构造如下图的最小生成树,我们选取一个顶点,这个顶点就是U集合,其他顶点就是V-U集合
在U集合当中的顶点和V-U集合当中的顶点中找一条权值最小的边,将边关联的顶点选取到我们的U集合,其他顶点为V-U集合
之后在U集合当中的顶点和V-U集合当中的顶点中找一条权值最小的边,V1到V2为6,V1到V4为5,V3到V2为5,V3到V4为5,V3到V5为6,V3到V6为4。所以选取权值最小的边4,其关联的顶点为V6,将其加入到U集合中。此时U集合为{V1,V3,V6}
重复上述操作,V1周边的权值为6,5,V3周边的权值为5,5,6,V6周边的权值为6,V4周边不能选(一旦选了有回路成环了)
我们选择权值最小的V3到V2的5,将V2加入到U集合中
1.5.3.2、Kruskal算法
克鲁斯卡尔(Kruskal)算法
算法思想:
-
设连通网 N=(V,E),令最小生成树初始状态为只有 n 个顶点而无边的非连通图 T=(V,{}),每个顶点自成一个连通分量
-
在E中选取代价最小的边,若该边依附的顶点落在T中不同的连通分量上(即:不能形成环),则将此边加入到T中;否则,舍去此边,选取下一条代价最小的边。
-
以此类推,直到T中所有顶点都在同一连通分量上为止。
将所有顶点不连通,选择权最小的连接,且不能形成环。
最小生成树可能不唯一
1.5.3.3、比较
算法名 | 普里姆算法 | 克鲁斯卡尔算法 |
---|---|---|
算法思想 | 选择点 | 选择边 |
时间复杂度 | O(N2) | O(eloge)(e为边数) |
适应范围 | 稠密图 | 稀疏图 |
1.5.3、最短路径
问题抽象:在有向网中A点(源点)到达B点(终点)的多条路径中,寻找一条各边权值之和最小的路径。即最短路径。
最短路径与最小生成树不同,路径上不一定包含n个顶点,也不一定包含n-1条边。
第一类问题:
第二类问题:
单源最短路径 - 用 Dijkstra 迪杰斯特拉算法
所有顶点间的最短路径 - 用 Floyd 弗洛伊德算法
1.5.3.1、Dijkstra算法
- 初始化:先找出从源点V0到各终点Vk的直达路径(V0,VK),即通过一条弧到达的路径。
- 选择:从这些路径中找出一条长度最短的路径(V0,u)
- 更新:然后对其余各条路径进行适当调整
- 若在图中存在弧(u,Vk),且(V0,u)+(u,Vk) < (V0,Vk),则以路径(V0,u,Vk)代替(V0,Vk)
- 在调整后的各条路径中,再找长度最短的路径,以此类推。
迪杰斯特拉算法:按路径长度递增次序产生最短路径
-
把V分成两组:
(1)S:已求出最短路径的顶点的集合
(2)T=V-S:尚未确定最短路径的顶点集合
-
将T中顶点按最短路径递增的次序加入到S中,保证:
(1)从源点V0到S中各顶点的最短路径长度都不大于从V0到T中的任何顶点的最短路径长度。
(2)每个顶点对应一个距离值:
S中顶点:从V0到此顶点的最短路径长度
T中顶点:从V0到此顶点的只包括S中顶点作中间顶点的最短路径长度。
多说无益,我们来看一个例子:
- 初始时令 S = {V0},T={其余顶点}
- 记录V0到其他顶点的距离:若<V0,Vi>存在,则为其权值,若不存在,则为∞。使用辅助数组D存放。
V0到V1可以直达,为其权值13。V0到V2可以直达,为其权值8。V0到V3不可以直达,为∞。以此类推,然后从T中选取一个距离值最小的顶点Vj,加入S。例如我们上图中的8最小,则将V2加入S。
解下来我们要计算 从V0直接出发到各个顶点的路径和从V0出发经过V2再到各个顶点的路径,看是否有距离减少,没有减少则不需要改值。
如上图,加入V2之后:
V0到V1的值为13,V0经过V2再到V1是无法直达为∞,所以V0到V1的最短路径依旧是13
V2已经加入到S集合,所以不需要管V2。
V0到V3无法直达为∞,V0经过V2再到V3可以直达为 8+5=13,所以V0到V3的最短路径为13
V0到V4直达为30,V0经过V2再到V4无法直达为∞,所以V0到V4的最短路径为30
V0到V5无法直达为∞,V0经过V2再到V5也无法直达为∞,所以V0到V5的最短路径为∞
V0到V6直达为36,V0经过V2再到V6无法直达为∞,所以V0到V6的最短路径为32
从上述最短路径中选出最小的为13,我们选择序号更小的V1,将其加入到S集合中。
此时S集合为V0、V1、V2
V0到V3的最短路径依旧13
V0到V4的最短路径依旧是30
V0到V5无法直达,但是可以经过V1再直达,所以最短路径为 13+9=22
V0到V6直达为32,但是可以经过V1再直达,所以最短路径为 13+7=20
从上述最短路径中挑选值最小的为13,所以将V3加入到集合S中。
最终结果如下:
1.5.3.2、Floyd算法
算法思想:逐个顶点试探,从Vi到Vj的所有可能存在的路径中,选出一条长度最短的路径。
步骤如下:
- 初始时设置一个n阶方阵,令其对角线元素为0,若存在弧 <Vi,Vj>,则对应元素为权值,否则为∞
- 逐步试着在原直接路径中增加中间顶点,若加入中间顶点后路径变短,则修改之,否则,维持原值。所有顶点试探完毕,算法结束
来看一个例子:
有3各顶点,则设置一个3阶方阵,令其对角线为0,若存在弧,则对应元素为权值。
例如<A,B>权值为4,<A,C>权值为11.
加入A顶点后:
A到B,A到C距离没有变化。B到A,B到C距离也没有变化。C到A距离没有变化,C到B之前没有直达路径,加入A顶点之后可以从C经过A再到B,所以距离为 3+4=7。
加入B顶点之后:
A带B距离无变化,A到C直达路径为11,但是经过B路径为 4+2=6。
B到A,B到C距离无变化。
C到A,C到B距离无变化。
加入C顶点后:
1.5.4、拓扑排序
有向无环图:无环的有向图,简称DAG图
1.5.4.1、AOV网-拓扑排序
用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网,简称AOV网。
- 若从i到j有一条有向路径,则i是j的前驱,j是i的后继。
- 若<i,j>是网中的有向边,则i是j的直接前驱,j是i的直接后继。
- AOV网中不允许有回路,因为如果有回路存在,则表明某项活动以自己为先决条件,显然这是荒谬的。
拓扑排序:在AOV网没有回路的前提下,我们将全部活动排列成一个线性序列,使得若AOV网中有弧<i,j>存在,则在这个序列中,i一定排在j的前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排序的算法称为拓扑排序。
步骤:
- 在有向图中选一个没有前驱的顶点且输出之。
- 从图中删除该顶点和所有以它为尾的弧。
- 重复上述两步,直至全部顶点均已输出,或者当图中不存在无前驱的顶点为止。
来看一个例子
:
拓扑序列:C1
拓扑序列:C1、C2、C3
拓扑序列:C1、C2、C3、C4、C5
最终拓扑序列:C1、C2、C3、C4、C5、C7、C9、C10、C11、C6、C12、C8
注意:一个AOV网的拓扑序列不是唯一的
检测AOV网中是否存在环的方法:
对有向图构造其顶点的拓扑有序序列,若网中所有顶点都在它的拓扑有序序列中,则该AOV网必定不存在环。
1.5.4.2、AOE网-关键路径
用一个有向图表示一个工程的各子工程及其相互制约的关系,以弧表示活动,以顶点表示活动的开始或结束时间,称这种有向图为边表示活动的网,简称为AOE网。
来看一个例子:
设一个工程有11项活动,9个事件。
事件V1 — 表示整个工程开始(源点:入度为0的顶点)
事件V9 — 表示整个工程结束(汇点:出度为0的顶点)
关键路径:路径长度最长的路径
路径长度:路径上各活动持续时间之和。
在确定关键路径时,我们需要定义4个描述量:
事件V1的最早发生时间为0,V2的最早发生时间为V1发生完的时间30
事件V4的最迟发生时间:V4到V7发生需要15,所以总时间180-15=165
活动a3的最早开始时间:活动a1发生完a3才能发生
活动a3的最迟发生时间:其他活动发生完,180-(45+15)=120,因为a7肯定是在a3发生完发生的,所以也要留a7的时间
l(i)-e(i)表示完成活动ai的时间余量。例如:l(3)-e(3)=90
关键活动:关键路径上的活动,即l(i)==e(i)的活动。
-
如何找 l(i)==e(i)的关键活动?
设活动ai用弧<j,k>表示,其持续时间记为Wj,k 则有
(1) e(i) = Ve(j) [第i个活动的最早开始时间=弧尾顶点j的最迟开始时间]
(2) l(i) = Vl(k) - Wj,k [第i个活动的最迟开始时间=弧头顶点k的最迟发生时间-持续时间]
-
如何求Ve(j)和Vl(j)?
(1)从Ve(1)=0开始向前递推,其中T是所有以j为头的弧的集合
V e ( j ) = M a x V e ( i ) + W i , j , < i , j > ∈ T , 2 ≤ j ≤ n Ve(j) = Max{Ve(i)+Wi,j},<i,j>∈T,2≤j≤n Ve(j)=MaxVe(i)+Wi,j,<i,j>∈T,2≤j≤n
(2)从Vl(n) = Ve(n)开始向后递推,其中S是所有以i为尾的弧的集合
V
l
(
i
)
=
M
i
n
V
l
(
j
)
−
W
i
j
,
<
i
,
j
>
∈
S
,
1
≤
i
≤
n
−
1
Vl(i) = Min{Vl(j) - Wij}, <i,j>∈S, 1≤i≤n-1
Vl(i)=MinVl(j)−Wij,<i,j>∈S,1≤i≤n−1
看够了上面的公式,我们直接看例子:
- 例如我们要求Vj的最早发生时间。需要找它之前的所有活动的时间最长的就是Vj的最早发生时间
- 若我们要求事件Vj的最迟发生时间
求关键路径步骤:
- 求Ve(i)、Vl(j) [事件i的最早发生时间、事件j的最迟发生时间]
- 求e(i)、l(i) [活动i的最早开始时间、活动i的最迟开始时间]
- 计算 l(i)-e(i) [活动i的最迟开始时间 - 事件i的最早发生时间]
王卓数据结构与算法树(八——3)
《数据结构与算法》---(哔哩哔哩--王卓老师笔记)史上最详细笔记
《数据结构与算法》---(哔哩哔哩-王卓老师笔记)--数据结构
《数据结构与算法》---(哔哩哔哩-王卓老师笔记)---线性表类型定义