最小生成树

来源:互联网 发布:php 用户登录次数 编辑:程序博客网 时间:2024/05/16 08:36

1.Kruskal算法

首先按照变得权值从小到大排序,每次从剩余的边中选出权值最小的且两个顶点不在同一集合中的边(为的是不产生回路),加入到生成树中,直到加入了n-1条边为止

对边进行快排O(MkogM),在m条边中选出n-1条边是O(MlogN),所以Kruskal算法的时间复杂度为O(MlogM +MlogN),通常M要比N大得多,因此最终算法的时间复杂度为

O(MlogM)

Kruskal算法是一步步将森林中的树合并

利用Kruskal算法可以求一个图的最大生成树和最小生成树(如果存在的话),不存在最下(最大)生成树的条件是并查集里面并没有n个点,也就是cnt<n-1


#include<iostream>    #include<cstdio>    #include<cstdlib>    #include<cstring>    #include<string>    #include<queue>    #include<algorithm>    #include<map>    #include<iomanip>    #define INF 99999999    #define maxn 20   using namespace std;        int f[maxn];//存放的i点的祖先 int n,m;struct node{int x,y;int w;}; node edge[maxn];//存储边的数组 int cmp(node a, node b)//比较函数,用来快排用,从小到大排序 {return a.w<b.w;}void init()//每个点的祖先都是他自己,其实就是每个点之间都没有关系 {for(int i=1;i<=n;i++)f[i]=i;}int getf(int i)//获得该点的祖先 {int temp;if(f[i]==i)return i;else{f[i]=getf(f[i]);return f[i];}}int merge(int a, int b)//将两个点合并到一棵树里,其实就是把a到b这条边放进生成树里 {int i=getf(a);int j=getf(b);if(j==i)return 0;else{f[j]=i;return 1;}}int main(){int sum=0,cnt=0;scanf("%d%d",&n,&m);for(int i=0;i<m;i++){scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].w);}sort(edge,edge+m,cmp);//进行边的权值的从小到大排序 init();//初始化每个点 //Kruskal算法的核心部分 for(int i=0;i<m;i++){if(cnt<=n-1)//当生成了n-1边,结束,最小生成树只有n-1条边 {if(merge(edge[i].x,edge[i].y))//如果两个点不在一个集合里,也就是说他俩的祖先不是同一个,就把这条边加入到生成树里面 {sum+=edge[i].w;//sum存放的是最小生成树的所有边的权值之和 cnt++;}}elsebreak;}printf("%d\n",sum);return 0;}/*输入数据 6 92 4 113 5 134 6 35 6 42 3 64 5 71 2 13 4 91 3 2输出结果 19*/



2.Prime 算法

(1)邻接表+堆排序+Dijkstra算法   时间复杂度为O(MlogN),次算法适用于稀疏图

Prime算法是通过每增加一条边来建立一棵树

Dijkstra算法是用来求一个图的最小生成树,但是无法求出一个图的最大生成树

该算法的一个关键地方理解dis数组的含义,dis数组里面存放的是每个点到这棵树的距离,最基本的Dijkstra算法中dis存放的是每个点到1号(假定求的是1号点到其他点的最短距离)点的距离吗,一定要把这个地方理解好

