图论-----最小生成树

Posted 2529102757ab

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论-----最小生成树相关的知识,希望对你有一定的参考价值。

第一次写题解,大佬们勿喷。

传送门

最小生成树其实是最小权重生成树的简称。一个有n个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有n个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或Prim(普里姆)算法求出。---来自于搜狗百科

下面本蒟蒻一一为大家解说

Prim算法

Prim算法其实是一种贪心算法,其基本思想和dijkstra差不多,他最初将无向图中的所有顶点分成两个集合Va和Vb,Va表示已经连入最小生成树的点,Vb反之,最开始Va只有任意取得一个点,从Vb依次取点---其到Va的距离最小直到Vb为空,结束

 

算法步骤

  1. 将一个图分为两部分,一部分归为点集U,一部分归为点集V,U的初始集合为{V1},V的初始集合为{ALL-V1}。
  2. 针对U开始找U中各节点的所有关联的边的权值最小的那个,然后将关联的节点Vi加入到U中,并且从V中删除(注意不能形成环)。
  3. 递归执行步骤2,直到V中的集合为空。
  4. U中所有节点构成的树就是最小生成树。

Prim写法

 1 #include<bits/stdc++.h>//万能头文件
 2 using namespace std;
 3 #define inf 0x7fffffff;//0x7fffffff是long int 的最大值
 4 int vst[5001];//用来标记当前的顶点i是否加入了最小生成树
 5 int d[5001];//表示i到生成树中所有连边的最小值
 6 int g[5001][5001];//用于存储邻接矩阵
 7 int ans,n,m;
 8 int i,j;
 9 void read()//读取数据
10 {
11     int x,y,z;
12     cin>>n>>m;
13     for( i=1;i<=n;i++)
14     {
15         for( j=1;j<=n;j++)
16         {
17             g[i][j]=inf;//把邻接矩阵初始化,注,邻接矩阵虽然好打,但是一个二维数组的内存惊人,最近在清北学堂上课时无意中听到了链表,感觉怪好用,内存还小,有兴趣可以去百度一下。
18         }
19     }
20     for( i=1;i<=m;i++)
21     {
22         cin>>x>>y>>z;
23         if(g[x][y]>=z)//这一点巨坑,某两个点之间会反复读取,如果不去这些值的最小值就会全WA,这是本蒟蒻用血换来的教训。
24         g[x][y]=g[y][x]=z;
25         if(g[x][y]<z)
26         continue;//比赛中推荐用continue,我有一次就用break然后炸了。
27     }
28 }
29 void prim(int v0)//重点
30 {
31     int minn;//最小值
32     int k;
33     memset(vst,0,sizeof(vst));//把vst初始化,一定要写-----来自一个金牌大佬
34     for( i=1;i<=n;i++)
35     {
36         d[i]=inf;
37     }
38     d[v0]=0;
39     ans=0;
40     for( i=1;i<=n;i++)//选择n个点
41     {
42         minn=inf;
43         for( j=1;j<=n;j++)//选择最小边
44             if(vst[j]==0&&minn>d[j])
45             {
46                 minn=d[j];
47                 k=j;
48             }
49             vst[k]=1;//把vst标记为已用过
50             ans=ans+d[k];
51             for(j=1;j<=n;j++)//把d数组修改了
52             {
53                 if(vst[j]==0&&d[j]>g[k][j])
54                 d[j]=g[k][j];
55             }
56     }   
57 }
58 int main()
59 {
60     read();
61     prim(1);
62     cout<<ans<<endl;
63     return 0;
64 }
65 //因为本题 经过骗分发现无orz,那句话纯属骗初学者。

 

Kruskal算法

Kruskal算法也是一个常用的算法,好处就是太好打了,一个并查集就没了(什么?),这导致本蒟蒻在比赛中就没打过Prim,后果嘛。。。很开心,TLE了2/5(看你们就迷上了Krusakl---大佬教育人的原话),所以稠密图 Prim > Kruskal,稀疏图 Kruskal > Prim。

