无圈连通图, E=V−1 E = V − 1 , 详细见树,
for v in V:
v.d = MAX
v.pre = None
v.isFind = False
root. isFind = True
root.d = 0
que = [root]
while que !=[]:
nd = que.pop(0)
for v in Adj(nd):
if not v.isFind :
v.d = nd.d+1
v.pre = nd
v.isFind = True
que.append(v)
时间复杂度 O(V+E) O ( V + E )
Θ(V+E) Θ ( V + E )
def dfs(G):
time = 0
for v in V:
v.pre = None
v.isFind = False
for v in V : # note this,
if not v.isFind:
dfsVisit(v)
def dfsVisit(G,u):
time =time+1
u.begin = time
u.isFind = True
for v in Adj(u):
if not v.isFind:
v.pre = u
dfsVisit(G,v)
time +=1
u.end = time
begin, end 分别是结点的发现时间与完成时间
利用 DFS, 结点的完成时间的逆序就是拓扑排序
同一个图可能有不同的拓扑排序
在有向图中, 强连通分量中的结点互达
定义 Grev G r e v 为 G G 中所有边反向后的图
将图分解成强连通分量的算法
在 Grev 上根据 G 中结点的拓扑排序来 dfsVisit, 即
compute Grev
initalization
for v in topo-sort(G.V):
if not v.isFind: dfsVisit(Grev,v)
然后得到的 DFS 森林 (也是递归树森林) 中每个树就是一个强连通分量
利用了贪心算法,
总体上, 从最开始 每个结点就是一颗树的森林中 (不相交集合, 并查集), 逐渐添加不形成圈的 (两个元素不再同一个集合), 最小边权的边.
edges=[]
for edge as u,v in sorted(G.E):
if find-set(u) != find-set(v):
edges.append(edge)
union(u,v)
return edges
如果并查集的实现采用了 按秩合并与路径压缩技巧, 则 find 与 union 的时间接近常数
所以时间复杂度在于排序边, 即 O(ElgE) O ( E l g E ) , 而 E<V2 E < V 2 , 所以 lgE=O(lgV) l g E = O ( l g V ) , 时间复杂度为 O(ElgV) O ( E l g V )
用了 BFS, 类似 Dijkstra 算法
从根结点开始 BFS, 一直保持成一颗树
for v in V:
v.minAdjEdge = MAX
v.pre = None
root.minAdjEdge = 0
que = priority-queue (G.V) # sort by minAdjEdge
while not que.isempty():
u = que.extractMin()
for v in Adj(u):
if v in que and v.minAdjEdge>w(u,v):
v.pre = u
v.minAdjEdge = w(u,v)
//note it's v, not vlgv
综上, 时间复杂度为 O(ElgV) O ( E l g V )
如果使用的是 斐波那契堆, 则可改进到 O(E+VlgV) O ( E + V l g V )
求一个结点到其他结点的最短路径, 可以用 Bellman-ford 算法, 或者 Dijkstra 算法.
定义两个结点 u,v 间的最短路
Dijkstra 算法不能处理, 只能用 Bellman-Ford 算法,
而且如果有负值圈, 则没有最短路, bellman-ford 算法也可以检测出来
def initialaize(G,s):
for v in G.V:
v.pre = None
v.distance = MAX
s.distance = 0
def relax(u,v,w):
if v.distance > u.distance + w:
v.distance = u.distance + w:
v.pre = u
性质
* 三角不等式: δ(s,v)⩽δ(s,u)+w(u,v) δ ( s , v ) ⩽ δ ( s , u ) + w ( u , v )
* 上界: v.distance⩾δ(s,v) v . d i s t a n c e ⩾ δ ( s , v )
* 收敛: 对于某些结点 u,v 如果 s->…->u->v 是图 G 中的一条最短路径,并且在对边,进行松弛前任意时间有 u.distance=δ(s,u) u . d i s t a n c e = δ ( s , u ) 则在之后的所有时间有 v.distance=δ(s,v) v . d i s t a n c e = δ ( s , v )
* 路径松弛性质: 如果 p=v0v1…vk p = v 0 v 1 … v k 是从源结点下 v0 到结点 vk 的一条最短路径,并且对 p 中的边所进行松弛的次序为 (v0,v1),(v1,v2),…,(vk−1,vk) ( v 0 , v 1 ) , ( v 1 , v 2 ) , … , ( v k − 1 , v k ) , 则 vk.distance=δ(s,vk) v k . d i s t a n c e = δ ( s , v k )
该性质的成立与任何其他的松弛操作无关,即使这些松弛操作是与对 p 上的边所进行的松弛操作穿插进行的。
def dag-shortest-path(G,s):
initialize(G,s)
for u in topo-sort(G.V):
for v in Adj(v):
relax(u,v,w(u,v))
def bellman-ford(G,s):
initialize(G,s)
for ct in range(|V|-1): # v-1times
for u,v as edge in E:
relax(u,v,w(u,v))
for u,v as edge in E:
if v.distance > u.distance + w(u,v):
return False
return True
第一个 for 循环就是进行松弛操作, 最后结果已经存储在 结点的 distance 和 pre 属性中了, 第二个 for 循环利用三角不等式检查有不有负值圈.
def dijkstra(G,s):
initialize(G,s)
paths=[]
q = priority-queue(G.V) # sort by distance
while not q.empty():
u = q.extract-min()
paths.append(u)
for v in Adj(u):
relax(u,v,w(u,v))
使用动态规划算法, 可以得到最短路径的结构
设 l(m)ij l i j ( m ) 为从结点 i 到结点 j 的至多包含 m 条边的任意路径的最小权重, 当 m = 0, 此时 i=j, 则 为 0,
可以得到递归定义
n = L.rows
L' = new matrix(nxn) for i in range(n): for j in range(n): l'[i][j] = MAX
for k in range(n):
l'[i][j] = min(l'[i][j], l[i][k]+w[k][j])
return L'
可以看出该算法与矩阵乘法的关系
L(m)=Wm L ( m ) = W m ,
所以可以直接计算乘法, 每次计算一个乘积是 O(V3) O ( V 3 ) , 计算 V 次, 所以总体 O(V4) O ( V 4 ) , 使用矩阵快速幂可以将时间复杂度降低为 O(V3lgV) O ( V 3 l g V )
def f(W):
L = W
i = 1
while i2
return L
同样要求可以存在负权边, 但不能有负值圈. 用动态规划算法:
设 d(k)ij d i j ( k ) 为 从 i 到 j 所有中间结点来自集合 {1,2,…,k} { 1 , 2 , … , k } 的一条最短路径的权重. 则有
由此得出此算法
def floyd-warshall(w):
n = len(w)
d= w
initial pre # 0
for k in range(n):
d2 = d.copy()
pre2 = pre.copy()
for j in range(n):
for i in range(v)
if d[i][j] > d[i][k]+d[k][j]:
d2[i][j] = min(d[i][j], d[i][k]+d[k][j])
pre2[i][j] = pre[k][j]
pre = pre2
d = d2
return d,pre
思路是通过重新赋予权重, 将图中负权边转换为正权, 然后就可以用 dijkstra 算法 (要求是正值边) 来计算一个结点到其他所有结点的, 然后对所有结点用 dijkstra
JOHNSON (G, u)
s = newNode
G' = G.copy() G'.addNode(s)
for v in G.V: G'.addArc(s,v,w=0) if BELLMAN-FORD(G' , w, s) ==FALSE
error "the input graph contains a negative-weight cycle"
for v in G'.V: # computed by the bellman-ford algorithm, delta(s,v) is the shortest distance from s to v h(v) = delta(s,v) for edge(u,v) in G'.E:
w' = w(u,v)+h(u)-h(v) d = matrix(n,n) for u in G: dijkstra(G,w',u) # compute delta' for all v in G.V
for v in G.V:
d[u][v] = delta'(u,v) + h(v)-h(u) return d
G 是弱连通严格有向加权图, s 为源, t 为汇, 每条边 e 容量 c(e), 由此定义了网络 N(G,s,t,c(e)),
* 流函数 f(e):E→R f ( e ) : E → R
由于其实现可以有不同的运行时间, 所以称其为方法, 而不是算法.
思路是 循环增加流的值, 在一个关联的” 残存网络” 中寻找一条” 增广路径”, 然后对这些边进行修改流量. 重复直至残存网络上不再存在增高路径为止.
def ford-fulkerson(G,s,t):
initialize flow f to 0
while exists an augmenting path p in residual network Gf:
augment flow f along p
return f
def ford-fulkerson(G,s,t):
for edge in G.E: edge.f = 0
while exists path p:s->t in Gf:
cf(p) = min{cf(u,v):(u,v) is in p}
for edge in p:
if edge in E:
edge.f +=cf(p)
else: reverse_edge.f -=cf(p)