平衡二叉搜索树(AVL)

(附代码,简洁好理解)

目录

什么是平衡二叉树?

如何保证二叉树平衡?

左旋/右旋

左右双旋/右左双旋

代码

树的结构:

 树的高度与平衡因子:

左/右旋: 

平衡维护: 

构建树: 


什么是平衡二叉搜索树?

简单来说就是二叉搜索树的升级版。一般的二叉搜索树在建树时,可能会建成一个长长的链,比如依次插入key值:1,2,3,4,5。则建出来的二叉搜索树会是一条长链。这样不利于查找搜索。为了解决这个问题,我们可以将二叉搜索树尽量建得平衡一些,即限制二叉树与二叉树的所有子树的左右子树的高度差绝对值不能超过1。这样的树就是平衡二叉搜索树。根结点的左右子树的高度差就称作平衡二叉搜索树的平衡因子。即平衡因子=左子树高度-右子树高度。

如何保证二叉树平衡?

左旋/右旋

如果左子树高度-右子树高度>1,即平衡因子>1,那么该怎么办?

我们可以将树整体向右“旋转”,即右旋。也可以理解为把树的右半部分拉长,把左半部分上提。这样就能够使树更加趋近平衡状态。如果平衡因子<-1,也是同理,将树整体向左“旋转”,即左旋。也可以理解为把树的左半部分拉长,把右半部分上提。

具体操作就是:如果左子树高度-右子树高度>1,那么我们将左子树的根结点作为现在的根节点整体往上提。然后为了保证二叉搜索树依然有序,我们将原先的根结点变作现在的根结点的右子树根节点。原先的根节点的左子树变作原先的左子树的右子树。(可能有点绕,其实就是将树看作三部分:[原先根结点的左子树,原先根结点的左子树的右子树,原先的根结点],这三者的左右顺序无论怎么换都要保持固定,这样或许会好理解一点?)。

代码可能更加直观:

//右旋
void R(node* &root){
    node* temp=root->l;
    root->l=temp->r;
    temp->r=root;
    updateH(root);
    updateH(temp);
    root=temp;
}

//左旋
void L(node* &root){
    node* temp=root->r;
    root->r=temp->l;
    temp->l=root;
    updateH(root);
    updateH(temp);
    root=temp;
}

可以这样想,既然左子树变作了根结点,那么我原先的根结点的左子树不就空出来了?但是不能让它空着,所以我们将选左子树的右子树作为原先的根节点的左子树(因为左子树上的结点包括他的右子树都要小于原先的根结点,所以这样换了之后能够依然有序)。那左子树变作了根节点,他的右子树又被原先的根结点抢走了,他现在的右子树岂不是也空出来了?没事,让原先的根结点作你的右子树呗。于是这样调整后二叉树依然有序,且平衡。

左右双旋/右左双旋

那么只需要这样左旋/右旋一次操作就能保持二叉树平衡嘛?

不一定,考虑一种情况,左子树高度-右子树高度>1,并且左子树的右子树高于左子树的左子树,那么我们进行一次右旋操作后是怎样的?我们会发现,由于左子树的右子树在进行右旋操作后变成了现在的右子树的左子树。深度不变,那么也就意味着现在的左右子树的高度差变成了原先的左子树的左右子树高度差+1(因为左子树高度提高了,所以+1)那么还不是高度差一样超过了1嘛。所以遇到这种情况,我们不能直接进行右旋,需要对左子树先进行一次左旋后,调整左子树的左右子树高度差,保证左子树的左子树高于或等于右子树,再对整体进行右旋。这样的操作就叫做左右双旋。反之,如果是左子树高度-右子树高度<-1,并且右子树的左子树高于右子树的右子树,那么我们就需要进行右左双旋操作。这个操作只需要在进行维护平衡时判定一下就行。

代码

看代码更加直观:

树的结构:

struct node{
	int val;
	int h;//多出一个高度属性
	node* l;
	node* r;
	node(int x):val(x),h(1),l(nullptr),r(nullptr){}
};

 树的高度与平衡因子:

//高度
int getH(node* root){
    if(root==nullptr)return 0;
    return root->h;
}
void updateH(node* root){
    root->h=max(getH(root->l),getH(root->r))+1;
}
//平衡因子
int getB(node* root){
    return getH(root->l)-getH(root-r);
}

左/右旋: 

//右旋
void R(node* &root){
    node* temp=root->l;
    root->l=temp->r;
    temp->r=root;
    updateH(root);
    updateH(temp);
    root=temp;
}

//左旋
void L(node* &root){
    node* temp=root->r;
    root->r=temp->l;
    temp->l=root;
    updateH(root);
    updateH(temp);
    root=temp;
}

平衡维护: 

//维护树的平衡
void keepB(node*&root){
    if(getB(root)>1){
        if(getB(root->l)<0)L(root->l);//如果,,先进行左旋
        R(root);
    }else if(getB(root)<-1){
        if(getB(root->r)>0)R(root->r);//如果,,先进行右旋
        L(root);
    }
}

构建树: 

//插入结点
void insert(node* &nod,int val){
    if(nod==nullptr){
        nod=new node(val);
        return ;
    }
    if(val<nod->val){
        insert(nod->l,val);
        updateH(nod);
        keepB(nod);
    }else {
        insert(nod->r,val);
        updateH(nod);
        keepB(nod);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值