赞
踩
在上一讲中提到,由于二叉排序树的复杂度很容易退化,因此在实际中的用途没有那么广。但是如果二叉排序树能实现平衡,那么二叉排序树的时间复杂度为,非常优秀,可以得到非常广泛的应用。
这里所谓的“平衡”,在不同的地方有着不同的定义, 常见的有高度平衡树、重量平衡树等。但其主要目标,都是保证结点最大深度不超过,以实现高效的查找效率。
在接下来的几节中,我们将通过下面的两道例题,来依次介绍替罪羊树、Treap、Splay等一些常用的平衡方法。
您需要写一种数据结构(可参考题目标题),来维护一个数集,其中需要提供以下操作:
1.插入一个整数x。
2.删除一个整数x(若有多个相同的数,只删除一个)。
3.查询整数x的排名(排名定义为比当前数小的数的个数+1)。
4.查询排名为x的数。
5.求x的前驱(前驱定义为小于x且最大的数)。
6.求x的后继(后继定义为大于x且最小的数)。
保证所有操作合法。
在上一题的基础上,扩大数据范围并增加了强制在线。
替罪羊树的原理在上一讲中已经做了详细说明,这里不再赘述。下面介绍替罪羊树的实现:
- struct node
- {
- int val,cnt,tot;
- node *lc,*rc;
- node(int _val=0,int _cnt=0,int _tot=0)
- {
- val=_val,cnt=_cnt,tot=_tot;
- lc=rc=NULL;
- }
- }*root,*null,**goat;
其中,val代表该结点中的数据(权值),cnt代表此数据出现次数,tot代表以该结点为根的子树大小(每个结点记cnt次)。
lc和rc代表该结点的左右儿子。
root代表根结点,null代表“空”结点(即一个所有变量均为0的结点,注意与NULL的区别),goat代表替罪羊结点(即深度最小的不平衡的结点,用于重构)。
首先,我们需要判定一个结点是否应重构。为此,我们引入一个平衡因子(取值在(0.5,1),一般采用0.7或0.8),若某结点的子结点大小占它本身大小的比例超过
,则该结点是不平衡的,应当重构。
另外,为了避免一些不必要的重构(例如某结点的儿子是不平衡的,重构完又发现该结点还是不平衡的,于是再次重构,而事实上只需重构该结点即可),我们引入替罪羊结点goat。goat表示深度最小的不平衡的结点,这样我们只需对goat进行重构即可。
- void update(node* &now)
- {
- now->tot=now->cnt+now->lc->tot+now->rc->tot;
- if(std::max(now->lc->tot,now->rc->tot)>alpha*now->tot)
- goat=&now;
- }
值得注意的是,这里选取子结点的tot(每个结点记cnt次)作为子结点大小,从而判断该结点是否平衡。事实上,还可以选取csiz(每个结点记1次)作为子结点大小,而且这种选取方式显得更加直观。可以证明,这两种选取方式均可保证树高是log级别的,具体证明留给读者思考。
重构分为两步——unfold(按中序遍历展开并存入数组)和rebuild(二分重建成树)。
- void balance(node* &now)
- {
- vector<node*> v;
- unfold(v,now);
- now=rebuild(v,0,v.size()-1);
- }
- void unfold(vector<node*> &v,node *now)
- {
- if(now==null)
- return;
- unfold(v,now->lc);
- if(now->cnt)
- v.push_back(now);
- unfold(v,now->rc);
- if(!now->cnt)
- delete now;
- }
- node* rebuild(vector<node*> &v,int l,int r)
- {
- if(l>r)
- return null;
- int mid=l+r>>1;
- v[mid]->lc=rebuild(v,l,mid-1);
- v[mid]->rc=rebuild(v,mid+1,r);
- update(v[mid]);
- return v[mid];
- }

