【資料結構與演算法】Size Balanced Tree(SBT)平衡二叉樹
Size Balanced Tree(SBT)平衡二叉樹
定義資料結構
struct SBT
{
int key,left,right,size;
} tree[N];
key:儲存值,left,right:左右子樹,size:保持平衡最終要的資料,表示子樹的大小
SBT樹的性質
定義一個節點x,同時滿足下面兩個條件
(a)、x.left.size >= max(x.right.right.size, x.right.left.size) (b)、x.right.size >= max(x.left.left.size, x.left.right.size)
即每棵子樹的大小不小於其兄弟子樹的大小
旋轉
左旋虛擬碼:
Left-Rotate (t)
1 k ← right[t]
2 right[t] ← left[k]
3 left[k] ← t
4 size[k] ← size[t]
5 size[t] ← size[left[t]] + size[right[t]] + 1
6 t ← k
void left_rot(int &x) { int y = tree[x].right; tree[x].right = tree[y].left; tree[y].left = x; tree[y].size = tree[x].size;//轉上去的節點數量為先前此處節點的size tree[x].size = tree[tree[x].left].size + tree[tree[x].right].size + 1; x = y; }
右旋虛擬碼:
Right-Rotate(t)
1 k ← left[t]
2 left[t] ← right[k]
3 right[k] ← t
4 size[k] ← size[t]
5 size[t] ← size[left[t]] + size[right[t]] + 1
6 t ← k
void right_rot(int &x) { int y = tree[x].left; tree[x].left = tree[y].right; tree[y].right = x; tree[y].size = tree[x].size; tree[x].size = tree[tree[x].left].size + tree[tree[x].right].size + 1; x = y; }
旋轉只需要理解如何維護子樹size即可,旋轉操作不能改變二叉查詢樹的基本性質
維護SBT性質(Maintain)
當我們插入或刪除一個結點後,SBT的大小就發生了改變。這種改變有可能導致性質(a)或(b)被破壞。這時,我們需要用Maintain操作來修復這棵樹。Maintain操作是SBT中最具活力的一個獨特過程;Maintain(T)用於修復以T為根的 SBT。呼叫Maintain(T)的前提條件是T的子樹都已經是SBT了。 我們需要討論的有4種情況。由於性質a和性質b是對稱的,所以我們僅僅詳細的討論性質a。第一種情況:x.left.left.size > x.right.size
即:insert(T.left,key)後A.size > R.size
1、首先執行Right-Ratote(t),這個操作使上圖變成下圖;
2、在這之後,有時候這棵樹還仍然不是一棵SBT,因為 s[C]>s[B] 或者 s[D]>s[B] 也是可能發生的。所以就有必要繼續呼叫Maintain(T)。 3、結點L的右子樹有可能被連續調整,因為有可能由於性質的破壞需要再一次執行Maintain(L)。
第二種情況:x.left.right.size > x.right.size
在執行完insert(T.left,key)後發生B.size > R.size,如圖5,這種調整要比情況1複雜一些。我們可以執行下面的操作來修復: 1、執行一次左旋操作Left-Ratote(L)後,就會變成下圖
2、接著執行一次右旋操作Right-Ratote(T),變成下圖:
3、在經過兩個巨大的旋轉之後,整棵樹就變得非常不可預料了。萬幸的是,子樹A;E; F;R 依然是容均樹,所以要依次修復L 和T,Maintain(L),Maintain(T)。 4、子樹都已經是容均樹了,但B 可能還有問題,所以還要呼叫Maintain(B)
第三種情況:x.right.right.size > x.left.size 與第一種情況相反第四種情況:x.right.left.size > x.left.size 與第二種情況相反 虛擬碼
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
void maintain(int &x,bool flag)
{
if(flag == false)//左邊
{
if(tree[tree[tree[x].left].left].size > tree[tree[x].right].size)//左孩子的左子樹大於右孩子
right_rot(x);
else if(tree[tree[tree[x].left].right].size > tree[tree[x].right].size)//右孩子的右子樹大於右孩子
{
left_rot(tree[x].left);
right_rot(x);
}
else return;
}
else //右邊
{
if(tree[tree[tree[x].right].right].size > tree[tree[x].left].size)//右孩子的右子樹大於左孩子
left_rot(x);
else if(tree[tree[tree[x].right].left].size > tree[tree[x].left].size)//右孩子的左子樹大於左孩子
{
right_rot(tree[x].right);
left_rot(x);
}
else return;
}
maintain(tree[x].left,false);
maintain(tree[x].right,true);
maintain(x,true);
maintain(x,false);
}
插入
在BST插入的基礎上新增一個Maintain操作,用來對size的維護
void insert(int &x,int key)
{
if(x == 0)
{
x = ++top;
tree[x].left = tree[x].right = 0;
tree[x].size = 1;
tree[x].key = key;
}
else
{
tree[x].size ++;
if(key < tree[x].key) insert(tree[x].left,key);
else insert(tree[x].right,key);//相同元素插入到右子樹中
maintain(x, key >= tree[x].key);//每次插入把平衡操作壓入棧中
}
}
刪除
與普通維護size域的BST刪除相同。 關於無需Maintain的說明by sqybi: 在刪除之前,可以保證整棵樹是一棵SBT。當刪除之後,雖然不能保證這棵樹還是SBT,但是這時整棵樹的最大深度並沒有改變,所以時間複雜度也不會增加。這時,Maintain就顯得是多餘的了。
下面給出兩種刪除方式,一種是找前驅替換,一種是找後繼替換 //後繼
int remove(int &x,int key)
{
tree[x].size --;
if(key > tree[x].key)
remove(tree[x].right,key);
else if(key < tree[x].key)
remove(tree[x].left,key);
else
{
//有左子樹,無右子樹
if(tree[x].left != 0 && tree[x].right == 0)
{
int temp = x;
x = tree[x].left;
return temp;
}
else if(tree[x].right !=0 && tree[x].left == 0)
{
int temp = x;
x = tree[x].right;
return temp;
}
//無左子樹和右子樹
else if(!tree[x].left && !tree[x].right)
{
int temp = x;
x = 0;
return temp;
}
//有右子樹
else //找到x右子樹中最小元素,也就是找後繼元素
{
int temp = tree[x].right;
while(tree[temp].left) temp = tree[temp].left;
tree[x].key = tree[temp].key;
//tree[x].cnt = tree[temp].cnt;
remove(tree[x].right,tree[temp].key);
}
}
}
//前驅
int remove(int &x,int key)
{
int d_key;
//if(!x) return 0;
tree[x].size --;
if((key == tree[x].key)||(key < tree[x].key && tree[x].left == 0) ||
(key>tree[x].key && tree[x].right == 0))
{
d_key = tree[x].key;
if(tree[x].left && tree[x].right)
{
tree[x].key = remove(tree[x].left,tree[x].key+1);
}
else
{
x = tree[x].left + tree[x].right;
}
}
else if(key > tree[x].key)
d_key = remove(tree[x].right,key);
else if(key < tree[x].key)
d_key = remove(tree[x].left,key);
return d_key;
}
獲取最小值
直接向左子樹找到最左葉子節點即可
int getmin()
{
int x;
for(x = root ; tree[x].left; x = tree[x].left);
return tree[x].key;
}
獲取最大值
int getmax()
{
int x;
for(x = root ; tree[x].right; x = tree[x].right);
return tree[x].key;
}
前驅
int pred(int &x,int y,int key)//前驅 小於
{
if(x == 0) return y;
if(tree[x].key < key)//加上等號,就是小於等於
return pred(tree[x].right,x,key);
else return pred(tree[x].left,y,key);
}//pred(root,0,key)
註解:
if(tree[x].key < key) 則說明前驅(小於key中所有元素最大的那個)在右子樹,並且設定當前節點x為其前驅
if(tree[x].key >= key)說明前驅在左子樹,當前節點x的key值也不是其前驅,所以設定其前驅仍為y
後繼
int succ(int &x,int y,int key)//後繼 大於
{
if(x == 0) return y;
if(tree[x].key > key)
return succ(tree[x].left,x,key);
else return succ(tree[x].right,y,key);
}
同前驅。
求第K小數
int select(int &x,int k)//求第k小數
{
int r = tree[tree[x].left].size + 1;
if(r == k) return tree[x].key;
else if(r < k) return select(tree[x].right,k - r);
else return select(tree[x].left,k);
}
註解:首先該原始碼如果插入的數有重複資料(即樹中會出現兩個或多個27,這樣的資料),此SBT是可以建樹的,同樣在查詢第K小數上述程式碼也不會產生錯誤,但是這裡需要消耗更多的儲存空間(這裡應該很容易明白),如果我們在資料結構中加上一個欄位cnt,專門用來記錄重複資料的個數,這樣的話樹中就沒有重複資料,因為它們已經被合併了,這裡需要修改insert函式和remove函式和旋轉操作,如果刪除操作每次刪除的是整個節點而不是cnt>2就僅僅將cnt--而是整個刪除,這樣就會對size造成很大的影響 ,這種情況的remove函式我暫時沒有想好如何去寫,首先可以確定思路,如果刪除節點是x,它的直接或間接父親節點的size都需要減去x.cnt,但是我們是用的替換刪除,這裡怎麼操作?還請哪位大牛指教,能夠寫出這樣的remove函式。 還是先解釋上面的程式碼,首先求出x的左子樹size,再加上自己本身,如果這時r == k 則說明x.key就是第K小數,如果r < k,就說明第K小數在以x為根的右子樹上,此時只需要在右子樹上求第k-r小數即可,如果r > k那麼說明第K小數在以x為根的左子樹上。 求元素的秩
就是求這個元素排第幾
int rank(int &x,int key)//求key排第幾
{
if(key < tree[x].key)
return rank(tree[x].left,key);
else if(key > tree[x].key)
return rank(tree[x].right,key) + tree[tree[x].left].size + 1;
return tree[tree[x].left].size + 1;
}
這裡就類似select的逆操作,程式碼就不解釋了。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <ctime>
using namespace std;
#define LL long long
const int N = 10005;
const int INF=0x7FFFFFFF;
struct SBT
{
int key,left,right,size;
} tree[N];
int root,top;
void left_rot(int &x)
{
int y = tree[x].right;
tree[x].right = tree[y].left;
tree[y].left = x;
tree[y].size = tree[x].size;//轉上去的節點數量為先前此處節點的size
tree[x].size = tree[tree[x].left].size + tree[tree[x].right].size + 1;
x = y;
}
void right_rot(int &x)
{
int y = tree[x].left;
tree[x].left = tree[y].right;
tree[y].right = x;
tree[y].size = tree[x].size;
tree[x].size = tree[tree[x].left].size + tree[tree[x].right].size + 1;
x = y;
}
void maintain(int &x,bool flag)
{
if(flag == false)//左邊
{
if(tree[tree[tree[x].left].left].size > tree[tree[x].right].size)//左孩子的左子樹大於右孩子
right_rot(x);
else if(tree[tree[tree[x].left].right].size > tree[tree[x].right].size)//右孩子的右子樹大於右孩子
{
left_rot(tree[x].left);
right_rot(x);
}
else return;
}
else //右邊
{
if(tree[tree[tree[x].right].right].size > tree[tree[x].left].size)//右孩子的右子樹大於左孩子
left_rot(x);
else if(tree[tree[tree[x].right].left].size > tree[tree[x].left].size)//右孩子的左子樹大於左孩子
{
right_rot(tree[x].right);
left_rot(x);
}
else return;
}
maintain(tree[x].left,false);
maintain(tree[x].right,true);
maintain(x,true);
maintain(x,false);
}
/*
*insert沒有合併相同的元素,如果出現相同的元素則把它放到右子樹上,這樣能保證求第k小數的時候對相同元素也能正確
*/
void insert(int &x,int key)
{
if(x == 0)
{
x = ++top;
tree[x].left = tree[x].right = 0;
tree[x].size = 1;
tree[x].key = key;
}
else
{
tree[x].size ++;
if(key < tree[x].key) insert(tree[x].left,key);
else insert(tree[x].right,key);//相同元素插入到右子樹中
maintain(x, key >= tree[x].key);//每次插入把平衡操作壓入棧中
}
}
int del(int &p,int w)
{
if (tree[p].key==w || (tree[p].left==0 && w<tree[p].key) || (tree[p].right==0 && w>tree[p].key))
{
int delnum=tree[p].key;
if (tree[p].left==0 || tree[p].right==0) p=tree[p].left+tree[p].right;
else tree[p].key=del(tree[p].left,INF);
return delnum;
}
if (w<tree[p].key) return del(tree[p].left,w);
else return del(tree[p].right,w);
}
int remove(int &x,int key)
{
int d_key;
//if(!x) return 0;
tree[x].size --;
if((key == tree[x].key)||(key < tree[x].key && tree[x].left == 0) ||
(key>tree[x].key && tree[x].right == 0))
{
d_key = tree[x].key;
if(tree[x].left && tree[x].right)
{
tree[x].key = remove(tree[x].left,tree[x].key+1);
}
else
{
x = tree[x].left + tree[x].right;
}
}
else if(key > tree[x].key)
d_key = remove(tree[x].right,key);
else if(key < tree[x].key)
d_key = remove(tree[x].left,key);
return d_key;
}
int getmin()
{
int x;
for(x = root ; tree[x].left; x = tree[x].left);
return tree[x].key;
}
int getmax()
{
int x;
for(x = root ; tree[x].right; x = tree[x].right);
return tree[x].key;
}
int select(int &x,int k)//求第k小數
{
int r = tree[tree[x].left].size + 1;
if(r == k) return tree[x].key;
else if(r < k) return select(tree[x].right,k - r);
else return select(tree[x].left,k);
}
int rank(int &x,int key)//求key排第幾
{
if(key < tree[x].key)
return rank(tree[x].left,key);
else if(key > tree[x].key)
return rank(tree[x].right,key) + tree[tree[x].left].size + 1;
return tree[tree[x].left].size + 1;
}
int pred(int &x,int y,int key)//前驅 小於
{
if(x == 0) return y;
if(tree[x].key < key)
return pred(tree[x].right,x,key);
else return pred(tree[x].left,y,key);
}
int succ(int &x,int y,int key)//後繼 大於
{
if(x == 0) return y;
if(tree[x].key > key)
return succ(tree[x].left,x,key);
else return succ(tree[x].right,y,key);
}
void inorder(int &x)
{
if(x==0) return;
else
{
inorder(tree[x].left);
cout<<x<<" "<<tree[x].key<<" "<<" "<<tree[x].size<<" "<<tree[tree[x].left].key<<" "<<tree[tree[x].right].key<<endl;
inorder(tree[x].right);
}
}
int main()
{
root = top = 0;
char ch;
int x,tmp;
while(scanf("%c %d",&ch,&x))
{
switch(ch)
{
case 'I':
insert(root,x);
break;
case 'D':
remove(root,x);
break;
case 'K':
tmp=select(root,x);
printf("%d\n",tmp);
break;
case 'C':
printf("%d\n",rank(root,x));
break;
case 'P':
tmp = pred(root,0,x);
printf("%d\n",tree[tmp].key);
break;
case 'S':
tmp = succ(root,0,x);
printf("%d\n",tree[tmp].key);
break;
case 'L':
inorder(root);
break;
}
}
return 0;
}
總結:SBT,Treap,紅黑樹,都很類似,其中SBT和Treap程式碼很好寫,SBT只需要正確理解旋轉操作,和如何維護size域,另外就是Maintain函式,這個是重點,其他的操作都和BST一樣了,SBT的高度是O(logn),Maintain是O(1),所有主要操作都是O(logn)。
最後還請大神解決下重複資料的remove操作函式如何寫(具體見求第K小數下的註解),歡迎指教。
原文:https://blog.csdn.net/acceptedxukai/article/details/6921334