Appearance
Kruskal
概述
K算法解决无向带权图的最小支撑树问题。输入图
算法伪代码及代码
伪代码
- Sort the edge such that
- Set
- For i=1 to m do:if
contains no cycle then
代码
算法的关键点在于如何判断添加边
python
def K_sig(vvw, n): # 输入三元组,标号判断回路的Kruskal算法
vvw = sorted(vvw, key=lambda x: x[2]) # 使用lambda函数,意为 key(x) = x[2]
parent = [i for i in range(n)] # 首先以每个点作为一棵子树
tree = []
for edge in vvw:
root1 = parent[edge[0]]
root2 = parent[edge[1]]
if root1 != root2:
tree.append(edge)
if len(tree) == n - 1:
break
for i in range(n):
if parent[i] == root2:
parent[i] = root1
if len(tree) < n - 1:
print("Don't connected")
else:
print(tree)
vvw = [
(0, 1, 1),
(0, 2, 2),
(1, 2, 2),
(1, 3, 4),
(1, 4, 3),
(3, 4, 2),
(2, 4, 4),
(2, 3, 4),
]
K_sig(vvw, 5)
算法复杂度分析
标号判断
根数判断
路径压缩
Graph-scanning Algorithm
概述
输入图
伪代码及代码
伪代码
- Set
, - If
, then stop;else choose - choose a
with ,if there is no such then set ,go to 2 , go to 2
代码
python
test_list = [[1,2,3],[0,5],[0,3,5],[0,2,4],[3,5],[1,4]]
n = 6
s = 0
def Graph_Scan(adjlist,n,s):
"""
Input: adjlist 邻接表
n 点数
s 起始点
Output: R 从s出发能够到达的所有点的集合
T R、T组成一个以s为根的树形图/树
"""
viewed = [0]*n
viewed[s] = 1
R = [s]
Q = [s]
T = []
while len(Q) != 0:
v = Q[-1]
w = s
for vertex in adjlist[v]:
if viewed[vertex] == 0:
w = vertex
break
if w == s:
Q.pop()
break
else:
R.append(w)
Q.append(w)
T.append([v,w])
viewed[w] = 1
return R,T
print(Graph_Scan(test_list,n,s))
正确性及算法复杂度
正确性
首先,
假设
算法复杂度
数据结构采用邻接表的情况下,可以证明算法复杂度达到
邻接表下,采用深度优先搜索,则每次搜索都是由邻接表存储的起点出发,沿指针指向的搜索所有存储单元,最终该算法在邻接表上遍历一次,故算法复杂度为
Prim Algorithm
概述
在割中找最小权的边
伪代码和代码
伪代码
- Choose
,Set - while
:choose an edge of minmum weight,
代码
python
def Triarray2adjlist(n, vvw): # 将三元数组转化为邻接表的形式
adjlist = []
for i in range(n):
col_edge = []
for edge in vvw:
v1, v2 = edge[0], edge[1]
if v1 == i:
col_edge.append((v2, edge[2]))
if v2 == i:
col_edge.append((v1, edge[2]))
adjlist.append(col_edge)
return adjlist
def Prim(n, adjlist, startv): # 输入邻接表
path = [-1] * n
lowcost = [float("inf")] * n # 初始定义所有点cost为无穷,起始点cost为0
lowcost[startv] = 0
left = set() # 记录未参与到划分中的点。
left.add(startv)
while len(left) > 0:
min, k = float("inf"), -1
for i in left:
if lowcost[i] < min:
min, k = lowcost[i], i
if k >= 0:
left.remove(k)
print("edge =(" + str(path[k]) + "," + str(k) + "), cost = " + str(lowcost[k]))
col_edge = adjlist[k] # 刚检测的点的邻接表所在行
for edge in col_edge:
j = edge[0]
if lowcost[j] > edge[1] and j in left: # j in left 才能进行
lowcost[j], path[j] = edge[1], k
elif lowcost[j] == float("inf"): # 在首次将所有的都加入了left,并对与现有划分求距离。
lowcost[j], path[j] = edge[1], k
left.add(j) # add
vvw = [
(0, 1, 1),
(0, 2, 2),
(1, 2, 2),
(1, 3, 4),
(1, 4, 3),
(3, 4, 2),
(2, 4, 4),
(2, 3, 4),
]
adjlist = Triarray2adjlist(5, vvw)
Prim(5, adjlist, 0)
算法复杂度分析
在Prim算法的一般实现中,需要用到两个一维数组,使用
算法实现需要
在上述实现过程中,选择
算法改进
采用二叉树数据结构可以改进到
Push-relabel Algorithm
概述
显然有两个过程,推流(更新
符号定义
- 超出量
: ,如果 ,我们称 正在点 守恒。 - 预流
:如果其满足 且 ,那么 为一个预流。特别的,如果 ,那么 为一个流。 - 活动点:
且 则称 为一个活动点。 - 距离标号及容许的:
为一个网络且 为预流。距离标号 且满足 以及 ,特别的若一条边 且 ,那么称它是容许的。
算法伪代码
伪代码
- For every
: .
The other: , for : . - While exist active vertex, choose active one
:
ifand is admissible: PUSH( ),else RELABEL(v).
PUSH
- compute
- Augment
along by
RELABEL
Set
代码
正确性及复杂度分析
算法复杂度为
对其正确性给出如下证明,首先,在算法执行过程中,容易证明
对于
Srong Connected component in digraph
概述
事实上算法在做这样的事情:
- 以DFS按有向图的所有点出发,得到树形森林,将点按照搜索过程出栈顺序放入
中。 - 将
中的点从后向前取点 ,以 出发按图逆邻接表能搜到且为进入其他强连通分支的所有点构成一个强连通分支。
伪代码及代码
伪代码
这里,
VISIT1
VISIT2
代码
python
n = 7
adjlist = [[5],[2],[1],[1,5],[0,1,2,3],[4],[2,4]]
viewed = [0]*n
P_dfs = []
scc = []
def dfs(adjlist,u): # 给到出栈顺序
viewed[u] = 1
for v in adjlist[u]:
if not viewed[v]:
dfs(adjlist,v)
P_dfs.append(u)
def reverse_adj(adjlist,n):
r_adjlist = [[] for _ in range(n)]
for v in range(n):
if len(adjlist[v])!= 0:
for w in adjlist[v]:
r_adjlist[w].append(v)
return r_adjlist
def rdfs(adjlist,u,scc):
viewed[u] = 1
scc.append(u)
for v in adjlist[u]:
if not viewed[v]:
rdfs(adjlist,v,scc)
dfs(adjlist,0) # 第一次dfs
P_dfs.reverse() # 反回出栈顺序,由于第二次dfs从后向前取点
viewed = [0]*n # 重置一下
r_adj = reverse_adj(adjlist,n) # 反向邻接表
for i in range(n):
if not viewed[i]: # i还不在任何强连通分支中
scc = []
rdfs(r_adj,i,scc)
print(scc)
可行性及算法复杂度
可行性
事实上,如果
令
算法复杂度
算法复杂度为
Floyd-warshall Algorithm
概述
FW算法用来解决图中所有点对间最短路的问题,即输入有向图
FW算法事实上在做这样一件事情:比较点对之间的经过中转点与不经过中转点的距离哪个更优。
伪代码及代码
python
vvw=[(1,2,3),(1,3,8),(1,5,-4),(2,4,1),(2,5,7),(3,2,4),(4,1,2),(4,3,-5),(5,4,6)]
n = 5
def find_mini_path(vvw,n):
'''
求解所有点对间的最短路径算法
Input
vvw :三元组邻接表
n :点数
Output
D :D[i][j]表示点i到点j的最短加权路的长度
P :P[i][j]表示i到j途径的点。
'''
MAX = float('inf')
m = len(vvw)
D = [[MAX for i in range(n)] for j in range(n)]
P = [[MAX for i in range(n)] for j in range(n)]
for i in range(m):
v1,v2 = vvw[i][0]-1,vvw[i][1]-1
D[v1][v2]=vvw[i][2]
P[v1][v2] = v1+1 # 注意,这里由于在输入时点记号为1-5,python第一位为0,方便将元素与点对应起来改为+1
for k in range(n): # 注意,中转点位于最外层
for i in range(n):
for j in range(n):
if D[i][k] + D[k][j] < D[i][j]:
D[i][j] = D[i][k] + D[k][j]
P[i][j] = P[k][j]
return D,P
D,P = find_mini_path(vvw,n)
print(D,P)
正确性及算法复杂度证明
在守恒权的假设下,FW算法能够正确运行,并具有
TIP
注意到算法对中转点的循环位于最外层,接下来说明,FW迭代过程中,
在守恒权的保证下,外循环
记
Dinic's Algorithm
概述
解决最大流问题的算法,游戏打多了头疼,改天再仔细看。
符号定义
- 剩余容量
: 。其中 表示流, 代表反向弧。 - 残余图
: 。 - 分层图
: - 阻塞流:一个
流 如果 中没有 路,那么 为阻塞流。注意,阻塞流未必是最大流。
算法伪代码与代码
伪代码
- Set
for all - Construct
of - Find a block flow
in , if then stop - Replace
with and go to 2.
代码
算法正确性证明
该算法在有限时间内能够找到最大流。
首先,说明每一次迭代,最短可扩路长度严格增加。令