本文包含了图的宽度优先遍历, 深度优先遍历,拓扑排序序列,prim算法, kruskal算法, dijkstra算法
class Custom(ValueError):
pass
class GraphQues:
def __init__(self, graph):
self.graph = graph
def BFS(self, node_name):
'''
从一个节点出发进行宽度优先遍历
:param node_name: 开始节点的名称
:return:
'''
# 从图中根据节点的名称获取节点
node = self.graph.nodes.get(node_name, None)
if not node: return
s = set()
q = queue.Queue()
q.put(node)
s.add(node)
while not q.empty():
item = q.get()
print(item.value, end=" ")
for i in item.nexts:
if i not in s:
q.put(i)
s.add(i)
return
def DFS(self, node_name):
"""
深度优先遍历
:param node_name: 开始节点名称
:return:
"""
node = self.graph.nodes.get(node_name, None)
if not node: return
stack = Stack() # 遍历的路径
s = set() # 纪录已经走过的节点
s.add(node)
stack.push(node)
while True:
flag = True
cur_node: GraphNode = stack.pop()
for next_node in cur_node.nexts:
if next_node not in s:
stack.push(cur_node)
stack.push(next_node)
s.add(next_node)
flag = False
break
if flag:
stack.push(cur_node)
break
return stack
def Topological_sort(self):
'''
图的拓扑排序序列
:return: 拓扑排序序列
'''
if not self.graph.is_directed:
raise Custom("无向图没有拓扑排序序列")
def get_in_0(nodes_dic):
"""
找到一个入度为0 的节点
:type nodes_dic: dict[Node.value:Node]
:return 找到了 返回节点, 没找到 抛出异常
"""
if not nodes_dic:
return
for inner_node in nodes_dic.values():
if inner_node.in_ == 0:
return inner_node
raise Custom("没有没有入度为0的节点了")
ans = []
nodes = self.graph.nodes.copy()
while node := get_in_0(nodes): # 获得入度为0 的节点
ans.append(node) # 结果序列扩充
for elem in node.nexts: # 使该节点的指向节点的入度-1
elem.in_ -= 1
del nodes[node.value] # 从节点序列中删除该节点
return ans # 返回节点序列
def prim(self, node: GraphNode = None):
"""
普利姆算法
要求 无向连通图
:param node: 起始节点 若不填则随即选择一个节点作为起始节点
:return: 最小生成树序列
"""
if not node or node.value not in self.graph.nodes: # 如果没有给初始值或者给的起始节点不再图中
node = self.graph.nodes.get(choice(self.graph.nodes)) # 随即选择一个节点作为起始节点
heap = [] # 边的最小堆
nodes_ = {node} #
ans = [] # 结果序列
while len(ans) < len(self.graph.nodes) - 1: # 最小生成树的边数应为 节点数 - 1
# 将当前节点的出边与已经存在的边的最小堆合并, 剔除指向节点已经在结果序列的边, 根据边权值排序
heap = list(heapq.merge(heap, filter(lambda x: x.to_ not in nodes_, node.edges), key=lambda x: x.weight))
elem = heap.pop(0) # 弹出边权值最小的边
if elem.to_ not in nodes_: # 查看边的指向节点是否已经找到了
node = elem.to_ # 节点指针移动到下一个节点
nodes_.add(elem.to_) # 节点集合扩充
ans.append(elem) # 结果序列扩充
return ans # 返回结果序列
def kruskal(self):
"""
克鲁斯卡尔算法
要求 无向图
:return: 最小生成树 边权序列
"""
inner_dsu = DSU(list(self.graph.nodes.values())) # 将所有节点初始化到并查集
edges = sorted(list(self.graph.edges), key=lambda x: x.weight) # 根据权值排序
ans = [] # 结果序列
for edge in edges: # 遍历所有边 最坏的结果找到权值最大的那条边
if not inner_dsu.is_connected(edge.from_, edge.to_): # 判断俩节点是否已经属于同一棵树
inner_dsu.union(edge.from_, edge.to_) # 如果不是连接两节点
ans.append(edge) # 结果序列添加该边
# 最小生成树 边数为节点数 - 1
if len(ans) >= len(self.graph.nodes) - 1:
return ans # 成功找到所有需要的序列 返回
def Dijkstra(self, begin_node_name):
"""
迪杰斯特拉算法
找到从一个起始节点开始到其他节点的距离
:param begin_node_name: 起始节点名称
:return:
"""
if begin_node_name not in self.graph.nodes:
raise Custom(f"不存在此节点, {begin_node_name}")
begin_node = self.graph.nodes.get(begin_node_name)
node_dic = {node: -1 for node in self.graph.nodes.values() if node != begin_node} # 给节点赋初值
node_dic[begin_node] = 0 # 将初始节点的距离置为0
select_nodes = [begin_node]
active_nodes = {begin_node}
def update(node: GraphNode):
"""
更新起始节点到其他节点的距离
更改 node_dic
:param node:
:return:
"""
nonlocal node_dic, select_nodes, active_nodes # 本地变量
for edge in node.edges: # 遍历出边
if edge.to_ in select_nodes:
continue # 如果出边是已经确定了最终距离的节点直接跳过
active_nodes.add(edge.to_) # 将找到的结点激活
weight = node_dic.get(edge.from_) + edge.weight # 获取从本节点到其他节点的距离
if node_dic.get(edge.to_) == -1 or node_dic[edge.to_] > weight:
# 条件1: 未被更新过
# 条件2: 更新过但是新的距离更短
node_dic[edge.to_] = weight # 如果符合条件 则更新距离
while len(select_nodes) < len(self.graph.nodes):
cur_node = sorted(active_nodes, key=lambda x: node_dic.get(x))[0] # 在激活的结点集中找一个现有距离最短的结点
select_nodes.append(cur_node) # 确定了到该节点的最终最短距离
update(cur_node) # 以 cur_node 为起始节点 激活直接相邻的结点
active_nodes.remove(cur_node) # 使结点从激活结点集中删除
return node_dic # 返回结果序列
输出函数
def show_stack(s: Stack):
if s.is_empty(): return
while not s.is_empty():
print(s.pop().show(), end="<-")
print("\b\b")
def show_node_list(nodes: list):
for node in nodes:
print(node.show(), end=" ")
print()
def show_edge_weight(edges: list):
# 输出全部边权集合
print("Print all node_node_weight sets".center(50, "*"))
index = 0
for edge in edges:
if index == 6:
index = 0
print()
index += 1
print(edge.show(), end=" ")
print()
print("End".center(50, "*"))
部分测试函数
if __name__ == '__main__':
g = ChangeToDefGraph.node_node_weight(src_data, False)
gq = GraphQues(g)
# 输出全部边权集合
show_edge_weight(g.edges)
# 深度优先遍历
# s = gq.DFS(0)
# show_stack(s)
# 拓扑排序序列
# res = gq.Topological_sort()
# show_node_list(res)
# res = gq.prim(g.nodes.get(0))
# res = gq.kruskal()
# show_node_list(res)
# res = gq.prim(g.nodes.get(0))
# show_node_list(res)
res = gq.Dijkstra('g')
print("---".center(50))
for key, value in res.items():
print(key.show(), value, end=" | ")