算法笔记9.5 平衡二叉树(AVL树)
原创
©著作权归作者所有:来自51CTO博客作者武大保安的原创作品,请联系作者获取转载授权,否则将追究法律责任
9.5.1 平衡二叉树的定义
上一节的二叉二叉查找树BST,若按照顺序插入,会退化为一条链,eg:使用序列{1,2,3,4,5}构建二叉树,会得到
于是需要对树的结构进行调整,使得树的高度在每次插入元素后仍然能保持O(logn)的级别,这样就产生了平衡二叉树。
所谓平衡二叉树是指:对AVL树的任意结点来说,其左子树与右子树的高度之差的绝对值不超过1(左右子树的高度之差称为该结点的平衡因子)
定义及结点高度的操作
定义结构中加一个变量height,用来记录以当前结点为根结点的子树的高度
//平衡二叉树的定义
struct node{
int v,height;//v为结点的权值,height为当前子树的高度
node *lchild,*rchild;//左右孩子结点
};
//新建结点
//生成一个新结点,v为结点权值
node* newNode(int v){
node* Node=new node;
Node->data=v;
Node->height=1;//注意初始高度为1 有v一定不空
Node->lchild=Node->rchild=NULL;
return Node;
}
//封装一下求子树root的高度
int getHeight(node* root){
if(root==NULL) return 0;
else return root->height;//等于NULL又使用->会空指针的
}
//计算平衡因子
int getBlanceFactor(node* root){
//左子树高度减去右子树高度
return getHeight(root->lchild)-getHeight(root->rchild);
}
//显然当前结点为根的子树高度(即当前结点的height)等于左子树的height与右子树的height的较大值+1
//更新结点的height
void updateHeight(node* root){
//max(左孩子height,右孩子height)+1
root->height=max(getHeight(root->rchild),getHeight(root->lchild))+1;
}
9.5.2平衡二叉树的基本操作
查找、插入、建立
1.查找操作
AVL(平衡二叉树)也是一个二叉查找树,查找操作和BST一样。但是AVL的高度为O(logn),查找时间复杂度为O(logn)
//AVL查找操作 和BST完全相同
void search(node* root,int x){
if(root==NULL) return NULL;
if(root->data==x) cout<<root->data<<endl;//找到访问之
if(root->data>x) search(root->lchild,x);
else search(root->rchild,x);
}
2.插入操作
左旋
//左旋代码
//插入操作
//左旋
void L(node* &root){
node* temp=root->rchild;//root指向根A temp指向右结点B
root->rchild=temp->lchild;//B的左子树成为A的右子树
temp->lchild=root;//A成为B的左子树
//更新A、B的高度不能忘了 AVL的保证
updateHeight(root);//更新A的高度
updateHeight(temp);//更新B的高度
//除了A、B,其他子树都没有变化,不用改东西
root=temp;//根结点设为B
}
//右旋
//右旋代码
//右旋代码
void R(node* &root){
node* temp=root->lchild;//root指向根B temp指向左结点A
root->lchild=temp->rchild;//左子树的右孩子变为根结点的左孩子(开始是,现在还是,大小关系未变)
temp->rchild=root;//根成为左子树temp的右孩子(根大)
//不能忘了更新高度
updateHeight(root);
updateHeight(temp);
root=temp;
}//写完后发现和左旋正好left变right而right变left 充分说明了对称性
开始插入
skill1:
平衡因子 RL
+ L
- R
skill2:
LL 2 1
LR 2 -1
RR -2 -1
RL -2 1
//都是先2后1 符号根据L+ R-来判断
skill3: 调整操作
LL 根右旋
LR L孩子L旋(变LL),根右旋 操作也是LR
RR 根左旋
RL R孩子R旋(变RR),根左旋 操作也是RL
//LL与RR完全对称 R->L L->R
//LR与RL完全对称 R->L L->R
//第一个字母决定了本次操作左孩子还是右孩子 L开头只动了左孩子 R开头只动了右孩子
//两对记住一对即可
//正式插入 相比较于BST每次插入之后多了两步:1.更新树高 2.判断失衡类型调整失衡(LL,LR,RR,RL)
//avl树root中插入值为v的结点
void insert(node* &root,int v){
if(root==NULL){
root=newNode(v);
return;
}
if(v<root->data){
insert(root->lchild,v);
updateHeight(root);//更新树高 左子树不为空则其高度会递归更新的 哪个结点的子树插入了,更新哪个结点,递归更新
//检查平衡态
if(getBlanceFactor(root)==2){//L (BF>1>0 肯定左边长了导致失衡 接下来判断左子树即可)
if(getBlanceFactor(root->lchild)==1){//LL 一次右旋即可
R(root);//下面直接也就return了 不用多写return了
}else if(getBlanceFactor(root->lchild)==-1){//LR 左子树左旋再根右旋 明确写出条件,直接else也行吧 只可能-1 1不会是0
L(root->lchild);
R(root);
}
}
}else{//往右边插
insert(root->rchild,v);
updateHeight(root);
//检查平衡状态 和上面完全对称
if(getBlanceFactor(root)==-2){//R 往右边插 不平衡只可能是负值 RR或RL
if(getBlanceFactor(root->rchild)==-1){//RR
L(root);
}else if(getBlanceFactor(root->rchild)==1){//RL
R(root->rchild);
L(root);
}
}
}
}
3.AVL的建立
插入写好了,建立就简单了
node* Create(int data[],int n){
node* root=NULL;//只能赋值NULL 不能赋别的
//插入就是在NULL处插的
for(int i=0;i<n;i++){
insert(root,data[i]);
}
return root;
}
综合实例
以{1,2,3,4,5,6,7,8}为插入序列建立一棵AVL,然后再插入9和6 每次插入都层序遍历一次
插入9
再插入6
综合代码
#include<iostream>
#include<queue>
using namespace std;
//平衡二叉树的定义
struct node{
int data,height;//v为结点的权值,height为当前子树的高度
node *lchild,*rchild;//左右孩子结点
};
//新建结点
//生成一个新结点,v为结点权值
node* newNode(int v){
node* Node=new node;
Node->data=v;
Node->height=1;//注意初始高度为1 有v一定不空
Node->lchild=Node->rchild=NULL;
return Node;
}
//封装一下求子树root的高度
int getHeight(node* root){
if(root==NULL) return 0;
else return root->height;//等于NULL又使用->会空指针的
}
//计算平衡因子
int getBlanceFactor(node* root){
//左子树高度减去右子树高度
return getHeight(root->lchild)-getHeight(root->rchild);
}
//显然当前结点为根的子树高度(即当前结点的height)等于左子树的height与右子树的height的较大值+1
//更新结点的height
void updateHeight(node* root){
//max(左孩子height,右孩子height)+1 新结点NULL->newNode 即0->1 然后依次递归+1
root->height=max(getHeight(root->rchild),getHeight(root->lchild))+1;
}
//AVL查找操作 和BST完全相同
void search(node* root,int x){
if(root==NULL) return;
if(root->data==x) cout<<root->data<<endl;//找到访问之
if(root->data>x) search(root->lchild,x);
else search(root->rchild,x);
}
//插入操作
//左旋
void L(node* &root){
node* temp=root->rchild;//root指向根A temp指向右结点B
root->rchild=temp->lchild;//B的左子树成为A的右子树
temp->lchild=root;//A成为B的左子树
//更新A、B的高度不能忘了 AVL的保证
updateHeight(root);//更新A的高度
updateHeight(temp);//更新B的高度
//除了A、B,其他子树都没有变化,不用改东西
root=temp;//根结点设为B
}
//右旋代码
void R(node* &root){
node* temp=root->lchild;//root指向根B temp指向左结点A
root->lchild=temp->rchild;//左子树的右孩子变为根结点的左孩子(开始是,现在还是,大小关系未变)
temp->rchild=root;//根成为左子树temp的右孩子(根大)
//不能忘了更新高度
updateHeight(root);
updateHeight(temp);
root=temp;
}//写完后发现和左旋正好left变right而right变left 充分说明了对称性
//正式插入 相比较于BST每次插入之后多了两步:1.更新树高 2.判断失衡类型调整失衡(LL,LR,RR,RL)
//avl树root中插入值为v的结点
void insert(node* &root,int v){
if(root==NULL){
root=newNode(v);
return;
}
if(v<root->data){
insert(root->lchild,v);
updateHeight(root);//更新树高 左子树不为空则其高度会递归更新的 哪个结点的子树插入了,更新哪个结点,递归更新
//检查平衡态
if(getBlanceFactor(root)==2){//L (BF>1>0 肯定左边长了导致失衡 接下来判断左子树即可)
if(getBlanceFactor(root->lchild)==1){//LL 一次右旋即可
R(root);//下面直接也就return了 不用多写return了
}else if(getBlanceFactor(root->lchild)==-1){//LR 左子树左旋再根右旋 明确写出条件,直接else也行吧 只可能-1 1不会是0
L(root->lchild);
R(root);
}
}
}else{//往右边插
insert(root->rchild,v);
updateHeight(root);
//检查平衡状态 和上面完全对称
if(getBlanceFactor(root)==-2){//R 往右边插 不平衡只可能是负值 RR或RL
if(getBlanceFactor(root->rchild)==-1){//RR
L(root);
}else if(getBlanceFactor(root->rchild)==1){//RL
R(root->rchild);
L(root);
}
}
}
}
node* Create(int data[],int n){
node* root=NULL;//只能赋值NULL 不能赋别的
//插入就是在NULL处插的
for(int i=0;i<n;i++){
insert(root,data[i]);
}
return root;
}
//层序遍历
void LayerOrder(node* root){
queue<node*> q;
q.push(root);
while(!q.empty()){
node* top=q.front();
q.pop();
cout<<top->data<<" ";
if(top->lchild!=NULL) q.push(top->lchild);
if(top->rchild!=NULL) q.push(top->rchild);
}
cout<<endl;
}
int main(){
const int n=8;
int data[]={1,2,3,4,5,6,7,8};
node* root=Create(data,n);
cout<<"开始:";LayerOrder(root);
insert(root,9);
cout<<"插入9:";LayerOrder(root);
insert(root,6);
cout<<"再插入6:";LayerOrder(root);
return 0;
}