什么是最小生成树
树其实是一种特殊的图,只不过树不会有环的存在。
当面对一幅无向有权图,假如现在要从图中找出一幅子图,该子图以最小的总权值连通所有节点。该如何是好?
我们要得到的是这样的:
如何做到呢?
Kruskal算法
图的表示方法:邻接表
江湖人称:加边大法。
1:
我们先将所以的边 按权值大小,从小到大排序。
2:
我们将图中所有节点都视作一棵棵树,每个树有一个根节点就是他本身
上图有6棵树。
遍历我们排好的边,从上到下。如果发现两节点都位于不同的树就将他们合并成一颗树。直到边数为 N - 1(N为结点的个数)结束。
3:
代码实现发现两节点都位于不同的树就将他们合并成一颗树这句话。
这里我们可以使用并查集的思想。
每一棵树我们选择该树中的根结点代表该树(可以将树理解为一个黑社会,根结点是该黑社会老大,根结点代表此树,黑社会老大代表黑社会)
我们首先声明一个列表,该列表的索引是结点,而索引对应的值是 结点 隶属的黑社会(即那棵树的根结点的值,黑社会老大)。
以上图为例(因为一开始大家都互不相干,自己就是自己的老大):
索引 : 0, 1, 2, 3, 4, 5
值 :[0, 1, 2, 3, 4, 5]
在此我们先弄懂,如何根据上述数组找到一个结点的黑社会老大?
“”“
参数:tar 图中的一个结点的值
nums 记录老大数组
返回:该结点对应的集合,并返回该集合代表的结点
”“”
def find(tar, nums):
while tar != nums[tar]:
tar = nums[tar]
return tar
假如我们的数组是这样的:
索引 : 0, 1, 2, 3, 4, 5
值 :[0, 1, 1, 2, 4, 5]
老大的标准是:索引的值和索引对应的值相同,即为老大,这里的老大有0,1,4,5
现在要找结点3的老大:
首先,访问记录老大数组索引3,对应的值为2,检查一下自己是不是老大,不是。结点3的上级是结点2。(结点2有可能是老大,但也可能只是比结点3高一级,不是老大。出现这种情况的原因:3之前认2为自己老大,之后2又认结点1为老大,那么3的老大已经不是2了,是结点1。)
接着,访问索引2对应的值为1,说明结点2不是老大,然后访问结点1
最后,结点1是老大,然后返回结点1。结点3的老大就是结点1
接着是合并函数的实现:
这里讲述的合并函数是最简单的,可以使用路径压缩算法,进一步优化。
# 假设 结点tar_2 要合并 到结点tar_1中:
def join(tar_1, tar_2, nums):
nums[tar_2] = tar_1
是的,直接修改记录老大数组,将 索引结点 tar_2 的值修改为 tar_1 即可。记录tar_2的老大是 tar_1。
有了上两个数组的支持,实现就十分简单了:
遍历排序好的边数组。如果发现边的两个结点,调用 find函数 找到他们的老大,如果发现老大不是同一个人(不隶属于同一个黑社会),记录该边,然后将这两个结点使用join函数合并(右结点的老大变为左结点的老大,(这里仅仅是方便,理论上应该是 人少的黑社会 加入 人多的黑社会))。
直到记录边的数组 >= N - 1 时,就返回边的数组,该树就整