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. postMan用法

    增加: 删除: 更改: 查询:

  2. 第 6 章 存储 - 040 - docker managed volume

    docker managed volume 与 bind mount 在最大区别是不需要指定 mount 源,指明 mount point 就行了 通过 -v 告诉 docker 需要一个 data ...

  3. 第 3 章 镜像 - 019 - 使用公共 Registry

    保存和分发镜像的最直接方法就是使用 Docker Hub.https://hub.docker.com/ Docker Hub 是 Docker 公司维护的公共 Registry.用户可以将自己的镜像 ...

  4. Create a Hadoop Build and Development Environment

    Create a Hadoop Build and Development Environment http://vichargrave.com/create-a-hadoop-build-and-d ...

  5. chmod 没有x权限怎么办

    解决方法1:    # /lib64/ld-linux-x86-64.so.2 /bin/chmod 755 /bin/chmod   //linux动态命令库   解决方法2:方法2提到的两种方法形 ...

  6. python记录_day23 正则表达式 re模块

    一. 正则表达式 使用python的re模块之前应该对正则表达式有一定的了解 正则表达式是对字符串操作的一种逻辑公式.我们一般使用正则表达式对字符串进行匹配和过滤. 正则的优缺点: 优点:灵活, 功能 ...

  7. seaweedFS

    那首先我们来分析一下seaweedfs是什么?seaweedfs是一个非常优秀的由 golang 开发的分布式存储开源项目.它是用来存储文件的系统,并且与使用的语言无关,使得文件储存在云端变得非常方便 ...

  8. 安卓——implements——OnClickListener

    //实现底部的按钮激活监听 设置不同src class click implements OnClickListener{ @Override public void onClick(View v) ...

  9. bzoj-2038-莫队

    2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 15784  Solved: 7164[Sub ...

  10. Leetcode 127 **

    class Solution { public: int ladderLength(string beginWord, string endWord, vector<string>& ...