最小生成树

                                                                                                                       最小生成树

1概述:

在一个具有几个顶点的连通图G中,如果存在子图G'包含G中所有顶点和一部分边,且不形成回路,则称G'为图G的生成树,代价最小生成树则称为最小生成树。

许多应用问题都是一个求无向连通图的最小生成树问题。例如:要在n个城市之间铺设光缆,主要目标是要使这n个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同;另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

处理的是无向图。

2求最小生成树的两种算法:

1Kruskal

方法:将图中边按其权值由小到大的次序顺序选取,若选边后不形成回路,则保留作为一条边,若形成回路则除去.依次选够(n-1)条边,即得最小生成树.(n为顶点数)

1如果题目要求一定要选择某些边,那就是应该提前把这些边的点合并到同一个集合,然后事先求出这些边的长度

模板:

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 10010

int n , m , ans;
int father[MAXN];/*父亲数组*/
int rank[MAXN];/*存储儿子节点的个数*/
struct Edge{
     int x;
     int y;
     int value;
}e[MAXN];

/*按照权值排序*/
bool cmp(Edge e1 , Edge e2){
     return e1.value < e2.value;
}

/*并查集的初始化*/
void Init_Set(){
     for(int i = 0 ; i <= n ; i++){
        father[i]  = i;
        rank[i] = 0;
     }
}

/*并查集的查找*/
int Find_Set(int x){
     if(x != father[x])
       father[x] = find_Set(father[x]);
     return father[x];
 } 

/*并查集的合并*/
void Union_Set(int x , int y){
    if(rank[x] > rank[y])
       father[y] = x;
    else{
       if(rank[x] == rank[y])
         rank[y]++;
      father[x] = y;
    }
}

/*Kruskal算法*/
void Kruskal(){
     Init_Set();
     sort(e , e+m , cmp);/*排序*/
     ans = 0;
     for(int i = 0 ; i < m ; i++){
        int root_x = Find_Set(e[i].x);/*找到根节点*/
        int root_y = Find_Set(e[i].y);
        if(root_x != root_y){/*如果不再同一个连通分量*/
          ans += e[i].value;
          Union_Set(root_x , root_y);/*合并为同一个连通分量*/
        }
     }
     printf("%d\n" , ans);
}

int main(){
    //freopen("input.txt" , "r" , stdin);
    while(scanf("%d%d" , &n , &m)  != EOF){
        for(int i = 0 ; i < m ; i++)
           scanf("%d%d%d" , &e[i].x , &e[i].y , &e[i].value);
        Kruskal();
    }
    return 0;
}

2Prime

Prime算法:
1算法描述:
普利姆算法求最小生成树时候,和边数无关,只和定点的数量相关,所以适合求稠密网的最小生成树,时间复杂度为O(n*n)。

2算法过程:
1.将一个图的顶点分为两部分,一部分是最小生成树中的结点(A集合),另一部分是未处理的结点(B集合)。
2.首先选择一个结点,将这个结点加入A中,然后,对集合A中的顶点遍历,找出A中顶点关联的边权值最小的那个(设为v),将此顶点从B中删除,加入集合A中。
3.递归重复步骤2,直到B集合中的结点为空,结束此过程。
4.A集合中的结点就是由Prime算法得到的最小生成树的结点,依照步骤2的结点连接这些顶点,得到的就是这个图的最小生成树。

3算法实现具体过程:
1.将第一个点放入最小生成树的集合中(标记visit[i]=1意思就是最小生成树集合)。

2.初始化lowcost[i]为跟1点相连(仅仅相连)的边的权值(lowcost[i]不是这个点的最小权值!在以后会逐步更新)。
3.枚举n个顶点

4.将找出来的最小权值的边的顶点加入最小生成树的集合中(标记visit[i]=
1),权值想加。
5.更新lowcost[j]集合。
关键步骤:实质就是每在最小生成树集合中加入一个点就需要把这个点与集合外的点比较,不断的寻找两个集合之间最小的边
6.循环上述步骤,指导将全部顶点加入到最小生成树集合为止。

