平衡樹算法


一、平衡樹用來干什么

您需要寫一種數據結構(可參考題目標題),來維護一些數,其中需要提供以下操作:

  1. 插入 xxx 數
  2. 刪除 xxx 數(若有多個相同的數,因只刪除一個)
  3. 查詢 xxx 數的排名(排名定義為比當前數小的數的個數 +1+1+1 )
  4. 查詢排名為 xxx 的數
  5. xxx 的前驅(前驅定義為小於 xxx,且最大的數)
  6. xxx 的后繼(后繼定義為大於 xxx,且最小的數)

 

 

 

二、平衡樹與二叉排序樹區別

平衡樹是二叉搜索樹和堆合並構成的數據結構,它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。平衡樹的平均查找長度要小於等於二叉排序樹的平均查找長度

平衡樹是二叉排序樹通過旋轉來達到最優二叉排序樹

平衡樹本身就是二叉排序樹

 

不懂二叉排序樹的可以看一下:二叉排序樹的構造 && 二叉樹的先序、中序、后序遍歷

 

三、二叉平衡樹復雜度

前提:包含n個頂點的二叉平衡樹(AVL樹)

1、查找一個節點時間復雜度為O(lgn)

2、插入的時間復雜度O(lgn)

3、刪除一個節點時間復雜度為O(lgn)

 

 

代碼中會發現每一次操作之后都會進行旋轉操作,這樣導致平衡樹的時間復雜度常數很大

 

四、平衡樹的構造

參考鏈接:https://blog.csdn.net/a_comme_amour/article/details/79382104

 

1、變量聲明

 

f[i]表示i的父結點,ch[i][0]表示i的左兒子,ch[i][1]表示i的右兒子,key[i]表示i的關鍵字(即結點i代表的那個數字),cnt[i]表示i結點的關鍵字出現的次數(相當於權值),sizes[i]表示包括i的這個子樹的大小;sz為整棵樹的大小,rt為整棵樹的根。

注意:

sz代表的是不同節點種類數,比如你要插入5 5 4 3 4這些數,那么sz的大小是3

sizes代表的就是節點有幾個,就不是種類個數了

 

幾個小函數:

void clears(int x) //刪除x點信息
{
    f[x]=cnt[x]=ch[x][0]=ch[x][1]=sizes[x]=key[x]=0;
}
bool get(int x) //判斷x是父節點的左孩子還是右孩子
{
    return ch[f[x]][1]==x;  //返回1就是右孩子,返回0就是左孩子
}
void pushup(int x)  //重新計算一下x這棵子樹的節點數量
{
    if(x)
    {
        sizes[x]=cnt[x];
        if(ch[x][0]) sizes[x]+=sizes[ch[x][0]];
        if(ch[x][1]) sizes[x]+=sizes[ch[x][1]];
    }
}

 

 

2、旋轉操作

 

 怎么旋轉看下面

 

 讓4旋轉到他的父親位置2(我們代碼中的旋轉就是讓一個節點移動到他的父親節點位置)

首先我們要知道平衡樹就是二叉排序樹,那么二叉排序樹兒子節點的特點就是左節點的值小於父節點的值,右節點的值大於父節點的值

那么4節點移動到了2節點(4節點的父親節點)的位置,4節點會把它的右兒子(也就是9號節點)給2號節點充當左兒子,然后2號節點在作為4號節點的右兒子節點出現

 

 旋轉操作代碼:

 1 void rotates(int x)  //將x移動到他父親的位置,並且保證樹依舊平衡
 2 {
 3     int fx=f[x],ffx=f[fx],which=get(x);
 4     //x點父親,要接受x的兒子。而且x與x父親身份交換
 5     ch[fx][which]=ch[x][which^1];
 6     f[ch[fx][which]]=fx;
 7 
 8     ch[x][which^1]=fx;
 9     f[fx]=x;
10 
11     f[x]=ffx;
12     if(ffx) ch[ffx][ch[ffx][1]==fx]=x;
13 
14     pushup(fx);
15     pushup(x);
16 }

 

3、splay操作

 

這個就是把樹上的一個節點旋轉的樹根位置,分為兩種操作:

<一>、

一個點p和他的父親(fa)同為一邊的兒子(如圖同為左兒子) 此時應先rotate(p.fa), 再rotate(p)

 

 

 

<二>、

