平衡二叉树总结二:avl树
来源:互联网 发布:安东尼·霍普金斯 知乎 编辑:程序博客网 时间:2024/05/17 22:33
avl树是第一个创造出来的平衡二叉树,之后很多的平衡树都是基于AVL树的基本操作——旋转来实现的。
avl树结构如下:
struct avl{ avl* left; avl* right; int val,h;//val值域,h高度。};typedef avl* tree;//获取高度的函数int height(tree t){ if(t==NULL)return -1; else{return t->h;}}
一、旋转操作(所有旋转操作都能保证不破坏有序性)
1.左单旋(LL)。
当左子树高度-右子树高度 >= 2 并且 左子树的左儿子高度 >= 右儿子时,可通过左单旋操作平衡树高度。
linux下面实在没找到好的画图软件,凑或看吧
上图中,旋转前,左子树高度为1,右子树高度为-1(NULL),旋转后,左子树高度为0右子树高度为1,并且旋转后依然是二叉搜索树。操作代码如下:
/*旋转操作不会改变二叉搜索树的有序性,即左<中<右。它只会调整左右子树的高度差,高的降低,低的提高。*///LL旋转(左单旋)tree LL(tree t){ tree t1 = t->left; t->left = t1->right; t1->right = t;//调整后t,t1的高度都会改变,但是t的子树高度未变,先//调整它。 t->h = max(height(t->left),height(t->right))+1;//再调整t1 t1->h = max(height(t1->left),height(t1->right))+1; return t1;}//RR旋转,LL操作的对称。tree RR(tree t){ tree t1 = t->right; t->right = t1->left; t1->left = t;//调整后t,t1的高度都会改变,但是t的子树高度未变,先//调整它。 t->h = max(height(t->left),height(t->right))+1;//再调整t1 t1->h = max(height(t1->left),height(t1->right))+1; return t1;}2.左双旋
当左子树高度-右子树高度 >= 2 并且 左子树的左儿子高度 < 右儿子时,可通过左双旋操作平衡树高度。
如图,先把节点6的左儿子赋值给2节点的右儿子,右儿子赋值给节点8的左儿子,再将其左右儿子分别设为2节点与8节点。这样操作后,树的左右儿子高都变为了1。
说的挺复杂,但是代码却出奇的简单。
//LR旋转,左双旋,虽然我也没明白为啥LR.tree LR(tree t){ t->left = RR(t->left); return LL(t);}//RL旋转tree RL(tree t){ t->right = LL(t->right); return RR(t);}3.右单旋,右双旋。
这两个操作是左旋的镜像操作,代码已经在上面给出了。
4.插入
插入操作有些复杂,主要在二叉搜索树的插入操作基础上,需要添加调整高度的代码。
/*比较难一点的插入操作*/tree insert(tree t,int val){ if(t==NULL){//插入到叶节点上。 t = new avl; t->val = val; t->left = t->right = NULL; t->h = 0; return t; } if(val > t->val){//走右边 t->right = insert(t->right,val); if(height(t->right)-height(t->left)==2){//判断是否//违反高度平衡条件。 if(val > t->right->val){//如果走的子树右边 t = RR(t); } else{//走的子树左边 t = RL(t); } } } else{ t->left = insert(t->left,val); //判断平衡条件 if(height(t->left)-height(t->right)==2){ if(val < t->left->val){ t = LL(t); } else{ t = LR(t); } } } t->h = max(height(t->left),height(t->right))+1; return t;}
5.最难的就是删除操作了,数据量不大时还是建议使用lazy 删除。
/*永远都最难的删除。*/tree del(tree t,int val){//首先找到需删除的节点。 tree v; if(t==NULL)return NULL;//为空说明没找到该值。 if(val > t->val){//往右走。 t->right = del(t->right,val); //删除可能会破坏平衡条件,调整,往右走,右子树可能变低。 if(height(t->left) - height(t->right) == 2){ if(height(t->left->left) >= height(t->left->right)){ t = LL(t); } else{ t = LR(t);} } } else if(val < t->val){ t->left = del(t->left,val); //左子树可能变低。 if(height(t->right) - height(t->left) == 2){ if(height(t->right->right) >= height(t->right->left)){ t = RR(t); } else{ t = RL(t);} } } else{ if(t->left == NULL){//只有一个儿子时,可以直接把另一个儿子接上来。 v = t->right;delete t; return v; } else if(t->right == NULL){ v = t->left;delete t; return v; } else{//双儿子时,查找左子树最大值。 v = findMax(t->left); if(v == t->left){//刚好是左儿子,直接把左儿子右子树接上来。 t->left = v->left; t->val = v->val; delete v;//调整高度。 t->h = max(height(t->left),height(t->right))+1; } else{//否则删除把最大值赋值给t,递归删除最大值,因为此时 //最大值必定没有右儿子。 t->val = v->val; del(t->left,v->val); } } } return t;}//avl树的删除编程起来确实异常复杂,所以实际上大家也很少用这种树,工程里更多是红黑树//以及splay,比赛更多是编码简单的treap,sbt等。
6.最后汇总一下,加了一些测试代码。
#include<iostream>#include<queue>using namespace std;struct avl{ avl* left; avl* right; int val,h;//val值域,h高度。};typedef avl* tree;int height(tree t){ if(t==NULL)return -1; else{return t->h;}}/*旋转操作不会改变二叉搜索树的有序性,即左<中<右。它只会调整左右子树的高度差,高的降低,低的提高。*///LL旋转(左单旋)tree LL(tree t){ tree t1 = t->left; t->left = t1->right; t1->right = t;//调整后t,t1的高度都会改变,但是t的子树高度未变,先//调整它。 t->h = max(height(t->left),height(t->right))+1;//再调整t1 t1->h = max(height(t1->left),height(t1->right))+1; return t1;}//RR旋转,LL操作的对称。tree RR(tree t){ tree t1 = t->right; t->right = t1->left; t1->left = t;//调整后t,t1的高度都会改变,但是t的子树高度未变,先//调整它。 t->h = max(height(t->left),height(t->right))+1;//再调整t1 t1->h = max(height(t1->left),height(t1->right))+1; return t1;}//LR旋转,左双旋,虽然我也没明白为啥LR.tree LR(tree t){ t->left = RR(t->left); return LL(t);}//RL旋转tree RL(tree t){ t->right = LL(t->right); return RR(t);}/*比较难一点的插入操作*/tree insert(tree t,int val){ if(t==NULL){//插入到叶节点上。 t = new avl; t->val = val; t->left = t->right = NULL; t->h = 0; return t; } if(val > t->val){//走右边 t->right = insert(t->right,val); if(height(t->right)-height(t->left)==2){//判断是否//违反高度平衡条件。 if(val > t->right->val){//如果走的子树右边 t = RR(t); } else{//走的子树左边 t = RL(t); } } } else{ t->left = insert(t->left,val); //判断平衡条件 if(height(t->left)-height(t->right)==2){ if(val < t->left->val){ t = LL(t); } else{ t = LR(t); } } } t->h = max(height(t->left),height(t->right))+1; return t;}/*寻找最大值*/tree findMax(tree t){ while(t->right)t=t->right; return t;}/*永远都最难的删除。*/tree del(tree t,int val){//首先找到需删除的节点。 tree v; if(t==NULL)return NULL;//为空说明没找到该值。 if(val > t->val){//往右走。 t->right = del(t->right,val); //删除可能会破坏平衡条件,调整,往右走,右子树可能变低。 if(height(t->left) - height(t->right) == 2){ if(height(t->left->left) >= height(t->left->right)){ t = LL(t); } else{ t = LR(t);} } } else if(val < t->val){ t->left = del(t->left,val); //左子树可能变低。 if(height(t->right) - height(t->left) == 2){ if(height(t->right->right) >= height(t->right->left)){ t = RR(t); } else{ t = RL(t);} } } else{ if(t->left == NULL){//只有一个儿子时,可以直接把另一个儿子接上来。 v = t->right;delete t; return v; } else if(t->right == NULL){ v = t->left;delete t; return v; } else{//双儿子时,查找左子树最大值。 v = findMax(t->left); if(v == t->left){//刚好是左儿子,直接把左儿子右子树接上来。 t->left = v->left; t->val = v->val; delete v;//调整高度。 t->h = max(height(t->left),height(t->right))+1; } else{//否则删除把最大值赋值给t,递归删除最大值,因为此时 //最大值必定没有右儿子。 t->val = v->val; del(t->left,v->val); } } } return t;}void travel(tree t){ if(!t)return; if(t->left)travel(t->left); cout << t->val << " "; if(t->right)travel(t->right);}void level(tree t){ if(!t)return; tree now,last=t; queue<tree> qu; qu.push(t); while(qu.size()){ now = qu.front();qu.pop(); if(now->left)qu.push(now->left); if(now->right)qu.push(now->right); cout << now->val << "(" << now->h << ")" << " "; if(now == last && qu.size()){last = qu.back();cout << endl;} } cout << endl;}int main(){ int a[10] = {1,8,3,0,9,5,6,2,4,7}; tree t = NULL; int i; for(i=0;i<10;i++){ t = insert(t,a[i]); } travel(t);cout << endl; level(t); t = del(t,8); travel(t);cout << endl; level(t);}
二、优化版本的avl树。
这个思路是网上看到的,其实我也一直再想因为左旋右旋操作是对称的,能不能写成一个函数。偶然还真看到了这种实现,记录下来。
主要思路是使用一个布尔值来表示左右子树,0代表左,1代表右。
#include<iostream>#include<queue>using namespace std;struct avl{ avl* child[2]; int val,h;//val值域,h高度。 avl(int v,int hei):val(v),h(hei){child[0] = child[1] = NULL;}};typedef avl* tree;int height(tree t){ if(t==NULL)return -1; else{return t->h;}}/*旋转操作不会改变二叉搜索树的有序性,即左<中<右。它只会调整左右子树的高度差,高的降低,低的提高。*///单旋tree SR(tree t,bool c){ tree t1 = t->child[c]; t->child[c] = t1->child[!c]; t1->child[!c] = t;//调整后t,t1的高度都会改变,但是t的子树高度未变,先//调整它。 t->h = max(height(t->child[c]),height(t->child[!c]))+1;//再调整t1 t1->h = max(height(t1->child[c]),height(t1->child[!c]))+1; return t1;}//双旋tree DR(tree t,bool c){ t->child[c] = SR(t->child[c],!c); return SR(t,c);}/*比较难一点的插入操作*/tree insert(tree t,int val){ if(t==NULL){//插入到叶节点上。 t = new avl(val,0); return t; } bool c=0; if(val>t->val)c=1; t->child[c] = insert(t->child[c],val); if(height(t->child[c])-height(t->child[!c])==2){//判断是否//违反高度平衡条件。 if( (val < t->child[c]->val) ^ c){t = SR(t,c);} else{t = DR(t,c);} } t->h = max(height(t->child[0]),height(t->child[1]))+1; return t;}/*寻找最大值*/tree findMax(tree t){ while(t->child[1])t=t->child[1]; return t;}/*永远都最难的删除。*/tree del(tree t,int val){//首先找到需删除的节点。 tree v; if(t==NULL)return NULL;//为空说明没找到该值。 bool c=0; if(val != t->val){ if(val > t->val)c = 1; t->child[c] = del(t->child[c],val); if(height(t->child[!c]) - height(t->child[c]) == 2){ if(height(t->child[!c]->child[!c]) >= height(t->child[!c]->child[c])){t = SR(t,c);} else{ t = DR(t,c);} } } else{ if(t->child[0] && t->child[1]){//双儿子时,查找左子树最大值。 v = findMax(t->child[0]); if(v == t->child[0]){//刚好是左儿子,直接把左儿子右子树接上来。 t->child[0] = v->child[1]; t->val = v->val; delete v;//调整高度。 t->h = max(height(t->child[0]),height(t->child[1]))+1; } else{//否则删除把最大值赋值给t,递归删除最大值,因为此时 //最大值必定没有右儿子。 t->val = v->val; del(t->child[0],v->val); } } else{ if(t->child[1])c=1; v = t->child[c]; delete t; return v; } } return t;}void travel(tree t){ if(!t)return; if(t->child[0])travel(t->child[0]); cout << t->val << " "; if(t->child[1])travel(t->child[1]);}void level(tree t){ if(!t)return; tree now,last=t; queue<tree> qu; qu.push(t); while(qu.size()){ now = qu.front();qu.pop(); if(now->child[0])qu.push(now->child[0]); if(now->child[1])qu.push(now->child[1]); cout << now->val << "(" << now->h << ")" << " "; if(now == last && qu.size()){last = qu.back();cout << endl;} } cout << endl;}int main(){ int a[10] = {1,8,3,0,9,5,6,2,4,7}; tree t = NULL; int i; for(i=0;i<10;i++){ level(t); t = insert(t,a[i]); } travel(t);cout << endl; level(t); t = del(t,8); travel(t);cout << endl; level(t);}
0 0
- 平衡二叉树总结二:avl树
- AVL平衡二叉树(二)
- AVL 平衡二叉树
- 平衡二叉树(AVL)
- 平衡二叉树(AVL)
- AVL 平衡二叉树
- avl平衡二叉树
- 二叉平衡树AVL
- 平衡二叉树(AVL)
- 平衡二叉树(AVL)
- 平衡二叉树 AVL
- 平衡二叉树(AVL)
- 平衡二叉树(AVL)
- 平衡二叉树(AVL)
- 平衡二叉树(AVL)
- 平衡二叉树(AVL)
- 二叉平衡树AVL
- AVL平衡二叉树
- 集合框架_Set集合概述及特点
- 带你一起分析cut the rope(切绳子游戏)中绳子的制作方法
- 常用正则表达式列举
- BZOJ 3932 [CQOI2015] 任务查询系统 可持久化线段树
- 【NOIP 模拟赛】平均数 涂色游戏 序列题解
- 平衡二叉树总结二:avl树
- OpenCV 应用fitEllipse函数一种异常问题分析
- Linux日常使用命令
- 冒泡排序(笔记)0.0.1
- [nodeJS]Node.js到底是什么?
- PAT 乙级 1005
- 欢迎使用CSDN-markdown编辑器
- 变量、常量、标识符、数据类型
- 开发眼中的一些前端交互优化