(第一篇)记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的分析。下次补上。第一次写,试试。
补充:
如图所示,是RL破坏的旋转示意图。最重要的恢复平衡步骤就是把RL节点移动到根节点,
NODE* tmp = root; //tmp用来保存原根节点 root = tmp->r->l; //root变成RL节点
只把根变为RL是不够的,RL如果有儿子,那么就由图中的3和6节点领养 -->根据搜索树规律:黄色节点一定比3大,红色节点一定比6小
来
NODE* tmp = root; root = tmp->l->r; tmp->l->r = root->l;//3的右儿子接上黄色节点 root->l = tmp->l;//只是注意一点,要先将6(原根)的左子树接到新根的左保存 tmp->l = root->r; //6的左儿子变为红色节点 root->r = tmp;
平衡因子的调整:
类似LL旋转的分析,设绿框高n,蓝框必高n+2,红框必高n,棕色框必高n+1。但是比如6的平衡因子要判断一下先,
因为左子树最高为n,也可能小于n,要根据原来5的平衡因子决定
比如-1说明是原来5的左树高,右树低,1相反,0说明5是新节点,
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; }
RL和LR操作相反。
AVL树的删除操作以后再说吧。
只是注意一点,要先将6的左节点空出