第二十二章 图的基本算法
1. 搜索算法是图算法的核心,两种表示方法:
a) 邻接表(稀疏图)
b) 邻接矩阵(稠密图)
2. 两种图的遍历方法:
广度优先遍历(求每条边都是单位权值的图的最短路径)
深度优先遍历(拓扑排序、将有向图分解为强连通子图)。
深度优先搜索除了创建一个深度优先森林外,深度优先搜索同时为每个顶点加盖时间戳。每个顶点v有两个时间戳:当顶点v第一次被发现时,记录下第一个时间戳d[v],涂成灰色,当结束检查v的邻接表时,记录下第二个时间戳f[v],涂成黑色。
如果开始用“(”结束用“)”,则u满足括号定理
许多基于深度优先搜索的图算法都用到了时间戳,它们对推算深度优先搜索的时行情况有很大的帮助。当记录下这2个时间戳之后,很多东西都可以由这对时间戳来推导出来了(比如拓扑排序、深度遍历的次序等)。
拓扑排序:判断有向图中是否有环,利用深度优先遍历所得到的时间戳f[i]逆向排序,可用于逻辑表达式的推理。
关键路径:求的影响项目进度的关键路径,提前完成关键活动进而加快工程进度
强连通分支:有向图G=(V, E)的一个强连通分支就是一个最大的顶点集合C,对于C中的每一对顶点u,v是互相可达的,时间 O(v+e)
a) 对G进行深度优先遍历得到每个顶点的时间戳f[x];
b) 求得G的反向图GT;
c) 按照f[x]的逆向顺序为顶点顺序对GT进行深度优先遍历,即按照G的拓扑排序的顺序对GT再进行深度优先遍历;
d) 步骤c得到的各棵子树就是原图G的各强连通分支。
第二十一章 最小生成树
1. 最小(权值)生成树:
由n-1条边,连接了所有的n个顶点,并且所有边上的权值和最小。
2. Kruskal算法和Prim算法:
这两种算法中都就可以很容易地达到O(ElgV)的运行时间。通过采用斐波那契堆,Prim算法的运行时间可以减少到O(E +VlgV),这对于稠密图来说是个很大的改进。
3. Krusal算法:
每次向集合A加入的边总是图中连接两个不同连通分支的最小权边。集合A是森林
使用【不相交集合数据结构】的森林表示,初始每个节点 make_set(v)
对边按权值排序
for 每一条边 E(u, v)
if ( find_set(u)!=find_set(v) )
A=A + E(u,v)
union(u,v)
4. Prim算法:每次加入的边是连接树与一个不在树中的顶点的最小权边,集合A是树
所有v进入优先队列Q,start.key=0, 其他节点都是无穷, u.parent=null
while (Q !=null)
u=extract_min(Q)
for( u的相邻节点 )
if v 属于Q and w(u,v )<key(v)
key[v]=w(u,v)
v.parent=u;
关键看使用什么实现优先队列
第二十四章 单源点路径
假设有向图使用邻接表存储;d[v] 代表v到定点s的权; v.parent代表父节点
1. 从渐近意义上来说,解决一对顶点的最短路径问题的复杂度与单源最短路径的复杂度相同。
2. DIJKSTRA算法假定输入图中的所有边的权值都是非负的,是贪心算法。而Ford算法允许输入边存在负权边,只要不存在从源点可达的负权回路。而且如果存在着负权回路,它还能检测出来。
3. Bellman—Ford算法,为O(ve)
start.key=0, 其他节点都是无穷
每条边按照一定的顺序 执行 V-1次松弛
for 每条边
if d[v] > d[u]+w(u,v)
return false; //有负权回路
return true;
4. 关键路径:
有向无回路图中的一条最长路径,是完成所有工作所需的时间下限
5. DIJKSTRA算法
和Prim非常类似,差别是相邻点判断,Prim 中 v 不属于Q,而且v.key是w(u,v)。 Dijkstra中 v.key 是 u.key+w(u,v)的和
和广度优先相比:前者的集合S相当于广度的黑色顶点集合
时间复杂度也看优先队列的实现
6. 差分约束的线性方程可以转换为约束图,通过Ford算法求得其可行解
第二十五章 每对定点间最短路径
1. Folyd-Warshall是一个动态规划算法,运行时间为O(V3); Johnson算法使用了几种算法作为其子算法,运行时间为O(V2lgV+VE),尤其适合对大型稀疏图。
2. Folyd-Warshall算法的运行时间为 O(V3),它允许权值为负的边,但是假定了不存在权值为负的回路。而且它的代码是紧凑的,而且不包含复杂的数据结构,隐含的常数因子很小。因此,即便对于中等规模的输入图来说 Folyd-Warshall算法仍然相当的实用。
3. Folyd-Warshall的核心在于:它改进了“最优子问题结构”,使用 dij(k)来表示从顶点i到顶点 j、且满足所有中间顶点个数<=k的一条最短路径的权值。这种限定了起始点的技巧大大的减少了实现的计算量,基本思路:
for k 1 to n
对于矩阵的每一个取 ( <=k-1 , k 个) 中间节点的最小值
4. 有向图的传递闭包:G的传递闭包定义为图G*=(V, E*),其中 E*={(i,j) : 图G中存在着一条从 i到 j的路径},解决此问题的整体思路与 Folyd-Warshall算法一样,只是把 Folyd-Warshall算法中的 min和+操作替换为相应的 OR 和AND逻辑运算来加快速度。
5. Johnson 算法可在 O(V2lgV+VE)时间内,求出每对顶点间的最短路径。Johnson 算法使用 Dijkstra 和 Bellman-Ford 算法作为其子程序,对所有情况 负权边和负权回路都可用,基本思路
a) 为原有图加入 起点s ,得到 G’;
b) 在 G’上调用 Bellman-Ford 算法。由于 Bellman-Ford 算法能够检测负权回路,如果存在负权回路则报告存在负权回路并结束整个算法;否则得到在 G’上调用Bellman-Ford算法得到的 h(x)函数;
c) 根据 h(x)函数对G 中的每一条边进行重赋权,使得G 中的每一条边都是非负的 w(u,v)+h(u)-h(v);
d) 对重赋权后的G 进行循环调用 Dijkstra算法,得到每对顶点间的最短路径;
e) 对得到的每对顶点间的最短路径再根据 h(x)函数反向构造出原来权值下的最短路径
第二十六章 最大流
1. 最大流问题:是关于流网络的最简单问题,它提出这样的问题:在不违背容量限制的条件下,把物质从源点传输到汇点的最大速率是多少?
2. 具有多个源点和多个汇点的网络最大流问题可以转化成普通的单源点、单汇点的最大流问题(通过添加一个单一的源点和一个单一的汇点并置新添加的边的容量为无穷大来达到)
3. Ford-Fulkerson算法是求最大流的一般方法,它利用了三点:残留网络、增广路径、最大流最小割定理。
a) 残留网络:G由可以网络中没有全用的边所组成;
b) 增广路径:为残留网络Gf上从 s到t的一条简单路径p,其中 p中所的边的最小权值为该增广路径的残留容量;
c) 最大流最小割定理:证明了 Ford-Fulkerson算法能够得到全局最优解“当一个流是最大流,当且仅当它的残留网络不包含增广路径”。
4. 抵消:利用了抵消处理,可以将两城市间的运输用一个流来表示,该流在两个顶点之间的至多一条边上是正的。也就是说,任何在两城市间相互运输球的情况,都可以通过抵消将其转化成一个相等的情况,球只在一个方向上传输:沿正网络流的方向。
这样:两个顶点之间至多有两条边,而这两条边至多会有一条有正的流值,另一条相应的边的流值为0(明白这点在理解算法时是有用的, Fulkerson就用到了,非常难理解)。
5. Ford-Fulkerson基本步骤:
a) 初始化G 中所有边的流为0;
b) 计算当前图与当前流所映射的残留网络Gf;
c) 从残留网络上选取一条增广路径并计算出残留容量,将残留容量添加到图的当前流上;
d) 循环步骤 b-c直到残留网络Gf中不存在增广路径为止;
e) 此时的流即为G 的最大流。
6. 使用“广度优先搜索”来求增广路径的 Ford-Fulkerson 算法即称之为 Edmonds-Karp 算法,这种使用广度优先搜索来求增广路径的算法能够改善 Ford-Fulkerson 算法的运行时间。
7. 最大二分匹配问题:这是最大流最重要的应用之一,并且有许多其它的问题可以归约成它,例如:把一个机器集合 L和要同时执行的任何集合R 相匹配。E中的边(u,v),就说明一台特定机器 u能够完成一项特定任务v,最大匹配可以为尽可能多的机器提供任务)
8. 解决最大二分匹配问题:先生成一个扩展图 G’(加入s, t),再对 G’的每条边赋予单位流量 1,然后求出最大流,就是该最大二分匹配问题的解。