AVL树实现
一、AVL的概念
AVL树是最先发明的自平衡二叉查找树,AVL是一颗空树,或者具有下列性质的二叉搜索树:其左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是一颗高度平衡搜索二叉树,通过控制高度差去控制平横。
AVL树实现这里我们引入一个平衡因子的概念,每个结点都有一个平衡因子,任何节点的平衡因子等于右子树的高度减去左子树的高度,也就是说,任何平衡因子等于0/-1/1,AVL树并不是必须要平衡因子,但有了平衡因子可以更方便我们去进行观察和控制树是否平衡。
AVL树整体结点数量和分布和完全二叉树类似,高度可以控制在logN,那么增删查改的效率也可以控制在O(logN),相比二叉树有了本质的提升。

如上图:为一颗AVL树

如上图:当其插入13后,右子树不满足AVL树的概念,该进行调整。
二、AVL树的实现
1.AVL树的结构
template<class T>
struct AVLTreeNode {using Node = AVLTreeNode<T>;
AVLTreeNode(const T& data=T()) :_parent(nullptr),_left(nullptr),_right(nullptr),_data(data),_bf(0){}Node* _parent;Node* _left;Node* _right;T _data;int _bf;
};节点定义为结构体,有三叉指针及其数据存储和平衡因子构成
template<class T,class Compare=Less<T>>
class AVLTree {
public:using AVLNode = AVLTreeNode<T>;
//...
private:
//...Compare compare;AVLTreeNode<T>* _root;
};AVL树储存根节点,这里我还尝试定义仿函数来控制比较。
2.AVL树的插入
2.1AVL树插入一个值的大概过程
(1)插入一个值,按照二叉搜索树规则进行插入。
(2)新增结点以后,只会影响祖先节点的高度,也就是可能影响部分祖先结点的平衡因子,所以更新从新增节点到根节点路径上的平衡因子,实际中最好的情况下要更新到跟,有些情况更新到中间就停止了。
(3)更新平衡因子过程中没有出现问题,则插入结束
(4)更新平衡因子过程中出现不平衡,对不平衡子树旋转,旋转后本质调平衡的同时,还降低了子树的高度,不会再影响上一层,所以插入结束。
总结就是:更新平衡因子->满足->结束
->不满足->旋转
2.2平衡因子的更新
更新原则:
平衡因子=右子树高度-左子树高度
只有子树高度变化才会影响当前节点的平衡因子
插入节点会增加高度,所以新增加结点在parent的右子树,parent的平衡因子++。在左子树,parent的平衡因子--
parent所在子树高度是否变化决定了是否会继续向上更新。
更新停止条件:
更新后parent的平衡因子等于0,更新中parent的平衡因子变化为-1->0或者1->0,说明更新前parent所在的子树一高一低,插入后parent所在的子树高度不变,不会影响到parent父亲节点的平衡因子,结束
更新后parent的平衡因子等于1/-1,更新前更新中parent的平衡因子变化为0->1/0->-1,说明更新前parent子树两边一样高,新增的插入节点后,parent所在的子树一边高一边低,parent所在子树符号平衡条件,但高度增加了1,会影响parent父亲节点的平衡因子,所以要继续向上更新
更新后parent的平横因子为2/-2,更新前更新中parent的平衡因子变化为1->2/-1->-2,说明更新前parent子树一边高一边低,新增的插入节点在高的那边,parent所在的子树高的那边更高了,破坏了平衡parent所在子树不符合平衡要求,需要旋转处理,旋转的目标有两个:1、把parent子树旋转平衡。2、降低parent子树的高度,恢复到插入结点以前的高度。所以旋转后不需要继续向上更新,插入结束(局部便可以处理,不用继续向上)
不断更新,更新到根,根的平衡因子是1/-1也停止了


