body, table{font-family: 微软雅黑; font-size: 13.5pt}
table{border-collapse: collapse; border: solid gray; border-width: 2px 0 2px 0;}
th{border: 1px solid gray; padding: 4px; background-color: #DDD;}
td{border: 1px solid gray; padding: 4px;}
tr:nth-child(2n){background-color: #f8f8f8;}

以下两种算法,结合给出的图的例子,完整实现代码地址:https://github.com/meihao1203/learning/tree/master/06292018/Graph_Prim_Kruskal
把构造连通网的最小代价生成树称为最小生成树(Minimum Cost Spanning Tree)
普里姆(Prim)算法:
  假设 N = (P,{E})是连通网,TE是N上最小生成树中边的集合。算法从U = {u0}(u0∈V),TE = {}开始,(U0是随便选取的一个顶点)重复执行下述操作:在所有ui∈U,vi∈V-U中找一条代价最小的边(ui,vi)∈ E 并入集合TE,同时vi并入U,直到U=V为止。此时TE中必有n-1条边,则T=(V,{TE})为N的最小生成树。
//图(2,1)错了,正确的是18
数组下标 0 1 2 3 4 5 6 7 8
vertex 0 0 0 0 0 0 0 0 0
weight 0 10 11

文件中读数组的时候,读到-1就填入int类型能表示的最大值
算法中用到的两个数组,先指定一个初始顶点0,,初始化后就是上面的样子,(0,0)=0,(1,0)=10,(2,0)=∞...
其中,weight=0表示该点已经在最小生成树中,初始选取第0个结点V0


执行完第一趟遍历,找到了一个最小边(0,1)=10,把1加入到生成树里面,1能得到一些新的边,此时就要更新vertex和weight


数组下标 0 1 2 3 4 5 6 7 8
vertex 0 0 1 0 0 0 1 0 1
weight 0 0 18 11 16 12
(1,2)=18,(1,6)=16,(1,8)=12
/* Prim.cpp */

#include"Prim.h"
#include<iostream>
namespace meihao
{
        void MiniSpanTree_Prim(const meihao::Graph& g)
        {
                //先获取图的顶点数用来建立相应的存储结构
                int vertexNum = g.getGraphVertexNumber();
                int* vertex = new int[vertexNum]();  //初始化一个数组来存储最终的最小生成树的结点信息
                //数组下标表示vi,对应的数组值表示vj  vertex[vi] = vj,动态申请空间时初始化,最初vertex都是0
                weight_vaule_type* weight = new weight_vaule_type[vertexNum]();
                //weight数组用来存放边的权值,在算法运行过程中要用来比较
                //weight中值为0,表示对应的下标表示的点已经在最小生成树中
                //vi对应的weight[vi]就表示(vi,ji) = weight[vi],其中vj = vertex[vi]
                //1、随便选取一个点开始求解最小生成树
                vertex[0] = 0; //(0,0)=0,选取从0号结点开始
                //2、从选取的第0个点开始初始化weight数组,相当于用邻接矩阵的第一行来初始化weight
                for(int idx=0;idx!=vertexNum;++idx)
                {
                        weight[idx] = g.getGraphEdgeWeight(0,idx);
                }//初始vertex都是0,表示0到其他节点,刚好对应初始化后的weight
                //weight[0]=0,vertex[0]=0,表示(0,0)=0->(vertex[0],0)=weight[0]
                //3、weight数组存放了从0顶点到其他顶点的距离,开始选一个权值最小边(v0,vj),同时把顶点vj加入vertex中 vertex[vj] = v0; weight[vj] = 0;
                for(int idx=0;idx!=vertexNum;++idx)
                {
                        weight_vaule_type min = max_weight_value;
                        int newVertex = 0;  //定义一个变量保存在一次遍历过程中找到的最小权值边的,初始值为0
                        for(int iidx=0;iidx!=vertexNum;++iidx)
                        {
                                if(0!=weight[iidx]&&
                                        weight[iidx]<min)  //weight[idx]=0,表示结点idx已经在我们最终要求的最小生成树中了
                                {
                                        //找到一条权值相对min小的边
                                        min = weight[iidx];   //更新min
                                        newVertex = iidx;  //记录结点,目前(0,iidx)边的权值最小
                                }
                        }
                        //输出边
                        //if(0!=newVertex)  //vertex[0]=0,存放的是最开始初始化的,(0,0)指向自身,不在最小生成树中
                                cout<<"("<<vertex[newVertex]<<","<<newVertex<<")"<<" ";
                        //把一次遍历找到的newVertex加入到最小生成树中
                        weight[newVertex] = 0;  
                        //这时候生成树多了一个结点,通过这个顶点又可以通过依附在这个点的边到达其他结点,所以这个时候要更新weight
                        for(int iiidx=0;iiidx!=vertexNum;++iiidx)
                        {
                                if(0!=weight[iiidx]&&
                                        g.getGraphEdgeWeight(newVertex,iiidx)<weight[iiidx])
                                {
                                        weight[iiidx] = g.getGraphEdgeWeight(newVertex,iiidx);
                                        //weight更新了,vertex存放对应的两个顶点信息,所以这里要同步更新
                                        vertex[iiidx] = newVertex;    //(iiidx,newVertex) = weight[iiidx];
                                }
                        }
                }//每次都能找出一个点,最终找到n个点,n-1条边,
        }
};

/* Prim.cpp */   根据上面的表,优化左边的算法,看起来逻辑更清晰

#include"Prim.h"
#include<iostream>
namespace meihao
{
        void MiniSpanTree_Prim(const meihao::Graph& g)
        {
                int vertexNum = g.getGraphVertexNumber();
                int* vertex = new int[vertexNum];  //这里可以直接写()全部初始化
                int* weight = new int[vertexNum];
                vertex[0] = 0;
                weight[0] = 0;
                for(int idx=1;idx!=vertexNum;++idx)
                {
                        vertex[idx] = 0;
                }
                for(int idx=1;idx!=vertexNum;++idx)
                {
                        weight[idx] = g.getGraphEdgeWeight(0,idx);
                }
                for(int idx=1;idx!=vertexNum;++idx)
                {
                        weight_vaule_type min = max_weight_value;
                        int newVertex;
                        for(int iidx=1;iidx!=vertexNum;++iidx)
                        {
                                if(0!=weight[iidx]&&weight[iidx]<min)
                                {
                                        min = weight[iidx];
                                        newVertex = iidx;   //相当于数组下标
                                }
                        }
                        //一趟遍历找到一条最小权值的边
                        cout<<"("<<vertex[newVertex]<<","<<newVertex<<")"<<" ";
                        //newVertex加入生成树,也就是修改weight
                        weight[newVertex] = 0;
                        //更新vertex和weight数组
                        for(int iiidx=1;iiidx!=vertexNum;++iiidx)
                        {
                                if(0!=weight[iiidx]&&g.getGraphEdgeWeight(newVertex,iiidx)<weight[iiidx])
                                {
                                        weight[iiidx] = g.getGraphEdgeWeight(iiidx,newVertex);
                                        vertex[iiidx] = newVertex;
                                }
                        }
                }
        }
};
/* data.txt */从文件中读取数据初始化图的时候,如果是-1就用最大值替代

9
0 1 2 3 4 5 6 7 8
0 10 -1 -1 -1 11 -1 -1 -1
10 0 18 -1 -1 -1 16 -1 12
-1 -1 0 22 -1 -1 -1 -1 8
-1 -1 22 0 20 -1 -1 16 21
-1 -1 -1 20 0 26 -1 7 -1
11 -1 -1 -1 26 0 17 -1 -1
-1 16 -1 -1 -1 17 0 19 -1
-1 -1 -1 16 7 -1 19 0 -1
-1 12 8 21 -1 -1 -1 -1 0 

/* testMain.txt */
#include"Graph.h"
#include"Prim.h"
#include"Kruskal.h"
#include<iostream>
using namespace std;
int main()
{
        meihao::Graph g("data.txt");
        cout<<"MiniSpanTree_Prim:"<<endl;
        meihao::MiniSpanTree_Prim(g);
        cout<<endl;
        system("pause");
}


利用结构体实现->->
#include"Prim.cpp"
#include<iostream>
namespace meihao
{
        typedef struct Arr
        {
                int vi;  //顶点vi
                weight_vaule_type weight;  //(vi,vj)的权值
        }node,*pNode;
        //思路:
        //从结点0开始,定义n-1个node的数组,分别赋值(0,1),(0,2)...
        void MiniSpanTree_Prim(const meihao::Graph& g)
        {
                //获取顶点个数
                int vertexNum = g.getGraphVertexNumber();
                node* arr = new node[vertexNum]();
                for(int idx=1;idx!=vertexNum;++idx)
                {
                        arr[idx].vi = 0;  //选取的初始结点0
                        arr[idx].weight = g.getGraphEdgeWeight(0,idx);
                }
                for(int idx=1;idx!=vertexNum;++idx)
                {
                        weight_vaule_type min = max_weight_value;
                        int newVertex;
                        for(int iidx=1;iidx!=vertexNum;++iidx)
                        {
                                if(0!=arr[iidx].weight&&arr[iidx].weight<min)
                                {
                                        min = arr[iidx].weight;
                                        newVertex = iidx;
                                }
                        }
                        cout<<"("<<arr[newVertex].vi<<","<<newVertex<<")"<<" ";
                        arr[newVertex].weight = 0;
                        //更新数组
                        for(int iiidx=1;iiidx!=vertexNum;++iiidx)
                        {
                                if(0!=arr[iiidx].weight&&g.getGraphEdgeWeight(newVertex,iiidx)<arr[iiidx].weight)
                                {
                                        arr[iiidx].vi = newVertex;
                                        arr[iiidx].weight = g.getGraphEdgeWeight(newVertex,iiidx);
                                }
                        }
                }
        }
};
克鲁斯卡尔(Kruskal)算法:
算法每次都选一个最小权值的边加入的生成树的集合中,在加入之前要判断是否会形成回路;所以算法先存储所有的边集数组,对其进行排序,如右图所示;最后每次选一个最小边加入最小生成树。 判断加入一条边是否会构成回路,就要利用一个数组(parent)来存放每个结点的父结点。初始全部为0

下标 0 1 2 3 4 5 6 7 8
parent 0 0 0 0 0 0 0 0 0

其中,parent[i] = j,表示i结点的父结点为j结点

加入第一条边(4,7)=7,parent[4]=0; parent[7]=0,4和7都是单独的一个根结点,加入边不会形成回路,默认一个加入规则,parent[4]=7,此时最小生成树有一条边,4→7,4的父结点是7。

下标 0 1 2 3 4 5 6 7 8
parent 0 0 0 0 7 0 0 0 0

加入第二条边(2,8)=8,同上  2→8  4→7

下标 0 1 2 3 4 5 6 7 8
parent 0 0 8 0 7 0 0 0 0

加入第三条边(0,1)=10, 0→1 2→8 4→7

下标 0 1 2 3 4 5 6 7 8
parent 1 0 8 0 7 0 0 0 0

加入第四条边(0,5)=11,这里parent[0]=1,0的父结点1,parent[1]=0,1的父结点0;parent[5]=0,所以最后parent[1]=5。这是后的生成树0→1→5 2→8 4→7

下标 0 1 2 3 4 5 6 7 8
parent 1 5 8 0 7 0 0 0 0

加入第五条边(1,,8)=12,parent[1]=5,parent[5]=0; parent[8]=0;
0→1→5→8 2→8 4→7   从上面的图可以看出,现在有两个顶点结合出现{0,1,5,8,2}和{4,7}

下标 0 1 2 3 4 5 6 7 8
parent 1 5 8 0 7 8 0 0 0
加入第六条边(3,7)=16,parent[3]=0;parent[7]=0;
0→1→5→8 2→8 4→7 3→7

下标 0 1 2 3 4 5 6 7 8
parent 1 5 8 7 7 8 0 0 0
加入第七条边(1,6)=16,parent[1]=5,parent[5]=8,parent[8]=0; parent[6]=0;
0→1→5→8→6 2→8 4→7 3→7

下标 0 1 2 3 4 5 6 7 8
parent 1 5 8 7 7 8 0 0 6
加入第八条边(5,6)=17,parent[5]=8,parent[8]=6,parent[6]=0; parent[6]=0;同一个顶点,不能加入6←6指向自身了
0→1→5→8 2→8 4→7 3→7  ,如果加入边(5,6),5-8-6-6,这就是一个环了

下标 0 1 2 3 4 5 6 7 8
parent 1 5 8 7 7 8 0 0 6
加入第九条边(1,2)=18,parent[1]=5,parent[5]=8,parent[8]=6,parent[6]=0; parent[2]=8,parent[8]=6,parent[6]=0;
0→1→5→8 2→8 4→7 3→7

下标 0 1 2 3 4 5 6 7 8
parent 1 5 8 7 7 8 0 0 6
加入第十条边(6,7)=19,parent[6]=0; parent[7]=0;
0→1→5→8 2→8 4→7 3→7 6→7

下标 0 1 2 3 4 5 6 7 8
parent 1 5 8 7 7 8 7 0 6

parent[7] = 0,表示没有父结点,树中只要一个结点没有双亲结点

加入第10条边之后,此时黄色部分已经有8个,9个顶点的生成树只能有8条表,此后再加入新的边,都会构成回路


这种查找一直用到了并查集的思想。初始时把每个对象看作是一个单元素集合;然后依次按顺序读入联通边,将连通边中的两个元素合并,即找到父结点。

优化1、
其实可以压缩搜寻路径,比如0→1→5→8,可以变成0→8,1→8,58,这样如果结点个数多的时候,效率就提升。

还有另外一种做法,完全按照并查集的搜索合并来解决,我觉得合并有点多余,我这里的优化1就是借鉴了他的https://www.cnblogs.com/yoke/p/6697013.html
他的最终目的生成树从(4,7)开始,最后会是4→7,4→2,4→8,... 反正就一个根结点,下面全是叶子结点。这个合并操作也多余了,不会提高效率。

最终结果
/* Kruskal.cpp */

#include"Kruskal.h"
#include<iostream>
#include<vector>
#include<algorithm>
namespace meihao
{
        bool cmp(const edges& a,const edges& b)
        {
                return a.weight < b.weight;  //从小到大排序
        }
        void readEgdesFromGraph(const meihao::Graph& g,vector<edges>& edgeArr)
        {//无向图邻接矩阵都是对称的,只读取上三角即可
                int vertexCnt = g.getGraphVertexNumber();
                for(int idx=0;idx!=vertexCnt;++idx)
                {
                        for(int iidx=idx;iidx!=vertexCnt;++iidx)
                        {
                                if(0!=g.getGraphEdgeWeight(idx,iidx)&&max_weight_value!=g.getGraphEdgeWeight(idx,iidx))
                                {
                                        edges tmp;
                                        tmp.begin = idx;
                                        tmp.end = iidx;
                                        tmp.weight = g.getGraphEdgeWeight(idx,iidx);
                                        edgeArr.push_back(::move(tmp));
                                }
                        }
                }
                sort(edgeArr.begin(),edgeArr.end(),cmp);  //从小到大排序
        }
        void MiniSpanTree_Kruskal(const meihao::Graph& g)
        {
                vector<edges> edgeArr;  //边集数组
                readEgdesFromGraph(g,edgeArr);
                //定义parent数组,数组下标对应唯一的图结点,数组值对应小标结点的父结点,最小生成树就是一棵树
                int vertexCnt = g.getGraphVertexNumber();
                int* parent = new int[vertexCnt]();  //初始化全部为0,parent[i] = 0,表示i结点没有父结点(只有一个根结点的树)
                int edgeCnt = edgeArr.size();  //边集数组大小,也就是图中边的数量
                for(int idx=0;idx!=edgeCnt;++idx)
                {
                        int firstFather = find(parent,edgeArr[idx].begin);
                        int secondFather = find(parent,edgeArr[idx].end);
                        if(firstFather!=secondFather)  //待加入的这条边的父结点相同,如果再把这条边加入,就会出现环。这里只能不等
                        {//这个过程就是一个找爹过程,最小生成树只能有一个根结点,如果待加入边对其两端的顶点去找爹找到相同的,这时候再加入这条边就出现环,∧->△
                                parent[firstFather] = secondFather;   //加入该条边,(firstFather,secondFather),firstFather的父结点secondFather
                                //输出找到的边
                                cout<<"("<<edgeArr[idx].begin<<","<<edgeArr[idx].end<<")"<<" ";
                        }
                }
                cout<<endl;
        }
//没有优化的find
        //int find(int* parent,int vi)
        //{
        //        while(parent[vi]>0)  //vi结点有父结点
        //        {
        //                vi = parent[vi];
        //        }
        //        return vi;
        //}
      
};
  int find(int* parent,int vi)
        {//优化1、
                int viTmp = vi;
                while(parent[vi]>0)  //vi结点有父结点
                {
                        vi = parent[vi];  //找父结点
                }
                while(vi!=viTmp)
                {//vi有父结点,遍历,如果有父结点还有祖先结点,假设eg:0→1→5(0的父结1,1的父亲5) 变成 0→5,1→5
                        int tmp = parent[viTmp];  //暂存最初vi结点(0)的父结点(tmp=1)
                        parent[viTmp] = vi;  //(parent[0]=5)
                        viTmp = tmp;  //0变成1;
                }
                return vi;
        }


/* Kruskal.h */
#ifndef __KRUSCAL_H__
#define __KRUSCAL_H__
#include"Graph.h"
namespace meihao
{
        typedef struct EdgeSetArr  //边集数组
        {
                int begin;  //边起点
                int end;    //边终点
                weight_vaule_type weight;  //边权值
        }edges;
        void readEgdesFromGraph(const meihao::Graph& g);  //从图中读出我们需要的边集数组
        int find(int* parent,int vi);
        void MiniSpanTree_Kruskal(const meihao::Graph& g);
};
#endif


/* maintext.cpp */
#include"Graph.h"
#include"Prim.h"
#include"Kruskal.h"
#include<iostream>
using namespace std;
int main()
{
        meihao::Graph g("data.txt");
        cout<<"MiniSpanTree_Prim:"<<endl;
        meihao::MiniSpanTree_Prim(g);
        cout<<endl<<endl;
        cout<<"MiniSpanTree_Kruskal"<<endl;
        meihao::MiniSpanTree_Kruskal(g);
        cout<<endl;
        system("pause");
}

边数较少可以用Kruskal,因为Kruskal算法每次查找权值最小的边。 边数较多可以用Prim,因为它是每次加一个顶点,对边数多的适用。


最小生成树 Prim算法 和 Kruskal算法,c++描述的更多相关文章

  1. 转载:最小生成树-Prim算法和Kruskal算法

    本文摘自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html 最小生成树-Prim算法和Kruskal算法 Prim算 ...

  2. 最小生成树Prim算法和Kruskal算法

    Prim算法(使用visited数组实现) Prim算法求最小生成树的时候和边数无关,和顶点树有关,所以适合求解稠密网的最小生成树. Prim算法的步骤包括: 1. 将一个图分为两部分,一部分归为点集 ...

  3. 最小生成树---Prim算法和Kruskal算法

    Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (gra ...

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

    (转自这位大佬的博客 http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html ) Prim算法 1.概览 普里姆算法(Pr ...

  5. 最小生成树——Prim算法和Kruskal算法

    洛谷P3366 最小生成树板子题 这篇博客介绍两个算法:Prim算法和Kruskal算法,两个算法各有优劣 一般来说当图比较稀疏的时候,Kruskal算法比较快 而当图很密集,Prim算法就大显身手了 ...

  6. hdu1233 最小生成树Prim算法和Kruskal算法

    Prim算法 时间复杂度:O(\(N^2\),N为结点数) 说明:先任意找一个点标记,然后每次找一条最短的两端分别为标记和未标记的边加进来,再把未标记的点标记上.即每次加入一条合法的最短的边,每次扩展 ...

  7. 最小生成树--Prim算法,基于优先队列的Prim算法,Kruskal算法,Boruvka算法,“等价类”UnionFind

    最小支撑树树--Prim算法,基于优先队列的Prim算法,Kruskal算法,Boruvka算法,“等价类”UnionFind 最小支撑树树 前几节中介绍的算法都是针对无权图的,本节将介绍带权图的最小 ...

  8. 最小生成树之Prim算法,Kruskal算法

    Prim算法 1 .概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (gr ...

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

    最小生成树算法 一个连通图可能有多棵生成树,而最小生成树是一副连通加权无向图中一颗权值最小的生成树,它可以根据Prim算法和Kruskal算法得出,这两个算法分别从点和边的角度来解决. Prim算法 ...

  10. 最小生成树(Minimum Spanning Tree)——Prim算法与Kruskal算法+并查集

    最小生成树——Minimum Spanning Tree,是图论中比较重要的模型,通常用于解决实际生活中的路径代价最小一类的问题.我们首先用通俗的语言解释它的定义: 对于有n个节点的有权无向连通图,寻 ...

随机推荐

  1. Top 命令解析

    TOP是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止.比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中C ...

  2. 协方差分析 | ANCOVA (Analysis of Covariance)

    If you are worried about leaving out covariates you could regress out them first and analyse the res ...

  3. 质控工具之TrimGalore使用方法

    TrimGalore 就是一个简单的perl wrapper,打包了fastqc和cutadapt,但是却非常实用. 因为cutadapt的参数选择实在是有够复杂,光接头类型就有5种,还有各种参数,大 ...

  4. 20170821xlVBA跨表公式套用

    Public Sub CopyModelHideBlankRows() AppSettings Dim StartTime As Variant Dim UsedTime As Variant Sta ...

  5. JavaScript动态加载资源

    //动态加载样式 function dynamicLoadingCss(path){ if(!path || path.length === 0){ return false; } var head ...

  6. fedora21 中lamp的搭建(测试没有问题)

    LAMP Stands for Linux,Apache,MySQL and PHP. Most of the websites works with the above combination. T ...

  7. python生成随机整数

    python生成随机不重复的整数,用random中的sample index = random.sample(range(0,10),10) 上面是生成不重复的10个从1~10的整数 python生成 ...

  8. CentOS中与网络相关的常用

    CentOS中与网络相关的常用配置文件 1. 常见的网络配置文件 /etc/hosts           本地域名解析表,用于解析主机名.对应于win系统中的C:\Windows\System32\ ...

  9. 前端Vue之vue的基本操作

    1.1 vue.js的快速入门使用 vue.js是目前前端web开发最流行的工具库之一,由尤雨溪在2014年2月发布的. 另外几个常见的工具库:react.js /angular.js 官方网站: 中 ...

  10. shiro中INI配置

    4.1 根对象SecurityManager 从之前的Shiro架构图可以看出,Shiro是从根对象SecurityManager进行身份验证和授权的:也就是所有操作都是自它开始的,这个对象是线程安全 ...