一個點p和他的父親不是同一邊的兒子 此時兩次rotate(p)即可

 1 void splay(int x)  //將x移動到數根節點的位置,並且保證樹依舊平衡
 2 {
 3     for(int fx; fx=f[x]; rotates(x))
 4     {
 5         if(f[fx])
 6         {
 7             rotates((get(x)==get(fx))?fx:x);
 8             //如果祖父三代連城一條線,就要從祖父哪里rotate
 9             //至於為什么要這樣做才能最快……可以去看看Dr.Tarjan的論文
10         }
11     }
12     rt=x;
13 }

注意:void splay(int x)  這個x是這個節點在樹上的位置

 

假設上面節點1、fa、3的cnt值都為1 

那么這個fa這個節點在樹上的位置就是2,3這個節點在樹上的位置就是3

 

4、void inserts(int x),插入一個節點(這個x是你要插入值的大小)

 

代碼實現

 1 void inserts(int x)
 2 {
 3     if(rt==0)
 4     {
 5         sz++;
 6         key[sz]=x;
 7         rt=sz;
 8         cnt[sz]=sizes[sz]=1;
 9         f[sz]=ch[sz][0]=ch[sz][1]=0;
10         return;
11     }
12     int now=rt,fx=0;
13     while(1)
14     {
15         if(x==key[now])
16         {
17             cnt[now]++;
18             pushup(now);
19             pushup(fx);
20             splay(now);  //splay的過程會rotates now點的所有祖先節點,這個時候它們所有子樹權值也更新了
21             return;
22         }
23         fx=now;
24         now=ch[now][key[now]<x];
25         if(now==0)
26         {
27             sz++;
28             sizes[sz]=cnt[sz]=1;
29             ch[sz][0]=ch[sz][1]=0;
30             ch[fx][x>key[fx]]=sz;  //二叉查找樹特性”左大右小“
31             f[sz]=fx;
32             key[sz]=x;
33             pushup(fx);
34             splay(sz);
35             return ;
36         }
37     }
38 }

注意:插入完之后要把這個剛插入到樹上的點給通過splay函數旋轉到樹根,至於為什么要這樣做。大家可以這樣想,假設插入之前的樹是一顆最優二叉排序樹,那么插入一個點之后可能就不最優了,所以我們要旋轉這棵樹,以保證這棵樹還是最優的

 

5、int rnk(int x)  //查詢x的排名

 

這個函數就是插入x這個值,他在平衡樹上的位置

代碼實現

 1 /*
 2 有人問:
 3 很想知道為什么rnk操作也要splay操作呢?如果del要用的話直接splay(x)是不是就可以了
 4 
 5 原博客答:
 6 呃不不不這個貌似不是隨便splay以下就可以的 首先find之后的splay就是將找到的這個點轉到根,
 7 當然你不加這個應該是也可以,只不過這道題加上的話對於這一堆操作來說比較方便,不過一般來說轉一轉splay的
 8 平衡性會好一點(當然也不要轉得太多了就tle了...) 但是del之前直接splay(x)要視情況而定,關鍵在於分清楚
 9 “點的編號”和“點的權值”這兩個概念。如果你已經知道了該轉的點的編號,當然可以直接splay(x),但是如果你只
10 知道應該splay的點的權值,你需要在樹里find到這個權值的點的編號,然后再splay 其實最后splay寫起來都是非
11 常靈活的,而且有可能一個點帶若干個權之類的。對於初學者的建議就是先把一些最簡單的情況搞清楚,比如說一
12 個編號一個權的這種,然后慢慢地多做題就能運用得非常熟練了。最好的方法就是多畫畫樹自己轉一轉,對之后
13 復雜題目的調試也非常有益
14 
15 我說:
16 我在洛谷上得模板題上交了一下rnk里面不帶splay(now)的,一共12個樣例,就對了兩個樣例。錯了一個樣例,其他全TLE
17 
18 我解釋:
19 為什么作者解釋可以刪去,但是刪過之后還錯了。因為它的代碼中函數之前是相互聯系的
20 就比如它調用rnk(x)函數之后就已經認為x為平衡樹樹根,然后直接對它進行下一步操作(這個假設在del函數里面)
21 
22 如果你光刪了rnk(x)里面的splay(),你肯定還要改其他地方代碼。。。。。。
23 */
24 int rnk(int x)  //查詢x的排名
25 {
26     int now=rt,ans=0;
27     while(1)
28     {
29         if(x<key[now]) now=ch[now][0];
30         else
31         {
32             ans+=sizes[ch[now][0]];
33             if(x==key[now])
34             {
35                 splay(now); //這個splay是為了后面函數的調用提供前提條件
36 //就比如pre函數的前提條件就是x(x是我們要求誰的前驅,那個誰就是x)已經在平衡樹樹根
37                 return ans+1;
38             }
39             ans+=cnt[now];  //cnt代表now這個位置值(key[now])出現了幾次
40             now=ch[now][1];
41         }
42     }
43 }

 

 

