C++_AVL树
首先我们先介绍一下搜索数据的方法
1暴力搜索
2二分搜索(问题:有序,伴随着插入维护成本高)
3二叉搜索树(问题: 极端场景下可能效率到达O(N))
4二叉平衡搜索树(红黑树/AVL树)
5多叉平衡搜索树(B树)
6哈希表
1. AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均 搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树
左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
2. AVL树节点的定义
template <class K, class V>
struct AVLTreeNode
{AVLTreeNode(const pair<K,V>& kv):_kv(kv),_parent(nullptr),_left(nullptr),_right(nullptr),_bf(0)//平衡因子 balanced {}pair<K, V> _kv;AVLTreeNode<K,V>* _parent;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;int _bf;
};
3 .AVL树的插入
插入节点后我们要去进行检查和调整!
插入的时候引入的平衡因子也要改变
1,新增在左,parent平衡因子减
2,新增在右,parent平衡因子加加
3,更新后parent的平衡因子== 0,说明parent所在的子树高度不变,不会影响祖先,不用继续沿着到root的路径去向上更新(插入结束)
3,更新后parent的平衡因子== 1or-1,说明parent所在的子树高度变化,会影响到祖先,需要沿着到root的路径去向上更新(返回到1,进行更新)
4,若更新后了parent的平衡因子== 2or-2,说明已经出问题了,这个子树已经不满足平衡,需要去对parent所在的树进行旋转,让它平衡(插入结束)
5更新到根节点 (插入结束)
4.AVL树的旋转
旋转分为很多种,我们要一一举例
4.1 左单旋
必须是插入到cur的右树,左树的情形我们之后会讲
举一个简单的栗子吧
我们左旋只改变三个节点
旋转的时候要注意
1,旋转后要满足搜索树
2,旋转后变成平衡树,且降低高度
通俗一点理解就是当parent 和cur的平衡因子为2 和1 的时候说明就是一遍高是一个斜线,我们为了使它平衡,就要去旋转,左旋也就是将向下压,左旋的结果就是增加左边的高度(parent向下压了,就使左树的高度升高与右树平衡)
下面这个图就是抽象图,可以发现旋转的结果就是cur 和parent 的平衡因子都变为0
最终的结果就是60的左边作为30的右边,30又作为60的左树(当然还要判断30是不是根,若不是根还要找到pparent(parent的父亲,这时候这个旋转的部分相当于一个子树)再去处理链接关系)
代码实现
void RotateL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;Node* pparent = parent->_parent;//先把父亲存起来parent->_parent = cur;if (pparent)//不为空说明是子树,parent不是根节点{if (pparent->_right==parent){pparent->_right = cur;}else{pparent->_left = cur;}cur->_parent = pparent;}else{_root = cur;cur->_parent = nullptr;}cur->_bf = parent->_bf = 0;
}
4.2右单旋
这个情形就是插入到较高左树的左侧,就是cur 的左树,与左单旋一样,目的就是为了让parent向右旋转,增加右树的高度,使左树高度降低
下面使抽象图
不多bb上代码,与左单旋的代码十分相似就是关系处理变了一下
void RotateR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;Node* pparent = parent->_parent;parent->_parent = cur;if (pparent){if (pparent->_right == parent){pparent->_right == cur;}else{pparent->_left = cur;}cur->_parent = pparent;}else{_root = cur;cur->_parent == nullptr;}parent->_bf = cur->_bf = 0;}
4.3双旋
1先右旋再左旋
这个图就是我们之前说的插入到cur的左边的情况,这个图形整体呈现一个折线,而不是一个直线
这就不是一边高了对于30 是右边高,对于60 是左边高
双旋的关键:因为双旋的情况是左边和右边都高,先右旋就会就会降低左树的高度,那么就是单纯的右边高,然后就去左旋(使右边高度降低)
经过这个图我们可以看到最终的结果就是60的左边给了30的右边,60的右边给了90的左边,30和60分别又作了60的左右子树
这里的平衡因子的变化才是最重要的!
1当60平衡因子为0
这时候就是h为0,60作为新插入的节点,经过变换,可知三者的平衡因子都为0
2当60的平衡因子为1,说明在60的右边插入
这时候h不为0,可知最终60的右边是90,且90是平衡的,而30(parent平衡因子为-1)
3当60的平衡因子为-1,说明在60的左边插入
这时候h不为0,可知最终60的左边是30,且30是平衡的,而60(cur平衡因子为1)
代码实现
void RotateLR(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(cur);RotateL(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curleft->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = 0;curleft->_bf = 0;}else if (bf == -1){parent->_bf = 0;cur->_bf = 1;curleft->_bf = 0;}else{assert(false);}}
2先左旋后右旋
对cur左旋就是让他左边升高,这样就变为了(90)光是左边高
跟前面一样直接上代码
void RotateLR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(cur);RotateR(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else{assert(false);}}
4.4AVL树的验证
验证原理很简单就是验证每一个节点右树减去左树的高度绝对值是不是小于二,同时可以去比较求的结果和_bf的结果是不是相同
int Height(Node* root)
{if (root == nullptr){return 0;}int Heightleft = Height(root->_left);int Heightright = Height(root->_right);return Heightleft > Heightright ? Heightleft + 1 : Heightright + 1;
}
bool isBalance()
{return _isBalance(_root);
}bool _isBalance(Node* root)
{//验证不能去中序遍历,而是要去看一看是不是左树高度与右树高度相差不超过2if (root == nullptr){return true;}int Heightleft = Height(root->_left);int Heightright = Height(root->_right);if (Heightright - Heightleft != root->_bf){cout << "平衡因子异常!" << root->_kv.first << "->" << root->_bf << endl;}return abs(Heightright - Heightleft) < 2 && _isBalance(root->_left) && _isBalance(root->_right);
}
4.5AVL树的删除(了解)
删除的步骤和插入很像,这一个部分了解就行,不用掌握
1 按照搜索树的规则删除
2 更新平衡因子
3 出现异常再去旋转
4.6AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这 样可以保证查询时高效的时间复杂度,即log_N。但是如果要对AVL树做一些结构修改的操 作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数 据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。