模板:

#define MAXN 1010
#define INF 0xFFFFFFF
int G[MAXN][MAXN];
int lowcost[MAXN];
int vis[MAXN];
int n , m , ans;

/*初始化点之间的距离为无穷大*/
void init(){
      for(int i = 1 ; i <= n ; i++){
           for(int j = 1 ; j <= n ; j++)
              G[i][j] = INF;
      }
}

/*Prime算法的模板*/
void Prime(){
      int pos , min;
      ans = 0;
      memset(vis , 0 , sizeof(vis));/*初始化为0*/
      vis[1] = 1;/*第一个点标记为1*/
      for(int i = 1 ; i <= n ; i++)/*初始化lowcost数组*/
          lowcost[i] = G[1][i];
      for(int i = 1 ; i <= n ; i++){/*枚举n个顶点*/
          min = INF;/*初始化为INF*/
          for(int j = 1 ; j <= n ; j++){/*找到最小权边对应顶点*/
               if(!vis[j] && min > lowcost[j]) {
                    min = lowcost[j];
                    pos = j;
              }
           }
           if(min == INF)/*如果min = INF表示已经不再有点可以加入最小生成树中*/
               break;
           ans += min;
           vis[pos] = 1;/*加入最小生成树中*/
           for(int j = 1 ; j <= n ; j++){/*更新可更新边的权值*/
                 if(!vis[j] && lowcost[j] > G[pos][j])
                   lowcost[j] = G[pos][j];
           }
      }
     printf("%d\n" , ans);
}

三、Kruskal和Prim对比

1)过程简单对比

Kruskal算法:所有的顶点放那,每次从所有的边中找一条代价最小的;

Prim:算法:在U,(V – U)之间的边,每次找一条代价最小的

2)效率对比

     效率上,稠密图 Prim > Kruskal;稀疏图 Kruskal > Prim

     Prim适合稠密图,因此通常使用邻接矩阵储存,而Kruskal多用邻接表。

3)空间对比

     解决最小生成树题目的时候,要根据给出数据的情况,来决定使用哪种算法,比如:

    1)当点少边多的时候,如1000个点500000条边,这样BT的数据,用prim做就要开一个1000 * 1000的二维数组,而用kruskal做只须开一个500000的数组,500000跟1000*1000比较相差一半。

     2)当点多边少的时候,如1000000个点2000条边,像这种数据就是为卡内存而存在的,如果用prim做,你想开一个1000000 * 1000000的二维数组,内存绝对爆掉,而用kruskal只须开一个2000的数组就可以了。

次小生成树

step 1.  先用prim求出最小生成树T.
               在prim的同时,用一个矩阵max[u][v] 记录 在T中连结任意两点u,v的唯一的
               路中权值最大的那条边的权值. (注意这里).
               这是很容易做到的,因为prim是每次增加一个结点s, 而设已经标号了的结点
               集合为W, 则W中所有的结点到s的路中的最大权值的边就是当前加入的这条边.
               step 1 用时 O(V^2).

step 2.  枚举所有不在T中的边uv, 加入边uv则必然替换权为max[u][v]的边。

举例poj 1679

/*
思路:最小生成树+次小生成树
分析:判断最小生成树是否是唯一的,那就可以去判断次小生成树是否大于最小生成树。这里就涉及到了次小生成树的求法
求解次小生成树:
step 1.  先用prim求出最小生成树T.
             在prim的同时,用一个矩阵max[u][v] 记录 在T中连结任意两点u,v的唯一的
             路中权值最大的那条边的权值. (注意这里).
             这是很容易做到的,因为prim是每次增加一个结点s, 而设已经标号了的结点
             集合为W, 则W中所有的结点到s的路中的最大权值的边就是当前加入的这条边.
             step 1 用时 O(V^2).
step 2.  枚举所有不在T中的边uv, 加入边uv则必然替换权为max[u][v]的边。

代码:
*/
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 1010
#define INF 0XFFFFFFF

