图论--最小生成树总结(Prim&&Kruskal)
来源:互联网 发布:如何将照片导入mac 编辑:程序博客网 时间:2024/05/20 23:34
今天才写了prim的堆优化,发现kruskal居然比prim跑得快。。。
回归正题:
以下是我个人对最小生成树各种算法的理解,以及我的代码。
以下我将点数称为n,边数称为m;
Prim
算法过程(来自百度百科):
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来描述所得到的最小生成树。
然后以下是我自己的解释:
初始时任选一个起点,并加入点集;
不断将离访问过的点集最近的未访问的点连边(或者说选最短的边),加入点集中,并将连边加入边集(或累加答案);
这样进行n-1次,每次加入一个点和一条边,结束后就会形成一棵n个节点,n-1条边的树;
正确性我也懒得证了,自己百度看吧。
时间复杂度:O(nm)
代码就不用看了,这个复杂度完全无法接受,必须优化;
Prim–堆优化
对于上面的过程,最有优化余地的一步是什么呢?
不断将离访问过的点集最近的未访问的点连边(或者说选最短的边)
很明显,粗体字部分可以优化(一般来说,将求最小值或最大值从n优化至logn级别都是常见的优化);
具体方法就是将有可能扩展的边都放入一个小根堆中,每次取最小的,这样可以将每次取最小值的过程优化至log级别的;(不知道堆的同学可以自行百度)
时间复杂度:低于O(nlogm)
手写堆又会增加编程复杂度,我们可以使用c++自带的优先队列(priority_queue),这就是一个堆;
priority_queue< T > (T为类型)(后面括号内为时间复杂度)
1. T top(void):返回堆顶(即最值);(1)
2. void pop(void):弹出堆顶;(logn)
3. void push(T):压入新元素;(logn)
4. bool empty(void):如果堆为空返回true,否则返回false;(1)
5. int size(void):返回堆的元素数量;(1)
另一种定义方式:(建议)
priority_queue< T , vector< T > ,less< T > > (大根堆)
priority_queue< T , vector< T > ,greater< T > > (小根堆)
用自定义类型的前提是那个类型有定义小于号(大根堆)/ 大于号(小根堆)
关于比较符号定义可以看我的代码;
我的代码:
//此题来自洛谷P3366 【模板】最小生成树,是一道模板题,大家可以去做一下。//我这里存边用的是邻接表,省空间,也方便;//也可以用vector来存边#include<bits/stdc++.h>using namespace std;struct edge{ //自定义边类 int to,next,w; bool operator > (const edge& y) const { //大于号 return w>y.w; }}e[400001];int n,m,tot;int head[5001]; //邻接表用int vis[5001]; //标记点是否访问过priority_queue<edge,vector<edge>,greater<edge> > q; //小根堆void addedge(int x,int y,int l){ //加边 tot++; e[tot].to=y; e[tot].next=head[x]; e[tot].w=l; head[x]=tot;}int prim(){ //我这里将1作为起点 for(int i=head[1];i;i=e[i].next){ //将1的邻边都放入优先队列 q.push(e[i]); } vis[1]=1; int ans=0; int left=n-1; //剩余需要加边数 while(left&&!q.empty()){ edge t=q.top(); q.pop(); if(vis[t.to]){ continue; } ans+=t.w; left--; int u=t.to; vis[u]=1; for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(!vis[v]){ q.push(e[i]); } } } return ans;}int main(){ scanf("%d %d",&n,&m); for(int i=1;i<=m;i++){ int x,y,l; scanf("%d %d %d",&x,&y,&l); addedge(x,y,l); addedge(y,x,l); } int ans=prim(); printf("%d",ans); return 0;}
Kruskal
算法过程(来自百度百科):
先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取(会出现环),而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。
我的描述:
将边排序后,从小到大只要不形成环就加边,直到加够n-1条边为止。
正确性很显然,可以自己去看一下具体证明。
时间复杂度(暴力判环):低于O(m^2)
很明显,判环这里可以用并查集优化到近似O(1),那么算法的时间就都会集中在对边进行排序上,时间也就是排序的时间;
时间复杂度(并查集判环):O(mlogm)
这样的复杂度在稀疏图优于Prim,稠密图劣于Prim,自己酌情使用。
Kruskal的好处在于它极易编写,而Prim编写难度较高。
下来看代码吧:
//这题也是洛谷P3366,大家可以再写一次Kruskal交一次。#include<bits/stdc++.h>#define inf 2000000000using namespace std;struct edge{ int a,b,w;}e[200001];int n,m;int f[5001];int sum=0;int find(int x){ //并查集 return x==f[x]?x:f[x]=find(f[x]);}void add(int i,int x,int y,int l){ e[i].w=l; e[i].a=x; e[i].b=y;}int cmp(edge x,edge y){ return x.w<y.w;}int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ f[i]=i; } for(int i=1;i<=m;i++){ int x,y,l; scanf("%d %d %d",&x,&y,&l); add(i,x,y,l); } sort(e+1,e+m+1,cmp); int tot=0; //Kruskal的主体只有这一个循环 for(int i=1;i<=m&&tot<=n-1;i++){ if(find(e[i].a)!=find(e[i].b)){ //判环 sum+=e[i].w; f[find(e[i].a)]=find(e[i].b); tot++; } } if(tot==n-1){ cout<<sum; } else{ cout<<"orz"; } return 0;}
交了之后不出意外应该是Kruskal略快于Prim,这是因为这题m较小。
不过,Kruskal一般情况都足够了,具体看数据范围。
以上便是我对最小生成树的总结。
end
- 图论--最小生成树总结(Prim&&Kruskal)
- 最小生成树基础总结(Prim Kruskal)
- 最小生成树算法(prim&kruskal)
- 最小生成树(Kruskal+Prim)
- 最小生成树(MST):Prim / Kruskal
- 最小生成树(prim&kruskal)模板
- 最小生成树[Kruskal&&Prim](学习)
- 最小生成树(prim和kruskal)
- 最小生成树(Prim与Kruskal)
- Prim、Kruskal最小生成树
- 最小生成树---Prim---Kruskal
- 最小生成树 Kruskal&&Prim
- 最小生成树 Prim Kruskal
- Kruskal/prim--最小生成树
- 最小生成树 PRIM KRUSKAL
- 图论 之 最小生成树 (Kruskal and Prim)
- 【图论】最小生成树之prim算法与kruskal算法
- 图的最小生成树原理PRIM、Kruskal(一)
- node.js 探索之路(概念进阶)
- Android 完整漂亮问卷、试卷 -- 支持单选、判断、不定项
- 二叉树的存储和遍历
- 561. Array Partition I
- 娜娜梦游仙境系列——多民族王国
- 图论--最小生成树总结(Prim&&Kruskal)
- 转 Java-Swing嵌入浏览器(一) 今天要说的额是浏览器的第一个版本是用DJnative-swt和swt包开发的调用本地浏览器和webkit浏览器的示例 这是我的工程目录【源码见最后】: i
- find查找文件忽略某些目录
- 算法学习笔记--2. Selection sort & Fibonacci sequence
- before和:after
- Java NIO(一)
- 我想成为一只特立独行的猪
- python 列表常用的操作符
- HDU 1874 畅通工程续 (最短路模板