OI骗分记之一:并查集的按秩合并与非递归路径压缩

本文介绍了并查集的两种常见优化方法:按秩合并与非递归路径压缩。按秩合并通过合理选择父节点减少树深,而非递归路径压缩避免了递归导致的栈溢出风险。

呼,本蒟蒻想了很久,人生第一次(写博客)应该给谁。。然后就随性地把上午刚学的东西写进来啦>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


  1. 相较于某些“不太经典的”,比如NOIP2010提高组关押罪犯 这题的AC解法之一
  2. 据说 α是个接近4的常数,具体怎么算出来的我不太清楚,不过实测确实快了很多
  3. 一般的,我放在网上的代码都是瞎敲的,就是说有可能过不了编译。。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值