目录
图
简单来说,图是由一组点和相对应边组成的,可以表示为G=<V,E>,V为Vertex(点集),E为Edge(边集)。细分下来根据点集之间的对等关系又可以分为,有向图(又可分为带权和无权有向图),无向图,混合图。图中点包含的另外一个的重要信息就是各自的出度(outdeg)和入度(indeg),表示了以该点作为出边和入边的个数。
图的复杂度
对于无向图而言,所有点的度之和为sum(deg)=2*|E|,度之和为边的数目的两倍。对于有向图而言,所有点的入度之和自然等于出度之和,也会等于边的数目,毕竟每一条边存在源点和终点。
稀疏图和稠密图及其储存方式
没有明确的界定公式表明什么是稠密图什么是稀疏图,一般而言如果边的数目远小于完全图的边数目的一半,那么认为是稀疏图。远大于则认为是稠密图。稀疏图适合使用使用邻接表储存,稠密图适合使用邻接矩阵储存。
拓扑排序
拓扑排序是对有向无环图的顶点的一种排序,可以使得如果存在一条从vi到vj的路径,那么vj一定排在vi后面。拓扑排序常见于工程的调度排班问题,子任务之间存在先后依赖关系。对有向图的拓扑排序算法伪代码如下
Graph=[...] # 初始化图的储存,使用邻接矩阵或者邻接表;
indegs=[deg0,deg1,deg2....] # 储存所有的点的入读
stack=[node0, node1...] # 使用栈stack储存所有入度为0的点;
ans=[] # 储存拓扑排序结果
while stack:
topNode=stack.pop(0) # 取出第一个出度为0的点,从图中直接抹去
ans.append(topNode)
for u in Graph[topNode]: # 遍历取出的点所连接的点
indegs[u]-=1 # 对应连接到的点入度减1
if indegs[u]==0:
stack.append(u) # 判断入度是否为0
实际代码为:
from collections import defaultdict
# 构建邻接表的有向图
N=7
graph={0:[1,5],
1:[4],
2:[4,6],
3:[2,4],
4:[5,6],
5:[],
6:[]}
# 计算每个点的入读
indegs=defaultdict(int)
for k,v in graph.items():
for node in v:
indegs[node]+=1
# 记录所有入度为0的起始点
stack=[]
for i in range(N):
if i not in indegs:
stack.append(i)
# 惊喜拓扑排序
ans=[] # 排序结果
while stack:
topNode=stack.pop(0)
ans.append(topNode)
for v in graph[topNode]:
indegs[v]-=1
if indegs[v]==0:
stack.append(v)
# 输出结果
print(ans)
最短路径算法
最短路径算法解决的是从给定的点开始求解到其他点的最短路径(边可以带权或者不带权值)。
无权边的最短路径算法
这种情况下可以认为边的权值为1,如果权值为1,那么这个问题可以简化为BFS搜索问题(或者DFS递归搜索问题),也就是寻找达到各个节点的最短路径。
Graph=[...] # 初始化图的储存,使用邻接矩阵或者邻接表;
stack=[(initNode,0)] # 初始节点入栈,一并记录最短路径
dist={} # 记录源点到达各个点的最短距离,除开源点之外其他的初始值设置为inf
visited=set() # 记录已经访问过的点
while stack:
cur=stack.pop(0) # 取出一个节点
for v in graph[cur]:
# 遍历下一层节点
实际代码为:
graph={0:[1,5],
1:[4],
2:[4],
3:[2,4],
4:[5,6],
5:[],
6:[2]}
# 假设4 2 6成环,利用BFS进行查找
N=7 # 节点数
minDist={i:float('inf') for i in range(N)}
initNode=0
minDist[initNode]=0
stack=[(initNode, 0)]
visited=set(stack)
while stack:
cur=stack.pop(0)
visited.add(cur[0])
for v in graph[cur[0]]:
minDist[v]=min(minDist[v], cur[1]+1)
if v in visited: continue
stack.append((v, minDist[v]))
print(minDist)
带权图最短路径算法-Dijkstra
Dijstra算法是典型的BFS算法,同样的从源点开始搜索,直到达到最后的终点,基本的步骤如下:
- 将所有的点划分为两个集合{S,U},其中S代表已经求出来的最短路径的顶点及其最短的路径长度,U表示还未求出的点的长度。最开始S中只包含一个起点,U中包含了其他所有的点。
- 从U中选出距离最短的k,并将k加入S中,并从U中移除k
- 更新U中节点到起点的距离,这一步操作是因为确定了k是最短路径的顶点,进而更新一些经过k的点的路径,例如存在<s, v> > <s,k>+<k, v>那么更新v点的最短路径
- 重复操作前面两步
from typing import List, Dict
graph = {'A': [['B', 12],
['G', 14],
['F', 16]],
'B': [['F', 7],
['C', 10],
['A', 12]],
'G': [['F', 9],
['E', 8],
['A', 14]],
'F': [['C', 6],
['E', 2],
['B', 7],
['G', 9],
['A', 16]],
'C': [['E', 5],
['D', 3],
['B', 10],
['F', 6]],
'E': [['C', 5],
['D', 4],
['F', 2],
['G', 8]],
'D': [['C', 3],
['E', 4]]}
def Dijstra(graph: Dict[str, List[List]], start: str) -> List[List]:
graphConvert = dict()
for k, v in graph.items():
for nodeName, dis in v:
graphConvert[k + nodeName] = dis
if start not in graph:
return [] # 源点不存在图中
U = []
S = [[start, 0]]
for k, _ in graph.items():
if k == start: continue
U.append([k, float('inf')])
for node, distance in graph[start]:
U.remove([node, float('inf')])
U.append([node, distance])
# 初始化S和U完毕
while U:
minNode, minDistance = '', float('inf')
for nd, dis in U:
if dis < minDistance: # 在U中选取最小距离的节点
minNode = nd
minDistance = dis
print(minNode, minDistance)
# 更新U中的节点距离信息,放置<s, v> > <s, v>+<v, k>出现
for i in range(len(U)):
if minNode + U[i][0] not in graphConvert: continue
if U[i][1] > minDistance + graphConvert[minNode + U[i][0]]:
U[i][1] = graphConvert[minNode + U[i][0]] + minDistance # 更新信息
# 将节点加入S, 从U中移除节点
S.append([minNode, minDistance])
U.remove([minNode, minDistance])
return S
ans = Dijstra(graph, 'D')