【C++】---AVL树详解
一、AVL树的概念
1、二叉搜索树的缺点
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
2、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 log_2 n log2n)。
二、AVL树的定义
由于要实现AVL树的增删改查,所以定义AVL树的节点,就需要定义parent,否则插入节点时,不知道要链接到树里面哪个节点下面。
1、AVL树的节点定义(以key_value模型为例)
cpp
template <class K ,class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
pair<K, V> _kv;// 结点
int _bf ; // 平衡因子
// 构造函数
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
,_kv(kv)
,_bf(0)
{
}
};
2、AVL树的定义
cpp
template<class K,class V>
struct AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
//构造函数
AVLTree()
:_root(nullptr)
{}
void _Destroy(Node* root)
{
if (root == nullptr)
{
return;
}
_Destroy(root->_left);
_Destroy(root->_right);
delete root;
}
//重载operator[]
V& operator[](const K& key)
{
pair<Node*, bool> ret = Insert(make_pair(key, V()));
return ret.first->_kv.second;
}
//析构函数
~AVLTree()
{
_Destroy(_root);
_root = nullptr;
}
private:
Node* _root;
};
三、AVL树的插入
1、插入节点
(1)插入的时候首先要判断特殊情况,如果这个树为空,我们就要这个插入的节点当做树的根。
(2)如果树不为空,分为以下三种情况:
- 插入的kv.first比cur的大,kv往右走
- 插入的kv.first比cur的小,kv往左走
- 相等,插入失败 (因为 二叉搜索树不允许数据冗余)
cpp
// insert插入
bool insert(const pair<K, V> kv)
{
//1. 处理特殊情况
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;// 用cur来遍历整个树
while (cur)
{
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
// 走到此处 说明cur走到要插入的位置了
cur = new Node(kv);
// 这就是要保存cur的parent的原因 便于插入后的双向链接
if (kv.first < parent->_kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;// 双向链接
}
2、控制平衡
(1)更新平衡因子
一个节点的平衡因子是否需要更新,取决于它的左右子树的高度是否发生变化。插入一个节点,如果它的父亲的平衡因子需要更新,那么它所在这条路径的从父亲到根的所有节点的平衡因子都需要更新。因此
①如果新增节点是父亲的左子树,那么parent->_bf--
②如果新增节点是父亲的右子树,那么parent->_bf++
更新后:
a.如果parent->_bf=0,则停止更新
b.如果parent->_bf==1||-1,需要继续往上更新(说明以parent为根的子树高度变了,由0变成了1或-1,有可能导致parent的parent的平衡因子=2或=-2)
c.如果parent->_bf=2||-2已经不平衡了,那么需要旋转处理
cpp
// 更新平衡因子
while (parent)
{
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 左单旋
if (parent->_bf == 2 && cur->_bf == 1)
{
LS_rotation(parent);
}
// 右单旋
else if (parent->_bf == -2 && cur->_bf == -1)
{
RS_rotation(parent);
}
// 右左双旋
else if (parent->_bf == 2 && cur->_bf == -1)
{
/*RS_rotation(parent->_right);
LS_rotation(parent);*/
RL_rotation(parent);
}
// 左右双旋
else if (parent->_bf == -2 && cur->_bf == 1)
{
/*LS_rotation(parent->_left);
RS_rotation(parent);*/
LR_rotation(parent);
}
break;
}
else
{
assert(false);
}
}
return true;
(2)旋转
旋转处理有4种:右单旋、左单旋、右左单旋、左右单旋
注意:目前以平衡因子 = 右子树的高度 - 左子树的高度 为标准
①右单旋
右单旋顾名思义就是根的左子树高度太高了,需要往右边旋转。
右单旋还有一个特征就是父亲的平衡因子为-2,父亲的左孩子的平衡因子为-1
cpp
// 右单旋
else if (parent->_bf == -2 && cur->_bf == -1)
{
RS_rotation(parent);
}
右单旋 的 代码块:
cpp
// 右单旋
void RS_rotation(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//
parent->_left = subLR;
//
if (subLR)
subLR->_parent = parent;
//
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
//
if (parent == _root)
{
subL = _root;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
parent->_bf = subL->_bf = 0;
}
②左单旋
左单旋顾名思义就是根的右子树高度太高了,需要往左边旋转。
左单旋还有一个特征就是父亲的平衡因子为2,父亲的右孩子的平衡因子为1
cpp
// 左单旋
if (parent->_bf == 2 && cur->_bf == 1)
{
LS_rotation(parent);
}
左单旋 代码:
cpp
// 左单旋
void LS_rotation(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
//
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
//
if (parent == _root)
{
subR = _root;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
parent->_bf = subR->_bf = 0;
}
③左右单旋
新节点插入较高左子树的右侧---左右:先左单旋 再右单旋
cpp
// 左右双旋
else if (parent->_bf == -2 && cur->_bf == 1)
{
/*LS_rotation(parent->_left);
RS_rotation(parent);*/
LR_rotation(parent);
}
cpp
// 左右双旋 : 最主要的是 更新平衡因子
void LR_rotation(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
// 我们以 subLR 的平衡因子为标准:有三种情况
// 1、如果在subLR的左边插入 那么最后旋转的结果 :insert的这个subL的右边!
// 2、如果在subLR的右边插入 那么最后旋转的结果 :insert的这个parent的左边!
// 3、subLR本身就是自增!
int bf = subLR->_bf;
// 旋转
LS_rotation(parent->_left);
RS_rotation(parent);
// 更新平衡因子
// (1)在subLR的左边插入:bf==-1
if (bf == -1)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = -1;
}
// (2)在subLR的右边插入:bf==1
else if (bf == 1)
{
subLR->_bf = 0;
subL->_bf = -1;
parent->_bf = 0;
}
// (3)subLR 是自增! bf==0
else if (bf == 0)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
④右左单旋
新节点插入较高右子树的左侧---右左:先右单旋 再左单旋
cpp
// 右左双旋
else if (parent->_bf == 2 && cur->_bf == -1)
{
/*RS_rotation(parent->_right);
LS_rotation(parent);*/
RL_rotation(parent);
}
cpp
// 右左双旋 : 最主要的是 更新平衡因子
void RL_rotation(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//
int bf = subRL->_bf;
RS_rotation(parent->_right);
LS_rotation(parent);
if (bf == -1)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 1;
}
else if (bf == 1)
{
parent->_bf = -1;
subRL->_bf = 0;
subR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 0;
}
else
{
assert(false);
}
}
(3)完整 四个旋转的详细图如下:
(4)完整 更新平衡因子(规律)图解如下:
四、AVL树的查找
查找比较简单:
(1)如果key比当前节点大,那就继续向右查找;
(2)如果key比当前节点小,那就继续向左查找;
(3)如果key恰好等于当前节点,找到了
cpp
// find函数
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key < cur->_kv.first)
{
cur = cur->_left;
}
else if (key > cur->_kv.first)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
五、AVL树的高度
计算树高度肯定要递归计算:
(1)计算左右子树的高度
(2)谁的高度大,那AVL树的高度就为较高子树的高度+1
cpp
int _Hight(Node* root)
{
if (root == nullptr)
{
return 0;
}
return max(_Hight(root->_left), _Hight(root->_right)) + 1;
}
六、判断是否为AVL树(即:判断是否平衡)
检查树是否是AVL树:
(1)获取左右子树高度
(2)根据左右子树高度计算平衡因子
(3)当平衡因子<=2 || -2时就是平衡的
cpp
bool _Is_Blance(Node* root)
{
if (root == nullptr)
{
return true;
}
int _leftHight = _Hight(root->_left);
int _rightHight = _Hight(root->_right);
// 不平衡
if (abs(_leftHight- _rightHight) >= 2)
{
return false;
}
// 顺变检查一下 平衡因子是否正确!
if ((_rightHight - _leftHight) != root->_bf)
{
cout << root->_kv.first << endl;
return false;
}
return _Is_Blance(root->_left)
&& _Is_Blance(root->_right);
}
七、AVL树的遍历
遍历也很简单:递归遍历左子树和右子树即可
cpp
// 中序遍历:
void InOrder()
{
_InOrder(_root);
cout << endl;
}
// 中序遍历 写在私有上面
void _InOrder(const Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
八、时间复杂度
AVL树的操作时,需要找到位置,因此时间复杂度为高度次 ,时间复杂度O( l o g 2 n log_2 n log2n)。