引例
有一张城市地图,图中顶点为城市,无向边代表两个城市间的连通关系,边上的权为在这两个城市间修建高速公路的造价,研究后发现,这个地图有一个特点,即每一对城市都是连通的。现在的问题是,要修建若干高速公路把所有城市联系起来,问如何设计可使的工程的总造价最少?
考虑问题的出发点是:为使生成树上边的权值和最小,则应使生成树中每一条边的权值尽可能的小。
进入正题
- Kruskal算法是一种巧妙地利用并查集来求最小生成树的算法
- Kruskal算法将一个连通块当做一个集合。首先将所有边按从小到大的顺序排序(一般使用快排),并认为每一个顶点都是孤立的,分别属于n个独立的集合,那么就把这条边加入最小生成树,这两个不同的集合就合并成了一个集合。如果这条边连接的两个点已经属于同一个集合,就跳过。知道选了n-1条边为止。
用个图表示一下(和引例不一样)
最终求得最小生成树权值为17(上图)
- Kruskal算法的时间复杂度为O(E*logE),E为边数。
小结
通过上边的模拟能够看到,Kruskal算法每次都选择一条最小的且能合并两个不同集合的边,一张n个顶点的图总共选取n-1次边。因为我们每次选的都是最小的边,所以最后的生成树一定是最小生成树。
每次我们选的边都能合并两个集合,最后n个点一定会合并成一个集合。
通过这样的贪心策略,Kruskal算法能得到一棵有n-1条边,连接着n个顶点的最小生成树。
伪代码
// 把所有边排序,记第i小的边为e[i]
tot=0;// 初始化最小生成树为空
father[i]=i;// 初始化并查集,让每个点自成一个独立的连通分量
k=0;//计数器
for(int i=0;i<m;i++){
if(e[i].u和e[i].v不在同一个连通分量里){
//合并e[i].u和e[i].v所在的连通分量
tot+=W(u,v);//把边加入最小生成树
k++;//如果k=n-1,说明最小生成树已经生成,则break
}
}
伪代码中最关键的部分在于连通分量的查询与合并:需要知道任意两点是否在同一个连通分量中,还需要合并两个连通分量。
关于查询与合并
最容易想到的方法是“暴力”。但是这个方法复杂而且效率不高。(需要写DFS或BFS)
有一种简介高效的方法可以用来处理这个问题:使用并查集
并查集
并查集的精妙之处就在于用树来表示集合。例如:若包含1、2、3、4、5、6的图有三个连通分量{1,3}、{2,5,6}、{4},则需要用三棵树来表示。这三棵树的具体形态无关紧要,只要有一体棵树包含1,3这两个点,一棵树包含2,5,6这三个点,还有一棵树只包含4这一个点即可。
如果把x的父节点保存在f[x]中(如果x没有父节点,则f[x]=x),那么不难写出“查找x所在树的的根节点”的递归方程:
int find(int x