定义
其实跟树差不多,但是树强调的是节点(data),图则既强调节点又强调边。
分类
- 有向图(边有方向)
- 无向图(边没方向)
例如,在无向图里<j,i>和<i,j>是同一条边。在有向图里<j,i>代表从节点j指向节点i的一条边,显然<j,i>和<i,j>就不是同一条边了。
术语
度
一个顶点所关联的边的数目。
有向图另外区分入度和出度。
- 入度:以该顶点为终点的边的数目
- 出度:以该顶点为起点的边的数目
- 度:出度和入度之和
图中所有顶点度数之和等于边数的二倍。
完全图
图中每个顶点都有与其他所有顶点的连线,这意味着有向图每两个节点之间是有两条线,来回各一条。
k阶完全图有 k ∗ ( k − 1 ) / 2 k*(k-1)/2 k∗(k−1)/2条边
路径
路径就是顶点序列(由于一对顶点可以表示一条边,那么该序列相邻两个顶点就是一条边,整个序列就是多个边首尾相接),该序列含有的边的数目就是路径长度。
若一条路径上除了开始点和结束点可以相同(注意,是可以,但未必一定得相同)外,其余顶点均不相同,则称此路径为简单路径。
连通
在无向图G中若两个顶点之间有路径则称这两个顶点是连通的。
若无向图G中任意两个顶点都是连通的则称图G为连通图。
这里有点绕,其实就是说一个点不管经过多少路径能走到另一个点,就说这俩是连通的。
权和网
边带权的图叫做带权图,也叫做网(net)
生成树与最小生成树
生成树
- 是无环子图
- 是树
- 包含了原图中的所有顶点
最小生成树其实是最小权重生成树的简称,强调的是在生成树的基础上,选择使得总权重最小的边的特性。
一个连通图的生成树是一个极小连通子图,其中含有图的全部顶点,和构成树的n-1条边。
强调连通图是因为要防止某些存在孤立无连接的顶点的特例
具体算法参见下方的基本运算
寻找生成树的算法不一定要选择边的权重最小,只需满足无环和连通性条件即可。而寻找最小生成树的算法旨在选择边的权重最小
处理最小生成树一般提到两种算法(具体内容均见后文):
- Prim
- Kruskal
两种方法结果是一样的,但是过程不太一样。
处理最短路问题一般提到两种算法(具体内容均见后文):
- Dijkstra算法
- Floyd算法
其中Floyd算法可以处理权值为负的情况
Dijkstra不可处理权值为负的情况,原因看下文
典型问题
杂
关于最小生成树
- 最小生成树代价唯一,但是树形可能不唯一(有多个权值相同的边)
- Prim和Kruskal得到的最小生成树是一样的
- n n n个节点的最小生成树的边共 n − 1 n-1 n−1条,它们未必是权值最小的 n − 1 n-1 n−1条,即有可能树中某条边的权值超过未选边的权值
关于有向图
- 只有无环有向图有拓扑序列 ⇔ \Leftrightarrow ⇔没有拓扑序列的必有环
- 邻接矩阵为三角矩阵的有向图必有拓扑序列,反正则不一定成立
- 拓扑排序的结果可能不唯一
关于无向图
- n n n个顶点的无向图含有 n ( n − 1 ) / 2 n(n-1)/2 n(n−1)/2条边
最小生成树和最短路的区别
最小生成树只是连通且保证边的权值之和最少,但是不保证任意两点之间是最短路径
最短路解决的问题是单源点的最短路径,它确保对于指定的点(源点)到任何其他点的距离最短。
联通子图与连通分量
连通子图:顾名思义,是子图,且是连通的
连通分量:指无向图G中的极大连通子图。因此连通图的连通分量只有自身一个,而非连通图有多个。
这里要对极大连通子图和极小连通子图饶舌一下:
- 极大连通子图就是连通分量。
- 极小连通子图是用最少的边实现所有的边连通。
上面说了两个顶点之间有路径则称这两个顶点是连通的,但是没说有多少路径(极大就是希望路径尽可能多)。对比看来,极大连通子图与极小连通子图相反,它要尽可能多地纳入原图的边,并且使这个图是连通的。
亦可以直接参考这篇文章讲的 图的连通
- 连通图只有一个极大连通子图
- 非连通图有多个极大连通子图
最小生成树和极小连通子图
- 生成树不唯一
- 最小生成树 ≠ \neq =极小连通子图
- 最小生成树是连通图的总权值最小的无环连通分量
包含图中全部顶点的极小连通子图,只有生成树满足这个极小条件,即生成树是一种特殊的极小连通子图。换句话说,如果一个极小连通子图是一个树,那么它也是原图的生成树。极小连通子图不一定包含所有顶点。
极大强联通子图
强连通图是每一对顶点都存在路径(路径 ≠ ≠ =边,显然路径是可以经过其他点的)
- 极大强联通子图=强联通分量(显然暗含的前提是有向图)
- 强连通图的极大强连通子图只有一个且是其本身
- 含 n n n个顶点的强连通图最多有 n ( n − 1 ) n(n-1) n(n−1)条边(此时每对顶点之间都有两条边),最少有 n n n条边(此时是有向环)
- 不存在极小强连通子图
判断回路
- 拓扑排序
- DFS(区分BFS是判断覆盖)
关于搜索
对比DFS和BFS的时间复杂度之前必须先指明存储方式
关于拓扑排序
因为拓扑排序的终止条件是“空或者不存在无前驱的节点”,所以排序完 ≠ \neq =图是有向无环图。即使它有唯一的拓扑序列也不行。必须是“拓扑序列包含所有顶点”才可以
存储方法
往前想一想,学数据结构之前那些栈和队列的实现,都是围绕数组和链表两种结构实现的,图这种数据结构也不例外。
注:由于邻接表含有链表,这意味着本身存储效率就有损耗。因为有一部分空间用于表示指针域,即维护各节点之间的关系。存储效率=有用数据占用空间/总占用空间
,非零节点是有用的数据,邻接表只存储非零节点,稀疏图零节点多。稠密图类似,零节点少,采用邻接矩阵。
- 邻接矩阵
- 邻接表
- 十字链表法
- 邻接多重表
- 前向星
注:
- 邻接表表示方法不唯一
- 邻接表存储的是点,而链式前向星存储的是边
基本运算
遍历
最小生成树的算法
kruskal
克鲁斯卡尔算法(Kruskal)详解
在原无向带权图中选择一些边构成新的图(显然也是树),选的原则是:
- 总选择权值最小的边加入作为结果的新图里
- 若选择的边与此前已选择的边会构成环则弃而选权值次小的
Prim
最短路
Dijkstra与Floyd
说实话文字叙述确实抽象,我的建议是看视频,而我恰好看到了这个视频讲的挺好的:
拓扑序列
性质/要求:对于对于有边或路径<i,j>则在拓扑序列中必有i在j前面
前提:图是有向图。只有有向无环图才有拓扑序,所以有向无环图又被称为拓扑图。有向有环图也可以输出拓扑序,只是输出的顶点序列是该图所有顶点的一部分。
在一个有向图中找一个拓扑序列的过程称为拓扑排序
拓扑排序的步骤是:
- 从图中选一个没有前驱(入度为0)的顶点(如果有多个则任选1个)输出
- 在图中去掉该顶点和它延伸出去的所有边
- 然后重复上述步骤
(显然由上面的1可以知道)拓扑序列可能不唯一
如果不能排出拓扑序列,则意味着有环(含有顶点数目大于1的强连通分量)
这时候就不得不提AOV网(Activity on Vertex Network)与AOE网(Activity On Edge)
AOV与AOE
AOV | AOE | |
---|---|---|
边表示 | 先后顺序 | 事件 |
顶点表示 | 事件 | 状态 |
边是否有权 | 否 | 是 |
AOV网是没有权值的,只是表示了事情的优先顺序,比如说上图(忽略掉边权值)可以看出来,先完成 V 1 V_1 V1才可以进行 V 2 V_2 V2和 V 3 V_3 V3表示的内容
AOE网有权值,表示完成该边代表事件所对应的代价(如所需时间),那么比如说从 V 1 V_1 V1这个状态到 V 2 V_2 V2需要完成时间 a 1 a_1 a1,耗时3小时,这就是一种可能具有的含义。
在AOE网中,状态 a i a_i ai的解锁当且仅当其所有前驱节点解锁
比如说上图只有 V 2 V_2 V2和 V 3 V_3 V3都完成了才能进行 V 4 V_4 V4
看下方的时间轴图,不用多说,立马明白关键路径(第一行)的意义
关键路径可以有多条
例如上图a1+a4+a8+a11也是
关键路径求法
显然对于某个关键事件
a
有ve(a)=vl(a)
定义是这样的:
ve(v)
是源点x
到v
所有路径的最大值,即
v l ( v ) = v e ( y ) − M A X { c ( p ) } vl(v)=ve(y)-MAX\{c(p)\} vl(v)=ve(y)−MAX{c(p)}
参考
李春葆《数据结构教程》
图的连通,连通图,连通分量,强连通分量
浅谈什么是图拓扑排序
7.1 图的定义与基本术语(这位仁兄的数据结构专栏看起来还可以啊)