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(log
2
n),搜索时间复杂度O ( l o g 2 n ) O(log_2 n)O(log
2
n).
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--; else parent->_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; else grand->_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; else grand->_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); }
平衡二叉树的验证我们很早就讲过了,所以相信大家能够很轻易看懂代码。测试时还可以根据左右子树高度差来判断平衡因子的正确性,大家可以自己下去试试。
大家下去可以用一些随机数来测测。
有需要的老铁可以去博主码云里看看:
好了,今天的分享就到这里了,我们下期再见。