Prim 和 Kruskal 算法寻找最小生成树
- 实验原理
-
堆
堆是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于(或不小于)其左子节点和右子节点的值。最大堆和最小堆是二叉堆的两种形式。最大堆:根结点的键值是所有堆结点键值中最大者。最小堆:根结点的键值是所有堆结点键值中最小者。本次实验使用的是最小堆。 -
Python heapq 模块
heapq 模块提供了相应的方法来对堆进行操作。
最常用的有如下方法:
heap = [] #创建了一个空堆
heappush(heap,item) #往堆中插入一条新的值
item = heappop(heap) #从堆中弹出最小值 item = heap[0] #查看堆中最小值,不弹出
heapify(x) #以线性时间将一个列表转化为堆
实验中用到了三个方法:
heapify(x):用第一个节点的所有邻接边创建堆,heappush(heap,item):将新的割边不断加入到堆中,heappop(heap):获取割边中最小权重的边; -
并查集:union-find
并查集:一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。常常在使用中以森林来表示。
集:就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的元素所在的集合合并。- 使用并查集的主要步骤:
(1) 初始化:把每个点所在集合初始化为其自身(根节点为自身), 时间复杂度均为 O(N);
(2) 查找:查找元素所在的集合,即根节点。(递归查找,查找次数为树的深度);
(3) 合并:将两个元素所在的集合合并为一个集合。
通常来说,合并之前,先判断两个元素是否属于同一集合,这可用“查找”操作实现。 - 实现思路
(1)初始化:将图的每个节点初始化为根节点是自身的树初始高度为 0;
(2)为了方便找到权重最小的边,先把所有的边按权重降序排序;然后获取最小边,判断两端节点是否属于一棵树上,如果不属于就将他们合并,并把边加入到最小生成树上;否则继续从列表中取边,一直到最小生成树里面的边数目等于图的节点数-1 为止;
- 使用并查集的主要步骤:
-
- 利用最小堆实现 prim 算法:
import Graph
from heapq import heapify, heappop, heappush
import Edge
def prim(graph):
"""
prim算法实现代码
:param graph: 传入要操作的图
:return: mst 最小生成树
"""
mst = Graph.Graph() # 最小生成树集合(特殊的图)
node_num = graph.get_node_num() # 图上的节点数
nodes_in_mst = [graph.get_node_list()[0]] # 树上的节点集
cut_edges = graph.get_node_edges(str(graph.get_node_list()[0])) # 割边集合,初始时是第一个节点的邻边
heapify(cut_edges) # 初始化最小堆,将割边加入到堆
while cut_edges:
weight, node_a, node_b = heappop(cut_edges) # 弹出最小割边
if node_b not in nodes_in_mst: # 若该点没有在树上
nodes_in_mst.append(node_b)
mst.add_edge(Edge.Edge(node_a, node_b, weight)) # 把边加到树上
if mst.get_edge_num() == node_num-1: # 树上边数比节点数少1
break
for edge in graph.get_node_edges(str(node_b)): # 遍历与node_b邻接的所有边
if edge[2] not in nodes_in_mst: # 如果该条边没有在树上
heappush(cut_edges, edge) # 将新的割边加入到堆中
return mst
- 利用并查集实现 kruskal 算法
import Graph
import Util
from Edge import Edge
from copy import deepcopy
from operator import itemgetter
def Kruskal(graph, tree_set):
"""
kruskal算法实现代码
@:param:graph 图
@:param:tree_set 图节点构成的森林(树的集合)
@:return:mst 最小生成树(特殊的图)
"""
edges = sorted(deepcopy(graph.get_all_edges()), key=itemgetter(0), reverse=True) # 对所有的边进行降序排序
node_num = graph.get_node_num() # 获取节点个数来控制循环次数
mst = Graph.Graph() # 最小生成树用图来表示
while edges:
weight, node_a, node_b = edges.pop()
"""
寻找某条边上的两个节点看其是否属于同一个集合
"""
root_a = Util.find(tree_set, node_a)
root_b = Util.find(tree_set, node_b)
if root_a != root_b: # 不属于同一个集合就将边加入到最小生成树上并且合并两个节点所在的集合
mst.add_edge(Edge(node_a, node_b, weight))
Util.union(tree_set, root_a, root_b) # 合并root_a 和root_b
if mst.get_edge_num() == node_num-1: # 最小生成树的边数与原图节点数-1相等就退出循环
break
return mst
- 测试数据
1 2 9
1 4 1
1 5 8
2 1 9
2 3 1
2 4 2
3 2 1
3 5 1
4 1 9
4 5 10
4 2 2
5 1 8
5 3 1
5 4 10
- 运行结果
- 微信公众号