最小生成树——Kruskal算法

先上最小生成树定义:

在一个给定的无向图G=(V,E)中, (u,v)代表连接顶点u和v的边,而w(u,v)代表这个边的权重。若存在T为E的子集,且为无循环图,使得

中的w(T)最小,则这个T为G的最小生成树(MST)

最小生成树的几个性质:

  • 最小生成树的边数必然是顶点数减一,|E| = |V| - 1。
  • 最小生成树不可以有循环。
  • 最小生成树不必是唯一的。

求解一个图的最小生成树有很多算法,这里介绍Kruskal算法。

算法的执行过程可以描述为:

1.将图中所有的边按照从小到大的顺序排序

2.从小到大依次考察每条边(u,v)是否要当前的生成树集中。考查结果分为两种情况:

  case 1: u和v在同一连通分量中,那么加入(u,v)后会形成环,因此要放弃这种情况.

  case 2: u,v不在同一连通分量中,那么加入边(u,v)一定是最优的。证明可以用cut&paste来解释。

3.对每一条边循环第二步,直到加入的边的数目=顶点数目-1(即形成了树)

根据上面的过程可以得到伪代码:

Kruskal:

 将所有边排序,记第i小的边为e[i](1<=i<m)

初始化MST为空

初始化连通分量,让每个点成为一个独立的连通分量

for(int i=0;i<m;i++)

  if(e[i].u和e[i].v不在同一连通分量中)

  {

    把边e[i]加入MST

    合并e[i].u和e[i].v所在的连通分量

  }

END

由上面伪代码我们可以看出,问题的关键是连通分量的查询和合并,自然我们就能想到高效的解决方案是并查集.

由此我们给出完整的代码:

假设第i条边的两个端点序号和权值分别保存在u[i],v[i]和w[i]中,而排序后第i小的边的序号保存在r[i]中(这里先将原顺序的序号放在r[i]中,然后按照w[i]来排序,排完后r[i]就是按照从小到大的序号排列的了——这称为间接排序!)

int cmp(const int i, const int j){   return w[i]<w[j]; }
int find(int x)
{
    if(x!=father[x]) return father[x]=find(father[x]);
    return x;
}
int Kruskal()
{
    int total=0,count=0;
    //对n个顶点初始化并查集
    for(int i=0;i<n;i++) 
        father[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 ru = find(u[e]);
        int rv = find(v[e]);
        if(ru!=rv)
       {
            total+=w[e];
            count++;
            father[rv] =ru;
        } 
         if(count=n-1) break;
     }
    return total;
}

由此由并查集实现的Kruskal算法就搞定了!

你可能感兴趣的:(最小生成树)