插入时,到达“空”结点则新建结点,找到对应结点则cnt++。return后,再update该结点即可(update已包含对goat的更新)。
- void __insert(node* &now,int x)
- {
- if(now==null)
- {
- now=new node(x,1,1);
- now->lc=now->rc=null;
- return;
- }
- if(x==now->val)
- now->cnt++;
- else if(x<now->val)
- __insert(now->lc,x);
- else
- __insert(now->rc,x);
- update(now);
- }

删除时,我们可以采用惰性删除,找到对应结点时直接cnt--,实际上并没有真的删除该结点(指cnt=0时)。那么何时删除该结点呢?当然是在unfold的时候了,若遇到一个cnt=0的结点,则不用将其存入数组,并delete它。
- void __erase(node* &now,int x)
- {
- if(x==now->val)
- now->cnt--;
- else if(x<now->val)
- __erase(now->lc,x);
- else
- __erase(now->rc,x);
- update(now);
- }
这时,你也许会问——若已删除结点过多,会不会影响效率?这个疑问是很自然的,毕竟这些结点占着地方,却是些空点,在插入/删除等操作中又难免要经过它们,不仅浪费时间,还浪费空间。
但是,我们不要忘了一件非常重要的事——替罪羊树是一棵平衡树。我们先前讲了那么多关于重构的知识,就是为了使替罪羊树成为一棵平衡树。
既然替罪羊树是一棵平衡树,那我们还有什么可担心的呢?树高可是log级别的啊!即使我们不去实时删除它们,也无非是多一点常数因子罢了。
如果你对这一点常数因子还是非常介意,那么这里提供两种解决思路:
(1)采用实时删除。既然问题是惰性删除带来的,那么我们不用惰性删除就好了。具体操作可以参考2.7.4。
(2)在结点中引入子树当前大小csiz(current size,每个结点记1次)和子树真实大小tsiz(true size,每个结点记1次)。若删除时发现cnt=0,则tsiz--。这样在update的时候,若发现tsiz占csiz的比例低于,则该结点亦应重构(即更新goat)。
其他基本操作与普通二叉排序树无异,下面给出完整代码实现。
- #include<bits/stdc++.h>
- using std::vector;
- struct tree
- {
- private:
- static constexpr double alpha=0.75;
- struct node
- {
- int val,cnt,tot;
- node *lc,*rc;
- node(int _val=0,int _cnt=0,int _tot=0)
- {
- val=_val,cnt=_cnt,tot=_tot;
- lc=rc=NULL;
- }
- }*root,*null,**goat;
- void update(node* &now)
- {
- now->tot=now->cnt+now->lc->tot+now->rc->tot;
- if(std::max(now->lc->tot,now->rc->tot)>alpha*now->tot)
- goat=&now;
- }
- void __insert(node* &now,int x)
- {
- if(now==null)
- {
- now=new node(x,1,1);
- now->lc=now->rc=null;
- return;
- }
- if(x==now->val)
- now->cnt++;
- else if(x<now->val)
- __insert(now->lc,x);
- else
- __insert(now->rc,x);
- update(now);
- }
- void __erase(node* &now,int x)
- {
- if(x==now->val)
- now->cnt--;
- else if(x<now->val)
- __erase(now->lc,x);
- else
- __erase(now->rc,x);
- update(now);
- }
- int __rank(node *now,int x)
- {
- if(now==null)
- return 1;
- if(now->cnt&&x==now->val)
- return now->lc->tot+1;
- if(x<now->val)
- return __rank(now->lc,x);
- return now->lc->tot+now->cnt+__rank(now->rc,x);
- }
- int __kth(node *now,int k)
- {
- if(k<=now->lc->tot)
- return __kth(now->lc,k);
- if(k>now->lc->tot+now->cnt)
- return __kth(now->rc,k-now->lc->tot-now->cnt);
- return now->val;
- }
- void balance(node* &now)
- {
- vector<node*> v;
- unfold(v,now);
- now=rebuild(v,0,v.size()-1);
- }
- void unfold(vector<node*> &v,node *now)
- {
- if(now==null)
- return;
- unfold(v,now->lc);
- if(now->cnt)
- v.push_back(now);
- unfold(v,now->rc);
- if(!now->cnt)
- delete now;
- }
- node* rebuild(vector<node*> &v,int l,int r)
- {
- if(l>r)
- return null;
- int mid=l+r>>1;
- v[mid]->lc=rebuild(v,l,mid-1);
- v[mid]->rc=rebuild(v,mid+1,r);
- update(v[mid]);
- return v[mid];
- }
- public:
- tree()
- {
- root=null=new node;
- }
- void insert(int x)//插入
- {
- goat=&null;
- __insert(root,x);
- balance(*goat);
- }
- void erase(int x)//删除
- {
- goat=&null;
- __erase(root,x);
- balance(*goat);
- }
- int rank(int x)//查排名
- {
- return __rank(root,x);
- }
- int kth(int k)//查k大
- {
- return __kth(root,k);
- }
- int prev(int x)//求前驱
- {
- return kth(rank(x)-1);
- }
- int next(int x)//求后继
- {
- return kth(rank(x+1));
- }
- };

