最小生成树小结

最小生成树属于图论的基本问题,其应用背景可以这样理解:

一个城市有若干个村庄,这时候我们想要给村庄修路,修一条路有修一条路的花费,我们要保证每个村庄保持联通(不必是两个村庄直接相连,保证从一个村庄通过第三者村庄的路到达另一个村庄也可以);我们想要在这样的前提之下,修路的最小花费是多少;

这个问题有两种解决办法,第一个是以点,即村庄本身为中心,每次将离最近的村庄放在一个集合中,这样每次贪心把村庄放在集合中直到所有村庄都被放进去了,这停止放入;

这是prim算法,大致操作可以理解为是简化版的dijkstra,由于操作麻烦,所以更高效且简便的kruskal算法就来了;

kruskal是以道路(边)为主体,每次放入集合的都是最小的边,这里最小的顺序可以通过sort实现,然后 初始的时候每个点都是一个独立的集合,这时候最小生成树是空的,我们将最短的一条边放入,

把这个边链接的两个点改为同一集合,如果重复,如果形成了环,则丢弃不用即可,直到所有的边都被取完,停止;

这两种算法其实都是贪心思想的具体体现,而对于MST问题来讲,kruskal无疑是首选的算法,他的方便性和简单性是应用很广的;

但是具体问题也要具体分析,对于稠密图来讲,kruskal就不合适了,所以说

kruskal适合稀疏图,而pirm适合稠密图,二者各有各的优点;

实战项目:

https://www.luogu.com.cn/problem/P3366

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int s[200010];
 4 struct Edge
 5 {
 6     int u;
 7     int v;
 8     int w;
 9 }edge[500010];
10 bool cmp(Edge a,Edge b)
11 {
12     return a.w<b.w;
13 }
14 int find(int u)//查找集合所属+路压 
15 {
16     if(u!=s[u])
17     {
18         s[u]=find(s[u]); 
19     }
20     return s[u];
21 }
22 int n,m;
23 long long ans;
24 long long num;
25 int kruskal()
26 {
27     for(register int i=1;i<=n;i++)//初始化 
28     {
29         s[i]=i;
30     }
31     sort(edge+1,edge+1+m,cmp);
32     for(register int i=1;i<=m;i++)
33     {
34         int b=find(edge[i].u);//边的前端点属于哪个集合 
35         int c=find(edge[i].v);//边的后端点属于哪个集合 
36         if(b==c)//形成环则丢弃 
37         continue;
38         s[c]=b;//合并集合 
39         num++;//统计边数 
40         ans+=edge[i].w;//加权 
41         if(num==n-1)//等于n-1就停止了 
42         break;
43     }
44     return ans;
45 }
46 int main()
47 {
48     std::ios::sync_with_stdio(false);
49     cin>>n>>m;
50     for(register int i=1;i<=m;i++)
51     {
52         cin>>edge[i].u>>edge[i].v>>edge[i].w;
53     }
54     kruskal();
55     if(num==n-1)//有n-1条边,联通 
56     cout<<ans<<endl;
57     else
58     cout<<"orz"<<endl;
59     return 0;
60 }

http://10.64.27.5:8888/p/P38

拿到这道题,直观的想法是首先对原图进行一边最小生成树,然后选取建发电站费用最小的矿井建发电站,与剩余矿井形成电网

这样并不行,因为我们可以建立多个互不相连的电网,每个电网有一个发电站,因为有可能有两个相离极远的矿井群,如果强行连接它们花费将灰常大,不如在两边各建一个发电站形成两个电网,也即跑一个最小生成森林,然后再每一个森林中建立发电站,但问题来了,我们并不知道哪些点一个森林,这就要用到一个处理森林的常用技巧:添加一个虚拟节点,让所有森林形成一棵树,而这样我们还顺便解决了贪心遍历的问题:直接把虚拟节点与各个节点的距离设为在这个节点上建立发电厂的费用即可

当然我们也可以这样理解:

建立发电厂其实是从一个一个强大的拥有电的矿坑取电(那个矿坑专门挖矿后发电),开销为到强大矿坑的距离,然后为了让所有矿坑用上电,就需要让所有矿坑(加上供电矿坑)形成电网

#include<bits/stdc++.h>//kruskal的具体应用,设计就地解决和最大限度解决
using namespace std;
const int MAXN=500;
struct edge{
    int u,v,w;
}e[MAXN*MAXN];
int tot;
int n;
int s[MAXN];
int m;
int find(int x){
    if(x!=s[x]){
        s[x]=find(s[x]);
    }
    return s[x];
}
bool cmp(edge x,edge y){
    return x.w<y.w;
}
void init_set()
{
    for(int i=1;i<=n;i++){
        s[i]=i;
    }
}
int kruskal(){
    int sum=0;
    init_set();
    sort(e+1,e+1+m,cmp);
    for(int i=1;i<=m;i++){
        int b=find(e[i].u);
        int c=find(e[i].v);
       if(b==c)
       continue;
       s[c]=b;
       sum+=e[i].w;
       /*tot++;
       if(tot=n-1)
       break;*/
    }
    return sum;
}
int main(){ 
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int temp;
        scanf("%d",&temp);
        e[++m].u=0;
        e[m].v=i;
        e[m].w=temp;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            int a;
            scanf("%d",&a);
            e[++m].u=i;
            e[m].v=j;
            e[m].w=a;
        }
    }
    printf("%d",kruskal());
    return 0;
}

 

posted @ 2022-04-25 20:31  江上舟摇  阅读(41)  评论(0编辑  收藏  举报