呼,本蒟蒻想了很久,人生第一次(写博客)应该给谁。。然后就随性地把上午刚学的东西写进来啦>w<
众所周知,并查集是一种很方便(比如实现Kruskal)的树形结构,经典版本1大多包含一个存“爸爸”的数组和“并”、“查”两个操作。这个算法的优化思路就是降低树深,基于此思路的递归版路径压缩(一行流)早已深得人心。今天要说的按秩合并则是另一个优化,通过规划合并时“谁当爹”来减少不必要的树深增加;非递归路径压缩则是避免爆栈(虽然我还没遇到过。。而且感觉大多数时候不会)的写法,技多不压身嘛。。。
据说两种优化单独使用都能将复杂度降到O(nlogn),同时使用则能进一步降到O(αn)2
按秩合并
简单地说,就是添加一个rank数组记录每棵树的“秩”,每次合并总是将秩小的树并入秩大的,若两树等秩则随意合并然后把爸爸的秩自增1
改动前的merge函数(c++源码,省略无关部分,下同)3:
void merge(const int & x, const int & y)
{
const int & a=getfa(x);
const int & b=getfa(y);
if(a!=b)
{
fa[a]=b;
}
}
按秩合并版:
int rank[MAXN]; //rank数组里的值要初始化,我习惯全部清0
//memset(rank,0,sizeof(rank) );
//...
void merge(const int & x, const int & y)
{
if(rank[x]<rank[y])
{
fa[x]=y;
return;
}
if(rank[x]>rank[y])
{
fa[y]=x;
return;
}
fa[x]=y;
rank[y]++; //等秩时让新爸爸的秩加1
}
顺带一提,std名字空间里似乎也有一个rank数组,我还不清楚那时干什么的。。。总之似乎有的编译器(比如Vijos的)会因为二义性报错(这不是最可怕的,最可怕的是NOI Linux里的GUIDE不报错),所以最好不要随便给数组起名叫rank,或者不要随便using namespace std而改为更优雅的using std::something
非递归路径压缩
不多说了,其实本质相同,可能略有时耗差异但应该不大。
递归一行流:
int getfa(const int & x)
{
return fa[x]==x?x:getfa(fa[x]);
}
非递归:
int getfa(const int & x)
{
int r,k,t;
r=x;
while(fa[r]!=r)
{
r=fa[r];
}
k=r;
r=x;
while(fa[r]!=k)
{
t=fa[r];
fa[r]=k;
r=t;
}
return k;
}
最后
马上复赛了,理所当然的是要停课一周w,希望不要考的太惨吧。。
加油!
最后,Markdown真好玩w
本文介绍了并查集的两种常见优化方法:按秩合并与非递归路径压缩。按秩合并通过合理选择父节点减少树深,而非递归路径压缩避免了递归导致的栈溢出风险。
416

被折叠的 条评论
为什么被折叠?