1.插入一个整数x:单点+1。
2.删除一个整数x:单点-1。
3.查询整数x的排名:查询x左边的数字的个数,即区间求和。
4.查询排名为x的数:在线段树上维护size,然后每次看往左还是往右,直到单点为止。
5.求x的前驱:综合3、4操作。
6.求x的后继:综合3、4操作。
复杂度分析:(n代表操作数,R代表值域大小)
实现方式 | 离散化 | 动态开点 |
时间复杂度 | O(n*log n) | O(n*log R) |
空间复杂度 | O(n) | O(n*log R) |
是否支持在线 | 否 | 是 |
值得注意的是,由于离散化实现的常数因子较大,故在实际表现中其时间效率不如动态开点,但其空间效率总是优于动态开点。
- #include<bits/stdc++.h>
- #define INF 10000000
- const int maxn=1e5+5;
- struct tree
- {
- private:
- int tot;
- struct node
- {
- int l,r;
- int lc,rc;
- int cnt;
- node(int _l=0,int _r=0)
- {
- l=_l,r=_r;
- lc=rc=0;
- cnt=0;
- }
- }a[maxn<<2];
- void modify(int now,int pos,int k)
- {
- if(a[now].l==a[now].r)
- {
- a[now].cnt=std::max(a[now].cnt+k,0);
- return;
- }
- int mid=a[now].l+a[now].r>>1;
- if(pos<=mid)
- {
- if(!a[now].lc)
- a[a[now].lc=++tot]=node(a[now].l,mid);
- modify(a[now].lc,pos,k);
- }
- else
- {
- if(!a[now].rc)
- a[a[now].rc=++tot]=node(mid+1,a[now].r);
- modify(a[now].rc,pos,k);
- }
- a[now].cnt=a[a[now].lc].cnt+a[a[now].rc].cnt;
- }
- int query_cnt(int now,int l,int r)
- {
- if(!now||a[now].r<l||r<a[now].l)
- return 0;
- if(l<=a[now].l&&a[now].r<=r)
- return a[now].cnt;
- return query_cnt(a[now].lc,l,r)+query_cnt(a[now].rc,l,r);
- }
- int query_val(int now,int x)
- {
- if(a[now].l==a[now].r)
- return a[now].l;
- if(x<=a[a[now].lc].cnt)
- return query_val(a[now].lc,x);
- return query_val(a[now].rc,x-a[a[now].lc].cnt);
- }
- public:
- tree()
- {
- a[++tot]=node(-INF,INF);
- }
- void insert(int x)
- {
- modify(1,x,1);
- }
- void erase(int x)
- {
- modify(1,x,-1);
- }
- int x_rank(int x)
- {
- return query_cnt(1,-INF,x-1)+1;
- }
- int rank_x(int x)
- {
- return query_val(1,x);
- }
- int prev(int x)
- {
- return rank_x(x_rank(x)-1);
- }
- int next(int x)
- {
- int cnt=query_cnt(1,x,x);
- return rank_x(x_rank(x)+cnt);
- }
- };

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。