第23章:最小生成树

对于任何一个连通无向图来说,其都存在一棵最小生成树,该树能够连接图中的所有顶点,并且具有最小的权重和。有两种算法能够求解该问题,一种是prim算法,另外一种是kruskal算法

prim算法

在prim算法的任一时刻,我们都可以看到一个已经被发现的顶点集,这些顶点集能够形成一棵树。此时,算法在每一个阶段找出一个新的顶点v,使之到已被发现的顶点集具有最少权重,并且把该新发现的顶点v添加到已经发现的顶点集中。当算法终止时,最小生成树形成。代码如下:

pair<vector<int>,vector<double>> prim_mst(const vector<list<int>>& graph,const vector<vector<double>>& edge_weights)
{
        const int source=0; //认为最小生成树的源顶点是标号为0的顶点;

        vector<bool> mstv(graph.size(),false); //标记结点是否被访问了

        const int NIL=-1;  //NIL表示不存在的顶点
        vector<int> pred(graph.size(),NIL);    //记录在最小生成树中每个结点的前向结点。
        vector<double> weight(graph.size(),DBL_MAX);//weight[v]记录的是未发现顶点v到已发现的所有顶点中最短的距离,
        //假设未发现顶点v到已发现顶点u的距离最少,则pred[v]=u,weight[v]为边(u,v)的权重;

        mstv[source]=true;
        for(auto iter=graph[source].begin();iter!=graph[source].end();++iter)
        {
                int tmp=*iter;
                pred[tmp]=source;
                weight[tmp]=edge_weights[source][tmp];
        }

        for(int i=0;i!=graph.size()-1;++i)
        {
                double min_weight=DBL_MAX;
                int v=-1;

                //找出连接树中结点所有边中具有最少权重边的顶点v.
                for(int j=0;j!=graph.size();++j)
                        if(!mstv[j]&&weight[j]<min_weight){
                                min_weight=weight[j];
                                v=j;
                        }

                if(v==-1)
                        throw runtime_error("the undirected graph is not connected");
                mstv[v]=true;

                for(auto iter=graph[v].begin();iter!=graph[v].end();++iter)
                {
                        int tmp=*iter;
                        if(!mstv[tmp]&&edge_weights[v][tmp]<weight[tmp]){
                                weight[tmp]=edge_weights[v][tmp];
                                pred[tmp]=v;
                        }
                                        }
        }

        return make_pair(pred,weight);
}

void prim_print_tree(const vector<list<int>>& graph, const vector<vector<double>>& edge_weights)
{
        auto mst=prim_mst(graph,edge_weights);
        const vector<int>& pred=mst.first;
        const vector<double>& weight=mst.second;


        double tree_weight=0;

        cout<<"tree edge\tcorresponding weight"<<endl;

        const int NIL=-1;  //NIL表示不存在的顶点
        for(int v=0;v!=graph.size();++v)
                if(pred[v]!=NIL){
                        tree_weight+=weight[v];
                        cout<<"( "<<pred[v]<<" to "<<v<<" )\t\t"<<weight[v]<<endl;
                }

        cout<<"minimum spanning tree weight: "<<tree_weight<<endl;
}

kruskal算法:

kruskal算法是连续地按照最小的权重选择边,并且当所选的边不产生回路时就把它作为取定的边。形式上,kruskal算法是在处理树的集合,也就是森林。开始的时候,存在|V|棵单结点树,而添加一边则将两棵树合并成一棵树,当算法终止时,就只剩下一棵树了,这棵树就是最小生成树。这与prim算法不同,prim算法是本来就有一棵树,这棵树慢慢长大最后变成了最小生成树,而kruskal算法是有不同树的集合,通过不断合并,最终变成一棵树。代码如下:

struct edge{
        edge(int s=0,int e=0,double w=0):start(s),end(e),weight(w){};
        bool operator<(const edge& e) const { return weight<e.weight;}

        int start,end;
        double weight;
};

vector<edge> kruskal_mst(const vector<list<int>>& graph,vector<edge>& graph_edge)
{
        DisjSets dj(graph.size());

        //将图中所有边按照权重从小到大排序;
        sort(graph_edge.begin(),graph_edge.end());

        vector<edge> mst_edge; //存储最小生成树中的边

        for(const auto& e:graph_edge)
        {
                int u=e.start;
                int v=e.end;

                int uset=dj.findSet(u);
                int vset=dj.findSet(v);
                if(uset!=vset){
                        mst_edge.push_back(e);
                        dj.unionSets(uset,vset);
                }
        }

        return mst_edge;
}

void kruskal_print_mst(const vector<list<int>>& graph,vector<edge>& graph_edge)
{
        auto mst_edge=kruskal_mst(graph,graph_edge);

        cout<<"edges\tcorresponding weight"<<endl;
        double tree_weight=0;
        for(const auto& e:mst_edge)
        {
                tree_weight+=e.weight;
                cout<<"( "<<e.start<<" to "<<e.end<<" )\t\t"<<e.weight<<endl;
        }

        cout<<"minimum spanning tree weight: "<<tree_weight<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值