【最小生成树】原理和代码实现

最小生成树(Minimum Spanning Tree)

对于一个给定的连通的无向图 G = (V, E),希望找到一个无回路的子集 T,T 是 E 的子集,它连接了所有的顶点,且其权值之和为最小,我们把他称为【最小生成树】。
《【最小生成树】原理和代码实现》

性质

(1)最小生成树不是唯一,但最小生成树的权值是唯一。

算法

(1)克鲁斯卡尔(Kruskal)算法
俗称“加边法”,算法复杂度:O(ElogV),其中E是边的数量,V是顶点数量,适合于边比较少的【稀疏图】。

Kruskal 算法提供一种在 O(ElogV) 运行时间确定最小生成树的方案。Kruskal 算法基于贪心算法(Greedy Algorithm)的思想进行设计,其选择的贪心策略就是,每次都选择权重最小的但未形成环路的边加入到生成树中。实现利用【并查集】合并和检查是否形成环。其算法结构如下:

1、将所有的边按照权重非递减排序;
2、选择最小权重的边,判断是否其在当前的生成树中形成了一个环路。
如果环路没有形成,则将该边加入树中,否则放弃。
3、重复步骤 2,直到有 V – 1 条边在生成树中。

《【最小生成树】原理和代码实现》

(2)普里姆(Prim)算法
俗称“加点法”,算法复杂度:O( V2 V 2 ),其中V是顶点数量,适合于边比较多,顶点较少的【稠密图】。

从图G={V,E}中的某一顶点Uo出发,选择与它关联的具有最小权值的边(Uo,v),将其顶点加入到生成树的顶点集合U中。以后每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(u,v),把它的顶点加入到集合U中。如此继续下去,直到网中的所有顶点都加入到生成树顶点集合U中为止。

《【最小生成树】原理和代码实现》

HDU 1233为模版题给出Kruskal算法和Prim算法的代码,文章的最后给出相应的练习题可以熟悉下这两种算法。

Kruskal算法

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int MAXNUM = 1000 + 10; //顶点的最大值
const int MAXM=1000000+10;   //边的最大值

int id[MAXNUM];
int Size[MAXNUM];

struct edge
{
    int u,v,w;
}edges[MAXM];

int n,m;

void make_set(int n){
    for(int i = 1 ; i <= n; i++){
        id[i] = i;
        Size[i] = 1;
    }
}

// 查找父节点
int Find(int p) {
    while (id[p] != id[id[p]]) {   //如果q不是其所在子树的根节点的直接孩子
         id[p] = id[id[p]];          //对其父节点到其爷爷节点之间的路径进行压缩
      }
    return id[p];
}

// 合并 p ,q节点
void Union(int p, int q) {
    int pRoot = Find(p);
    int qRoot = Find(q);

    if (pRoot == qRoot) {
        return;
    }
    // 按秩进行合并
    if (Size[pRoot] > Size[qRoot]) {
        id[qRoot] = pRoot;
        Size[pRoot] += Size[qRoot];
    } else {
        id[pRoot] = qRoot;
        Size[qRoot] += Size[pRoot];
    }
}

int cmp (const void *a, const void *b)
{
    return ((edge *)a)->w - ((edge *)b)->w;
}
void kruskal()
{
    int sumweight = 0;  //生成树的权值
    int num = 0;          //已经选用的边的数目
    int u, v;
    make_set(n);
    for(int i = 0; i < m ;i++)  //遍历每条边
    {
        u = edges[i].u; //取起点
        v = edges[i].v;   //取终点
        //是否为同一个连通分量
        //是否在同一棵树
        //判断是否在之前的边出现过了
        if(Find(u) != Find(v))    //不同则加入到构造的生成树中
        {
            //printf("%d %d %d\n",u,v,edges[i].w);
            sumweight+=edges[i].w;
            num++;
            Union(u,v);  //建立树
        }
        if(num >= n - 1)
            break;
    }
    printf("%d\n",sumweight);

}

int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    while(scanf("%d",&n)!=EOF && n != 0){
        m = n * (n - 1) / 2;
        for(int i = 0;i < m; i++)
        {
            scanf("%d %d %d",&edges[i].u,&edges[i].v,&edges[i].w);
        }
        //按照边权排序
        qsort(edges,m,sizeof(edges[0]),cmp);
        kruskal();
    }
    return 0;
}

Prim算法

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int MAXNUM = 1000 + 10; //顶点的最大值
const int INF= 0x3f3f3f3f;   //边的最大值

