图 论
使用邻接表和邻接矩阵中的邻接矩阵描述
图的遍历
图例如下, 0为起点. 0 /
1 2 \ /
3 4
深度优先搜索(DFS)
N = 5
G = [
[ 0, 1, 1, 0, 0 ],
[ 1, 0, 0, 1, 0 ],
[ 1, 0, 0, 1, 1 ],
[ 0, 1, 1, 0, 0 ],
[ 0, 0, 1, 0, 0 ]
]
V = [False for i in range(N)]
def dfs(n):
print(n)
for j in range(N):
if not V[j] and G[n][j]:
V[j] = True
dfs(j)
V[0] = True
dfs(0)
广度优先搜索(BFS)
from queue import Queue
N = 5
G = [
[ 0, 1, 1, 0, 0 ],
[ 1, 0, 0, 1, 0 ],
[ 1, 0, 0, 1, 1 ],
[ 0, 1, 1, 0, 0 ],
[ 0, 0, 1, 0, 0 ]
]
V = [False for i in range(N)]
Q = Queue()
def bfs(n):
Q.put(n)
V[0] = True
while not Q.empty():
i = Q.get()
print(i)
for j in range(N):
if not V[j] and G[i][j]:
Q.put(j)
V[j] = True
bfs(0)
最小生成树
问题: 将一个图的所有顶点构成树, 要求边的权值和最小. 普里姆算法:从起点开始选择边, 每次添加一个顶点(边权值和最小)并调整已添加好的顶点集与其他顶点的最短距离(边权值最小), 递归此步骤. 克鲁斯卡尔算法:每次选择最短边, 如果构成环, 则放弃这条边, 递归此步骤.
普里姆(prim)算法
X = 100
N = 6
G = [
[ X, 6, 1, 5, X, X ],
[ 6, X, 5, X, 3, X ],
[ 1, 5, X, 1, 5, 4 ],
[ 5, X, 1, X, X, 2 ],
[ X, 3, 5, X, X, 6 ],
[ X, X, 4, 2, 6, X ]
]
class Edge:
def __init__(self, begin, end, lens):
self.begin, self.end = begin, end
self.lens = lens
def __len__(self):
return self.lens
# init edge info
E = []
for i in range(1, N):
E.append(Edge(0, i, G[0][i]))
# set kth edge
for k in range(0, N-1):
# find shortest edge
minz, idx = X, -1
for i in range(k, N-1):
if len(E[i]) < minz:
minz = len(E[i])
idx = i
# swap edge
E[k], E[idx] = E[idx], E[k]
v = E[k].end
# adjust edge
for i in range(k+1, N-1):
d = G[v][E[i].end]
if d < len(E[i]):
E[i].lens = d
E[i].begin = v
for item in E:
print(item.begin, item.end, len(item))
克鲁斯卡尔(kruskal)算法
查找是否在相同集合来避免产生环, 集合为存放后继的顶点链.
X = 100
N = 6
G = [
[ X, 6, 1, 5, X, X ],
[ 6, X, 5, X, 3, X ],
[ 1, 5, X, 1, 5, 4 ],
[ 5, X, 1, X, X, 2 ],
[ X, 3, 5, X, X, 6 ],
[ X, X, 4, 2, 6, X ]
]
class Edge:
def __init__(self, begin, end, lens):
self.begin, self.end = begin, end
self.lens = lens
def __len__(self):
return self.lens
# init edge info
E = []
for i in range(N):
for j in range(i+1, N):
if G[i][j] != X:
E.append(Edge(i, j, G[i][j]))
E.sort(key=lambda x: x.lens)
P = [0 for i in range(N)]
# save next vertex
def find_set(f):
while P[f] > 0:
f = P[f]
return f
# set kth edge
cnt_edge = 0
for i in range(len(E)):
# find set
n = find_set(E[i].begin)
m = find_set(E[i].end)
# if no circle
if n != m:
# add next vertex for vertex n
P[n] = m
print(E[i].begin, E[i].end, len(E[i]))
# N-1 edges finish
cnt_edge += 1
if cnt_edge == N-1:
break
最短路径
问题: 计算一个顶点到其他所有顶点的最短路径. 迪杰斯特拉算法: 设置一个起点, 先确定一个最短路径顶点v(第一次确定是相邻的顶点), 再将v作为中间点, 调整起点与各顶点的距离, 递归此步骤.
迪杰斯特拉(dijkstra)算法
INF = 400
N = 5
G = [
[ 0, 10, INF, 30, 100 ],
[ INF, 0, 50, INF, INF ],
[ INF, INF, 0, INF, 10 ],
[ INF, INF, 20, 0, 60 ],
[ INF, INF, INF, INF, 0 ]
]
# init S & D & P
# S(Set for Vertex)
# D(Distance for vertex 0 to others)
# P(Prev vertex for vertex)
S, D, P = [], [], []
S.append(0)
for i in range(N):
D.append(G[0][i])
P.append(0)
# set kth vertex
for k in range(1, N):
# select shortest edge
minz = INF
for i in range(N):
if not i in S:
if D[i] < minz:
minz = D[i]
v = i
# add new vertex to S
S.append(v)
# adjust D(distance)
for i in range(N):
if not i in S:
if D[i] > D[v]+G[v][i]:
D[i] = D[v]+G[v][i]
P[i] = v
# show shortest distance
print(D)
# show shortest path
for i in range(1, N):
prev = P[i]
print(i, end='')
while prev != 0:
print('<--%d' % prev, end='')
prev = P[prev]
print('<--%d' % 0)
网络流
二分图匹配问题 问题: 求二分图的最大边匹配(最小点覆盖). 匈牙利算法: 依次匹配, 遇到点已经被匹配的情况下, 尝试能否让被匹配的点的对点匹配其他点(寻找增广路径), 递归此步骤. 二分图待配对情况: 0 <=> 0, 1 1 <=> 1, 2 2 <=> 0 3 <=> 2
匈牙利(Hungary)算法
N = 4
G = [
[ 1, 0, 1, 0 ],
[ 1, 1, 1, 0 ],
[ 0, 1, 0, 1 ],
[ 0, 0, 0, 0 ]
]
R = [-1 for i in range(N)]
def find_path(n):
for j in range(N):
if not V[j] and G[n][j]:
V[j] = True
if R[j]==(-1) or find_path(R[j]):
R[j] = n
return True
return False
cnt = 0
for i in range(N):
V = [False for i in range(N)]
if find_path(i):
cnt += 1
print(cnt)
for i in range(N):
if R[i] != (-1):
print('%d <==> %d' % (i, R[i]))
最大流问题 问题: 起点s, 终点t, 途中经过的管道(边)都有一个最大流量, s到t的最大水流量是多少? 定义: 残量网络 => 当前管道还可以容纳的水量(随着搜索变化), 加入反向边, 如果寻找到的增广路是错误路径, 可以通过反向边修正. 增广路算法: BFS找最短的增广路径, 修改这条路径流量值(修改残量网络边权), 递归此步骤.
增广路(Ford-Fulkerson)算法
import copy
from queue import Queue
INF = 400
N = 6
G = [
[ 0, 5, 5, 0, 0, 0],
[ 0, 0, 0, 5, 5, 0],
[ 0, 0, 0, 5, 0, 0],
[ 0, 0, 0, 0, 0, 5],
[ 0, 0, 0, 0, 0, 5],
[ 0, 0, 0, 0, 0, 0]
]
def find_path(R, P):
V = [False for i in range(N)]
Q = Queue()
Q.put(S)
V[S] = True
# BFS find_path
while not Q.empty():
top = Q.get()
for i in range(N):
if not V[i] and R[top][i]>0:
Q.put(i)
V[i] = True
P[i] = top
# if find vertex T
return V[T] == True
def FF():
# R => Rest Flow Graph(Use Reverse R)
R = copy.deepcopy(G)
max_flow = 0
# P => Path Of Flow
P = [0 for i in range(N)]
# if has path
while find_path(R, P):
min_flow = INF
# find min flow
v = T
while v != S:
u = P[v]
min_flow = min(min_flow, R[u][v])
v = P[v]
# adjust R
v = T
while v != S:
u = P[v]
R[u][v] -= min_flow
# reverse path => fix prior error path
R[v][u] += min_flow
v = P[v]
# add max flow
max_flow += min_flow
return max_flow
S, T = 0, N-1
max_flow = FF()
print(max_flow)