#include<iostream>#include<cstring>#include<string>    #include<queue>    #include<algorithm>    #include<map>    #include<iomanip>    #define inf 0x3f3f3f3f    #define maxn 20   using namespace std;  int n,m,sum;  int heap[maxn],pos[maxn];//pos数组是一个重要部分,用来记录dis中对应的编号在heap中的对应位置 int head[maxn],book[maxn];int dis[maxn];struct node{int front;int to;int w;int next;};node edge[maxn];void swap(int i, int j){//交换堆中i,j位置中的值 int t;t=heap[i];heap[i]=heap[j];heap[j]=t;//改变pos数组pos[heap[i]]=i;pos[heap[j]]=j; return;}void siftdown(int i){int t,flag=1;while(i*2<=n && flag){if(dis[heap[i*2]]<dis[heap[i]])t=i*2;else t=i;if(i*2+1<=n){if(dis[heap[i*2+1]]<dis[heap[t]])t=i*2+1;}if(i!=t){swap(i,t);i=t;}else flag=0;}return;}void siftup(int i){int t,flag=1;while(i!=1 && flag)//当i=1时,就没法再继续向上寻找了 {if(dis[heap[i/2]]<dis[heap[i]])//父节点比子节点小,符合要求,不需要交换位置 flag=0;else swap(i,i/2);i=i/2;//这句很重要一定要有,交换子节点和父节点的位置后,i更新为父节点位置,方便下次继续向上探索 }return;}void creat()//创建堆 {for(int i=n/2;i>=1;i--)siftdown(i);}int pop()//弹出堆中的第一个元素 {int t;t=heap[1];heap[1]=heap[n];pos[heap[n]]=1;n--;//记得n一定要减1 siftdown(1);return t;} int main(){int num;scanf("%d%d",&n,&m);num=n;for(int i=0;i<m;i++)scanf("%d%d%d",&edge[i].front,&edge[i].to,&edge[i].w);memset(head,-1,sizeof(head));for(int i=m;i<2*m;i++)//无向图 ,做题的时候一方要看清是无向图还是有向图,因为图的存储方式会不一样 {edge[i].front=edge[i-m].to;//这个地方一定要看清楚front与to相反的,与小于m相比 edge[i].to=edge[i-m].front;edge[i].w=edge[i-m].w;}//邻接表的建立for(int i=0;i<2*m;i++){edge[i].next=head[edge[i].front];head[edge[i].front]=i;} //堆的初始化 for(int i=1;i<=n;i++){heap[i]=i;//存放的是dis数组的编号,是按照dis数组编号里面存储的数值的大小进行堆排序的 pos[i]=i;//存放的是dis数组编号在heap中的位置 }//dis数组的初始化 dis[1]=0;for(int i=2;i<=n;i++)//dis一定要全部初始化为inf dis[i]=inf;int k=head[1];while(k!=-1){dis[edge[k].to]=edge[k].w;k=edge[k].next;}book[1]=1;creat();//堆的创建 pop();//单源最短路径--Dijkstra算法核心部分 while(n>0){int t;t=pop();//弹出的是heap[1]的值,即dis中最小的编号         //每弹出一个值,就相当于堆的大小要减1 book[t]=1;sum+=dis[t];k=head[t];while(k!=-1)//松弛 {if(book[edge[k].to]==0 && dis[edge[k].to]>edge[k].w) //松弛的每个点到这棵生成树的距离{dis[edge[k].to]=edge[k].w;siftup(pos[edge[k].to]);//因为有堆的存在,所以每次改变dis数组里面的值都要相当于对dis输入进行一次重新排序, }                           //确保heap数组第一个元素的值是最小的,所以要想上调整 k=edge[k].next;}}printf("%d\n",sum);return 0; }/*输入数据 6 92 4 113 5 134 6 35 6 42 3 64 5 71 2 13 4 91 3 2输出结果19 */ 


(2)没有使用堆的Prime算法

该算法的时间复杂度为O(N*N),该算法适用于稠密图,在稠密图里面适用该算法会比Kruskal算法快特别多,稠密图不适用于Prime算法+堆,因为这样的话时间复杂度会变成

N*Nlong(N),时间复杂度大于O(N*N)

#include<iostream>#include<cstring>#include<string>    #include<queue>    #include<algorithm>    #include<map>    #include<iomanip>    #define inf 0x3f3f3f3f    #define maxn 20   using namespace std;  int n,m,sum;  int edge[maxn][maxn],book[maxn];//稠密图用邻接矩阵来存储就行了 int  dis[maxn]; int main(){int x,y,w,cnt=0;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(i==j)edge[i][j]=0;else edge[i][j]=inf;//邻接矩阵建立for(int i=0;i<m;i++) {scanf("%d%d%d",&x,&y,&w);edge[x][y]=w;edge[y][x]=w;//无向图,注意要再反向存储一遍 }//dis数组初始化,第一个点选择1号顶点,因为每一个点都要选到,所以先选哪一个点无所谓 for(int i=1;i<=n;i++)dis[i]=edge[1][i];book[1]=1;cnt++;//Djikstra算法的核心部分  while(cnt<n)//这里一开始我写的是cnt<=n,结果错误,因为当cnt==n时,所有的点都已经选完了,再往下算就多加边了,自然出错,所以cnt<n,其实此时的cnt已经等于n了, {           //也就是选完所有的点了,跳出while循环 int t,Min=inf;//找出dis数组中最小的边 for(int i=1;i<=n;i++){if(book[i]==0 && dis[i]<Min){Min=dis[i];t=i;}}book[t]=1;sum+=dis[t];printf("%d\n",dis[t]); //松弛 for(int i=1;i<=n;i++){if(book[i]==0 && edge[t][i]<dis[i]) //松弛的是每个点到这棵生成树的距离dis[i]=edge[t][i];}cnt++;}printf("%d\n",sum);return 0; }/*输入数据 6 92 4 113 5 134 6 35 6 42 3 64 5 71 2 13 4 91 3 2输出结果19 */ 



3.总结

因为kruskal算法是每次选最短的边,所以时间复杂的与边的数量有关,所以Kruskal算法适用于稀疏图(因为边的数量少),Prime算法每次选的是点,对边的依赖比较小,所以稠密图要用Prime算法,这个方法仅用来记忆用吗,具体算法的时间复杂度还需要进一步计算

0 0