[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)

  1. 贪心选择性质:每次选择都可以生成最优解,即每次选择安全safe
  2. 最优子结构:此次贪心选择最优+子问题最优 = 全局最优,即上述每次贪心选择的合并仍是最优解

在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,有

  1. vr.d <= v1.d + 1
  2. 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,结论成立

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值