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


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

    传送门

    最小生成树其实是最小权重生成树的简称。一个有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适合边多点少

  • 相关阅读:
    SQL Server 2012 自动增长列,值跳跃问题(自增增加1000)
    根据城市表生成json数据
    LeetCode_257. Binary Tree Paths
    LeetCode_242. Valid Anagram
    LeetCode_237. Delete Node in a Linked List
    LeetCode_235. Lowest Common Ancestor of a Binary Search Tree
    LeetCode_234. Palindrome Linked List
    LeetCode_232. Implement Queue using Stacks
    LeetCode_231. Power of Two
    LeetCode_225. Implement Stack using Queues
  • 原文地址:https://www.cnblogs.com/2529102757ab/p/10732351.html
Copyright © 2020-2023  润新知