11.2.1kruskal算法
第一步,是给所有边从小到大的顺序排列。这一步可以直接使用qsort,sort,接下来依次考查每条边(u,v)
情况1:u和v在同一个连通分量中,那么加入(u,v)后会形成环,因此不能选择
情况2:如果u和v在不同的连通分量,那么加入(u,v)一定是最优的
最关键的部分在于“连通分量的查询与合并”:需要知道任意两个点是否在同一个连通分量中,还需要合并两个连通分量
最容易想到的是暴力,然后暴力很浪费时间
并查集。
有一种简洁高效的方法可以处理这个问题:使用并查集,可以把每个连通分量看成一个集合,改集合包含了连通分量中的所有点,这些点两两连通,而具体的连通方式无关紧要,就好比集合中的所有元素没有先后顺序之分。在图中,每个点恰好属于一个连通分量,对应到集合表示中,每个元素恰好属于一个集合,换句话说,图中所有连通分量可以用若干个不相交集合表示
并查集的精妙之处在于用树来表示集合,例如,若包含点1,2,3,4,5,6的图有3个连通分量{1,3},{2,5,6},{4},则需要3个树表示。这3个树的具体形态无关紧要,只要一棵树包含1,3两个点,一棵树包含2,5,6,一棵树包含4.规定每颗树的根结点是这棵树所对应的集合代表有
如果把x的父结点保存在p[x]中,没有父结点等于本身。
改进:
既然每棵树表示的只是一个集合,因此树的形态无关紧要的,并不需要在“”操作之后保证形态不变,只要顺便把遍历过得结点改成树根的子节点
假设第i条边的两个端点的序号和权值分别保存在u[i],v[i],w[i]中。而排序后第i小的边的序号保存在r[i]中(叫做间接排序。排序的关键字是对象的“代号”,而不是对象本身)。
int cmp(const int i,const int j){
return w[i]<w[j];
} //间接排序函数
int find(int x){
return p[x]==x? x: p[x]=find(p[x]); //并查集的find
}
int Kruskal(){
int ans=0;
for(int i=0;i<n;i++) p[i]=i; //初始化并查集
for(int i=0;i<m;i++) r[i]=i; //初始化边序号
sort(r,r+m,cmp); //给边排序
for(int i=0;i<m;i++){
int e=r[i];
int x=find(u[e]);
int y=find(v[e]); //找出当前边两个端点所在集合编号
if(x!=y)
{
ans+=w[e];
p[x]=y;
}
}
return ans;
}