算法步骤

  1. 把图中的所有边从小到大排好,至于你使用快排还是用SHELL排序或是什么大佬专属大法------只要排好就行 
  2. 按边权大小一次选择,若当前边加入树中会出现环就舍弃当前边,反之就将其标记并sum++。
  3. 重复②直到生成树中有n-1条边,若跑完全图还是不到n-1条边,表示最小生成树不存在,就是真的要输出orz

注释:

  1. 将边(E,F)加入R中。  边(E,F)的权值最小,因此将它加入到最小生成树结果R中。  
  2. 将边(C,D)加入R中。  上一步操作之后,边(C,D)的权值最小,因此将它加入到最小生成树结果R中。  
  3. 将边(D,E)加入R中。  上一步操作之后,边(D,E)的权值最小,因此将它加入到最小生成树结果R中。 
  4. 将边(B,F)加入R中。上一步操作之后,边(C,E)的权值最小,但(C,E)会和已有的边构成回路;因此,跳过边(C,E)。同理,跳过边(C,F)。将边(B,F)加入到最小生成树结果R中。
  5. 第5步:将边(E,G)加入R中。  上一步操作之后,边(E,G)的权值最小,因此将它加入到最小生成树结果R中。  
  6. 将边(A,B)加入R中。  上一步操作之后,边(F,G)的权值最小,但(F,G)会和已有的边构成回路;因此,跳过边(F,G)。同理,跳过边(B,C)。将边(A,B)加入到最小生成树结果R中。

Kruskal写法

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 struct node
 4 {
 5     int x,y,z;
 6 }a[5000001];//结构体
 7 int n,m,ans=0,bj;
 8 int prt[500001];
 9 int cmp(const node &x,const node &y)//结构体快排,虽然还是要打,但已经比c语言省事多了
10 {
11     return x.z<y.z;
12 }
13 int find(int x)//并查集,基础的东西,一定要掌握,可以看看大佬的题解,会让你感叹这个世界的神奇,真的是清醒脱俗(手动滑稽)--->https://www.luogu.org/problemnew/solution/P3367
14 {
15     if(prt[x]==x)
16     return x;
17     else
18     return prt[x]=find(prt[x]);
19 }
20 void kruskal()//重点
21 {
22     int f1,f2,k,i;
23     k=0;
24     for(i=1;i<=n;i++)
25     prt[i]=i;//初始化
26     for(i=1;i<=m;i++)
27     {
28         f1=find(a[i].x);
29         f2=find(a[i].y);
30         if(f1!=f2)
31         {
32             ans=ans+a[i].z;
33             prt[f1]=f2;//合并两个不同的集合
34             k++;
35             if(k==n-1)
36             break;
37         }
38     }
39     if(k<n-1)//判断最小生成树是否存在
40     {
41         cout<<"orz"<<endl;[](https://www.cnblogs.com/ECJTUACM-873284962/p/7141078.html)
42         bj=0;
43         return ;
44     }
45 }
46 int main()
47 {
48     cin>>n>>m;
49     ans=0;
50     bj=1;
51     for(int i=1;i<=m;i++)
52     cin>>a[i].x>>a[i].y>>a[i].z;
53     sort(a+1,a+m+1,cmp);//结构体快排就是好用,但大佬说STL里除了快排其他的都慢。。。
54     kruskal();
55     if(bj)
56     cout<<ans<<endl;
57     return 0;
58 }

 Kruskal和Prim的比较

  • 方法上:

              Kruskal在所有边中不断寻找最小的边,Prim在U和V两个集合之间寻找权值最小的连接,共同点是构造过程都不能形成环

  • 时间上:

              Prim适合稠密图,复杂度为O(n n),因此通常使用邻接矩阵储存,复杂度为O(e loge),而Kruskal多用邻接表,稠密图 Prim > Kruskal,稀疏图 Kruskal > Prim。

  • 空间上:

              Prim适合点少边多,Kruskal适合边多点少

以上是关于图论-----最小生成树的主要内容,如果未能解决你的问题,请参考以下文章

图论之最小生成树最小生成树专题

图论——迪杰斯特拉算法和最小生成树

图论 最小生成树

怎么求最小生成树 (离散数学 图论)

浅谈图论——最小生成树

图论讲解——最小生成树