您的位置:首页 > 其它

并查集、Kruskal算法与Prim算法

2014-03-31 21:25 204 查看
并查集:
【用途】并查集是用来整理图的连通分量的,并查集过程完成后,可以确定两点是否连通,也可得知有多少个连通分量。推广到集合论里就是已知若干对元素,a在某集合里能推知b在集合里,用并查集确定各个元素归属于哪个集合,也可以顺便确定集合的数量。

【思想】并查集的思想其实很简单,每次合并都是尝试把两棵树合并。
如果树根相同说明其实这是一棵树,不操作;如果树根不同说明这确实是两棵树,把其中一棵的根挂在另一棵的根上视作合并。
【优化】
路径压缩:并查集有一些优化方法,普遍使用的是路径压缩,就是每次在查询根结点是谁时,把一路上遇到的结点都直接挂在根上,下次查就能更快找到根。
秩优化:就是在合并两棵树时把结点更少的树挂在结点多的树下。设a树有n个子结点,b树有m个,n<m。
a挂b上,n个点找根时路径长度增加了1;
b挂a上,m个点找根时路径长度增加了1.
这样秩优化就省下m-n个找根步骤。
(实际上,路径压缩已经是一个足够强的优化以至于可以将秩优化的效果忽略)
int father
;               //开始一系列并查集操作前把father数组初始化为全0
int 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
, rank
;       //开始一系列并查集操作前把father和rank数组初始化为全0
int 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
, 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),适合用来理解,不适合实际使用:
#include<cstdio>
#include<vector>
using namespace std;
const int N = 100;
int g

, n;
int prim()
{
bool ok
= {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;               //边权值不超过7
const int N = 2000;              //最多可达2000个点
char t
[8];
int g

, n;                 //建立邻接矩阵
int closestV
;                 //closestV表示点状态和距离最近的点
int lowCost
;                  //存储连边中的最小权值,跟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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息