平衡二叉树(AVL树)

平衡二叉树和二叉查找树的关系:

平衡二叉树本质上也是一颗二叉查找树,是二叉查找树的改进,只是在其基础上增加了“平衡”的要求。

AVL树,防止出现链条状树,将时间复杂度保持为O(log n),链条状树会导致起不到使用二叉查找树来进行数据查询优化的目的。

如:使用序列1,2,3,4,5形成二叉查找树,那么将会形成一条链条,将时间复杂度提高到O(n)。

平衡二叉树的定义和含义:

//因此二叉平衡树的高度为log n,如log 6 约等于2,满足要求。

//平衡指的是左右子树高度差不超过1,即node[lchild].hight - node[rchild].hight<=1

//两子树的高度差叫做“平衡因子”,结点存放的是高度,用于判断二叉树是否失衡。

//初始化新结点的时候,高度初始化为1,因为叶子结点的高度就是1,新结点生成位置是叶子结点,旧结点的高度随新结点的生成而更新。

//父亲结点不能通过儿子结点的平衡因子推出自己的平衡因子,可以画个图看看明显就是错误的。

平衡二叉树的基本操作:

//二叉查找树是按一定规律建造的,因此不能随意修改二叉查找树的值,因此基本操作不包括修改,删除操作较为复杂,这里主要讲查找,插入和建立

1.平衡二叉树的查找:

//每一递归一次走到下一层,因此递归次数和高度有关。

  1. 平衡二叉树的插入:(也较为复杂)

(1)左旋

引入二叉查找树中的前驱和后继的概念,则A的前驱是五角星,A的后继是深棱形,A的后继是大于A的最小值,放到数列里面就是A后面的哪个值。

左旋的过程中,A作为B的左孩子(向左扭过去),B多出了一个孩子,A刚好缺少一个右孩子(多一个补一个)因为深棱形是A的后继,比A大,应该作为A的右孩子。

了解了过程,总结就是:B的左孩子变成了A的右孩子,B的左孩子由A来补充

助记:旋转的点要扔掉原来的孩子才能拥有新的孩子

(2)右旋

右旋和左旋是对称的过程

B的左孩子向右边扭,B作为A的右孩子,A多了一个右孩子,B少了一个左孩子,A的左孩子来到B作为其左孩子。

对于B来说黑色方格是B的前驱,是小于B的最大值,因此应该作为B的左孩子

了解了过程,总结就是:A的右孩子变成了B的左孩子,A的右孩子用B来补充

助记:旋转的点要扔掉原来的孩子才能拥有新的孩子,扔孩子捡孩子

左旋和右旋后,AB的相对位置发生了改变,记得旋转完后更新A,B的高度,再更新根结点

(3)一颗已平衡的二叉树中插入结点,一定会有结点的平衡因子发生改变(不一定失衡),此时有可能失衡,那么如何将失衡的二叉树进行调整?

由图所示,插入结点后B的失衡,A也跟着失衡,根结点到插入点后前两个结点都失衡了,不难发现只要将插入结点去掉或者调整好,A,B就能恢复平衡。

失衡的话只有两种情况,就是靠近插入结点,第一个失衡的结点,也就是上图的B点,对于B点来说,失衡只有两种可能就是平衡因子为2或者-2。

(1)当平衡因子为2

说明左树比右树高,那么左树高也有两种情况,那么树的类型只有可能是如下的两种:

//分别是LL类型和LR类型(指的是树的类型,不是指左右旋的意思),之前的哪个图是LL类型的一种情况,B的左子树的平衡因子是1。

若是LL类型,只需要A右旋即可:

A右旋:B的右孩子变成A的左孩子,A变成B的右孩子,B成为根结点。

//右旋B成为根结点,B的平衡因子变成了0。

若是LR类型,先将B进行左旋,就可以变成LL类型,然后再将A进行右旋即可

(2)平衡因子为-2

若是RR类型,只需要A左旋即可:

若是RL类型,先将B右旋,再将A左旋

