文章目录
- 前言
- 一、普通二叉搜索树的删除
- 1. 删除结点的左右结点都不为空
- 2. 删除结点的左结点为空,右节点不为空
- 3. 删除结点的右结点为空,左节点不为空
- 4. 删除结点的左右结点都不为空
- 二、AVL树的删除
- 1. 删除结点,整棵树的高度不变化
- 1.1 parent的平衡因子在删除结点之前为0
- 1.1.1 删除结点为parent的左节点
- 1.1.2 删除结点为parent的右节点
- 1.2 parent的平衡因子在调整过程中为2
- 1.2.1 删除结点为parent的左节点
- 1.2.1.1 parent的右节点的平衡因子为0
- 1.2.1.2 parent的右节点的平衡因子为1
- 1.2.2 删除结点为parent的右节点
- 1.2.2.1 parent的左节点的平衡因子为0
- 1.2.2.2parent的左节点的平衡因子为 -1
- 2. 删除结点,整棵树的高度变化
- 2.1 删除结点为parent的左节点
- 2.1.1 parent的平衡因子在删除结点之前为 -1
- 2.1.2 parent的平衡因子为2且parent的右结点的平衡因子为-1
- 2.1.2.1 PRleft的平衡因子为1
- 2.1.2.2 PRleft的平衡因子为-1
- 2.2 删除结点为parent的右节点
- 2.1.1 parent的平衡因子在删除结点之后为 0
- 2.1.2 parent的平衡因子为-2且parent的左节点的平衡因子为1
- 2.1.2.1 PLright的平衡因子为 1
- 2.1.2.2 PLright的平衡因子为-1
- 实现代码
- 详细图解(建议收藏)
- 总结
前言
由于AVL树也是二叉搜索树,为了降低一点难度,我们就基于普通二叉搜索树的删除的铺垫,再进行相关的操作。
一、普通二叉搜索树的删除
第一步:先找到要删除的结点,没有返回false。
情况分析:
1. 删除结点的左右结点都不为空
- 父节点为根节点。
例:
删除1,但父节点也是1,直接将根节点置为空即可。
- 父节点不为根节点。
例:
删除5结点,将其父节点的指向,指向空即可。
2. 删除结点的左结点为空,右节点不为空
- 父节点为根节点
删除1,这里只需要将2改为根节点。
- 父节点不为根节点。
删除5结点,将其父节点,指向5的右结点即可,再删除6即可。
3. 删除结点的右结点为空,左节点不为空
- 父节点为根节点
例:
直接将0置为根节点即可。
- 父节点不为根节点。
例:
将0的父节点的左孩子的指向改为-1,再释放0结点。
4. 删除结点的左右结点都不为空
这里我们就采用置换法的找左子树的最大节点进行值交换即可。
如果删除结点为根节点,由于我们置换的是值,再对交换后的结点进行讨论:
- 交换后的结点,必然不为根节点。
- 交换后的结点的右结点必然为空。
- 交换后的结点的左节点可能不为空。
- 交换后的结点,其父节点的左边可能为交换后的结点,父节点的右边也坑为交换后的结点,就此点展开讨论。
说明:交换后的结点,原先为左子树的最大节点,我们用left_most_node
进行表示。
- 父节点的左节点为
left_most_node
例:
待删除结点为1,
left_most_node
为0,值交换后1变0, 0变1,然后父节点的左节点指向left_most_node的左结点即可。
- 父节点的右节点为
left_most_node
例:
待删除结点为1,
left_most_node
为-1,值交换后1变-1, -1变1,然后父节点的右节点指向left_most_node的左结点即可。
如果连普通的二叉搜索树的相关知识有所缺陷,可以看一下我之前写的这篇文章——二叉搜索树。本文的普通二叉树的删除,也是源自于这篇文章。
二、AVL树的删除
说明一点,在正式讲AVL树之前,最好先把AVL树的插入及四种旋转了解清楚,因为删除说白了就是在这个基础上分类讨论的结果。
如果不清楚, 请看此篇文章:AVL树的插入与验证
OK,铺垫完成,正文开始。
删除时,再基于上面删除的四种情况外,我们还要考虑两大类情况。即整棵树的高度变化(降一) + 高度不变化。高度变化会影响上面的树的平衡因子,因此需要向上更新。
1. 删除结点,整棵树的高度不变化
两种情况,parent的平衡因子为0或者为2
1.1 parent的平衡因子在删除结点之前为0
1.1.1 删除结点为parent的左节点
整棵树的高度未发生变化,调整结束。
1.1.2 删除结点为parent的右节点
情况与左节点类似,上面理解,这块可以不用看。
整棵树的高度未发生变化,调整结束。
1.2 parent的平衡因子在调整过程中为2
1.2.1 删除结点为parent的左节点
1.2.1.1 parent的右节点的平衡因子为0
进行左旋,更新parent_right和parent的平衡因子分别为 -1 与 1,整棵树的高度不发生变化,调整结束。
1.2.1.2 parent的右节点的平衡因子为1
左旋之后,parent与parent_right的平衡因子变为0,结束调整。
1.2.2 删除结点为parent的右节点
情况与左节点类似,上面理解,这块可以不用看。
1.2.2.1 parent的左节点的平衡因子为0
进行右旋,更新parent_left与parent的平衡因子分别为1与-1,调整结束。
1.2.2.2parent的左节点的平衡因子为 -1
进行右旋,更新parent_left与parent的平衡因子0,调整结束。
2. 删除结点,整棵树的高度变化
2.1 删除结点为parent的左节点
2.1.1 parent的平衡因子在删除结点之前为 -1
调整parent的平衡因子为0,继续向上进行调整。
2.1.2 parent的平衡因子为2且parent的右结点的平衡因子为-1
为了便于描述。这里把parent右节点记作Pright,Pright的左节点记作PRleft
2.1.2.1 PRleft的平衡因子为1
进行右左双旋,且把parent的平衡因子调整为-1,PRleft的平衡因子调整为0,Pright的平衡因子调整为0。
2.1.2.2 PRleft的平衡因子为-1
进行右左双旋,且把parent的平衡因子调整为0,PRleft的平衡因子调整为0,Pright的平衡因子调整为1。
2.2 删除结点为parent的右节点
2.1.1 parent的平衡因子在删除结点之后为 0
parent的平衡因子改为0,继续向上调整。
2.1.2 parent的平衡因子为-2且parent的左节点的平衡因子为1
为了便于描述。这里把parent左节点记作Pleft,Pleft的右节点记作PLright
2.1.2.1 PLright的平衡因子为 1
进行左右双旋,调整PLright的平衡因子为0,parent的平衡因子为0,Pleft的平衡因子为-1。继续向上进行调整。
2.1.2.2 PLright的平衡因子为-1
进行左右双旋,调整PLright的平衡因子为0,parent的平衡因子为0,Pleft的平衡因子为-1。继续向上进行调整。
实现代码
- 每个人的写出来的代码可能不一样,但思路还是一致的,因此重点不在代码,而在分析思路上,这里只是贴出来方便大家对照。
Node* find(const Key &val)
{
Node* cur = _root;
while (cur)
{
if (cur->_key > val)
{
//左子树中查找
cur = cur->_left;
}
else if (cur->_key < val)
{
//右子树中查找
cur = cur->_right;
}
else
{
//找到了
return cur;
}
}
return nullptr;
}
//为了避免删除时的代码过于冗余,将重复利用的代码写成一个函数较好。
void UpDateBFactor(Node* parent, Node* cur)
{
while (parent)
{
if (parent->_left == cur)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
//判断平衡因子
if (parent->_bf == 1 || parent->_bf == -1)
{
break;
}
else if (parent->_bf == 0)
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2)
{
if (parent->_right->_bf == 0)
{
//进行左单旋
Node* parent_right = parent->_right;
RotateL(parent);
parent->_bf = 1;
parent_right->_bf = -1;
//高度不变
break;
}
else if(parent->_right->_bf == 1)
{
//左单旋
RotateL(parent);
//更新平衡因子
}
else
{
//进行右左双旋
RotateRL(parent);
}
cur = parent->_parent;
parent = cur->_parent;
}
else if (parent->_bf == -2)
{
//这还有一种特殊情况
if (parent->_left->_bf == 0)
{
Node* parent_left = parent->_left;
RotateR(parent);
parent->_bf = -1;
parent_left->_bf = 1;
//此时高度不变
break;
}
else if (parent->_left->_bf == -1)
{
//右单旋
RotateR(parent);
//高度降低1
}
else
{
//左右双旋
RotateLR(parent);
//高度降低1
}
cur = parent->_parent;
parent = cur->_parent;
}
else
{
cout << parent->_key << ":";
perror("平衡因子有误");
exit(-1);
}
}
}
//AVL树结点的删除
bool erase(const Key& val)
{
Node* cur = find(val);
//对cur进行判空
if (cur == nullptr)
{
//说明没找到。
return false;
}
else
{
Node* parent = cur->_parent;
Node* delnode = cur;
Node* delparent = cur->_parent;
//更新平衡因子,然后判断旋转与删除。
//左加加,右减减
//分析四种情况
//1.cur的left和right都为空。
//2.cur的left为空,right不为空。
//3.cur的right为空,left不为空。
//4.cur的right与left都不为空。
//在此基础上还要判断cur是否为根节点。
//1.cur的left和right都为空。
if (cur->_left == nullptr && cur->_right == nullptr)
{
//更新平衡因子
//判断cur是否为根节点
if (cur == _root)
{
_root = nullptr;
}
else
{
UpDateBFactor(parent, cur);
//将父节点的指向清空
parent = delnode->_parent;
if (parent->_left == delnode)
{
parent->_left = nullptr;
}
else
{
parent->_right = nullptr;
}
}
//删除结点。
delete delnode;
}
//左不为空,并且右为空
else if (cur->_left && cur->_right == nullptr)
{
if (cur == _root)
{
//更改根节点
_root = cur->_left;
//改变根节点的父节点的指向,因为平衡因子的绝对值必然小于等于1,
//因此其左节点只有一个结点。
//因此只需更改父节点的指向即可。
_root->_parent = nullptr;
//无需更新平衡因子
delete delnode;
}
else
{
Node* cur_left = cur->_left;
//更新平衡因子
UpDateBFactor(parent, cur);
//更新cur_left到cur的位置
cur_left->_parent = delnode->_parent;
//更新其父节点的指向
if (parent->_left == delnode)
{
parent->_left = cur_left;
}
else
{
parent->_right = cur_left;
}
//删除delnode
delete delnode;
}
}
else if (cur->_left == nullptr && cur->_right)
{
if (cur == _root)
{
//更新根节点
_root = cur->_right;
//说明:为了保证树为AVL树,cur->_right必然无左右孩子
//更新根节点的信息
_root->_parent = nullptr;
//释放delnode
delete delnode;
}
else
{
Node* parent = cur->_parent;
Node* cur_right = cur->_right;
//更新平衡因子
UpDateBFactor(parent, cur);
//首先改变cur_right的父节点的指向
cur_right->_parent = delparent;
//其次改变parent其孩子的指向
if (delparent->_left == delnode)
{
delparent->_left = cur_right;
}
else
{
delparent->_right = cur_right;
}
//最后删除cur
delete delnode;
}
}
//4.cur的right与left都不为空。
else
{
//可以找左子树的最大节点,也可以找右子树的最小节点。
//进行交换,然后更新平衡因子
Node* mostleft = cur->_left;
while (mostleft && mostleft->_right)
{
mostleft = mostleft->_right;
}
Node* curr = mostleft->_left;
//直接进行赋值。
cur->_key = mostleft->_key;
cur->_val = mostleft->_val;
Node* parent = mostleft->_parent;
UpDateBFactor(parent,mostleft);
if (mostleft == cur->_left)
{
if (curr != nullptr)
{
//改变cur与curr的指向。
curr->_parent = cur;
cur->_left = curr;
}
cur->_left = curr;
}
else
{
Node* parent = mostleft->_parent;
parent->_right = curr;
if (curr != nullptr)
{
//改变curr的父节点的指向
curr->_parent = parent;
}
}
//释放mostleft
delete mostleft;
//更新平衡因子
}
return true;
}
}
详细图解(建议收藏)
总结
删除的例子我们已经举完了,那么这里我们再总结一下:
- 调整完后,整棵树的高度变化(降低一个高度),会影响上面树的高度,需要向上调整。
- 调整完后,整棵树的高度不变,不会影响上面树的高度,无需调整。
- 最坏调整到根节点结束。
具体情况的分析,我等价替换一下:
- 以删除的结点为左节点举例。
- parent的平衡因子为2,且其右节点的平衡因子为-1,等价于插入的右左双旋。
- parent的平衡因子为2,且其右节点的平衡因子为1, 等价于插入的左双旋。
说明: 这里的等价是包括平衡因子的调整的!
- 核心情况
- parent的平衡因子为2且parent_right的平衡因子为0.
这种情况是需要我们在插入的基础上,进行左旋之后,手动调平衡因子的。
- 在更新平衡因子时,插入是左减减,右加加。删除是左加加,右减减。
- 判断是否需要向上更新时,插入是1或者-1向上调整,删除是0向上调整。
其实分析了那么多情况,其实也就这几点需要我们真正注意的,剩余的用插入剩下的轮子套用即可。
最后如果觉得文章不错的话,点个赞鼓励一下吧!