并查集、Kruskal算法与Prim算法
来源:互联网 发布:ubuntu系统联网 编辑:程序博客网 时间:2024/05/18 09:22
并查集:
【用途】并查集是用来整理图的连通分量的,并查集过程完成后,可以确定两点是否连通,也可得知有多少个连通分量。推广到集合论里就是已知若干对元素,a在某集合里能推知b在集合里,用并查集确定各个元素归属于哪个集合,也可以顺便确定集合的数量。
【思想】并查集的思想其实很简单,每次合并都是尝试把两棵树合并。
如果树根相同说明其实这是一棵树,不操作;如果树根不同说明这确实是两棵树,把其中一棵的根挂在另一棵的根上视作合并。
【优化】
路径压缩:并查集有一些优化方法,普遍使用的是路径压缩,就是每次在查询根结点是谁时,把一路上遇到的结点都直接挂在根上,下次查就能更快找到根。
秩优化:就是在合并两棵树时把结点更少的树挂在结点多的树下。设a树有n个子结点,b树有m个,n<m。
a挂b上,n个点找根时路径长度增加了1;
b挂a上,m个点找根时路径长度增加了1.
这样秩优化就省下m-n个找根步骤。
(实际上,路径压缩已经是一个足够强的优化以至于可以将秩优化的效果忽略)
int father[N]; //开始一系列并查集操作前把father数组初始化为全0int get_father( int x ){ return (!father[x])?x:( father[x] = get_father(father[x]) );}void Union( int x , int y ) //并查集处理有向图,Union两个点时注意让x为前驱点,无向图无所谓{ int fx = get_father(x); int fy = get_father(y); if( fx != fy ) father[fy] = fx;}既然说了秩优化我就忍不住手痒要写一下了:
int father[N] , rank[N]; //开始一系列并查集操作前把father和rank数组初始化为全0int get_father( int x ){ return (!father[x])?x:( father[x] = get_father(father[x]) );}void Union( int x , int y ) //有向图就别用秩优化了{ int fx = get_father(x); int fy = get_father(y); if( fx != fy ) { if( rank[fy] < rank[fx] ) { father[fy] = fx; rank[fx] += rank[fy]; } else { father[fx] = fy; rank[fy] += rank[fx]; } }}
Kruskal:
【用途】用来求稀疏图的最小生成树,比起prim来,只用排一次序,效率也不错,那么会了kruskal何必还要学prim呢?prim适合在描述图用邻接矩阵而不逐边描述时。但是既然是用邻接矩阵描述图,那就是个稠密图,图比较大时,逐边抽出来再排序,相比起来prim就占便宜一些了。
【思想】把所有的边都排序,从小到大挨个往图里放,放了会成环就跳过这条边,假设有n个点,那么放过n-1条边最小生成树就建好了。
【耗时点】所有边按权排序。
#include<cstdio>#include<cstring>#include<vector>#include<algorithm>using namespace std;const int N = 100;struct edge{ edge( int A , int B , int V ):a(A),b(B),v(V){} int a , b , v; bool operator < ( const edge &e )const { return v < e.v; }};vector<edge> list;int father[N] , ans , eN;int get_father( int x ){ return (!father[x]) ? x : ( father[x] = get_father( father[x] ) );}void Union( int x , int y , int v ){ int fx = get_father(x); int fy = get_father(y); if( fx == fy ) return; father[fx] = fy; ans += v; eN++;}void kruskal( int n ){ eN = 0; memset( father , 0 , sizeof(father) ); for( vector<edge>::iterator it = list.begin() ; it != list.end() ; it++ ) { if( eN == n-1 ) return; Union( (*it).a , (*it).b , (*it).v ); } }int main(){ int n , t , a , b , v; while( scanf("%d",&n) , n ) { t = n*(n-1)/2; ans = 0; while(t--) { scanf("%d%d%d",&a,&b,&v); list.push_back( edge( a , b , v ) ); } sort( list.begin() , list.end() ); if( n > 1 ) kruskal( n ); printf("%d\n",ans); list.clear(); } return 0;}
Prim:
【用途】前面讲kruskal时说了,prim是用来求稠密图的最小生成树的。
【思想】建立一个点集,找任一点放入点集,遍历跟点集中点临接且不在点集中的点,找连边权值最小的那个点加入点集,然后再扩展。所有点都加入点集时最小生成树就完成了。
【耗时点】每一次扩展时的遍历。
下面是我按照算法思想写的朴素算法,O(N^3),适合用来理解,不适合实际使用:
下面是我按照算法思想写的朴素算法,O(N^3),适合用来理解,不适合实际使用:
#include<cstdio>#include<vector>using namespace std;const int N = 100;int g[N][N] , n;int prim(){ bool ok[N] = {false}; int sum = 0 , tmp , min_v; vector<int> s; s.push_back(0); ok[0] = true; while( s.size() < n ) { tmp = -1; for( int i = 0 ; i < s.size() ; i++ ) { for( int j = 0 ; j < n ; j++ ) { if( ok[j] || s[i] == j ) continue; if( tmp == -1 || tmp > 0 && g[s[i]][j] < tmp ) { tmp = g[s[i]][j]; min_v = j; } } } sum += tmp; s.push_back(min_v); ok[min_v] = true; } return sum;}int main(){ while( ~scanf("%d",&n) ) { for( int i = 0 ; i < n ; i++ ) for( int j = 0 ; j < n ; j++ ) scanf("%d",&g[i][j]); printf("%d\n",prim()); } return 0;}
//这是我补充了邻接矩阵初始化的poj1789代码,O(N^2),可当模板#include<cstdio>const int INF = 8; //边权值不超过7const int N = 2000; //最多可达2000个点char t[N][8];int g[N][N] , n; //建立邻接矩阵int closestV[N]; //closestV表示点状态和距离最近的点int lowCost[N]; //存储连边中的最小权值,跟closestV //一起描述每个点与邻接点的关系//Prim算法int Prim(){ //初始化 int selectVNum = 1; int ans = 0; int newV = 0; //将点0作为新选中的点 for( int i = 0 ; i < n ; i++ ) closestV[i] = 0; //最近点都先指向起点等待更新 closestV[0] = -1; //closestV[i]=-1表示选中点i for( int i = 0 ; i < n ; i++ ) lowCost[i] = INF; //连边最小权值置为无限等待更新 //扩展选中点集 while(selectVNum < n) { int minCost = INF , VtoAdd; for( int i = 0 ; i < n ; i++ ) if(closestV[i] != -1)//不在选中点集中 { int cost = g[newV][i]; if(cost < lowCost[i]) { //更新邻接信息,O(N^3)到O(N^2)的关键 lowCost[i] = cost; closestV[i] = newV; } if(lowCost[i] < minCost) minCost = lowCost[VtoAdd=i]; } ans += minCost; closestV[newV=VtoAdd] = -1; selectVNum++; } return ans;}void initG(){ for( int i = 0 ; i < n ; i++ ) for( int j = 0 ; j < n ; j++ ) g[i][j] = INF;}int main(){ while( scanf("%d", &n) , n ) { for( int i = 0 ; i < n ; i++ ) scanf("%s", t[i]); //initG(); //如果邻接矩阵没被权值填满就需要初始化 for( int i = 0 ; i < n ; i++ ) for( int j = i+1 ; j < n ; j++ ) { int d = 0; //获得边权值 for( int k = 0 ; k < 7 ; k++ ) if( t[i][k] != t[j][k] ) d++; //向邻接矩阵赋边权值,看得出来是稠密图,相当稠密 g[i][j] = d; g[j][i] = d; } printf("The highest possible quality is 1/%d.\n", Prim()); } return 0;}
0 0
- 并查集、Kruskal算法与Prim算法
- Kruskal算法 (并查集)
- Prim算法(并查集)
- hdu 1102 最小生成树 prim算法+Kruskal算法(并查集)
- hdu 1102 (最小生成树kruskal算法--并查集,prim死活过不了)
- 并查集(union-find set)与Kruskal算法
- 运用并查集与最小堆实现Kruskal算法
- hdu1879(并查集+Kruskal算法)
- Kruskal算法+并查集实现
- HDU1863并查集和kruskal算法
- 图论 Kruskal算法 并查集
- poj1251 Kruskal算法+并查集
- 并查集实现Kruskal算法
- Kruskal算法的并查集实现
- Kruskal算法(并查集)
- [容易] kruskal 算法并查集实现
- HDU 1233.还是畅通工程【最小生成树 kruskal算法(并查集)+prim算法】【1月8】
- Prim算法与Kruskal算法探索
- Latex Note
- Tomcat Port 8009 与AJP13协议
- 机器学习自学指南
- 计数排序算法详解
- 【pair_简单贪心】#3 A. Shortest path of the king
- 并查集、Kruskal算法与Prim算法
- java创建对象得四种方法
- 一种肝门静脉血管树建模方法及其系统
- Unity Shader
- C++primer plus第六版课后编程题答案7.1
- Linux系统新手学习的11点建议
- Cocos2d-x内存管理研究<一>(转载特兹卡特的百度空间)
- c++运算符重载
- C语言结构体及其成员地址的互算