Root of AVL Tree (25 分)---AVL樹的證明式分析
(第一篇)記PAT習題,AVL樹的原理的見解
04-樹5 Root of AVL Tree (25 分)
題目意思就是給出一個N,接著插入N個值,以AVL樹的方式插入,然後求AVL樹的根節點
首先是樹的節點結構體,網上有人實現是節點本身儲存了一個額外資訊就是樹高,而不儲存平衡因子這個資訊。而我的實現是儲存平衡因子不儲存樹高。
typedef struct NODE { int n; int bl; NODE* l; NODE* r; }NODE; //我設定的是右樹高減左樹高
AVL樹的平衡調整方式有4種,分別是LL旋轉,RR旋轉,LR旋轉,RL旋轉。RR與LL旋轉是對稱的,LR與RL是對稱的。
我不知道有沒有人是和我一樣的想法的,AVL樹的四種平衡調整的一個難點在於找到“失衡節點”,如何找到失衡節點我覺得是一個很有探索空間的地方。
我的實現方式是:對於任意一個失衡節點,它只關心它自己的失衡原因,對於一個非失衡節點,它只要返回(上報給父節點)它的高度變化原因。
為什麼這樣?
觀察這張圖,紅線左側,是“RR失衡模板”---------6失衡了(總樹高4層是為了更有通用性)。這棵樹調整為紅線右側即可恢復平衡。
其中沒有標數字的節點代表旋轉前後不影響的節點,比如紅色節點,因為在6的右側,故比6大,所以調整後,6的右子樹空缺,若紅色節點存在,則接到6的右樹上。
模擬一下RR旋轉檢測與調整過程:比如我們插入的是元素12,找到位置之後,插入12。對於12本身,因為它是從無到有,所以它的平衡因子是0,上報(遞迴的返回)父節點我----》作為一棵樹,
增高了,並且增高原因是由於自己(從無到有)。
返回到10,10發現右樹來報它增高了,增高原因是右樹本身。檢測自己平衡因子,原來是0,現在是1.於是它又上報它的父親節點,報告,我也增高了
(為什麼用又?)增高原因是我的右兒子。
現在到8,它的平衡因子原來是0,右樹報告它增高了,增高原因是它的右兒子。8檢查自己的平衡因子,從0到1,仍未失衡,又往上報告---》我增高了,增高原因是我的右子樹(至於是右子樹自己還是右子樹的左或右,其父並不care)。
ok,到了6節點,它發現他的平衡因子本來就是1,右樹增高,+1,變成2.於是疾呼,來人啊!我失衡了!-----》源於右子樹(故R_旋轉),而右子樹報告其右樹長高,故為RR旋轉。
RR旋轉:對於原根節點,簡單來說就是右樹提上去,自己左沉,原右樹若是有左樹,那便接到自己右樹。因為自己的右樹任何元素都比自己大,故肯定不虧。
對於RR操作,很多原始碼,把上面這行翻譯一下就可以了。重點是關於重新調整平衡因子!!
看圖 ↑,假設藍框部分樹高n,(其實就是原根的左樹),那麼,因為根RR失衡 所以,必然是此刻右樹的高度比左樹高2,所以綠框部分樹高為n+2,因為節點只能一個一個插入。麻煩節點在
一次插入之時只會有一個 ,-—》12。10的左子肯定為空,否則在插入10的左子時就已經失衡了。再者,RR失衡,8對6的返回值是“右樹增高導致我增高”。也說明8右樹必然高於左樹,若左樹右樹一樣高,說明插入之後它的高度未變,則不會給6彙報自己增高了,而8未失衡,故8一定是右樹比左樹高1。所以灰色框裡樹高一定是n+1,橙色框裡樹高一定為n。
再看圖右側,旋轉之後。10和自己的子節點未發生變化。故平衡因子不變。6的左樹是藍框,高n,右側是橙框,高n,故6的新平衡因子是0。同理,8的左樹必然高n+1,同右樹,8的平衡因子也一定為0。
#include<iostream> #define CAN_NOT -5 #define REPEAT -4 #define SHOULD_NOT_BE_THERE -3 enum status { NON,ME,BYLEFT,BYRIGHT };//列舉表示增高原因,未增高,自己,左,右 typedef struct NODE { int n; int bl; NODE* l; NODE* r; }NODE; //我設定的是右樹高減左樹高 NODE* AVL=NULL; void RR(NODE* &root); void RL(NODE* &root); void LL(NODE* &root); void LR(NODE* &root); status Insert(NODE* &root,int a) { status ret; if (root == NULL) { root = new NODE; root->bl = 0; root->l = NULL; root->r = NULL; root->n = a; return ME; }//是我自己導致我的樹增高,返回ME else if (a > root->n) { //往右子樹插入 ret = Insert(root->r, a); if (ret == NON) return NON; else { //右樹增高,待處理 ++root->bl; if (root->bl <= 0) return NON; else if (root->bl < 2) { return BYRIGHT; } else { //自己失衡,原因RR或RL, if (ret == BYRIGHT) { RR(root); return NON; } else if(ret==BYLEFT){ RL(root); //RL旋轉 return NON; } else { exit(CAN_NOT);//ME導致失衡,不可能 } } } } else if (a == root->n) { exit(REPEAT); //不允許相等 } else { ret = Insert(root->l, a); if (ret == NON) { return NON; } else{ --root->bl; if (root->bl >= 0) { return NON; //依然OK } else if(root->bl>-2) { return BYLEFT; } else { if (ret == BYLEFT) { //LL旋轉 LL(root); return NON; } else if(ret==BYRIGHT) { LR(root); return NON; } else { exit(CAN_NOT);//ME導致失衡,不可能 } } } } exit(SHOULD_NOT_BE_THERE);//不應該走到這個位置 好長的insert函式 } int main() { int n; int tmp; NODE* AVL=NULL; std::cin >> n; for (int i = 0; i < n; ++i) { std::cin >> tmp; Insert(AVL, tmp); } std::cout << AVL->n; return 0; } void RR(NODE* &root) { //RR旋轉 NODE* tmp = root; //root變更為右樹 root = tmp->r; tmp->r = root->l; root->l = tmp; //調整平衡因子 tmp->bl -= 2; //推算可得 root->bl = 0; //大致是,因2破壞,設左樹高n,右樹高n+2,RR破壞,右右樹高n-1,右樹未破壞且右右高,右左高n+1。。。 return; } void LL(NODE* &root) {//紙筆推算一下,失衡的一些確定推斷 NODE* tmp = root; root = tmp->l; tmp->l = root->r; root->r = tmp; tmp->bl += 2; root->bl = 0; return; } void RL(NODE* &root) { NODE* tmp = root; root = tmp->r->l; tmp->r->l = root->r; root->r = tmp->r; tmp->r = root->l; root->l = tmp; //RL旋轉複雜一些,不過都可以通過設樹高論證原理 if (root->bl == -1) { //重新設定平衡因子,比較繁瑣,推論可知 root->r->bl = 1; root->bl = 0; root->l->bl = 0; } else if (root->bl == 1) { root->r->bl = 0; root->bl = 0; root->l->bl = -1; } else { root->l->bl = 0; root->r->bl = 0; } return; } void LR(NODE* &root) { NODE* tmp = root; root = tmp->l->r; tmp->l->r = root->l; root->l = tmp->l; tmp->l = root->r; root->r = tmp; if (root->bl == -1) { root->r->bl = 1; root->bl = 0; root->l->bl = 0; } else if (root->bl == 1) { root->r->bl = 0; root->bl = 0; root->l->bl = -1; } else {//LR旋轉導致失衡,且root ->bl為0,說明LR是新產生的節點 root->l->bl = 0; root->r->bl = 0; } return; }
LL旋轉的分析基本等同RR旋轉。程式碼裡有些無用的本來用於除錯可能的錯誤原因,結果意外的好,就改了一個bug就過了。
後面是對LR的分析。下次補上。第一次寫,試試。