图论_最小生成树_2020-5-31

​什么是生成树?

 

一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。

什么是最小生成树?

在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

 

注意是在无向图中。

求解最小生成树的两种经典方法!

  1. Kruskal 克鲁斯卡尔算法 :(加边法)

  2. Prim   普里姆算法算法      (加点法)

 

算法实现:

Prim   普里姆算法算法      (加点法)

/*  2020-5-28  无向图  最小代价生成树Prim算法: 普里姆算法思想: 从图中任意取出一个顶点,把这个顶点当做一颗树,然后选取这颗树相邻接的边中,最短的边(权重最小),然后将选中这条最短的边的顶点也并入这颗树。此时树中有两个顶点。然后又从这颗新的树,选取相邻接最短的边,并将这条选中边的顶点并入树中。一直重复上诉步骤,直到图中所有定点都并入树中。算法过程: lowCost[i] 生成树中到顶点i最短的一条边的权重从图中任取一个顶点 v0开始,构造生成树 1:将v0到其他所有顶点的所有边都当做候选边 2:重复以下步骤n-1次,使得n-1个顶点都被并入到生成树中     1: 从候选边中选取权重最小的边,并将该边的另一端相接的顶点v并入生成树中     2: 对剩余顶点vi ,if (v,vi)的权值比lowCost[i] 小,则更新lowCost[i]=(v,vi)权值*/#include<iostream>using namespace std ;#define MaxSize 50      //图最大顶点个数#define inf 999999999  //无穷大值,在图中表示不连通typedef  int  datatype; //可以根据需要改变数据类型,也可以自己封装数据类型//邻接矩阵typedef struct{    int num;    //顶点编号    char info;  //顶点信息}Node ; // 顶点类型typedef struct{  datatype Weight[MaxSize][MaxSize] ; //保存图的,边集权重信息  int n,e ;   // n 顶点的总数, e 边数  Node nodes[MaxSize] ; //保存顶点信息}MGraph;int visited[MaxSize] ;   //访问记录数组int pre[MaxSize] ;   //保存当前访问结点的前一个已经访问节点/*  构造图的初始函数 */MGraph InitiaL_Graph(){    MGraph graph ;    int n ,e ;  // n: 结点数 e: 边数    int i, j ;    cout<<"初始化图操作:请输入你要构造的图的 顶点数(n) 和边数(e)"<<endl ;    cout<<"请输入结点数n: \t";    cin>>n;    cout<<"请输入变数e: \t";    cin>>e;    graph.n = n ;    graph.e = e ;    for(i=1;i<=n;i++ ){        for(j=1;j<=n;j++){           graph.Weight[i][j] =inf; 把每条边赋值-inf,表示不连通        }    }    cout<<"请输入边值权重"<<endl ;    cout<<" edgs[i][j] = weight,如果没有图中没有weight,请全输入1占位: 输入格式 i ,j ,weight"<<endl ;    while(e--){        datatype weight ;        cin>>i>>j>>weight ;        graph.Weight[i][j] = weight ;  //注意是无向边, 所有 (i,j) =(j,i)         graph.Weight[j][i] = weight ;    }    return graph ;}/*  Prim:    graph: 图    v : 图中任意一个顶点    return : sumCost 图->生成树的最小代价    注意: 如果图数据比较多,用引用或者指针传递参数*/int  Prim(MGraph graph , int v ){    int  n = graph.n ;   //图中顶点 个数    int sumCost = 0 ;    //生成树最小代价    int lowCost[n] ;     //记录生成树,到图中还未被并入的顶点边的最小权值    int vset[n] ;     // vset[i] 标记顶点i 是否并入生成树中    int i,j ,k,index,minWeight ;    for(i = 1 ;i<=n;i++){  //初始化生成树只有一个顶点V        lowCost[i] =graph.Weight[v][i] ; //到图中所有顶点最小权值, 不通的顶点为inf        vset[i] = 0 ;     //      0: 表示未被并入生成树中 1: 并入    }    k=v ;  //打印并入生成树的结点    vset[v] =1 ; //第一个顶点并入,还剩下n-1 个    cout<<"图中第"<<k++<<"个并入生成树结点"<<v<<endl ;    for(i =1; i<n;i++){        minWeight = inf ;        for(j=1;j<=n;j++){    //找出候选边中最小者            if(vset[j]==0&&lowCost[j]<minWeight){ //注意是图中剩余的顶点vset[j]=0                minWeight = lowCost[j];                index = j ;            }        }        vset[index] =1 ; //选中最小的顶点 index ,并入生成树中        sumCost+=minWeight ; //代价求和        v=index ; //更新生成树中新来的顶点到到图中还未被并入的顶点边的最小权值        cout<<"图中第"<<k++<<"个并入生成树结点是: "<<v<<"  weight = "<<minWeight<<endl ;        for(j=1 ;j<=n;j++){            if(vset[j]==0 && graph.Weight[v][j]<lowCost[j]){                lowCost[j] = graph.Weight[v][j] ;            }        }    }    return  sumCost ;}int main(){    MGraph graph ;    int cost ;    graph = InitiaL_Graph() ; //初始化图    cost =Prim(graph,1) ;//从图中顶点1开始生成最小生成树    cout<<"这个图生成最小生成树代价"<<endl ;    cout<<cost <<endl ;    return (1) ;}/*input: n=5  e=81 2 51 3 11 4 22 3 32 4 53 4 63 5 24 5 3*/

 