6、int kth(int x)

 

這個是查找樹上面第x大的數是多少

 1 int kth(int x)
 2 {
 3     int now=rt;
 4     while(1)
 5     {
 6         if(ch[now][0] && x<=sizes[ch[now][0]])
 7         {
 8             //滿足這個條件就說明它在左子樹上
 9             now=ch[now][0];
10         }
11         else
12         {
13             int temp=sizes[ch[now][0]]+cnt[now];
14             if(x<=temp) //這個temp是now左子樹權值和now節點權值之和
15                 return key[now]; //進到這個判斷里面說明他不在左子樹又不在右子樹,那就是now節點了
16             x-=temp;
17             now=ch[now][1];
18         }
19     }
20 }

 

7、求根節點的前驅節點和后繼結點在樹上的位置

int pre()//由於進行splay后,x已經到了根節點的位置
{
    //求x的前驅其實就是求x的左子樹的最右邊的一個結點
//為什么呢,因為這是平衡樹(帶有二叉排序樹特點),根據二叉排序樹中序遍歷結果我么可以知道,一個數的前序就在
//x的左子樹的最右邊的一個結點
    int now=ch[rt][0];
    while(ch[now][1]) now=ch[now][1];
    return now;
}
int next()
{
    //求后繼是求x的右子樹的最左邊一個結點
//為什么呢,因為這是平衡樹(帶有二叉排序樹特點),根據二叉排序樹中序遍歷結果我么可以知道,一個數的前序就在
//x的右子樹的最左邊一個結點
    int now=ch[rt][1];
    while(ch[now][0])  now=ch[now][0];
    return now;
}

 

8、刪除一個值

 1 /*
 2 刪除操作是最后一個稍微有點麻煩的操作。
 3 step 1:隨便find一下x。目的是:將x旋轉到根。
 4 step 2:那么現在x就是根了。如果cnt[root]>1,即不只有一個x的話,直接-1返回。
 5 step 3:如果root並沒有孩子,就說名樹上只有一個x而已,直接clear返回。
 6 step 4:如果root只有左兒子或者右兒子,那么直接clear root,然后把唯一的兒子當作根就可以了(f賦0,root賦為唯一的兒子)
 7 剩下的就是它有兩個兒子的情況。
 8 step 5:我們找到新根,也就是x的前驅(x左子樹最大的一個點),將它旋轉到根。然后將原來x的右子樹接到新根的
 9 右子樹上(注意這個操作需要改變父子關系)。這實際上就把x刪除了。不要忘了update新根。
10 */
11 void del(int x)
12 {
13     rnk(x);
14     if(cnt[rt]>1)//如果這個位置權值大於1,那就不用刪除這個點
15     {
16         cnt[rt]--;
17         pushup(rt);
18         return;
19     }
20     if(!ch[rt][0] && !ch[rt][1]) //這個就代表平衡樹只有一個節點
21     {
22         clears(rt);
23         rt=0;
24         return;
25     }
26     if(!ch[rt][0]) //只有左兒子,樹根只有左兒子那就把樹根直接刪了就行
27     { //然后左兒子這棵子樹變成新的平衡樹
28         int frt=rt;
29         rt=ch[rt][1];
30         f[rt]=0;
31         clears(frt);
32         return;
33     }
34     else if(!ch[rt][1]) //只有右兒子,和上面差不多
35     {
36         int frt=rt;
37         rt=ch[rt][0];
38         f[rt]=0;
39         clears(frt);
40         return;
41     }
42     int frt=rt;
43     int leftbig=pre();
44     splay(leftbig); //讓前驅做新根
45     ch[rt][1]=ch[frt][1];  //這個frt指向的還是之前的根節點
46     /*
47     看着一點的時候就會發現,數在數組里面的位置一直沒有改變,平衡樹旋轉改變的是根節點兒子數組ch[x][]指向的值
48     */
49     f[ch[frt][1]]=rt;
50     clears(frt);
51     pushup(rt);
52 }

 

五、例題