int t , n , m;
int vis[MAXN];
int pre[MAXN];/*记录节点的上一个节点*/
int lowcost[MAXN];
int maxvalue[MAXN][MAXN];
int G[MAXN][MAXN];
int mark[MAXN][MAXN];/*标记还有哪些边没被用*/

/*初始化*/
void init(){
    memset(mark , 0 , sizeof(mark));
    memset(maxvalue , 0 , sizeof(maxvalue));
    for(int i = 1 ; i <= n ; i++){
        pre[i] = 1;/*把所有点的上一点都记录为1*/
        for(int j = 1 ; j <= n ; j++)
           G[i][j] = INF;
    }
}

/*prime算法*/
void prime(){
     /*求解最小生成树*/
     memset(vis , 0 , sizeof(vis));
     int ans , pos , min;
     ans = 0;
     vis[1] = 1;
     for(int i = 1 ; i<= n ; i++)
         lowcost[i] = G[1][i];
     for(int i = 1 ; i <= n ; i++){
         pos = -1;
         for(int j = 1 ; j <= n ; j++){
            if(!vis[j] && (pos == -1 || lowcost[j] < lowcost[pos]))
              pos = j;
         }
         vis[pos] = 1;
         ans += lowcost[pos];
         mark[pre[pos]][pos] = mark[pos][pre[pos]] = 0;
         for(int j = 1 ; j <= n ; j++){
             if(vis[j])/*记录maxalue数组*/
                 maxvalue[j][pos] = maxvalue[pos][j] = lowcost[pos];
             if(!vis[j] && lowcost[j] > G[pos][j]){
                 lowcost[j] = G[pos][j];
                 pre[j] = pos;/*记录这些点的上一个节点*/
             }
         }
     }
     /*求解次小生成树*/
     min = INF;
     for(int i = 1 ; i <= n ; i++){
        for(int j = 1 ; j <= n ;j++){
           if(mark[i][j]){
             int tmp = ans-maxvalue[i][j]+G[i][j];
             if(tmp < min)
                 min = tmp;
             mark[i][j] = mark[j][i] = 0;
           }
        }
     }
     if(ans < min)/*如果不同则说明最小生成树是唯一的*/
         printf("%d\n" , ans);
     else
         printf("Not Unique!\n");
}

int main(){
    int x , y , v;
    scanf("%d" , &t);
    while(t--){
       scanf("%d%d" , &n , &m);
       init();
       for(int i = 0 ; i < m ; i++){
          scanf("%d%d%d" , &x , &y , &v);
          if(G[x][y] > v){
              G[x][y] = G[y][x] = v;
              mark[x][y] = mark[y][x] = 1;
          }
       }
       prime();
    }
    return 0;
}
时间: 2024-08-03 12:28:44

最小生成树的相关文章

数据结构-最小生成树的答案错误,求解

问题描述 最小生成树的答案错误,求解 #include #include #define INFINTY 51//不知道怎么定义,才算无穷大 #define MAX_VERTEX_NUM 50 typedef struct { int a[MAX_VERTEX_NUM];//顶点向量 int edges[MAX_VERTEX_NUM[MAX_VERTEX_NUM];//邻接矩阵 int n,;//顶点数 }Graph; int Prim(Graph G){ int *a=(int *)mallo

在图采用邻接表存储时,求最小生成树的Prime算法的时间复杂度为?

问题描述 在图采用邻接表存储时,求最小生成树的Prime算法的时间复杂度为? 在图采用邻接表存储时,求最小生成树的Prime算法的时间复杂度为? A o(n^2) B o(n^3) C o(n) D o(n+e) 答案是o(n+e)...不理解..求过程 解决方案 不对,这题应该选A 求顶点的入度的时间复杂度为O(e)*n=O(n*e) 遍历顶点的时间复杂度是O(n^2) 相加是O(n^2+n*e)=O(n^2) 解决方案二: 详细的解释http://www.cskaoyan.com/redir

