💓博主CSDN主页:麻辣韭菜💓
⏩专栏分类:C++知识分享⏪
🚚代码仓库:C++高阶🚚
🌹关注我🫵带你学习更多C++知识
🔝🔝
目录
前言
AVL 树
1.1 AVL树的概念
1.2 AVL树节点的定义
编辑
1.3左旋
1.3右旋
1.4双旋
前言
C++ set&&map 这篇从了解到使用map,map也是搜索二叉树,因为搜索二叉树会出现歪脖子树的情况,本篇就讲如何解决歪脖子树。记住口令 旋转 旋转 旋转 !!! 重要的事说三边。 搜索二叉树加入平衡因子 旋转 就成了传说之中的AVL树
AVL 树
1.1 AVL树的概念
二叉搜索树虽可以缩短查找的效率,但 如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下 。因此,两位俄罗斯的数 G.M.Adelson-Velskii和E.M.Landis在 1962 年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过 1( 需要对树中的结点进行调整 ) ,即可降低树的高度,从而减少平均搜索长度。一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
- 如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在
- $O(log_2 n)$,搜索时间复杂度O($log_2 n$)。
这里需要强调一下:AVL树不一定有平衡因子, 还有
递归更新高度:在插入或删除节点后,AVL树会递归地更新从该节点到根节点的所有祖先节点的高度。这是必要的,因为平衡因子是基于节点的高度来计算的。通过递归更新高度,AVL树能够准确地维护每个节点的平衡因子
这里我们用平衡因子来实现,因为比较好理解!!!
1.2 AVL树节点的定义
我们先把AVL节点定义出来
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)
{}
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
private:
Node* _root = nullptr;
};
插入代码
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->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
}
插入之后 根据下图的规则,我们更新_bf
//更新平衡因子
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
parent->_bf--;
if (parent->_bf == 1 || parent->_bf == -1)
{
//继续更新
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 0)
{
break;
}
1.3左旋
对上图新增插入10 这时右树的平衡因子 8 7 就变成了2 绝对值大于1,已经失衡了!
这时就要通过旋转来调节平衡高度。
这里强调代码不是重点 画图理解才是重点,前面有二叉树基础,代码非常好写,画图才能理清这里的关系!!!
这里我们从上面得出几个结论:
平衡因子发生变化 决定是否更新父亲节点,而爷爷节点更新也是取决于父亲节点的平衡因子是否发生变化 变了就继续往上更新,不变则不更新。
那就有3种情况:
- parent-> bf == 1 || parent-> bf == -1 parent的子节点发生变化,继续更新。
因为 插入之前 parent-> bf == 0 插入之后要么在左、要么在右 说明高度变了
- parent-> bf ==2 || parent-> bf == -2 这种情况 parent的子树明显不平衡,需要旋转处理
- parent-> bf == 0 这种情况 说明插入之前的parent这个节点是一高一低的,插入后刚好填到矮的那一边,两边子树的高度平衡不需要处理。
那既然 parent的bf是2或者-2这两个值需要旋转处理。那什么情况左旋转?
我先说结论:右边的子树高 就左旋转。看图
图画出来就好办了,把图用代码实现。
else if (parent->_bf == 2 || parent->_bf == -2)
{
//旋转
if (parent->_bf == 2 && cur->_bf == 1) //左旋
{
RotateL(parent);
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
subR->_left = parent;
parent->_right = subRL;
if(subRL)
subRL->_parent = parent;
Node* pparent = parent->_parent;
parent->_parent = subR;
if (pparent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
subR->_parent = pparent;
}
parent->_bf = subR->_bf = 0;
}
1.3右旋
else if (parent->_bf == -2 && cur->_bf == -1) //右旋
{
RotateR(parent);
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
subL->_right = parent;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* pparent = parent->_parent;
parent->_parent = subL;
if (pparent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
subL->_parent = pparent;
}
parent->_bf = subL->_bf = 0;
}
1.4双旋
双旋一共有两种情况:
- 先右旋,再左旋。
- 先左旋,再右旋 。
那什么时候先右旋,再左旋? 什么时候又先左旋,再右旋?先说结论:
- 新增节点插入较高左子树的右侧,那么就先左单旋再右单旋。
- 新增节点插入较高右子树的左侧,那么就先右单旋再左单旋。
先来讲解左右双旋
那如果我们复用之前的左右函数就会出现一个问题,parent 和sub*这两个节点的_bf设置为0 如上图 parent的_bf是1 ,subl是0。这时又有三种情况。
第一种情况就入上图所示。
第二种情况 新增节点插入到C这个子树 那么parent的_bf就是0 subl的_fd为-1。
第三种情况 如果h等于0那60就是新增节点,它们的_bf为0没错。
else if (parent->_bf == -2 && cur->_bf == 1) //先左再右
{
RotateLR(parent);
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
右左双旋
第一种情况就入上图所示。
第二种情况 新增节点插入到b这个子树 那么parent的_bf就是0 subR的_fd为1。
第三种情况 如果h等于0那60就是新增节点,它们的_bf为0没错。
else if (parent->_bf == 2 && cur->_bf == -1) //先右再左
{
RotateRL(parent);
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
代码 我是单独拆分了,直接复制可能会报错,为了大家好理解我做成模块化!
这里强调的是 其实AVL树,我们根本就不需要手撕,前人已经帮我们造好轮子了,我们自己根本就不需要自己造轮子。主要是画图理解旋转的过程。
要看代码的完整性可以去看我的码云