Kruskal 克鲁斯卡尔算法 :(加边法)

/*@date:   2020-5-28@author:wby克鲁斯卡尔 Kruskal算法:算法步骤:1:将图中边按照权值从小到大排序2:然后从最小边开始,检测当前边是否为候选边(该边的并入不会构成回路)        if  yes ,将此边并入生成树中,下一条边继续检测,        else: 下一条边继续检测      直到到所有边都被检测完为止*/#include<iostream>#include <algorithm>using namespace std ;#define MaxSize 50      //图最大顶点个数#define inf 999999999  //无穷大值,在图中表示不连通typedef  int  datatype; //可以根据需要改变数据类型,也可以自己封装数据类型typedef struct{ //存储图中所有的边 (u,v) 和这条边上的权值 int u ; int v ; datatype weight ;}edge;//邻接矩阵typedef struct{  datatype Weight[MaxSize][MaxSize] ; //保存图的,边集权重信息  int n,e ;   // n 顶点的总数, e 边数}MGraph;int unioFindSet[MaxSize] ; //并查集edge edges[MaxSize] ;//保存图中所有的边,全局变量,便于传参/*  构造图的初始函数 */MGraph InitiaL_Graph(){    MGraph graph ;    int n ,e ;  // n: 结点数 e: 边数    int i, j ;    cout<<"初始化图操作:请输入你要构造的图的 顶点数(n) 和边数(e)"<<endl ;    cout<<"请输入结点数n: \t";    cin>>n;    cout<<"请输入变数e: \t";    cin>>e;    graph.n = n ;    graph.e = e ;    for(i=1;i<=n;i++ ){        for(j=1;j<=n;j++){           graph.Weight[i][j] =inf; 把每条边赋值-inf,表示不连通        }    }    int k=1 ;    cout<<"请输入边值权重"<<endl ;    cout<<" edgs[i][j] = weight,如果没有图中没有weight,请全输入1占位: 输入格式 i ,j ,weight"<<endl ;    while(e--){        datatype weight ;        cin>>i>>j>>weight ;        graph.Weight[i][j] = weight ;  //注意是无向边, 所有 (i,j) =(j,i)        edges[k].u = i; //保存边        edges[k].v = j ;        edges[k].weight  =weight ;        k++ ;    }    return graph ;}/*getRoot:    v: 任意一个结点    return : v结点的根结点*/int getRoot(int v) //在并查集中查找根结点{     while(v!=unioFindSet[v]){         v= unioFindSet[v] ;     }     return v ;}/* 结构体排序规则 权值按照从小到大,将所有边排序 */int compare(const edge  &edge1 ,const edge &edge2){    return edge1.weight < edge2.weight ;}/*Kruskal:    graph: 给定一个图 邻接矩阵存储    return : 最小生成树代价*/int Kruskal(MGraph graph ){    datatype minCost = 0 ;    int u,v ; //(u,v) 边    int i;    int n ,e ;    n = graph.n ; //顶点个数    e = graph.e ;  //边个数    for(i = 1;i<=n;i++){  //初始化        unioFindSet[i]= i ; //开始所有结点的根结点都是自己, n 个结点n 颗树    }    //结构体排序:权值按照从小到大,将所有边排序    sort(edges+1,edges+e+1,compare) ;//图中顶点从1开始,没用0这个位置。    cout<<"边集排序"<<endl ;//输出排序后的情况    for(i=1 ;i<=e;i++)        cout<<"( "<<edges[i].u<<" ,"<<edges[i].v<<") weight= "<<edges[i].weight<<endl ;    for(i=1;i<=e;i++){         int a=edges[i].u ; //当前边(a,b)         int b =edges[i].v ;         u = getRoot(a);  //查找当前结点的根结点         v = getRoot(b) ;         if(u!=v){ //如果a,b 的根结点不同,将它们合并到生成树中             unioFindSet[u] =v ;             minCost+=edges[i].weight ;             cout<<"将边("<<a<<" ,"<<b<<" )并入生成树中,"<<"  weight: "<<edges[i].weight <<endl ;         }    }   return minCost ;}int main(){    MGraph graph;    int cost ;    graph =InitiaL_Graph() ;    cost = Kruskal(graph) ;    cout<<"Kruskal求最小生成树花费代价:" ;    cout<<cost<<endl ;    return 1 ;}/*input:n =5 e=81 2 51 3 11 4 22 3 32 4 53 4 63 5 24 5 3*/

 

 

 

Reference:

[1]: 算法导论–第三版

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值