其实平时写题目的时候像下面这样画出简化图是最好理解的(但要记得扔孩子捡孩子的细节

总结一下:

要懂得左旋和右旋是什么意思,然后就是按根结点的平衡因子和根结点的左右子树的平衡因子分成LL,LR,RR,RL四种类型,要懂得这四种类型如何处理才能使得其根结点的平衡因子下降为0。

在以上基础上可以探讨如何对平衡二叉树进行插入了

void insert(node* &root, int v) {
    if(root  == NULL) {
        root = newNode(v);
        return ;
    }
    if(root->data > v) {
        insert(root->lchild, v);
        updateHeight(root);//插入结点后,前面的路径的每一个结点都要更新其高度; 
        if(getBalanceFactor(root) == 2) {
            if(getBalanceFactor(root->lchild) == 1) {//LL类型; 
                R(root);
            } else if(getBalanceFactor(root->lchild) == -1) {
                R(root->lchild);
                L(root);
            }            
        }
    } else {
        insert(root->rchild, v);
        updateHeight(root);
        if(getBalanceFactor(root) == -2) {
            if(getBalanceFactor(root) == 1) {//Rl类型; 
                R(root->rchild);
                L(root);
            } else if(getBalanceFactor(root) == -1) {
                L(root);
            }            
        }
    }
} 

平衡二叉树的插入和创造的全部代码:

#include<iostream>

using namespace std;
struct node {
    int data;
    int height;
    node* lchild;
    node* rchild;
};

node* newNode(int x) {
    node* root = new node;
    root->data = x;
    root->height = 1;//平衡二叉树创造结点记得给高度初始化,叶子结点高度为1; 
    root->lchild = NULL;
    root->rchild = NULL;
    return root;
}

int getHeight(node* root) {
    if(root == NULL)//记得要NULL,否则叶子结点在计算平衡因子的时候会对NULL使用->操作; 
        return 0;
    return root->height;
}

void updateHeight(node* root) {
    root->height = max(getHeight(root->lchild), getHeight(root->rchild)) + 1;
    return ;
}

int getBalanceFactor(node* root) {
    return getHeight(root->lchild) - getHeight(root->rchild);
}

void R(node* &root) {
    node* next = root->lchild;
    root->lchild = next->rchild;
    next->rchild = root;
    updateHeight(root);
    updateHeight(next);
    root = next;
}

void L(node* &root) {//左旋和右旋记得都要使用引用,因为树的结构都发生了改变; 
    node* next = root->rchild;
    root->rchild = next->lchild;
    next->lchild = root;
    updateHeight(root);
    updateHeight(next);
    root = next;//由此处可以体现要使用引用; 
}

void insert(node* &root, int x) {//记得使用引用,因为这层是对上一层的根结点的左或者右结点本身进行操作; 
    if(root == NULL) {
        root = newNode(x);
        return ;
    }
    if(root->data > x) {
        insert(root->lchild, x);//插入结点成功后,插入过程中经历的结点结点会在回溯过程中更新,并通过判断更新后孩子高度来判断是否该结点是否失衡,从下往上调整;
        updateHeight(root);//插入结点后路径上的所有结点的高度记得要更新; 
        if(getBalanceFactor(root) == 2) {
            if(getBalanceFactor(root->lchild) == 1) {
                R(root);
            } else if(getBalanceFactor(root->lchild) == -1) {
                L(root->lchild);
                R(root);
            }
        }
    } else {
        insert(root->rchild, x);
        updateHeight(root);
        if(getBalanceFactor(root) == -2) {
            if(getBalanceFactor(root->rchild) == -1) {
                L(root);
            } else if(getBalanceFactor(root->rchild) == 1) {
                R(root->rchild);
                L(root);
            }
        }
    }
}
const int Maxn = 10010;
int n;
int arr[Maxn] = {0};

node* create(int arr[], int n) {
    node* root = NULL;
    for(int i = 0; i < n; i++) {
        insert(root, arr[i]);
    }
    return root;
}

int main() {
    cin >> n;
    for(int i = 0; i < n; i++)
        cin >> arr[i];
    node* root = create(arr, n);
    cout << root->data;    
    return 0;
}

//复习平衡二叉树的最好办法就是练习写一颗平衡二叉树

ProblemSet Problem - 1066 Root of AVL Tree (pintia.cn)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值