hdu4750Count The Pairs(最小生成树找瓶颈边)

/* 题意:就是给你一个图,图的每两个点都有多条路径,每一条路径中都有一条最大边, 所有最大边的最小边(也就是瓶颈边)就是这两点之间的val值!然后给你一个值f, 问有多少个顶点对的val>=f! (u,v) 和 (v, u)是不同的顶点对! 思路:最小生成树(kruskral)那么每两个节点(u,v)的瓶颈边就是这一棵树中u到v 的路径中的最大边值! 在利用并查集的过程中,A, B两个集合,如果有u属于A,v属于B,且u-v可以将 A-B集合连接起来,因为边值由小到大选取,那么以u-v边为瓶颈

最小生成树之Prim算法

Prim算法: 假设N = (V,{E})是连通网,TE是N上最小生成树中边的集合.算法从U={u0}(u0属于V),TE={}开始,重复执行下述操作:在所有u属于U,v属于V-U的边(u,v)属于E中找到一条代价最小的边(u0,v0)并入集合TE,同时v0并入U,直至U=V为止,此时TE中必有n-1条边,则T=(V,{TE})为N的最小生成树. 为实现这个算法,需附设一个辅助数组closedge,以记录从U到V-U具有最小代价的边.对每个顶点vi属于V-U,在辅助数组中存在一个相应分量clos

POJ 1258 Agri-Net:最小生成树 Prim 模版题

Agri-Net:http://poj.org/problem?id=1258 大意:新镇长竞选宣言就是将网络带到每一个农场,给出农场个数,两两之间建光缆的耗费,求所有都联通的最小耗费. 思路:最小生成树,因为边比较稠密,用Prim做. PS:对于比较稠密的图,用Prim,对于比较稀疏的图,用 Kruskal.Kruskal是找边的过程,稀疏的话会比较快. 更多精彩内容:http://www.bianceng.cnhttp://www.bianceng.cn/Programming/sjjg/

POJ 1789 Truck History:最小生成树 Prim

Truck History:http://poj.org/problem?id=1789 大意:用一个7位的string代表一个编号,两个编号之间的距离代表这两个编号之间不同字母的个数.一个编号只能由另一个编号变化的来,变化的字母的数量就是这两个编号之间相应的距离,现在要找出一个变化方案,使得总代价最小,也就是距离之和最小. 思路:将每个字符串当成一个节点,求出每个节点之间需要变化的次数为边的权值,用Prim建立最小生成树(稠密图). 更多精彩内容:http://www.bianceng.cnh

POJ 2485 Highways:最小生成树 Prim

Highways:http://poj.org/problem?id=2485 大意:给你一个用邻接矩阵形式存储的有n个顶点的无向图,让你求它的最小生成树并求出在这个生成树里面最大的边的权值. 思路:用Prim求,判断条件改一下就行. PS:dis数组初始化的时候用memset一直RE,希望有知道怎么回事的不吝赐教,谢了~ 更多精彩内容:http://www.bianceng.cnhttp://www.bianceng.cn/Programming/sjjg/ #include <stdio.h

最小生成树算法:Kruskal算法的Java实现

闲来无事,写个算法,最小生成树的Kruskal算法,相对比Prim算法实现起来麻烦一点点 package trees; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.PriorityQueue; import java.util.Set; /** * 最小生成树的Kruskal算法, * For a connected weighted graph G, a s

poj 3522 Slim Span:枚举+最小生成树

链接: http://poj.org/problem?id=3522 题目: Slim Span Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 4962   Accepted: 2587 Description Given an undirected weighted graph G, you should find one of spanning trees specified as follows. The grap

HDU 2489 Minimal Ratio Tree (DFS枚举+最小生成树Prim)

链接: HDU : http://acm.hdu.edu.cn/showproblem.php?pid=2489 POJ  : http://poj.org/problem?id=3925 题目: Problem Description For a tree, which nodes and edges are all weighted, the ratio of it is calculated according to the following equation. Given a comp