最小生成树算法——Prim & Kruskal个人总结

最小生成树算法——Prim & Kruskal总结与代码实例


Prim算法

    Prim算法的思想是:对于原始图G(V,E),建立一个集合S,从图的某个顶点开始(顶点可以是指定顶点,也可以是随机的顶点),逐步从点集V-S中选择到S中顶点距离最小的顶点加入S,并把该条最短路径加入最小生成树。
在实现的过程中,Prim算法与求解单源最短路径算法Dijkstra算法几乎一致,唯一不同之处在于最短距离数组d[MAXN]的含义与处理方式。下面给出邻接矩阵版的prim算法的实现例子。


代码实例

/*codeup 622A*/
/*稠密图的最小生成树算法—prim算法的应用*/
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;


const int MAXN = 110;
const int INF = 1000000000;
int G[MAXN][MAXN];
int d[MAXN];
bool visit[MAXN] = {false};
int N;


int prim(){
    int ans = 0;
    fill(d,d+MAXN,INF);
    d[1] = 0;   //序号1-N,默认从1开始
    for(int i=1;i<=N;i++){       //循环N次
        int u = -1,MIN = INF;
        for(int j=1;j<=N;j++){
            if(visit[j] == false && d[j] < MIN){
                u = j;
                MIN = d[j];
            }
        }
        if(u == -1)
            return -1;
        visit[u] = true;
        ans += d[u];     //将最小的边加入
        for(int v=1;v<=N;v++){     //遍历通过u连接的路径
            if(visit[v] == false && G[u][v] != INF && G[u][v] < d[v]){  //顶点v未被访问且u->v有边且通过u可以使得v到最小生成树节点集合距离变小
                d[v] = G[u][v];    //更新距离
            }
        }
    }
    return ans;
}


int main(){
    fill(G[0],G[0]+MAXN*MAXN,INF);
    int c1,c2,cost;
    while(cin>>N){
      if(N == 0)
          break;
      for(int i=0;i<N*(N-1)/2;i++){
         cin>>c1>>c2>>cost;
          G[c1][c2] = G[c2][c1] = cost;
      }
         cout<<prim()<<endl;
}
    return 0;
}

由于Prim算法与Dijkstra算法一致,复杂度均为O(N^2),故顶点的数量会影响算法的性能,所以Prim算法常被用于顶点数量较少而边数量较多(即稠密图)的情况,若是稀疏图,则应该尽量使用下面介绍的Kruskal算法


Kruskal算法

    Kruskal 算法也是求解图的最小生成树的常用算法,与Prim算法不同,Kruskal算法采用的是"边贪心"的策略,即以图中的边为主导。其算法过程是首先将图中所有的顶点各自看成独立的连通块,然后对图中的各边按照边权从小到大的顺序进行排列,然后按照排序后的顺序遍历图的各个边。当处理一条边时,检查与该边相连的两个顶点是否属于同一个连通块,如果属于,则跳过该边,反之则将该边加入最小生成树,然后让最小生成树的边数量加1。当最小生成树的边的数量为顶点数量减一时,跳出遍历循环。结束边的遍历循环后,检查最小生成树的边的数量,若不是N-1,则表明原图不连通。
    在实现Kruskal算法时有两个关键问题:一是如何知晓边的两个顶点属于同一连通块(如何表示连通块),二是如何将一条边加入最小生成树(如何表示一个最小生成树)。以上两个问题可以使用一个简单的结构来描述——并查集。判断是否属于同一连通块,就找寻两个顶点在并查集中的根结点是否一致,边加入最小生成树其实就是两个集合的合并过程。

下面给出基于并查集实现的Kruskal算法

代码实例

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


const int MAXN = 200;    //定义最大顶点数与最大边数
const int MAXE = 10010;


//边集定义
struct edge{
    int u,v;   //起点,终点编号
    int cost;   //边权
}E[MAXE];
//cmp()函数,用以sort()
bool cmp(edge a,edge b){
    return a.cost < b.cost;
}


int father[MAXN];   //顶点的并查集
//寻找根结点函数
int findFather(int x){
    int a = x;
    while(x != father[x]){
        x = father[x];
    }
    
    //路径压缩,其实也可以不做
    while(a != father[a]){
        int z = a;
        a = father[a];
        father[z] = x;    //将查询顶点到根结点路径上所有的顶点的父节点均记为根节点
    }
    return x;    //返回根节点编号
}


//Kruskal算法
int Kruskal(int n, int m){
    int ans = 0;
    int num_edge = 0;
    for(int i=0;i<n;i++){
        father[i] = i;     //并查集初始化
    }
    sort(E,E+m,cmp);    //边结构体数组排序
    for(int i=0;i<m;i++){   //从小到大遍历边
        int faU = findFather(E[i].u);
        int faV = findFather(E[i].v);
        if(faU != faV){   //如果边的两个顶点不再同一连通块
            father[faU ] = faV;   //合并
            ans += E[i].cost;   //边权累加
            num_edge++;
            if(num_edge == n-1)   //最小生成树边数已达顶点数减一,跳出
                break;  
        }
    }
    if(num_edge != n-1)
        return -1;    //不连通
    else
        return ans;
}


int main(){
    int n,m;
    cin>>n>>m;
    for(int i=0;i<m;i++){
        cin>>E[i].u>>E[i].v>>E[i].cost;
    }
    int ans = Kruskal(n,m);
    cout<<ans<<endl;
    return 0;
}

参考

算法笔记10.5 最小生成树

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值