1 AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度 O ( l o g 2 n ) O(log_2 n) O(log2n).
2 AVL树结点的定义
代码:
template<class K,class V>
class AVLNode
{
public:AVLNode<K, V>* _left;AVLNode<K, V>* _right;AVLNode<K, V>* _parent;pair<K, V> _kv;int _bf;//balance factory (右边++,左边--)public:AVLNode(const pair<K, V> kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLNode<K, V> Node;
private:Node* _root=nullptr;
};
这个跟我们讲解的普通搜索二叉树的思路几乎是一样的,很容易理解。
3 AVL树的插入
这是我们今天要讲解的重点,也是难点。实现AVL树的方式有很多,博主采用的是平衡因子加三叉链这种方式来实现,因为相对于其他方式这种方式的理解要稍微简单些。
首先基本的框架搭建好:
bool insert(const pair<K, V> kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else return false;}cur = new Node(kv);if (parent->_kv.first > kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}return true;}
这个是我们讲解普通二叉搜索树玩剩下的,很好理解。
现在的关键是如何更新平衡因子?
我们不难发现:
- 当插入在parent左边时,平衡因子–,插入在parent右边时平衡因子++;
- 插入后parent的平衡因子为0,便不用向上更新了,如果为-1/1,便还要向上更新;
- 当parent的平衡因子为-2/2时说明已经出问题了,我们就要使出旋转大法修正,旋转完毕后便不用向上更新了。
代码解释:
//更新平衡因子while (parent){if (cur == parent->_left)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0)break;else if (parent->_bf == 1 || parent->_bf == -1){parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//已经出错了,需要旋转处理if (parent->_bf == -2 && cur->_bf == -1){//右单旋RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){//左单旋RotateL(parent);}else if (parent->_bf == 2 && cur->_bf == -1){//先右单旋,再左单旋RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){//先左单旋,再右单旋RotateLR(parent);}else{assert(false);}break;//旋转完毕已经平衡了,记得break出去}else{assert(false);}}
其中出错要旋转不外乎份4种情况:左单旋,右单旋,左右双旋,右左双旋
我们一个一个来看:
3.1 右单旋
抽象图是这样的:
看着也很好理解,这样旋转后30 60 的平衡因子都变成了0,整个树也是平衡的。
我画了一个具象图来帮助大家理解:
h==0时:
h==1时:
h==2时:
代码实现:
void RotateR(Node* parent){Node* childL = parent->_left;Node* childLR = childL->_right;Node* grand = parent->_parent;parent->_left = childLR;if (childLR)childLR->_parent = parent;childL->_right = parent;parent->_parent = childL;//别忘了,还要链接grand与childL的关系if (grand == nullptr){_root = childL;childL->_parent = nullptr;}else{if (grand->_left == parent)grand->_left = childL;elsegrand->_right = childL;childL->_parent = grand;}//更新平衡因子parent->_bf = childL->_bf = 0;}
3.2 左单旋
左单旋和右单旋类似,只是换了下位置,这里我就只给出抽象图了,不画具象图:
代码实现:
void RotateL(Node* parent){Node* childR = parent->_right;Node* childRL = childR->_left;Node* grand = parent->_parent;parent->_right = childRL;if (childRL)childRL->_parent = parent;childR->_left =parent ;parent->_parent = childR;if (grand == nullptr){_root = childR;childR->_parent = nullptr;}else{if (grand->_left == parent)grand->_left = childR;elsegrand->_right = childR;childR->_parent = grand;}parent->_bf = childR->_bf = 0;}
3.3 右左双旋
像下面这种情况,我们只是用单旋是解决不了问题的,要用双旋解决:
先以90结点右单旋转化成了我们前面讲的单旋问题,再以30左单旋即可,但是要注意分析插入后未旋转前新节点后60的平衡因子,因为60的平衡因子不同我们旋转后所更新结点的平衡因子就有所差异。注意插入后60的平衡因子可能为0.(这里建议大家画图分析)
代码实现:
void RotateRL(Node* parent){Node* childR = parent->_right;Node* childRL = childR->_left;int bf = childRL->_bf;RotateR(childR);RotateL(parent);//更新平衡因子childRL->_bf = 0;if (bf == -1){childR->_bf = 1;parent->_bf = 0;}else if (bf == 1){parent->_bf = -1;childR->_bf = 0;}else if (bf == 0){;}else{assert(false);}}
3.4 左右双旋
跟右左双旋类似,这里就不在多讲了:
代码实现:
void RotateLR(Node* parent){Node* childL = parent->_left;Node* childLR = childL->_right;int bf = childLR->_bf;RotateL(childL);RotateR(parent);//更新平衡因子childLR->_bf = 0;if (bf == -1){childL->_bf = 0;parent->_bf = 1;}else if (bf == 1){parent->_bf = 0;childL->_bf = -1;}else if (bf == 0){;}else{assert(false);}}
4 AVL树的验证
代码:
private:Node* _root=nullptr;void _inorder(Node* root){if (root == nullptr)return;_inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_inorder(root->_right);}size_t _height(Node* root){if (root == nullptr)return 0;int leftHeight = _height(root->_left);int rightHeight= _height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}bool _isbalance(Node* root){if (root == nullptr)return true;int leftHeight = _height(root->_left);int rightHeight = _height(root->_right);return (abs(leftHeight - rightHeight) <= 1) && _isbalance(root->_left) && _isbalance(root->_right);}public:void inorder(){_inorder(_root);}size_t height(){return _height(_root);}bool isbalance(){return _isbalance(_root);}
平衡二叉树的验证我们很早就讲过了,所以相信大家能够很轻易看懂代码。测试时还可以根据左右子树高度差来判断平衡因子的正确性,大家可以自己下去试试。
大家下去可以用一些随机数来测测。
有需要的老铁可以去博主码云里看看:
点击这里
好了,今天的分享就到这里了,我们下期再见。