P3369 【模板】普通平衡樹

 

題目描述

您需要寫一種數據結構(可參考題目標題),來維護一些數,其中需要提供以下操作:

  1. 插入 xxx 數
  2. 刪除 xxx 數(若有多個相同的數,因只刪除一個)
  3. 查詢 xxx 數的排名(排名定義為比當前數小的數的個數 +1+1+1 )
  4. 查詢排名為 xxx 的數
  5. xxx 的前驅(前驅定義為小於 xxx,且最大的數)
  6. xxx 的后繼(后繼定義為大於 xxx,且最小的數)

輸入格式

第一行為 nnn,表示操作的個數,下面 nnn 行每行有兩個數 opt\text{opt}opt 和 xxx,opt\text{opt}opt 表示操作的序號( 1≤opt≤6 1 \leq \text{opt} \leq 6 1opt6 )

輸出格式

對於操作 3,4,5,63,4,5,63,4,5,6 每行輸出一個數,表示對應答案

輸入輸出樣例

輸入 #1
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
輸出 #1
106465
84185
492737

說明/提示

【數據范圍】
對於 100%100\%100% 的數據,1≤n≤1051\le n \le 10^51n105,∣x∣≤107|x| \le 10^7x107

 
代碼:
  1 /*
  2 注意:
  3 1、看平衡樹之前你要注意,對於1 3 5 3 2這一組數據。sz的值是4,因為sz保存的是節點種類
  4    為什么要這樣,因為sz涉及到要為幾個點開空間
  5 
  6 2、sizes[x]保存的是以x為樹根的子樹上節點數量,比如x這顆子樹所有節點為1,2,1.那么它的sizes[x]=3
  7    而且實際上不會有兩個1放在樹上。而是給1的權值數組cnt[1]加1
  8 
  9 3、對於1 3 5 3 2這一組數據。sz的值是4。那么1對應位置就是1,3對應位置就是2,5對應位置就是3,2對應位置就是4
 10    之后他們所對應的位置都不會改變
 11    在平衡樹旋轉的過程中只是每一個點的兒子節點ch[x][0]和ch[x][1]里面保存的值在改變
 12 */
 13 #include<stdio.h>
 14 #include<string.h>
 15 #include<algorithm>
 16 #include<iostream>
 17 using namespace std;
 18 const int maxn=1e5+10;
 19 int f[maxn],cnt[maxn],ch[maxn][2],sizes[maxn],key[maxn],sz,rt;
 20 /*
 21 f[i]:i節點的父節點,cnt[i]每個點出現的次數,ch[i][0/1]:0表示左孩子,
 22 1表示右孩子, size[i]表示以i為根節點的子樹的節點個數
 23 key[i]表示點i代表的數的值;sz為整棵樹的節點種類數,rt表示根節點
 24 */
 25 void clears(int x) //刪除x點信息
 26 {
 27     f[x]=cnt[x]=ch[x][0]=ch[x][1]=sizes[x]=key[x]=0;
 28 }
 29 bool get(int x) //判斷x是父節點的左孩子還是右孩子
 30 {
 31     return ch[f[x]][1]==x;  //返回1就是右孩子,返回0就是左孩子
 32 }
 33 void pushup(int x)  //重新計算一下x這棵子樹的節點數量
 34 {
 35     if(x)
 36     {
 37         sizes[x]=cnt[x];
 38         if(ch[x][0]) sizes[x]+=sizes[ch[x][0]];
 39         if(ch[x][1]) sizes[x]+=sizes[ch[x][1]];
 40     }
 41 }
 42 void rotates(int x)  //將x移動到他父親的位置,並且保證樹依舊平衡
 43 {
 44     int fx=f[x],ffx=f[fx],which=get(x);
 45     //x點父親,要接受x的兒子。而且x與x父親身份交換
 46     ch[fx][which]=ch[x][which^1];
 47     f[ch[fx][which]]=fx;
 48 
 49     ch[x][which^1]=fx;
 50     f[fx]=x;
 51 
 52     f[x]=ffx;
 53     if(ffx) ch[ffx][ch[ffx][1]==fx]=x;
 54 
 55     pushup(fx);
 56     pushup(x);
 57 }
 58 void splay(int x)  //將x移動到數根節點的位置,並且保證樹依舊平衡
 59 {
 60     for(int fx; fx=f[x]; rotates(x))
 61     {
 62         if(f[fx])
 63         {
 64             rotates((get(x)==get(fx))?fx:x);
 65             //如果祖父三代連城一條線,就要從祖父哪里rotate
 66             //至於為什么要這樣做才能最快……可以去看看Dr.Tarjan的論文
 67         }
 68     }
 69     rt=x;
 70 }
 71 /*
 72 將x這個值插入到平衡樹上面
 73 如果這個值在樹上存在過,那就sz不再加1,更新一下權值即可
 74 如果這個值在樹上不存在,那就sz加1,再更新一下權值
 75 
 76 sz是書上節點種類數
 77 sizes[x]是x這棵子樹上有多少節點
 78 */
 79 void inserts(int x)
 80 {
 81     if(rt==0)
 82     {
 83         sz++;
 84         key[sz]=x;
 85         rt=sz;
 86         cnt[sz]=sizes[sz]=1;
 87         f[sz]=ch[sz][0]=ch[sz][1]=0;
 88         return;
 89     }
 90     int now=rt,fx=0;
 91     while(1)
 92     {
 93         if(x==key[now])
 94         {
 95             cnt[now]++;
 96             pushup(now);
 97             pushup(fx);
 98             splay(now);  //splay的過程會rotates now點的所有祖先節點,這個時候它們所有子樹權值也更新了
 99             return;
100         }
101         fx=now;
102         now=ch[now][key[now]<x];
103         if(now==0)
104         {
105             sz++;
106             sizes[sz]=cnt[sz]=1;
107             ch[sz][0]=ch[sz][1]=0;
108             ch[fx][x>key[fx]]=sz;  //二叉查找樹特性”左大右小“
109             f[sz]=fx;
110             key[sz]=x;
111             pushup(fx);
112             splay(sz);
113             return ;
114         }
115     }
116 }
117 /*
118 有人問:
119 qwq很想知道為什么find操作也要splay操作呢?如果del要用的話直接splay(x)是不是就可以了
120 
121 原博客答:
122 呃不不不這個貌似不是隨便splay以下就可以的 首先find之后的splay就是將找到的這個點轉到根,
123 當然你不加這個應該是也可以,只不過這道題加上的話對於這一堆操作來說比較方便,不過一般來說轉一轉splay的
124 平衡性會好一點(當然也不要轉得太多了就tle了...) 但是del之前直接splay(x)要視情況而定,關鍵在於分清楚
125 “點的編號”和“點的權值”這兩個概念。如果你已經知道了該轉的點的編號,當然可以直接splay(x),但是如果你只
126 知道應該splay的點的權值,你需要在樹里find到這個權值的點的編號,然后再splay 其實最后splay寫起來都是非
127 常靈活的,而且有可能一個點帶若干個權之類的。對於初學者的建議就是先把一些最簡單的情況搞清楚,比如說一
128 個編號一個權的這種,然后慢慢地多做題就能運用得非常熟練了。最好的方法就是多畫畫樹自己轉一轉,對之后
129 復雜題目的調試也非常有益
130 
131 我說:
132 我在洛谷上得模板題上交了一下rnk里面不帶splay(now)的,一共12個樣例,就對了兩個樣例。錯了一個樣例,其他全TLE
133 
134 我解釋:
135 為什么作者解釋可以刪去,但是刪過之后還錯了。因為它的代碼中函數之前是相互聯系的
136 就比如它調用rnk(x)函數之后就已經認為x為平衡樹樹根,然后直接對它進行下一步操作(這個假設在del函數里面)
137 
138 如果你光刪了rnk(x)里面的splay(),你肯定還要改其他地方代碼。。。。。。
139 */
140 int rnk(int x)  //查詢x的排名
141 {
142     int now=rt,ans=0;
143     while(1)
144     {
145         if(x<key[now]) now=ch[now][0];
146         else
147         {
148             ans+=sizes[ch[now][0]];
149             if(x==key[now])
150             {
151                 splay(now); //這個splay是為了后面函數的調用提供前提條件
152 //就比如pre函數的前提條件就是x(x是我們要求誰的前驅,那個誰就是x)已經在平衡樹樹根
153                 return ans+1;
154             }
155             ans+=cnt[now];  //cnt代表now這個位置值(key[now])出現了幾次
156             now=ch[now][1];
157         }
158     }
159 }
160 int kth(int x)
161 {
162     int now=rt;
163     while(1)
164     {
165         if(ch[now][0] && x<=sizes[ch[now][0]])
166         {
167             //滿足這個條件就說明它在左子樹上
168             now=ch[now][0];
169         }
170         else
171         {
172             int temp=sizes[ch[now][0]]+cnt[now];
173             if(x<=temp) //這個temp是now左子樹權值和now節點權值之和
174                 return key[now]; //進到這個判斷里面說明他不在左子樹又不在右子樹,那就是now節點了
175             x-=temp;
176             now=ch[now][1];
177         }
178     }
179 }
180 int pre()//由於進行splay后,x已經到了根節點的位置
181 {
182     //求x的前驅其實就是求x的左子樹的最右邊的一個結點
183 //為什么呢,因為這是平衡樹(帶有二叉排序樹特點),根據二叉排序樹中序遍歷結果我么可以知道,一個數的前序就在
184 //x的左子樹的最右邊的一個結點
185     int now=ch[rt][0];
186     while(ch[now][1]) now=ch[now][1];
187     return now;
188 }
189 int next()
190 {
191     //求后繼是求x的右子樹的最左邊一個結點
192 //為什么呢,因為這是平衡樹(帶有二叉排序樹特點),根據二叉排序樹中序遍歷結果我么可以知道,一個數的前序就在
193 //x的右子樹的最左邊一個結點
194     int now=ch[rt][1];
195     while(ch[now][0])  now=ch[now][0];
196     return now;
197 }
198 /*
199 刪除操作是最后一個稍微有點麻煩的操作。
200 step 1:隨便find一下x。目的是:將x旋轉到根。
201 step 2:那么現在x就是根了。如果cnt[root]>1,即不只有一個x的話,直接-1返回。
202 step 3:如果root並沒有孩子,就說名樹上只有一個x而已,直接clear返回。
203 step 4:如果root只有左兒子或者右兒子,那么直接clear root,然后把唯一的兒子當作根就可以了(f賦0,root賦為唯一的兒子)
204 剩下的就是它有兩個兒子的情況。
205 step 5:我們找到新根,也就是x的前驅(x左子樹最大的一個點),將它旋轉到根。然后將原來x的右子樹接到新根的
206 右子樹上(注意這個操作需要改變父子關系)。這實際上就把x刪除了。不要忘了update新根。
207 */
208 void del(int x)
209 {
210     rnk(x);
211     if(cnt[rt]>1)//如果這個位置權值大於1,那就不用刪除這個點
212     {
213         cnt[rt]--;
214         pushup(rt);
215         return;
216     }
217     if(!ch[rt][0] && !ch[rt][1]) //這個就代表平衡樹只有一個節點
218     {
219         clears(rt);
220         rt=0;
221         return;
222     }
223     if(!ch[rt][0]) //只有左兒子,樹根只有左兒子那就把樹根直接刪了就行
224     { //然后左兒子這棵子樹變成新的平衡樹
225         int frt=rt;
226         rt=ch[rt][1];
227         f[rt]=0;
228         clears(frt);
229         return;
230     }
231     else if(!ch[rt][1]) //只有右兒子,和上面差不多
232     {
233         int frt=rt;
234         rt=ch[rt][0];
235         f[rt]=0;
236         clears(frt);
237         return;
238     }
239     int frt=rt;
240     int leftbig=pre();
241     splay(leftbig); //讓前驅做新根
242     ch[rt][1]=ch[frt][1];  //這個frt指向的還是之前的根節點
243     /*
244     看着一點的時候就會發現,數在數組里面的位置一直沒有改變,平衡樹旋轉改變的是根節點兒子數組ch[x][]指向的值
245     */
246     f[ch[frt][1]]=rt;
247     clears(frt);
248     pushup(rt);
249 }
250 int main()
251 {
252     int n;
253     scanf("%d",&n);
254     for (int i=1; i<=n; i++)
255     {
256         int type,k;
257         scanf("%d%d",&type,&k);
258         if (type==1) inserts(k);
259         if (type==2) del(k);
260         if (type==3) printf("%d\n",rnk(k));
261         if (type==4) printf("%d\n",kth(k));
262         if (type==5)
263         {
264             inserts(k);
265             //插入操作中存在splay操作,這樣的話插入之后平衡樹樹根就是k
266             printf("%d\n",key[pre()]);
267             del(k);
268         }
269         if (type==6)
270         {
271             inserts(k);
272             printf("%d\n",key[next()]);
273             del(k);
274         }
275     }
276     printf("%d %d %d %d\n",sz,sizes[1],sizes[2],sizes[3]);
277     return 0;
278 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM