[DataStructure] [Summary]Graph Algorithm
Minimum Spanning Tree
Prim’s Algorithm
描述:
无向图G 邻接矩阵Adj
k[n] 保存连入边的最小权重
p[n] 保存每个节点的父节点 (p: parent)
Q 维护一个Min-Piority Queue 包含余下未Span的节点
PiorityQueue 的 key: i, value: k[i]
初始化k[n] 全部元素为无穷大
初始化任选一个节点a作为root
使其k[a] = 0, p[a] = NULL
while Q not empty
u = ExtractMin(Q)
# Extract出剩余节点中k[i]最小的key i.e. 连入
for i in Adj[u]
# 连入新节点后更新k[n]
if ( i存在Q中且w(u,i) < k[i] )
p[i] = u
# 更新连入的父节点
Decreasekey( i , w(u,i) )
# 更新连入边的权重 保持其最小
时间复杂度
(!!! E:边的条数 V:节点数)
图的时间复杂度较难分析,一般可以从 边 入手
一个优先队列操作(heap实现): lgV
ExtractMin 做V次 ----> 总计 O(VlgV)
for语句:
在邻接链表&无向图中长度共计2E
每条边每次 lgV # Heap Ops.
每次累计 O(ElgV) # 按边算
In total O(VlgV + ElgV) = O(E*lgV) # E > V
另一种简单粗暴WrostCase分析: Vmax*dmax = O(V2) #d为度数
正确性证明
本质:贪心算法
贪心算法正确性证明即证明 (概括自CLRS)
- 贪心选择性质:每次选择都可以生成最优解,即每次选择安全safe
- 最优子结构:此次贪心选择最优+子问题最优 = 全局最优,即上述每次贪心选择的合并仍是最优解
在Prim算法中,需证明:
1.每次选择safe,即每次选择的min-weight edge不形成cycle
2.选择结果可以生成最优解,即是某个最优解的一部分
上述两点反证法很容易证得。在Generic-MST算法正确的前提下,Prim算法正确性证明结束。
Kruskal Algorithm
描述:
Simply put,
At each step, add the cheapest edge, if it were to form a cycle then don’t add it.
Prim:从Root出发,找cheapest的边Span
Kruskal:考察所有边,找cheapest的边Span
结果是Kruskal算法会形成很多forest
(找cheapest边的具体实现: 把边都放入min-piority queue)
时间复杂度
O(ElgV)
瓶颈在于判断要加的边会不会形成cycle很费时
(对策:union-find structure Find-Set(u) != Find-Set(v) 即u v 不在同一个集合----> 不成环)
正确性证明
在某一轮选择里,把所有节点cut成两派,且保证cut没有割裂已经连好的forest.
Claim:
若(u,v)是这一轮被选择的边,那么
对于任何与(u,v)交叉的cut,(u,v)必须是此时的cheapest边。
显然成立 逆否命题更显然成立
结合Prim算法证明,每次选最cheap的,结果符合MST.
关于正确性证明,可见这篇博文
BFS
Idea: Expand frontier across the breadth
(类比大物的波面)
using a FIFO queue
Implement:
队列Q
用color[u]标记各节点状态
–White 未入队
–Gray 这在队列中
–Black 已出队遍历过了
p[n]记录parent
d[n]记录到root距离
从根节点开始入队,更新color
while Q 不为空
u = Q.head
for i in Adj[u]
if color[i] == White
p[i] = u # 更新p[i]
d[i] = d[u] + 1
color[i] = Gray
Enqueue(Q, i) # 之后要遍历这个节点,入队
Dequeue(Q) # Q.head 遍历完了可以出队了
color[u] = Black
时间复杂度
O(V+E) = 每个点都要入队出队 O(V) + 每条边都要判断 O(E)
正确性证明
BFS的正确性指:
1.发现了每个s可以连接到的节点
2.结束后,到root的距离d = 到root的最短路径长度
3.root到节点v的最短路径之一为 root到v父节点最短路径 + (v父节点, v)
CLRS上先证明了几个Lemma
定义:shortest path from s to v = sp(s, v)
Lemma 1
对于任何s, 若u, v为直接相连两节点,有
sp(s, u)长度 <= sp(s, v)长度 + 1
(可以根据三角形两边之和大于第三边记忆)
利用路径最短的性质证明
Lemma 2
BFS完成后,d[v] >= sp(s,v)长度
数学归纳法证明
Lemma 3
对队列Q中节点v1,v2,…vr,有
- vr.d <= v1.d + 1
- vi.d <= vi+1.d
证明:
注意需要证明入队操作后 出队操作后均满足此性质
(这里CLRS看得很迷 全靠课件)
数学归纳法
(思考这里的induction是什么!)
对v1 = root两个性质显然成立
出队:
Q<v1,v2,…vr> ----> Q’<v2,…vr>
归纳证明即 证明若Q成立,Q’也成立
性质1:由 vr.d <= v1.d + 1 && v1.d <= v2.d 推得
性质2:显然成立
入队:
Q<v1,v2,…vr> ----> Q’<v1,…vr+1>
性质1:考察入队前后算法的具体操作,新节点入队后,Q.head出队前,算法正在搜索Q.head的子节点。故vr+1.d = v1.d + 1
性质2:又由前述得 vr.d <=v1.d + 1=vr+1.d。证毕。
Lemma3推论:Q队列中 vi.d 随 i 递增
需证明Q始终非空,再由前述引理归纳证得
最终证明BFS正确性
Slides Page120写得很清楚
证明新节点可以被发现 && v.d == sp(s,v)
DFS
类似BFS,用color标记每个节点状态。(不同之处是实现一个 FILO stack)
p[n]记录父节点,初始化为NULL
用d[n]、f[n]记录时间戳
d[i]为将 i 压栈的时刻
f[i]为 i 出栈的时刻
time记录时刻
DFS(G)
for u in V
if color[u] == white
DFS-visit(G, u)
DFS-visit(G, u)
color[u] = Gray
d[u] = ++time
# 开始遍历u的子节点
for v in Adj[u]
if color[v] == White
p[v] = u
DFS-visit(G, v)
# 遍历完成
color[u] = Black
f[u] = ++time
时间复杂度
每个点都要入栈出栈,即DFS-visit被call V 次----> O(V)
每条边都会被DFS-visit的for loop循环 ----> E = O(E)
(最小生成树是2E,因为我们讨论的是无向图)
Total = O(V + E)
相关定理
白色路径定理 略
边的类型
DFS引入了四种边的类型
Tree edge: 发现新节点(from gray to white)
Back edge: 从子孙到祖先(from gray to gray)
Forward Edge: 从祖先到子孙(from gray to black)
Cross Edge: 连接不同树与子树(from gray to black)
(Forward Tree 出现即子孙已被其他节点发现 该子孙并非新节点)
无向图中不会出现Forward Edge和Cross Edge
反证法证得
判断有无环
当且仅当DFS无back edge,此图为有向无环图(DAG)
Appendix
DAG一定存在一个只有出度无入度的节点
证明:
In a dag, there must be at least one vertex with no incoming edge, or a vertext with indegree 0. (Suppose not. Then for each vertex, we can move backwards through an incoming edge. There are only n vertices overall. If we walk backwards n times, we must hit a vertex we have seen before, which means there is a cycle, so the graph is not a dag.)
摘自IARCS
反证:如果每个点都有入度,那么对任意节点都可以沿着连入的边往回走到父节点上。总共n个节点,当往回走了n次肯定会碰到之前已经走过的一个点。也就是成环了。
应用:拓扑排序
DFS遍历完的节点后输出,再逆序即得拓扑排序。
正确性证明即:证明 若有边(u,v) --> u遍历结束时间晚于v
讨论(u,v)
为tree edge : 显然成立
为back edge: 成环了,不可能为back edge
为forward / cross edge: 此时v==Black已经遍历结束了,正在遍历u,结论成立