一、平衡樹用來干什么
您需要寫一種數據結構(可參考題目標題),來維護一些數,其中需要提供以下操作:
- 插入 xxx 數
- 刪除 xxx 數(若有多個相同的數,因只刪除一個)
- 查詢 xxx 數的排名(排名定義為比當前數小的數的個數 +1+1+1 )
- 查詢排名為 xxx 的數
- 求 xxx 的前驅(前驅定義為小於 xxx,且最大的數)
- 求 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 }
五、例題
題目描述
您需要寫一種數據結構(可參考題目標題),來維護一些數,其中需要提供以下操作:
- 插入 xxx 數
- 刪除 xxx 數(若有多個相同的數,因只刪除一個)
- 查詢 xxx 數的排名(排名定義為比當前數小的數的個數 +1+1+1 )
- 查詢排名為 xxx 的數
- 求 xxx 的前驅(前驅定義為小於 xxx,且最大的數)
- 求 xxx 的后繼(后繼定義為大於 xxx,且最小的數)
輸入格式
第一行為 nnn,表示操作的個數,下面 nnn 行每行有兩個數 opt\text{opt}opt 和 xxx,opt\text{opt}opt 表示操作的序號( 1≤opt≤6 1 \leq \text{opt} \leq 6 1≤opt≤6 )
輸出格式
對於操作 3,4,5,63,4,5,63,4,5,6 每行輸出一個數,表示對應答案
輸入輸出樣例
10 1 106465 4 1 1 317721 1 460929 1 644985 1 84185 1 89851 6 81968 1 492737 5 493598
106465 84185 492737
說明/提示
【數據范圍】
對於 100%100\%100% 的數據,1≤n≤1051\le n \le 10^51≤n≤105,∣x∣≤107|x| \le 10^7∣x∣≤107
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 }