• 最小生成树(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 << "请输入弧的信息:
    ";
        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 << "最小生成树路径为:
    ";
        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的高度
    int vertex_num,arc_num;//顶点数,弧数
    int case_num;//测试组数
    int sum;//权值总和
    
    void init(int n);//初始化
    int find(int x);//查找根节点并且压缩路径
    bool  unite(int x,int y);//合并
    bool compare(const Edge&a,const Edge&b);
    
    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算法时间复杂度为,适用于求稀疏的网的最小生成树。

  • 相关阅读:
    Vjios P1736 铺地毯【暴力,思维】
    Vijos P1116 一元三次方程求解【多解,暴力,二分】
    Vijos P1131 最小公倍数和最大公约数问题【暴力】
    Vijos P1786 质因数分解【暴力】
    C++课程设计类作业4
    析构函数的用法【简单理论讲解】
    C++课程设计类作业3
    最短路径Floyd算法【图文详解】
    HDU 1874 畅通工程续【Floyd算法实现】
    C++课程设计类作业2
  • 原文地址:https://www.cnblogs.com/LuRenJiang/p/7301331.html
Copyright © 2020-2023  润新知