首页 > 代码库 > 最小生成树

最小生成树

最近考试,好几天没敲代码了,感觉怪怪的。

这两天考了4科.今天下午考完了大学物理,晚上可以浪一下。下周考其他乱七八糟的东西,我不管,今晚就要浪ヽ( ̄▽ ̄)?

今天晚上有cf,写了一道A题,wa了。。。B题看不懂题意,估计看懂题意也写不出来(T▽T),那就把老大上周给我讲的最小生成树写一下吧。

我一开始自己学并查集的时候后面有最小生成树的题,题解看不懂(;′д`)ゞ,太菜了(T ^ T),还是要老大教我。

 

最小生成树:

中文名 最小生成树

外文名 Minimum Spanning Tree,MST

一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。

最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。

 

在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集(即)且为无循环图,使得
的 w(T) 最小,则此 T 为 G 的最小生成树。
技术分享
 
最小生成树其实是最小权重生成树的简称。
 
应用: 生成树和最小生成树有许多重要的应用。
 
例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。
 
性质:设G=(V,E)是一个连通网络,U是顶点集V的一个非空真子集。若(u,v)是G中一条“一个端点在U中(例如:u∈U),另一个端点不在U中的边(例如:v∈V-U),且(u,v)具有最小权值,则一定存在G的一棵最小生成树包括此边(u,v)。
 
证明:为方便说明,先作以下约定:
①将集合U中的顶点看作是红色顶点,②而V-U中的顶点看作是蓝色顶点,③连接红点和蓝点的边看作是紫色边,④权最小的紫边称为轻边(即权重最"轻"的边)。
于是,MST性质中所述的边(u,v)  就可简称为轻边。
      
用反证法证明MST性质:
假设G中任何一棵MST都不含轻边(u,v)。则若T为G的任意一棵MST,那么它不含此轻边。
根据树的定义,则T中必有一条从红点u到蓝点v的路径P,且P上必有一条紫边(u‘,v‘)连接红点集和蓝点集,否则u和v不连通。
当把轻边(u,v)加入树T时,该轻边和P必构成了一个回路。
删去紫边  (u‘,v‘)后回路亦消除,由此可得另一生成树T‘。
T‘和T的差别仅在于T‘用轻边(u,v)取代了T中权重可能更大的紫边(u‘,v‘)。因为w(u,v)≤w(u‘,v‘),所以
w(T‘)=w(T)+w(u,v)-w(u‘,v‘)≤w(T)
即T‘是一棵比T更优的MST,所以T不是G的MST,这与假设矛盾。
所以,MST性质成立。
 
算法描述:
求MST的一般算法可描述为:针对图G,从空树T开始,往集合T中逐条选择并加入n-1条安全边(u,v),最终生成一棵含n-1条边的MST。
当一条边(u,v)加入T时,必须保证T∪{(u,v)}仍是MST的子集,我们将这样的边称为T的安全边。
 
Prim算法简述:(点)
时间复杂度:O(n^2)
 
1).输入:一个加权连通图,其中顶点集合为V,边集合为E;
2).初始化:Vnew= {x},其中x为集合V中的任一节点(起始点),Enew= {},为空;
3).重复下列操作,直到Vnew= V:
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;
4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。

Kruskal算法简述:(边)
时间复杂度:(O(n*log(n))
 
假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
以上都来自百度百科,好多字,看着就烦。。。
 
n个节点  n-1条边  sigma(wi)==min;
图-->最小生成树
 
这样就简单好多 ̄ω ̄=
 
原理啥的不知道,现在的目标是会用就可以了 (=′ω`=)
直接上题了。。。
 
先贴一个用Kruskal的题(〃‘▽‘〃)
 
HDU2988

Dark roads

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1056    Accepted Submission(s): 463


Problem Description
Economic times these days are tough, even in Byteland. To reduce the operating costs, the government of Byteland has decided to optimize the road lighting. Till now every road was illuminated all night long, which costs 1 Bytelandian Dollar per meter and day. To save money, they decided to no longer illuminate every road, but to switch off the road lighting of some streets. To make sure that the inhabitants of Byteland still feel safe, they want to optimize the lighting in such a way, that after darkening some streets at night, there will still be at least one illuminated path from every junction in Byteland to every other junction.

What is the maximum daily amount of money the government of Byteland can save, without making their inhabitants feel unsafe?

 

Input
The input file contains several test cases. Each test case starts with two numbers m and n, the number of junctions in Byteland and the number of roads in Byteland, respectively. Input is terminated by m=n=0. Otherwise, 1 ≤ m ≤ 200000 and m-1 ≤ n ≤ 200000. Then follow n integer triples x, y, z specifying that there will be a bidirectional road between x and y with length z meters (0 ≤ x, y < m and x ≠ y). The graph specified by each test case is connected. The total length of all roads in each test case is less than 231.
 

Output
For each test case print one line containing the maximum daily amount the government can save.
 