int n,m;
int edges[MAXNUM][MAXNUM];
int lowcost[MAXNUM];
int near[MAXNUM];
void Prim()
{
    int sumweight=0;
    for(int i = 1;i <= n;i++)
    {
        lowcost[i] = edges[1][i];
        near[i] = 1;
    }
    near[1]=-1;

    for(int i = 1;i < n; i++)
    {
        int Min=INF;
        int v=-1;

        //在lowcost数组里中找未被访问的最最小值
        for(int j=1;j<=n;j++)
        {
          if(near[j] != -1 && lowcost[j] < Min)
           {
             Min = lowcost[j];
             v = j;
           }
        }

         if(v != -1)
         {
             //printf("%d %d %d\n",near[v],v,lowcost[v]);
             near[v]=-1;
             sumweight+=lowcost[v];
             for(int j = 1;j <= n;j++)
             {
                 if(near[j] != -1 && edges[v][j] < lowcost[j])
                 {
                     lowcost[j] = edges[v][j];
                     near[j] = v;
                 }
             }
         }
    }
    printf("%d\n",sumweight);
}


int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    int u, v, w;
    while(scanf("%d",&n)!=EOF && n != 0){
        m = n * (n - 1) / 2;
        for(int i = 0;i < m; i++)
        {
            scanf("%d %d %d",&u,&v,&w);
            edges[u][v] = edges[v][u] = w;
        }
        for(int i = 1;i <= n;i++)
            for(int j = 1;j <= n;j++)
            {
                if(i == j)
                    edges[i][j] = 1;
                else if(edges[i][j] == 0)
                    edges[i][j] = INF;
            }
        Prim();
    }
    return 0;
}

基于优先队列的Prim算法

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;

const int MAXNUM = 1000 + 10; //顶点的最大值
const int INF= 0x3f3f3f3f;   //边的最大值

int n , m;

bool visit[MAXNUM];
int value[MAXNUM][MAXNUM];
int link[MAXNUM][MAXNUM];

struct Edge
{
    int u,v,w;
  friend bool operator<(const Edge &, const Edge &);
};
bool operator<(const Edge &e1, const Edge &e2)
{
  return e1.w > e2.w;    // 最小优先队列
}

void Prim()
{
    priority_queue<Edge> edgePQ;
    visit[1] = true;
    int totVisit = 2;
    int curVertex = 1;
    int ans = 0;
    Edge tmp;
    while (totVisit++ <= n)
    {
        for (int i = 1; i != link[curVertex][0]; ++i)
        {
            if (!visit[link[curVertex][i]])
            {
                tmp.u = curVertex;
                tmp.v = link[curVertex][i];
                tmp.w = value[tmp.u][tmp.v];
                edgePQ.push(tmp);
            }
        }
        while (!edgePQ.empty() && visit[edgePQ.top().v])
        {
            edgePQ.pop();
        }
        tmp = edgePQ.top();
        visit[tmp.v] = true;
        ans += tmp.w;
        curVertex = tmp.v;
        edgePQ.pop();
    }
    printf("%d\n",ans);
}


int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    int u, v, w;
    while(scanf("%d",&n)!=EOF && n != 0){
        m = n * (n - 1) / 2;
       for (int i = 1; i <= n; ++i) {
            visit[i] = false;
            link[i][0] = 1;
       }
       for (int i = 0;i < m;++i)
       {
            scanf("%d %d %d",&u, &v, &w);
            value[u][v] = value[v][u] = w;
            link[u][link[u][0]++] = v;
            link[v][link[v][0]++] = u;
       }

       Prim();
    }
    return 0;
}

练习题

HDU 1233POJ 2421模版题(Kruskal)
HDU 1863 模版题(Kruskal)
HDU 1879 模版题(Kruskal、Prim)
HDU 1102 模版题(Kruskal、Prim)
HDU 1875 模版题(Kruskal、Prim)
POJ 1258 模版题 (Prim)
POJ 1251HDU 1301 模版题 (Prim)
POJ 1287 模版题 (Kruskal)
POJ 2395 Kruskal、Prim
ZOJ 1586 Prim

POJ 2377 Kruskal、Prim
POJ 1789 Prim
POJ 2485 Prim
POJ 3723 Kruskal
POJ 2031 Prim
POJ 1861 Kruskal
POJ 1751 Prim
POJ 2349 Prim
POJ 3026 Prim + bfs

点赞