插入结点及更新平衡因子的代码实现
bool insert(const T& data) {AVLTreeNode<T>* Node = new AVLTreeNode<T>(data);if (_root == nullptr) {_root = Node;}AVLTreeNode<T>* parent = _root;while (parent!=nullptr) {//右边if (compare(parent->_data, data)) {if (parent->_right != nullptr) {parent = parent->_right;}else {parent->_right = Node;Node->_parent = parent;break;}}//去重else if (data == parent->_data) {return false;}//左边else {if (parent->_left != nullptr) {parent = parent->_left;}else {parent->_left = Node;Node->_parent = parent;break;}}}while (parent) {//如果更新到根节点,就结束//更新平衡因子if (parent->_left != nullptr) {if (parent->_left == Node)parent->_bf--;}if (parent->_right != nullptr) {if (parent->_right == Node)parent->_bf++;}//判断//等于0结束if (parent->_bf == 0) {break;return true;}//等于1/-1继续向上更新else if(parent->_bf==1||parent->_bf==-1){Node = parent;parent = parent->_parent;}///等于2/-2不平衡了,开始旋转else if (parent->_bf == 2 || parent->_bf == -2) {//此处为旋转代码break;}else {assert(false);}}return true;}3.旋转
3.1旋转的原理
1.保持搜索树的规则
2.让旋转的树从不平衡变平衡,其次降低树的高度
旋转分为四种,左单旋/右单旋/左右双旋/右左双旋
3.2右单旋
如图,展示的是以10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0)(为什么要抽象,是因为情况会有很多很多中,这里以简代繁),a/b/c均符合AVL树的要求,它代表了所有右单旋的情景,实际右单旋的形态有很多种。
在a子树中插入一个新结点,导致a子树的高度变为h+1,不断向上更新平衡因子,导致10的平衡因子变为-2,10为根的树的左右高度差超过1,违反平衡规则。10为根的树左边太高了,需要往右边旋转,控制两棵树平衡。
旋转核心步骤。因为5<b子树的值<10.所以将b变为10的左子树,10变为5的右子树,5变为这课树新的根,符合搜索树的规则,控制了平衡,同时这棵树的高度恢复到了插入之前的h+2,符合旋转规则。如果10为整个AVL树的子树,旋转后不会再影响上一层,插入结束了。

这种情况下,a/b/c都为空,与上面一样。
3.3 右单旋代码实现
void RotateR(AVLNode* parent) {AVLNode* SubL = parent->_left;AVLNode* NodeR = SubL->_right;//爷爷辈和左子树根节点连if (parent == _root) {//父节点为根节点,左子树的根节点要成为AVL树的根结点,因此将其parent置为nullptr_root = SubL;SubL->_parent = nullptr;}else {//不为根节点就要找爷爷结点,因为右旋后父结点会改变,爷爷结点要与父亲结点相连AVLNode* pparent = parent->_parent;//找父节点是爷爷结点的左结点还是右结点if (pparent->_right == parent) {pparent->_right= SubL;SubL->_parent = pparent;}else {pparent->_left = SubL;SubL->_parent = pparent;}}//左子树和parent左链接parent->_left = SubL->_right;if(NodeR!=nullptr)NodeR->_parent = parent;//parent和左子树的根节点连parent->_parent = SubL;SubL->_right = parent;//更新平衡因子SubL->_bf = 0;parent->_bf = 0;
}3.4 左单旋
如图展示的是10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0),a/b/c均符合AVL树的要求,10可以是整棵树的根也可以是局部的根。
在a子树插入一个新结点,导致a子树的高度从h变为h+1,不断向上更新平衡因子,导致10的平衡因子从1变为2,10为根的树左右高度差超过1,违反平衡规则,10为根的树右边太高了,需要往左边旋转,控制两棵树的平衡
旋转核心步骤,因为10<b的子树<15,因此,让b作为10的子树,15作为a和10的parent,符合搜索树的规则,控制了平衡,同时这棵树的高度恢复到了插入之前的h+2,符合原则,如果插入之前10整颗树的一个局部子树,旋转后不会再影响上一层,插入结束。

3.5 左单旋代码实现
//左单旋
void RotateL(AVLTreeNode<T>* parent) {AVLNode* SubR = parent->_right;AVLNode* NodeL = SubR->_left;//爷爷辈和左子树根节点连if (parent == _root) {//parent为根节点,此时右子树的根节点要成为AVL树的根结点,右子树的根结点的parent要为nullptr_root = SubR;SubR->_parent = nullptr;}else {//不为空,则找爷爷结点,让右子树的根节点的parent为爷爷结点AVLNode* pparent = parent->_parent;//找parent为爷爷结点的左孩子还是右孩子,因为要更新parent结点if (pparent->_left == parent) {pparent->_left = SubR;SubR->_parent = pparent;}else {pparent->_right = SubR;SubR->_parent = pparent;}}//左子树和parent左链接parent->_right = SubR->_left;if(NodeL!=nullptr)NodeL->_parent = parent;//parent和左子树的根节点连parent->_parent = SubR;SubR->_left = parent;//更新平衡因子SubR->_bf = 0;parent->_bf = 0;
}3.6 左右双旋
如图可以看出,左边高时,如果插入位置不在a,而是插入在b子树,b子树高度从h变为h+1,引发旋转,有单旋无法解决问题,右单旋后,我们的树依旧不平衡。右单旋解决的纯粹的左边高,但在插入b子树中,10为根的树不在是单纯的左边高,对10为左边高,对5为右边高,需要两次旋转才能解决,以5为旋转点进行一个左单旋,以10为旋转点进行一个右单旋,这课树就平衡了

