1. 程式人生 > >【資料結構與演算法】Size Balanced Tree(SBT)平衡二叉樹

【資料結構與演算法】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