Size Balanced Tree(SBT)是一种平衡二叉查找树。它的论文由中国广东中山纪念中学的陈启峰于2006年底完成,
并在Winter Camp 2007中发表。由于SBT的拼写很容易找到中文谐音, 它常被中国的OIer们戏称为 “傻X树”、
“Super BT”等。但它的性能并不SB,编写起来也并不BT。恰恰相反,SBT易于实现,且据陈启峰论文中所言 ,
“这是目前为止速度最快的高级二叉搜索树”。它能在O(logn)的时间内完成所有BST的相关操作。而且由于SBT赖
以保持平衡的是Size域而不是其他“无用”的域,它可以很方便地实现动态顺序统计中的select和rank。
Size Balanced Tree(SBT)是一种通过大小(Size)域来保持平衡的二叉搜索树,它也因此得名。它总是满足:
对于SBT的每一个结点 t:
即每棵子树的大小不小于其兄弟的子树大小。
如图(圈代表结点,三角代表SBT,下同):
SBT的旋转(Rotations)与其他许多高级BST相同。它是下面提到的Maintain操作的基础。
Left-Rotate (t) 1 k ← right[t] 2 right[t] ← left[k] 3 left[k] ← t 4 s[k] ← s[t] 5 s[t] ← s[left[t]] + s[right[t]] + 1 6 t ← k
Right-Rotate(t) 1 k ← left[t] 2 left[t] ← right[k] 3 right[k] ← t 4 s[k] ← s[t] 5 s[t] ← s[left[t]] + s[right[t]] + 1 6 t ← k
当我们插入或删除一个结点后,SBT的大小就发生了改变。这种改变有可能导致性质(a)或(b)被破坏。这时,我们需要用Maintain操作来修复这棵树。Maintain操作是SBT中最具活力的一个独特过程;Maintain(T)用于修复以T为根的 SBT。调用Maintain(T)的前提条件是T的子树都已经是SBT了。
我们需要讨论的有4种情况。由于性质a和性质b是对称的,所以我们仅仅详细的讨论性质a。
通过前面的分析,很容易写出一个普通的Maintain。
Maintain (t) 01 If s[left[left[t]]>s[right[t]] then //case1 02 Right-Rotate(t) 03 Maintain(right[t]) 04 Maintain(t) 05 Exit 06 If s[right[left[t]]>s[right[t]] then //case2 07 Left-Rotate(left[t]) 08 Right-Rotate(t) 09 Maintain(left[t]) 10 Maintain(right[t]) 11 Maintain(t) 12 Exit 13 If s[right[right[t]]>s[left[t]] then //case1' 14 Left-Rotate(t) 15 Maintain(left[t]) 16 Maintain(t) 17 Exit 18 If s[left[right[t]]>s[left[t]] then //case2' 19 Right-Rotate(right[t]) 20 Left-Rotate(t) 21 Maintain(left[t]) 22 Maintain(right[t]) 23 Maintain(t)
前面的标准过程的伪代码有一点复杂和缓慢。通常我们可以保证性质a和性质b的满足,因此我们只需要检查情况1和情况2或者情况3和情况4,这样可以提高速度。所以在那种情况下,我们需要增加一个布尔(boolean)型变量:flag,来避免毫无意义的判断。如果flag是false,那么检查情况1和情况2;否则检查情况3和情况4。
Maintain (t,flag) 01 If flag=false then 02 If s[left[left[t]]>s[right[t]] then //case1 03 Right-Rotate(t) 04 Else 05 If s[right[left[t]]>s[right[t]] then //case2 06 Left-Rotate(left[t]) 07 Right-Rotate(t) 08 Else //needn’t repair 09 Exit 10 Else 11 If s[right[right[t]]>s[left[t]] then //case1' 12 Left-Rotate(t) 13 Else 14 If s[left[right[t]]>s[left[t]] then //case2' 15 Right-Rotate(right[t]) 16 Left-Rotate(t) 17 Else //needn’t repair 18 Exit 19 Maintain(left[t],false) //repair the left subtree 20 Maintain(right[t],true) //repair the right subtree 21 Maintain(t,false) //repair the whole tree 22 Maintain(t,true) //repair the whole tree
为什么Maintain(left[t],true)和Maintain(right[t],false)被省略了呢?您可以在陈启峰论文第六部分的分析中找到答案。
其他可以从论文中获得的信息:每次SBT后树的总深度递减的证明;Maintain的平摊运行时间是O(1)的证明(也就是说你不必担心Maintain这个递归过程是否会永不停止)等。
SBT的查找操作与普通BST完全相同。下面的过程将返回指向目标节点的指针。
Search(x,k) 1 if x=NULL or k=key[x] //找到了目标节点或目标节点不存在则返回x 2 then return x 3 if k<key[x] 4 then return Search(left[x],k) 5 else return Search(right[x],k)
由于SBT本身已经维护了size,因此这两项可用Select操作完成。
SBT的后继操作与普通BST完全相同。
SBT的前趋操作与普通BST完全相同。它与上面的后继操作对称。
SBT的插入操作很简单。它仅仅比普通BST的多出了一个Maintain操作和对s的简单维护。下面这个过程将一个节点v插入SBT中。
Insert (t,v) 1 If t=0 then 2 t ← v 3 Else 4 s[t] ← s[t]+1 5 If v<key[t] then 6 Insert(left[t],v) 7 Else 8 Insert(right[t],v) 9 Maintain(t,v≥key[t])
与普通维护size域的BST删除相同。
关于无需Maintain的说明by sqybi:
在删除之前,可以保证整棵树是一棵SBT。当删除之后,虽然不能保证这棵树还是SBT,但是这时整棵树的最大深度并没有改变,所以时间复杂度也不会增加。这时,Maintain就显得是多余的了。
由于SBT本来就是靠着size域来维持平衡的,当我们进行动态顺序统计操作时,我们就无需去“额外”维护一个size域来进行数据结构的扩张。这样,以下操作就与其他高级BST扩张后的动态顺序统计操作完全一样了。
下面这个过程将返回一个指向以x为根的子树中包含第i小关键字的节点的指针。
Select(x,i) 1 r ← size[left[x]] + 1 2 if(i=r) 3 then return x 4 else if i<r 5 then return Select(left[x],i) 6 else return Select(right[x],i-r)
SBT的rank操作与普通BST完全相同。
SBT的高度是O(logn),Maintain是O(1),所有主要操作都是O(logn)。
C
C++
Pascal
【问题描述】
OIER公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的工资。这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好,就可能把每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把他们的工资扣除一个相同的量。我真不知道除了调工资他还做什么其它事情。
工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。每位员工的工资下界都是统一规定的。每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘了一位新员工,我就得为他新建一个工资档案。
老板经常到我这边来询问工资情况,他并不问具体某位员工的工资情况,而是问现在工资第k多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后告诉他答案。
好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样,不是很困难吧?
【输入文件】
cashier.in第一行有两个非负整数n和min。n表示下面有多少条命令,min表示工资下界。
接下来的n行,每行表示一条命令。命令可以是以下四种之一:
名称 格式 作用 I命令 I_k 新建一个工资档案,初始工资为k。如果某员工的初始工资低于工资下界,他将立刻离开公司。 A命令 A_k 把每位员工的工资加上k S命令 S_k 把每位员工的工资扣除k F命令 F_k 查询第k多的工资
_(下划线)表示一个空格,I命令、A命令、S命令中的k是一个非负整数,F命令中的k是一个正整数。
在初始时,可以认为公司里一个员工也没有。
【输出文件】
输出文件cashier.out的行数为F命令的条数加一。
对于每条F命令,你的程序要输出一行,仅包含一个整数,为当前工资第k多的员工所拿的工资数,如果k大于目前员工的数目,则输出-1。
输出文件的最后一行包含一个整数,为离开公司的员工的总数。
【样例输入】
9 10 I 60 I 70 S 50 F 2 I 30 S 15 A 5 F 1 F 2
【样例输出】
10 20 -1 2
【约定】
I命令的条数不超过100000
A命令和S命令的总条数不超过100
F命令的条数不超过100000
每次工资调整的调整量不超过1000
新员工的工资不超过100000
数组实现的 Size Balanced Tree
#include <fstream> using namespace std; ifstream fin("cashier.in"); ofstream fout("cashier.out"); const unsigned int MAX_N=100001; int ZUISHAO; //最低工资 int ADD_PAY=0; unsigned int C_NUM=0; int front = 0; struct node { int key; int size, llink, rlink; }OIER[MAX_N]; void LeftRotate(int &x)//左旋 { int y = OIER[x].rlink; if (y == 0) return; OIER[x].rlink = OIER[y].llink; OIER[y].llink = x; OIER[y].size = OIER[x].size; OIER[x].size = OIER[OIER[x].llink].size+OIER[OIER[x].rlink].size+1; x = y; } void RightRotate(int &x)//右旋 { int y = OIER[x].llink; if (y == 0) return; OIER[x].llink = OIER[y].rlink; OIER[y].rlink = x; OIER[y].size = OIER[x].size; OIER[x].size = OIER[OIER[x].llink].size+OIER[OIER[x].rlink].size+1; x = y; } void Maintain(int &root, bool flag)//维护 SBT 树 { if (!root) return; if (flag) { if(OIER[root].llink && OIER[OIER[root].llink].llink && (!OIER[root].rlink || OIER[OIER[OIER[root].llink].llink].size > OIER[OIER[root].rlink].size)) RightRotate(root); else if(OIER[root].llink && OIER[OIER[root].llink].rlink && (!OIER[root].rlink || OIER[OIER[OIER[root].llink].rlink].size > OIER[OIER[root].rlink].size)) { LeftRotate(OIER[root].llink); RightRotate(root); } else return; } else { if (OIER[root].rlink && OIER[OIER[root].rlink].rlink && (!OIER[root].llink || OIER[OIER[OIER[root].rlink].rlink].size > OIER[OIER[root].llink].size)) LeftRotate(root); else if (OIER[root].rlink && OIER[OIER[root].rlink].llink && (!OIER[root].llink || OIER[OIER[OIER[root].rlink].llink].size > OIER[OIER[root].llink].size)) { RightRotate(OIER[root].rlink); LeftRotate(root); } else return; } Maintain(OIER[root].llink, true); Maintain(OIER[root].rlink, false); Maintain(root, true); Maintain(root, false); } void Insert(int &root, int x)//插入关键字 x { if (!root) { root = ++front; OIER[root].key = x; OIER[root].size = 1; } else { ++OIER[root].size; Insert(x <= OIER[root].key ? OIER[root].llink : OIER[root].rlink, x); Maintain(root, x <= OIER[root].key); } } int Delete(int &root)//删除 { int t=0,sum=0; if(!root) return root; if(OIER[root].key+ADD_PAY<ZUISHAO) { sum+=OIER[OIER[root].llink].size+1; OIER[root].size-=sum; OIER[root].llink=0; t=Delete(OIER[root].rlink); sum+=t; OIER[root].size-=t; OIER[OIER[root].rlink].size=OIER[root].size; root=OIER[root].rlink; } else { t=Delete(OIER[root].llink); sum=t; OIER[root].size-=t; } return sum; } int Select(int R, int x)//返回第 x 大的元素 { if(OIER[R].rlink==0)OIER[OIER[R].rlink].size=0; int r = OIER[OIER[R].rlink].size+1; if (x<r) return Select(OIER[R].rlink, x); else if (x>r) return Select(OIER[R].llink, x-r); if(x==r) return OIER[R].key; } int main(void) { unsigned int N; char command; int pay,root=0; int i; fin>>N>>ZUISHAO; for(i=1;i<=N;i++) { fin>>command>>pay; if(command=='I'){ if(pay>=ZUISHAO)Insert(root,pay-ADD_PAY); } if(command=='F'){ if(pay>OIER[root].size) fout<<-1<<endl; else fout<<Select(root, pay)+ADD_PAY<<endl; } if(command=='A')ADD_PAY+=pay; if(command=='S'){ ADD_PAY-=pay; C_NUM+=Delete(root); } } fout<<C_NUM<<endl; return 0; }