图
由顶点集V与边集E组成,记为G(V,E),其中V(G)表示图G中顶点的有限非空集;E(G)表示图G中边的集合
-
|V|表示图G中顶点个数,又称图的阶
-
|E|表示图G中边的条数
-
图必非空(即顶点集V非空),但边集E可以为空。
图的基本概念
无向图与有向图
若E是无向边的有限集合,则图G为无向图,即边(v,w)等价于边(w,v)
- 无向图中的度:顶点v的度是指依附于该顶点的边的条数,记为TD(v)
- 无向图的全部顶点的度之和等于边数的2倍
若E是有向边(又称弧)的有限集合,则图G为有向图,弧是顶点的有序对,记为<v,w>,v就是弧尾,w是弧头
- 入度:以顶点v为终点的有向边的条数,记为ID(v);
- 出度:以顶点v为起点的有向边的条数,记为OD(v);
- 顶点的度=入度与出度之和,即TD(v)=ID(v)+OD(v);
简单图与多重图
简单图:不存在重复边,不存在顶点到自身的边。
多重图:图中某两个结点之间的边数多于一条,允许顶点通过同一条边和自己关联。
顶点与顶点的关系
路径:顶点Vp到顶点Vq的一条路径,指一个顶点序列,Vp,Vi1,Vi2,,,Vim,Vq 。
- 顶点之间可能不存在路径
回路:第一个顶点和最后一个顶点相同的路径称为回路或环。
- 简单路径:路径序列中,顶点不重复出现的路径
- 简单回路:除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路
路径长度:路径上边的数目
点到点的距离:从顶点u到顶点v的最短路径若存在,则此路径长度称为u到v的距离,若u到v不存在路径,则该距离记为无穷。
连通图
- 在无向图中,若顶点v到顶点w有路径存在则称v与w连通
- 在有向图中,若顶点v到顶点w和从w到v之间都有路径,则称v与w强连通
若图G中任意两个顶点是连通的,则称图G为连通图,否则称为非连通图
对于n个顶点的无向图G,若G为连通图,则最少有n-1条边,非连通则最多有$C_{n-1}^2$条边
- 若图G中任意一对顶点强连通,则称此图为强连通图,有n个顶点,则最少有n条边(形成回路)
图与图的关系
子图:
若有满足V(G’)=V(G)的子图G’,则称该子图为G的生成子图
连通分量:无向图中的极大连通子图(子图必须连通且包含尽可能多的顶点和边)称为连通分量
强连通分量与连通分量类似,其描述的对象为有向图
生成树:包含途中全部顶点的一个极小连通子图(边尽可能的少,但要保持连通),且并不一定唯一。
- 若图中顶点数为n,则其生成树含有
n-1
条边,对于生成树而言,砍去一条边,就会变成非连通图,加上一条边就会产生回路 - n个顶点的图,若|E|>n-1,则一定有回路
生成森林:在非连通图中,连通分量的生成树构成了非连通图的生成森林。
- 带权路径长度:一条路径上所有边的权值之和,称为该路径的带权路径长度
有向完全图与无向完全图
无向完全图:无向图中任意两个顶点之间都存在边
- 若顶点数|V|=n,则$|E|= n(n-1)/2$
有向完全图:有向图中任意两个顶点之间都存在方向相反的两条弧
- 若定点数|V|=n,则$|E| = n(n-1)$
确保无向图为连通图的方法:取n-1个顶点构成完全无向图,再添一条边用于第n个结点的连通
图的存储
邻接矩阵(顺序)
- 0表示两个结点相互不邻接(无边)
- 1表示两个结点相互邻接(有边)
|
|
|
|
对于无向图的邻接矩阵:
- 第i个结点的度 = 第i行/列的非零元素个数
对于有向图的邻接矩阵:
-
第i个结点的出度 = 第i行的非零元素个数
-
第i个结点的入度 = 第i列的非零元素个数
-
第i个结点的度 = 第i列i行所有非零元素个数之和
空间复杂度O(|V|2),只与顶点数有关,和实际边数无关
- 适合用于存储稠密图
无向图的邻接矩阵是对称矩阵且唯一,所以可以压缩存储(存上三角或下三角)
性质:
- 邻接矩阵
A[i][j]
表示顶点i到顶点j长度为n的路径的数目 - 只要确定了顶点编号,图的邻接矩阵表示方式唯一
邻接表(顺序+链式)
|
|
对于无向图:由于其特性,每条边在表中将重复出现两次,故空间复杂度O(|V|+2|E|)
对于有向图:每条边只会出现一次,故空间复杂度O(|V|+|E|)
- 图的邻接表表示方式不唯一
- 适合存储稀疏图
十字链表(仅有向图)
- 顺着顶点结点的绿色指针
firstout
走到尾可以找到所有从结点向外发射的所有弧 - 顺着顶点结点的橙色指针
firstin
走到尾可以找到所有指向这个结点的弧
例:找A->B与A->C,即寻找以A为弧尾的所有弧,从顶点结点出发根据firstout
指针依次寻找,弧结点的headvex
将指明弧尾A指向了什么弧头
空间复杂度:O(|V|+|E|),仅适用于有向图
邻接多重表(仅无向图)
空间复杂度:O(|V|+|E|),每条边只对应一份数据
图的操作
图的广度优先遍历(BFS)
类似二叉树的层序遍历算法
- 找到与一个顶点相邻的所有顶点
- 标记哪些顶点被访问过
- 需要一个辅助队列
- 核心操作:
FirstNeighbor(G,x)
:该操作返回G中顶点x的第一个邻接点号
,没有该顶点或无邻接点返回-1NextNeighbor(G,x,y)
:该操作返回x的除邻接点y以外的下一个邻接点顶点号
,若y是x的最后一个结点,返回-1
|
|
空间复杂度:需要设置辅助队列,为O(|V|)
时间复杂度:对于邻接表存储方式,为O(|V|+|E|),对于邻接矩阵,为O(|V|2)
图的深度优先遍历(DFS)
与树的先根遍历类似,使用递归实现
|
|
- 空间复杂度来源于函数调用栈,最坏递归深度为O(|V|);
- 时间复杂度=访问各节点所需时间+探索各条边所需时间
- 邻接矩阵存储的图时间复杂度为O(|V|2)
- 邻接表存储的图时间复杂度为O(|V|+|E|)
图的应用
最小生成树(MST)
使图的所有生成树集合中,取所有边的权值之和最小的数作为最小生成树
- 最小生成树不唯一,权值之和唯一
- 最小生成树的边数等于顶点数-1
Prim算法
从某一个顶点开始构建生成树,每次将代价最小的顶点纳入生成树直到所有顶点全部纳入为止。
- 初始化:向空树添加图中任意结点
- 循环:从图中选择还未加入到树中,离树最近,且代价最小的结点与边
- 树中已含所有结点,退出循环
- 时间复杂度O(|V|2),不依赖于边的个数,适用于边稠密图,即边数|E|接近|V|2的图
Kruskal算法
每次选择一条权值最小的边,使这条边的两头连通(原本已经连通就不选,直至所有结点连通。
- 初始化:将图初始化为n个顶点无边的非连通图T,此时每个节点都是一个连通分量
- 循环:按照边的权值,每次选择一条未被选过且最小的边,若该边两头的顶点落在不同的连通分量上(即不会产生回路),则加入到T中,否则舍弃选择下一条边
- 直到所有结点落在一个连通分量上,退出循环
-
判断是否连通属于并查集的范畴
-
时间复杂度O(|E|log~2~|E|),依赖于边数,适用于边稀疏而顶点较多的图
单源最短路径问题
给定一个源点,求解源点到各顶点间的最短距离问题
BFS算法
仅适用于无权图或所有边权值相同的图
基于广度优先遍历对visit函数进行改动即可
|
|
Dijkstra算法
适用于带权图与无权图,使用dist[j]
,表示源点到顶点j的最短距离,使用arc[i][j]
表示表示有向边<i,j>的权值,集合S保存顶点
-
初始化:
dist[i]=arc[0][i]
,即初态下,dist[]
将源点的邻接点的弧作为各源点邻接点的最短路径,集合S仅含源点 -
从不在集合S的顶点集中选出Vj,满足
dist[j]
在dist[]
中最小,并将该顶点加入到集合S -
查看Vj顶点的所有邻接点Vk,循环判断
dist[j]+arc[j][k]
与dist[k]
的大小关系,小于则说明源点通过Vj可获得源点到Vk的最短路径,直到所有邻接点Vk都比较完毕 -
重复2,3步骤直到所有结点加入集合S
基于贪心策略,该算法时间复杂度为O(|V|^2^)
Dijkstra算法并不适用于负权值带权图
Floyd算法
适用于带权、无权图、可解决负权图
基于动态规划,对于一对结点Vj ,Vi,用A[i][j]
表示其最短距离
- 初始时将
arc[i][j]
作为其最短距离 - 随后尝试在路径中添加中间结点Vk,若添加后的距离变短,更新路径序列值
- 即$A^{k}[i][j]=min(A^{k-1}[i][j],A^{k-1}[i][k]+A^{k-1}[k][j])$
- 经过n次迭代后,方阵A中保存了任意一对结点的最短路径信息
|
|
- 时间复杂度O(|V|3)
- 空间复杂度O(|V|2)
- 不能解决负权回路图,有可能根本没有最短路径
有向无环图
描述表达式
若一个有向图中不存在环,则称为有向无环图(DAG),用它描述表达式时,顶点不会出现重复的操作数
- 各个操作数不重复的排成一排
- 标出各个运算符的生效顺序
- 自底向上按顺序加入运算符,注意分层,并合并
拓扑排序
AOV网(用顶点表示活动的网)
- 用DAG图,即有向无环图表示一个工程,顶点表示活动,有向边<Vi ,Vj>表示活动Vi必须先于Vj
拓扑排序:找到做事的先后顺序
实现:
-
从AOV网中选择一个没有前驱(入度为0)的顶点并输出
-
从网中删除该顶点和所有以它为起点的有向边
-
重复上述步骤直到AOV网为空或当前网中不存在无前驱的顶点为止
- 当前网中不存在无前驱的顶点,说明有向图中必存在环,表明拓扑排序不存在,只有DAG图才可以拓扑排序
每个AOV网都有一个或多个拓扑排序序列
关键路径
AOE网:在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销,称之为用边表示活动的网络。
- 只有在某顶点所代表的时间发生后,从该顶点出发的各有向边所代表的活动才能开始
- 只有在进入某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才能发生。另外,有些活动是可以并行进行的。
在AOE网中仅有一个入度为0的顶点,称为开始顶点(源点),它表示整个工程的开始
也仅有一个出度为0的顶点,称为结束顶点(汇点),它表示整个工程的结束
-
从源点到汇点的所有路径中,具有最大路径长度的路径称为关键路径,而把关键路径上的活动称为关键活动
-
完成整个工程的最短时间就是关键路径的长度,若关键活动不能及时完成,则整个工程的完成时间就会延长。
计算关键路径的一些定义:
-
事件Vk最早发生时间
ve(k)
:决定了所有Vk开始的活动能够开工的最早时间 -
活动ai最早发生时间
e(i)
:该活动弧的起点所表示的事件的最早发生时间 -
事件Vk的最迟发生时间
vl(k)
:在不推迟整个工程完成的前提下,事件最迟必须发生的时间 -
活动ai的最迟开始时间
l(i)
:该活动弧终点的所表示事件最迟发生时间于该活动所需时间之差 -
活动ai的时间余量
d(i)=l(i)-e(i)
,不增加完成整个工程所需总时间的情况下,活动ai可以拖延的时间
如果活动ai的时间余量为0,则活动ai为关键活动,由关键活动组成的路径称为关键路径
求关键路径的步骤:(仅应试),每个结点用(l(i),e(i))
的形式表示
-
正序找一条权值最大的路径:从0开始加,更新结点的
l(i)
,到达汇点时l(i)
值就是关键路径的长度 -
逆序找一条权值最小的路径:逆序出发依次减掉权值最小的路径权值更新
e(i)
-
值相等的结点组成关键路径:寻找
l(i)
与e(i)
相等的结点
参考:
关键路径不唯一,只提高一条关键路径的关键活动速度不能提高整个工程的工期