kruskal算法
kruskal算法主要解决稀疏图,代替堆优化的prim算法。
假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:
先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。
之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,
也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。kruskal算法限制每次选取的边不能使生成树构成环。
依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
可以看出,kruskal算法和prim算法的不同之处,prim算法每次选取一个顶点,kruskal算法每次选取一条边。
依据kruskal算法的过程可知不需要像prim算法那样存储整个图的结构,只需要知道边的信息即可,
- 由于每次选取的是权值最小的边,所以可以根据边的权值从小到大排序,然后再遍历。
- 由于每次选取的边(a,b)的两个顶点要求不在一棵树上,因此需要并查集操作,判断a,b是否在一个集合。
如果点a b在一个集合,会出现以下三种情况:
- a b是间接构成回路,有a->b的边时,如果插入就会构成回路;
- 重边:已有a->b的直接边,再次插入就没有必要,因为前面已经插入的权值必定小于后面
- 自环,肯定已经构成回路,必不可能插入。
因此每次需要判断两个点是否在一个连通图中
那么什么时候可以停止循环呢?
prim算法中循环N次可以停止,因为每次就会确定一个顶点;
但是kruskal算法不能够根据循环次数决定,因为不是每次循环的边都能加入集合,因此退出可以有两个条件:
- 已经统计到了n-1条边,可以退出循环
- 找到了边权为INF的边,此时说明存在不可达的点,是非连通图,不存在最小生成树,但是输入数据时并不会输入不存在的边,所以不存在会遍历到权值为INF的边,遍历的m条边都是可达的。
因此,循环提前退出时的条件只能是第1个,在遍历m条边之前就已经得到了最小生成树。
然后,当循环退出时,可能此时统计到了n-1条边;
也可能是非连通图,没有统计到n-1条边,如果没有统计到n-1条边,那就是不存在最小生成树。
最坏的情况下,全部遍历m条边才能停止。时间复杂度:O(mlogm)
因为遍历m条边过程中还涉及到并查集操作。
题目描述
给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。
给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。
由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。
输入格式
第一行包含两个整数n和m。
接下来m行,每行包含三个整数u,v,w,表示点u和点v之间存在一条权值为w的边。
输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。
数据范围
1≤n≤105,
1≤m≤2∗105,
图中涉及边的边权的绝对值均不超过1000。
输入样例:
4 5
1 2 1
1 3 2
1 4 3