Sample Input
7 11
0 1 7
0 3 5
1 2 8
1 3 9
1 4 7
2 4 5
3 4 15
3 5 6
4 5 8
4 6 9
5 6 11
0 0
 

Sample Output
51
 
直接贴代码了(??ω??)(老大的代码)
 
#include<bits/stdc++.h>
using namespace std;
const int N=2*1e5+100;
int parent[N];
int ans;
int m,n;
struct node{
    int u,v,w;
}a[N];
bool cmp(node x,node y){
    return x.w<y.w;
}
void init(){
    for(int i=0;i<=N;i++)parent[i]=i;
}
int find(int x){
    int r=x;
    while(parent[r]!=r)r=parent[r];
    int i=x;
    int j;
    while(i!=r){
        j=parent[i];
        parent[i]=r;
        i=j;
    }
    return r;
}
void Kruskal(){
    for(int i=0;i<m;i++){
        int x=find(a[i].u);
        int y=find(a[i].v);
        if(x!=y){
            parent[x]=y;
            ans=ans+a[i].w;
        }
    }
}
int main(){
    while(~scanf("%d%d",&n,&m)){
        init();
        if(m==0&&n==0)break;
        int sum=0;
        ans=0;
        for(int i=0;i<m;i++){
            scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].w);
            sum=sum+a[i].w;
        }
        sort(a,a+m,cmp);
        Kruskal();
        printf("%d\n",sum-ans);
    }
    return 0;
}

 

不想解释了,不会解释也没什么好解释的了(???)

 

下一个是用Prim的题(?ω?`ll)

HDU1233

上一个畅通工程是并查集?(????ω????)?

还是畅通工程

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 45926    Accepted Submission(s): 20922


Problem Description
某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
 


Input
测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
当N为0时,输入结束,该用例不被处理。
 


Output
对每个测试用例,在1行里输出最小的公路总长度。
 


Sample Input
3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0
 


Sample Output
3
5
Hint
Hint
Huge input, scanf is recommended.
 
 
代码1:(老大的代码)
 
#include<bits/stdc++.h>
using namespace std;
const int N=100+10;
const int INF=0x3f3f3f3f;
int  mp[N][N];
int vis[N],dis[N];
int n,u,v,w;
void prim(){
    int ans=0;
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    for(int i=1;i<=n;i++)dis[i]=mp[1][i];
    vis[1]=1;
    for(int i=1;i<=n-1;i++){
        int pos;
        int minn=INF;
        for(int j=1;j<=n;j++){
            if(vis[j]==0&&minn>dis[j]){
                pos=j;
                minn=dis[j];
            }
        }
        vis[pos]=1;
        ans=ans+minn;
        for(int j=1;j<=n;j++){
            if(vis[j]==0&&dis[j]>mp[pos][j])dis[j]=mp[pos][j];
        }
    }
    cout<<ans<<endl;
}
int main(){
    while(~scanf("%d",&n)){
        if(n==0)break;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)mp[i][j]=INF;
        int m;
        if(n%2==0)m=n/2*(n-1);
        else m=(n-1)/2*n;
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&u,&v,&w);
            mp[u][v]=w;          //因为没有方向,所以要这么写。
            mp[v][u]=w;
        }
        prim();
    }
    return 0;
}

 

代码2:(渣渣我自己的代码)

 

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=110;
int m[N][N],l[N];
bool visit[N];
int num,sum;
void prim(){
    int temp,k;
    sum=0;
    memset(visit,false,sizeof(visit));
    visit[1]=true;
    for(int i=1;i<=num;i++)
        l[i]=m[1][i];
    for(int i=1;i<=num;i++){
            temp=INF;
      for(int j=1;j<=num;j++)
        if(visit[j]==false&&temp>l[j])
          temp=l[k=j];
        if(temp==INF)break;
          visit[k]=true;
          sum=sum+temp;
          for(int j=1;j<=num;j++)
            if(visit[j]==false&&l[j]>m[k][j])
            l[j]=m[k][j];
    }
}
int main(){
    int a,b,cost,e;
    while(scanf("%d",&num)){
        if(num==0) break;
        e=num*(num-1)/2;
        memset(m,INF,sizeof(m));
        for(int i=1;i<=e;i++){
            scanf("%d%d%d",&a,&b,&cost);
            if(cost<m[a][b])
                m[a][b]=m[b][a]=cost;
        }
        prim();
        printf("%d\n",sum);
    }
    return 0;
}

 

这些题中也用到了并查集,以后回来再看看。

不想写了,没心情了。

考完试,写了一道cf的A题(wa了(╥╯^╰╥))之后感觉虚了,我现在累了,我要滚去休息一会了。。。(/~0~)/

最后谢谢我的老大---梅大大(??ω??)

我滚了==

Σ(っ°Д°;)っ

 
 

 

 

 

 

 

 
 
 
 
 
 

 

 

最小生成树