最小生成树(Prim算法和Kruskal算法)

Posted 路人姜。

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最小生成树(Prim算法和Kruskal算法)相关的知识,希望对你有一定的参考价值。

1)最小生成树

给定一个无向图,如果它的某个子图中任意两个顶点都互相连通并且是一棵树,那么这棵树就叫生成树。如果边上有权值,那么使得边权和最小的生成树叫做最小生成树(MST,Minimum Spanning Tree)

2)应用

比如让你为一个镇的九个村庄架设通信网络,每个村庄相当于一个顶点,权值是村与村之间可通达的直线距离,要求你必须用最小的成本完成这次任务;或者村庄之间建公路,连通N个村庄要N-1条路,如何让建路成本最低之类的问题。

1、Prim算法

①该算法是构建最小生成树的算法之一。它是以某顶点为起点,不断添加各顶点上最小权值的边,来构建最小生成树。

②算法流程:

第一步:设图G顶点集合为U,首先任意选择图G中的一点作为起始点a,将该点加入集合V;

第二步:从集合U中找到另一点b使得点b到V中任意一点的权值最小,此时将b点也加入集合V;

以此类推,现在的集合V={a,b},再从集合U中找到另一点c使得点c到V中任意一点的权值最小,此时将c点加入集合V;直至所有顶点全部被加入V,此时就构建出了一棵MST。

③参考代码:

#include <iostream>
#include <string.h>
#define INT_MAX 1000000000
using namespace std;

int matrix[100][100];//邻接矩阵
bool visited[100];//标记数组
int path[100];//记录生成树路径
int lowcost[100];//边的权值
int vertex_num,arc_num;//顶点数,弧数
int sum;//权值总和
int source;//源点
void prim(int source);
int main()
{
    cout << "请输入图的顶点数(<=100):";
    cin >> vertex_num;
    cout << "请输入图的弧数:";
    cin >> arc_num;

    for (int i = 0; i < vertex_num; i++)
        for (int j = 0; j < vertex_num; j++)
            matrix[i][j] = INT_MAX;  //初始化matrix数组

    cout << "请输入弧的信息:\n";
    int u, v, w;
    for (int i = 0; i < arc_num; i++)
    {
        cin >> u >> v >> w;
        matrix[u][v] = matrix[v][u] = w;
    }

    cout << "请输入起点(<" << vertex_num << "):";
    cin >> source;
    prim(source);

    cout << "最小生成树权和为:" << sum << endl;
    cout << "最小生成树路径为:\n";
    for (int i = 0; i < vertex_num; i++)
        if (i != source)
            cout << i << "----" << path[i] << endl;

    return 0;
}
void prim(int source){
    memset(visited,0,sizeof(visited));
    visited[source]=true;
    for(int i=0;i<vertex_num;i++){
        lowcost[i] = matrix[source][i];
        path[i]=source;
    }

    int min_cost,min_cost_index;//最小的权值,和其下标
    sum=0;
    for(int i=1;i<vertex_num;i++){//找除源点外的n-1个点,如果这里写多了,
                                //那么下边的for里if进不去,那么sum的值也会错误
            min_cost=INT_MAX;

        for(int j=0;j<vertex_num;j++){//遍历所有顶点
            if(visited[j]==false&&lowcost[j]<min_cost){//找到与源点的权值最小的点
                min_cost=lowcost[j];
                min_cost_index=j;
            }
        }
        visited[min_cost_index]=true;//讲找到的顶点进行标记
        sum+=min_cost;//权值总和

        for(int j=0;j<vertex_num;j++){
            if(visited[j]==false&&matrix[min_cost_index][j]<lowcost[j]){//更新lowcost,以便找下个顶点
                lowcost[j]=matrix[min_cost_index][j];
                path[j] = min_cost_index;
            }
        }
    }


}

摘自http://www.61mon.com/index.php/archives/199/comment-page-1#comments

2、Kruskal算法

①该算法是构建最小生成树的另一个算法,其是对边进行操作,来构建最小生成树的。

②算法流程:

第一步:把所有的边按权值从小到大排列。

第二步:按照顺序选取每条边,如果这条边的两个端点不属于同一集合,那么就将它们合并。

重复第二步,直到 所有的点都属于同一个集合。     

第二步用并查集来实现,又因为每次都选的权值最小的,所以这其实就是运用并查集的贪心算法。

③为什么这样做就可以构建最小生成树呢?因为N个顶点只需要N-1条边就够了,那么选哪N-1条呢,为了让权值和最小,当然选从小到大排序的前N-1条边喽。

④参考代码:

#include <iostream>
#include <algorithm>
#define MAX_INT 100
using namespace std;
struct Edge{//
int a;//边上的两个点
int b;
int weight;//权值
}edge[MAX_INT];
int par[MAX_INT];//i的父亲的编号
int rank[MAX_INT];//i的高度
void init(int n);//初始化
int find(int x);//查找根节点并且压缩路径
bool  unite(int x,int y);//合并
bool compare(const Edge&a,const Edge&b);
int vertex_num,arc_num;//顶点数,弧数
int case_num;//测试组数
int sum;//权值总和
int main()
{
    cout<<"请输入测试组数:"<<endl;
    cin>>case_num;
    while(case_num){
        sum=0;
        cout<<"请输入顶点数和弧数:"<<endl;
        cin>>vertex_num>>arc_num;

        init(vertex_num);

        cout<<"请输入"<<arc_num<<"条弧的信息:"<<endl;
        for(int i=0;i<arc_num;i++){
            cin>>edge[i].a>>edge[i].b>>edge[i].weight;
            }

        sort(edge,edge+arc_num,compare);

        cout<<"最小生成树的路径为:"<<endl;
        int j;
        for(j=0;j<arc_num;j++){
            if(unite(edge[j].a,edge[j].b)){
                sum+=edge[j].weight;
                cout<<edge[j].a<<"----"<<edge[j].b<<" "<<edge[j].weight<<endl;
        }
        }
        if(j==arc_num){
            cout<<"最小生成树的权值和为:"<<sum<<endl;
        }else if(j<arc_num){
            cout<<"data error!"<<endl;
        }

        case_num--;
    }
    return 0;
}
void init(int n){
    for(int i=0;i<n;i++){
        par[i]=i;//父节点都先初始化为自己
        rank[i]=0;//高度初始化为0
    }

}

int find(int x){
if(x==par[x]){
    return x;
}else{
    return par[x]=find(par[x]);
}
}
bool  unite(int x,int y){
    x=find(x);
    y=find(y);
    if(x==y){
        return false;
    }else{
    if(rank[x]<rank[y]){
        par[x]=y;
    }else{
        par[y]=x;
        if(rank[x]==rank[y])
            rank[x]++;
    }
    return true;
    }

}

bool compare(const Edge&a,const Edge&b){//按从小到大的顺序排序
    return a.weight<b.weight;
}

代码参考自:http://blog.csdn.net/niushuai666/article/details/6689285

3、Kruskal算法和Prim算法的区别(或者说什么时候用Prim什么时候用Kruskal)?

关于这个问题,我也不是很明白,后续会补充,欢迎在评论区写下自己对此的理解,一起来讨论下吧??

 

以上是关于最小生成树(Prim算法和Kruskal算法)的主要内容,如果未能解决你的问题,请参考以下文章

图解:如何实现最小生成树(Prim算法与Kruskal算法)

关于最小生成树的Prim算法和Kruskal算法

最小生成树之Kruskal算法和Prim算法

最小生成树及Prim算法及Kruskal算法的代码实现

贪心算法-最小生成树Kruskal算法和Prim算法

图的最小生成树算法(Prim和Kruskal)