高阶数据结构(壹)——一步一步教你手撕AVL树(增删查改)画图详解,内含代码实现包含注释)
Posted 努力学习的少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高阶数据结构(壹)——一步一步教你手撕AVL树(增删查改)画图详解,内含代码实现包含注释)相关的知识,希望对你有一定的参考价值。
🍎作者:努力学习的少年
🍎个人简介:双非大二,一个正在自学c++和linux操作系统,写博客是总结知识,方便复习
🍎目标:进大厂
🍎 如果你觉得文章可以的话,麻烦你给我点个赞和关注,感谢你的关注!
目录
📚 1. AVL树的概念
- AVL树一颗二叉搜索树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1。
如果一颗AVL树有n个节点,其高度可以保持在O(logn),搜索的时间复杂度是O(logn).
📚 2. AVL树节点的定义
template<class K,class V>
struct AVLTreeNode
AVLTreeNode* _left;//左孩子节点
AVLTreeNode* _right;//右孩子节点
AVLTreeNode* parent;//父亲节点
int _bf;//平衡因子
pair<K, V> _kv;//存储数据
AVLTreeNode(pair<K,V> kv)//节点的构造函数
:_left(nullptr)
,_right(nullptr)
,parent(nullptr)
,_kv(kv)
,_bf(0)
;
📚 3. AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
- 按照二叉搜索树的方式直接插入新节点。
- 调整平衡因子(插入在左孩子节点,平衡因子-1,插入到右孩子节点,平衡因子+1)
新节点插入到二叉搜索树中:
插入的过程:根据搜索树的性质找到空位置,将新节点插入到该空位置。
插入过程:
插入的代码:
bool insert(const pair<K, V>& kv)
if (_root == nullptr)//如果是空树,直接插入
_root = new node(kv);
return true;
node* cur = _root;
node* parent = nullptr;
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;
node* newnode = new node(kv);//创建新的节点
newnode->parent = parent;//将新的节点与parent进行连接
if (parent->_kv.first > kv.first)//判断新节点是连接在parent的左孩子还是右孩子
parent->_left = newnode;
parent->_bf--;//插入到parent的左孩子,parent的平衡因子-1
else if (parent->_kv.first < kv.first)
parent->_right = newnode;
parent->_bf++; //插入到parent的右孩子,parent的平衡因子 +1 1
.......
.......
当然,AVL树中插入新节点后还需要保持是AVL树,所以我们需要向上调整平衡因子。
在插入之前,parent的平衡与因子可能取值是0,1,-1(AVL树的左右子树的高度差不超过1)
插入时,parent的平衡因子的调整分为以下两种情况
- 如果cur是插入到parent右节点,则parent的平衡因子加1
- 如果是插入到parent的左节点,平衡因子-1;
插入后,parent的平衡因子的可能取值是0,1,-1,2,-2.
- 如果parent的平衡因子是0,那么插入前的parent的平衡因子可能取值是1或者-1.插入后被调整为0,此时AVL树高度不变,则插入成功。 如下图:插入13节点后,parent高度不变,不需要往上更新平衡因子。
- 如果parent的平衡因子是-1或者+1,那么插入前的parent的平衡因子一定是0,此时高度增加,需要对parent上面的节点的平衡因子进行调整。如下图插入25这个节点后,则parent的高度增加,则上面的节点的树的高度也增加,需要调整上面的平衡因子。
- 如果parent的平衡因子是-2或2,那么parent则违反AVL树的性质,需要对它进行旋转使该树保持平衡。旋转后高度降低,不需要向上调整平衡因子。具体旋转如下解释。
旋转可以分为右单旋,左单旋,右左双旋,左右双旋这4中旋转
📚 3. 右单旋
新节点插入到较高的左子树的左侧,则需要进行有单旋,如下这种情况。
触发右单旋的条件:parent的平衡因子为-2,sub的平衡因子为-1.
上面这个图是抽象图,a,b,c代表着数种情况,但总之,a,b,c的高度都为h。
在上图插入之前,AVL树是平衡的,但我们在节点30的左子树插入一个节点后,导致以根节点的60的二叉平衡树不平衡,即60的平衡因子为-2.并且新插入的节点是在左子树的左子树上,所以我们需要对它进行右单旋。
在旋转的过程中,我们需要知道:60可能是根节点,也可能是是子树
过程:parent连接subL中的右子树,然后subL的右子树连接parent,parent的parent连接subL,subL
的parent连接pparent,pparent连接subL需要根据以下三种情况讨论。
右单旋的代码实现:
void RightRevole(node* parent)
node* pparent = parent->parent;
node* subL = parent->_left;
parent->_left = subL->_right;//subL的右孩子连接到parent的左孩子节点上
if (pparent)//pparent不为空
if (pparent->_left == parent)//parent位于pparent左节点上
pparent->_left = subL;//subL连接到pparent的左孩孩子节点上
else if (pparent->_right == parent)//parent位于pparent右节点上
pparent->_right = subL;//subL连接到pparent的右孩孩子节点上
subL->parent = pparent;//pparent连接到subL的parent的节点上
else//pparent为nullptr
_root = subL;//subL直接变为根节点
subL->parent = nullptr;//subL的parent指向空
subL->_right = parent;//subL的右节点连接parent
parent->parent = subL;//parent的parent连接subL
subL->_bf = parent->_bf = 0;//subL和parent的平衡因子变为0
📚 4. 左单旋
实际情况跟右单旋基本差不多的,只要看懂右单旋,左单旋也就会了。
左单旋的触发条件是:parent的平衡因子为2,subR的平衡因子为1。
左单旋的过程:
void LeftRevole(node*& parent)
node* pparent = parent->parent;
node* subR = parent->_right;
parent->_right = subR->_left;
if (pparent)//pparent不为空
if (pparent->_left == parent)//parent为pparent的左孩子的节点上
pparent->_left = subR;//subR连接到pparent的左孩子节点上
else if (pparent->_right == parent)//parent为pparent的右孩子的节点上
pparent->_right = subR;//subR连接到pparent的右孩子节点上
subR->parent = pparent;//subR的parent连接pparent
else//pparent为空
_root = subR;//subR直接为根节点
_root->parent = nullptr;//_root的parent的指向空
subR->_left = parent;//subR的left连接parent
parent->parent = subR;//parent的parent连接subR
subR->_bf = parent->_bf = 0;//subR和parent的平衡因子变为为0
📚 5. 左右双旋
新节点插入到较高左子树的右侧上,此时的就需要进行左右双旋。
左右双旋的条件:parent的平衡因子为-2,subL的平衡因子为1.
过程:先对subL进行左单旋,然后在对parent进行右单旋,最后在调节平衡因子(下列中有分析)。
例如下面这种情况:
左右单旋的平衡因子有三种情况:
- 如果新节点插入到subLR的左子树(b子树)上,旋转前subLR的平衡因子为-1.那么结果如上图所示,旋转后subLR的平衡因子为0,subL的平衡因子为0,parent的平衡因子为1.
- 如果新节点插入到subLR的右子树(c子树)上,旋转前subLR的平衡因子为1,如下图所示,旋转后subLR的平衡因子为0,subL的平衡因子为-1,parent的平衡因子为0.
3. 另外一种情况是subLR的平衡因子为0,通过左右旋转后subLR,subL,parent的平衡因子都为0.
void LRrevole(node* parent)
node* subL = parent->_left;
node* subLR = parent->_left->_right;
LeftRevole(subL);//对subL进行左单旋
RightRevole(parent);//对parent进行右单旋
if (subLR->_bf == -1)//subLR的平衡因子为-1
parent->_bf = 1;
subLR->_bf = 0;
subL->_bf = 0;
else if (subLR->_bf == 1)//subLR的平衡因子为1
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
else if (subLR->_bf == 0)//subLR的平衡因子
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 0;
📚 6. 右左双旋
新节点插入到较高右子树的左侧,此时就需要进行右左双旋。
右左双旋的条件为:parent的平衡因子为2,subR的平衡因子为-1。
右左双旋后的平衡因子有三种情况:
- 如果subRL的平衡因子为1,则旋转后parent的平衡因子为-1,subR的平衡因子为0,subRL的平衡因子为0.
- 如果subRL的平衡因子为-1,则旋转后parent的平衡因子为0,subR的平衡因子为1,subRL的平衡因子为0.
- 如果subRL的平衡因子为0,则旋转后parent,subR和subRL的平衡因子都为0.
代码实现:
void RLrevole(node* parent)
node* subR = parent->_right;
node* subRL = subR->_left;
RightRevole(subR);//对subR的进行右单旋
LeftRevole(parent);//对parent进行左单旋
if (subRL->_bf == -1)//subRL的平衡因子为-1
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
else if (subRL->_bf== 1)//subRL的平衡因子为1
parent->_bf = -1;
subRL->_bf = 0;
subR->_bf = 0;
else if (subRL->_bf == 0)//subRL的平衡因子为0
subRL->_bf = 0;
subR->_bf = 0;
parent->_bf = 0;
📚 7. 插入的总的代码(含注释)
bool insert(const pair<K, V>& kv)
if (_root == nullptr)//如果是空树,直接插入
_root = new node(kv);
return true;
node* cur = _root;
node* parent = nullptr;
while (cur)//cur不为空
if (cur->_kv.first > kv.first)//如果值小于该节点的值,则去它的左子树寻找
parent = cur;
cur = cur->_left;
else if (cur->_kv.first < kv.first)//如果值大于该节点的值,则去它的右子树寻找
parent = cur;
cur = cur->_right;
node* newnode = new node(kv);//创建新的节点
newnode->parent = parent;//将新的节点与parent进行连接
if (parent->_kv.first > kv.first)//判断新节点是连接在parent的左孩子还是右孩子
parent->_left = newnode;
parent->_bf--;
else if (parent->_kv.first < kv.first)
parent->_right = newnode;
parent->_bf++;
//向上更新平衡因子
while (parent)
node* pparent = parent->parent;
if (parent->_bf == 0)//平衡因子等于0直接跳出循环
break;
else if (parent->_bf == 1 || parent->_bf == -1)//平衡因子为1或者-1,需要更新上面的平衡因子
if (pparent==nullptr)
break;
else if (pparent->_left == parent)//parent是位于pparent的左节点
pparent->_bf--;//pparent的平衡因子-1
else if (pparent->_right == parent)//parent是位于pparent的右节点
pparent->_bf++;//pparent的平衡因子+1
parent = pparent;//parent向上爬
else if (parent->_bf == 2 || parent->_bf == -2)//进行旋转
if (parent->_bf == 2)//parent的平衡因子为2
if (parent->_right->_bf == 1)//parent的右孩子的平衡因子为1
//左单旋
LeftRevole(parent);
else if (parent->_right->_bf == -1)//parent的右孩子的平衡因子为-1
//先右旋在左单旋
RLrevole(parent);
else if (parent->_bf == -2)//parent的平衡因子为-2
if (parent->_left->_bf == -1)//parent的左孩子的平衡因子为-1
//右单旋
RightRevole(parent);
else if (parent->_left->_bf == 1)//parent的右孩子的平衡因子为1
//先左单旋在右单旋
LRrevole(parent);
break;//旋转之后都是平衡树,直接跳出循环
📚 8. AVL树查找的代码
node* find(const K& key)
node* cur = _root;
while (cur)
if (cur->_kf->first > key)//key值小于cur,到左子树找
cur = cur->_left;
else if (cur->_kf->first < key)//key值大于cur,到右子树找
cur = cur->_right;
else if (cur->_kf->first == key)//找到了,返回该节点
return cur;
return nullptr;//找不到,返回空
📚 9. 删除
AVL树的删除也是分为两步:
- 找到要删除节点,将该节点删除。
- 调整平衡因子
删除AVL树有四种情况:
情况一
删除的节点不存在左右子树。
那么我们直接将该节点删除即可,例如我们要删除cur,我直接将cur删除掉即可,然后在调整parent的平衡因子。
情况二
要删除的节点只存在左子树,不存在右子树。
例如删除15这个节点,我们需要将subL和parent互相连接,然后parent的平衡因子+1或者-1.
过程如下:
情况三
要删除的节点只存在右子树,不存在左子树。
例如删除4这个节点,我们需要将subR和parent互相连接,然后parent的平衡因子+1或者-1.
过程如下:
情况四
要删除的节点存在左右子树。
例如我们要删除11这个节点,我们可以找到cur右子树上的最小值Rightmin(也就是右子树上最左边上的值)然后将该Rightmin节点的值赋值给cur这个节点,将Rightmin这个节点删除掉,在删除Rightmin节点之前,需要先让parent指向Rightmin的parent,调整parent的平衡因子,因为我们删除的是Rightmin,是会影响Rightmin以上的平衡因子。
对parent及以上进行调整平衡因子:
在这里我们只讨论左单旋跟右单旋其中两种特殊情况旋转后的平衡因子,其它的旋转调整跟插入是一样的。
在之前的插入,当parent的平衡因子为-2,subL的平衡因子为-1,才对parent节点进行左旋,左旋后的parent和subL的平衡因子都为0,当删除60的时候,parent节点的平衡因子为-2,就需要进行左旋,由于subL节点的平衡因子为0,所以它的左旋后的parent平衡因子变为-1,subL的平衡因子为1。如下图:
删除前:
删除60节点后:
左旋转后:
同样的,当删除后parent平衡因子为2,subR的平衡因子为0,需要进行右旋。
删除13这个节点前:
删除13节点后:
进行右旋后:右旋后的parent的平衡因子为1,subR的平衡因子为-1.
每次旋转后都不需要往上更新平衡因子,因为旋转后高度都会降低,不会影响上面节点的平衡因子。
bool erase(const K& key)
node* cur = find(key);//查找cur
if (cur == nullptr)//找不到
return false;
//找到了
node* parent = cur->parent;
if (cur->_left == nullptr && cur->_right == nullptr)//如果要删除的节点的左右孩子为空
if (parent->_left == cur)//cur为parent的左边,平衡因子+1
parent->_bf++;
else//cur为parent的右边,平衡因子-1
parent->_bf--;
delete cur;//直接删除
cur = nullptr;
//平衡因子
else if (cur->_left && cur->_right)//要删除的节点中的左右孩子都存在
node* rightmin = cur->_right;//右子树的最小节点
while (rightmin->_left)//查找右子树的最小值
rightmin == rightmin->_left;
cur->_kv = rightmin->_kv;//将右子树的最小值给要删除的节点,然后删除rightmin
parent = rightmin->parent;//rightmin的父亲
if (rightmin->_right)//rightmin存在右子树
node* right = rightmin->_right;
parent->_right = right;//将rightmin的右子树给parent
right->parent = parent;//右子树的父亲连接parent
else
parent->_right = nullptr;//右子树不存在,连接nullptr
delete rightmin;//直接删除rightmin
rightmin = nullptr;
parent->_bf--;//right的父亲的平衡因子-1
else if (cur->_left)//cur的左子树不为空,右子树不存在
if (cur == _root)//cur为根节点
_root = cur->_left;
_root->parent = nullptr;
else//cur不是根节点
node* subL = cur->_left;//cur的左子树
if (parent->_left == cur)//cur是parent的左子树
parent->_left = subL;//parent的左子树连接subL
parent->_bf++;//parent的平衡因子+1
else
parent->_right = subL;//parent的右子树连接subL
parent->_bf--;//parent的平衡因子-1
subL->parent = parent;//subL的parent连接cur的parent
delete cur;
cur == nullptr;
else if (cur->_right)//cur的右子树不为空,左子树不存在
if (cur == _root)
_root = cur->_right;
_root->parent = nullptr;
else
node* subR = cur->_right;//cur的右子树
if (parent->_left == cur)//cur是parent的左子树
parent->_left = subR;//parent的左子树连接subR
parent->_bf++;
else
parent->_right = subR;//parent的右子树连接subR
parent->_bf--;
subR->parent = parent;
delete cur;
cur == nullptr;
//更新平衡因子
while (parent)//parent为nullptr,则停下来
node* pparent = parent->parent;
if (parent->_bf == 0)//平衡因子等于0
break;
else if (parent->_bf == 1 || parent->_bf == -1)
if (pparent == nullptr)//parent是根节点
break;
else if (pparent->_left == parent)//parent是pparent的左节点
pparent->_bf--;
else if (pparent->_right == parent)//parent是pparent的右节点
pparent->_bf++;
parent = pparent;//parent向上移动
else if (parent->_bf == 2 || parent->_bf == -2)//parent
if (parent->_bf == 2)
if (parent->_right->_bf == -1)//parent的右孩子节点的平衡因子为-1
//先右旋在左单旋
RLrevole(parent);
else //parent的右孩子节点的平衡因子为1或者0
//左单旋
node* subR = parent->_right;
LeftRevole(parent);
if (subR->_bf == 0)//parent的右孩子节点的平衡因子为0
//改变平衡因子
subR->_bf = -1;
parent->_bf = 1;
else if (parent->_bf == -2)
if (parent->_left->_bf == 1)
//先左单旋在右单旋
LRrevole(parent);
else //parent的右孩子节点的平衡因子为-1或者0
//右单旋
node* subL = parent->_left;
RightRevole(parent);
if (subL->_bf == 0)//parent的右孩子节点的平衡因子为0
parent->_bf = -1;
subL->_bf = 1;
break;//旋转之后都是平衡树,直接跳出循环
下篇高阶数据结构预告:手撕红黑树
感谢你的关注和收藏!!!
种一颗树最后是十年前,其次是现在!!!!!
以上是关于高阶数据结构(壹)——一步一步教你手撕AVL树(增删查改)画图详解,内含代码实现包含注释)的主要内容,如果未能解决你的问题,请参考以下文章