图一

图二
图一图二分别为左右双旋中h==0和h==1具体场景分析。下面我们将a/b/c子树抽象为高度h的AVL子树进行分析,另外我们需要把b子树的细节进一步展开为8和左子树e和右子树f(高度为h-1),因为我们要对b的父亲5进行左单旋,左单旋会移动b的左子树。b子树新增结点的位置不同,平衡的细节也不同,通过观察8的平衡因子不同,这里我们分三种场景讨论
场景1:h>=1时,新增结点插入在e子树,e子树高度变为h并不断更新8 5 10平衡因子,导致旋转,其中10的平衡因子为-2,5的平衡因子为1,符合(左边高的右边高)进行左右双旋,旋转后的5平衡因子为0,8的平衡因子也为0,10的平衡因子为1.
场景2:h>=1时,新增结点插入在f子树,f子树的高度变为h并不断更新8 5 10平和因子,导致旋转,其中10的平衡因子为-2,5的平衡因子为1,符合(左边高的右边高)进行左右双旋,旋转后5
的平横因子为-1,8的平衡因子为0,10的平衡因子为0.
场景3:h==0时,a/b/c都是空树,b自己是一个新增结点,不断更新5->10平衡因子,引发旋转,其中8的平衡因子为0,旋转后8和10和5的平衡因子均为0.

3.7 左右双旋代码实现
//左右双旋
void RotateLR(AVLNode* pParent) {//左边高的右边高AVLNode* parent = pParent->_left;AVLNode* SubR = parent->_right;int bf = SubR->_bf;RotateL(parent);RotateR(pParent);if (bf == 0) {SubR->_bf = 0;parent->_bf = 0;pParent->_bf = 0;}else if (bf == 1) {parent->_bf = -1;SubR->_bf = 0;pParent->_bf = 0;}else if (bf == -1) {parent->_bf = 0;SubR->_bf = 0;pParent->_bf = 1;}
}3.8 右左双旋
与左右双旋类似

3.9 右左双旋代码实现
//右左双旋
void RotateRL(AVLNode* pParent) {//右边高的左边高AVLNode* parent = pParent->_right;AVLNode* SubL = parent->_left;int bf = SubL->_bf;RotateR(parent);RotateL(pParent);if (bf == 0) {SubL->_bf = 0;parent->_bf = 0;pParent->_bf = 0;}else if (bf == 1) {parent->_bf = 0;SubL->_bf = 0;pParent->_bf = -1;}else if (bf == -1) {parent->_bf = 1;SubL->_bf = 0;pParent->_bf = 0;}
}4.AVL树平衡检测
我们实现的AVL树是否合格,我们通过检查左右子树高度差的程序进行反向验证,同时检查一下结点的平衡因子更新是否出了问题。
//判断树深int _Heighttree(AVLTreeNode<T>* root) {if (root == nullptr) {return 0;}return _Heighttree(root->_left) >_Heighttree(root->_right)? _Heighttree(root->_left) + 1: _Heighttree(root->_right)+1;}
//判断是不是AVL树bool _IsAVLTree(AVLNode* root) { if (root == nullptr) {return true;}bool left = _IsAVLTree(root->_left);bool right = _IsAVLTree(root->_right);if (_Heighttree(root->_right) - _Heighttree(root->_left) == root->_bf) {return true&& left && right;}else {return false;}}测试代码
void AVLTreeTest() {const int N = 100000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++) {v.push_back(rand() + i);}size_t begin2 = clock();AVLTree<int> t;for (auto e : v) {t.insert(e);}size_t end2 = clock();cout << "insert:" << end2 - begin2 << endl;cout << t.IsAVLTree() << endl;cout <<"height:"<< t.Heighttree() << endl;cout << "size:" << t.size() << endl;}