既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
}
else
{
//插入的节点key值等于当前位置的key值,插入失败
return false;
}
}
//开始插入
cur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else if (parent->_kv.first < kv.first)
{
parent->_left = cur;
}
//连接parent
cur->_parent = parent;
//控制平衡
//1、更新平衡因子
while (parent)
{
if (cur == parent->right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == -1 || parent->_bf == 1)//也可以用abs
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == -2 || parent->_bf == 2)
{
//说明parent所在的子树已经不平衡了,需要旋转
if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);//左单旋
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);//右单旋
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);//左右双旋
}
break;
}
else
{
assert(false);//在插入前树的平衡因子就有问题
}
}
return true;
}
### 四. AVL树的旋转
#### 🥑左单旋
新节点插入**较高右子树的右侧**—右右:左单旋
**⚡动图展示:**
![请添加图片描述](https://img-blog.csdnimg.cn/f09faf584324495ebdeb4381ac5cd5aa.gif)
**抽象图过程解析:**
![在这里插入图片描述](https://img-blog.csdnimg.cn/743926ee76914427a748853353380c46.png)
其中h可以等于0、1、2等等,不过都可以归纳到这种大情况,处理情况都一样,都是引发parent 等于2,cur等于1
**左单旋旋转步骤:**
1. subRL变成parent的右子树(`subL和parent`的关系,要注意**🔥subL可能为空**)
2. parent成为subR的左子树(`parent和subLR`的关系)
3. subR成为根节点(`ppNode 和 subL`关系,也要注意**🔥parent是子树的根还是整棵树的根**)
4. 最后更新平衡因子
![在这里插入图片描述](https://img-blog.csdnimg.cn/91edc096487e4555baa7cbbabc736afd.png)
为什么要这样旋转?**要符合二叉搜索树规则**
* subR的左子树的值本身就比parent的值要大,所以可以作为parent的右子树
* parent及其左子树当中结点的值本身就比subR的值小,所以可以作为subR的左子树
平衡因子更新:
![在这里插入图片描述](https://img-blog.csdnimg.cn/3c70c6e52e23445385fcfc2ee129c00d.png)
可以看见,左单旋后树的高度就平衡了,也就无需继续向上更新平衡因子了
代码实现如下:(详细注释)
void RotateL(Node\* parent)
{
//三叉链
Node\* subR = parent->_right;
Node\* subLR = subR->_left;
Node\* ppNode = parent->_parent;
//subR 和 parent之间的关系
subR->_left = parent;
parent->_parent = subR;
//subRL 和 parent之间的关系
subRL = parent->_right;
if (subRL)
subRL->parent = parent;
//ppNode 和 subR的关系
if (ppNode == nullptr)
{
_root = subR;
subR->_parent = nullptr;//没有父节点,所以为空
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
//更新平衡因子
subR->_bf = parent->_bf = 0;
}
#### 🥑右单旋(和左单旋高度相似)
新节点**插入较高左子树的左侧**—左左:右单旋
**动图演示:**
![请添加图片描述](https://img-blog.csdnimg.cn/52b22cfc05b04656a4e6ee326f1cf976.gif)**抽象图过程解析:**
![在这里插入图片描述](https://img-blog.csdnimg.cn/87daa1e4a0b6478a9f7c89bc49d831fe.png)
**右单旋旋转步骤:**
与左单旋雷同,看上面就行
同样**也要满足二叉搜索树的性质**:也是和左单旋雷同,看上面就行
平衡因子更新如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/5b2e723e7bef494cb48562a89de56f95.png)同样右单旋后,parent的平衡因子为0,左右子树高度相等,也就无需继续往上更新平衡因子了
话不多说上代码:
//右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* ppNode = parent->_parent;
//subL 和 parent的关系
subL->_right = parent;
parent->_parent = subL;
//subLR 和 parent之间的关系
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
//ppNode 和 subL的关系
if (ppNode == nullptr)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left == subL;
}
else
{
ppNode->_right == subL;
}
subL->_parent = ppNode;
}
//更新平衡因子
subL->_bf = parent->_bf = 0;
}
#### 🔥左右单旋
新节点**插入较高左子树的右侧**—左右:**先左单旋再右单旋**、
**动图演示:**
![请添加图片描述](https://img-blog.csdnimg.cn/2224678a2b9f4e04a66a4f8a787515d4.gif)
在b树或者c树中新增节点,均会引发左右双旋
**旋转示意图如下:**
1、插入新节点
![在这里插入图片描述](https://img-blog.csdnimg.cn/745e255748264247b9e949aa5b29f217.png)
2、以30为旋转点进行**左单旋**
![在这里插入图片描述](https://img-blog.csdnimg.cn/18e6a3de2e234adf83005217d6eabc59.png)
3、以90为旋转点进行**右单旋**
![在这里插入图片描述](https://img-blog.csdnimg.cn/fc27fa454a3f49af9ea3302505fac256.png)
**左右单旋的步骤如下:**
1. 以subL为节点左单旋
2. 以parent为节点右单旋
3. **更新平衡因子**(这才是重点)
**左右双旋后满足二叉搜索树的性质:**
实际上就是把subLR的左子树和右子树,分别作为subL和parent的右子树和左子树,再让subL和parent分别作为subLR的左右子树,最后让subLR作为整个子树的根(看图理解)
* subLR左子树的节点值比subL的值大,所以可以作为subL的右子树
* subLR右子树的节点值比parent的值小,因此可以作为parent的左子树
* 前两个步骤之后,subL以及子树的值,和parent的值均符合,所以可以当subLR的左右子树
重点来了:(**以subLR为突破口**)
左右双旋后,平衡因子的更新随着**subLR原始平衡因子**的不同分为以下三种情况:
1. 当subLR原始平衡因子是-1时,左右双旋后parent、subL、subLR的平衡因子分别更新为1、0、0
![在这里插入图片描述](https://img-blog.csdnimg.cn/4dad546fb0c3414aae0c123918006885.png)
2. 当subLR原始平衡因子是1时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、-1、0
![在这里插入图片描述](https://img-blog.csdnimg.cn/b37311abc905492e8d38cd2c542eb7bd.png)
3. 当subLR原始平衡因子是0时,左右双旋后parent、subL、subLR的平衡因子分别更新为0、0、0
![在这里插入图片描述](https://img-blog.csdnimg.cn/0f66691730604d1e9a67250dd0c4fbe7.png)
经过左右双旋后,即树的高度没有发生变化,所以无需继续往上更新平衡因子
话不多说,代码实现一下吧:
void RotateLR(Node\* parent)
{
Node\* subL = parent->_left;
Node\* subLR = subL->_right;
int bf = subLR->_bf;
//subL节点左单旋
RotateL(subL);
//parent节点进行右单旋
RotateR(parent);
//更新平衡因子
if (bf == 1)
{
subLR->_bf = 0;
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == -1)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 1;
}
else if(bf == 0)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);//旋转前的平衡因子就出错了
}
}
#### 🔥右左单旋
**动图演示:**
![请添加图片描述](https://img-blog.csdnimg.cn/ba7ccbe8276047fdb3484204ca55178d.gif)
**旋转图演示过程:**
1、插入新节点
![在这里插入图片描述](https://img-blog.csdnimg.cn/e9215ad6a5164b669ff5a15400a89178.png)
2、以subR的节点进行右单旋
![在这里插入图片描述](https://img-blog.csdnimg.cn/d6bcd434c26a44f3a80ab47f96c565fd.png)
3、以parent的节点进行右单旋
![在这里插入图片描述](https://img-blog.csdnimg.cn/77b7e30aa9e54d319a933960057b63f8.png)
旋转步骤和左右双旋雷同
重点来了:(**以subRL为突破口**)
左右双旋后,平衡因子的更新随着**subRL 原始平衡因子**的不同分为三种情况分别对应`subRL` = 0、1、2情况,此处就不多赘述了,详细可以浏览左右双旋的,情况一样
代码实现
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
//subR右单旋
RotateR(subR);
//parent进行左单旋
RotateL(parent);
if (bf == 1)
{
subRL->_bf = 0;
subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subRL->_bf = 0;
subR->_bf = 1;
parent->_bf = 0;
}
else if (bf == 0)
{
subRL->_bf = 0;
subR->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
### 五. 验证AVL树
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
1. **验证其为二叉搜索树**
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
2. **验证其为平衡树**
* 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
* 节点的平衡因子是否计算正确
先验证是否为二叉搜索树
void \_InOrder(Node\* root)
{
if (root == nullptr)
{
return;
\_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
\_InOrder(root->_right);
}
但中序遍历只能代表是二叉搜索树,不能代表是AVL树,为此还需要验证二叉树的平衡性,查平衡因子有一种监守自盗的感觉,因为平衡因子我们刚修改完,所以我们去查高度俩判断!
* 如果是**空树**,证明平衡,是AVL树
* 根高度差不大于2,并且递归子树的高度差都不大于2,即是AVL树
* 特殊情况,平衡因子和该点的高度差对不上,要判断一下
//是否平衡
bool IsBalance(Node\* root)
{
if (root == nullptr)
{
return true;
}
int leftHT = Height(root->_left);
int rightHT = Height(root->_right);
int diff = rightHT - leftHT;
if (diff ! = root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
//小于2就为真 并且递归调用子树判断是否平衡
return abs(diff) < 2
&& \_IsBalance(root->_left)
&& \_IsBalance(root->_right);
}
//后序查找
int Height(Node\* root)
{
if (root == nullptr)
return 0;
int leftHT = Height(root->_left);
int rightHT = Height(root->_right);
return max(leftHT, rightHT) + 1;
}
### 六. AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即
l
![img](https://img-blog.csdnimg.cn/img_convert/a95149e53a0dbccaf0662423018a94f7.png)
![img](https://img-blog.csdnimg.cn/img_convert/8fccc4cc0e0719d25a19415ce176f7f7.png)
![img](https://img-blog.csdnimg.cn/img_convert/7cf1bc2b3d923df1d04d8542101050c3.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
eight(root->_left);
int rightHT = Height(root->_right);
return max(leftHT, rightHT) + 1;
}
六. AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即
l
[外链图片转存中…(img-83syzqHn-1715638637582)]
[外链图片转存中…(img-7GGUx5N1-1715638637582)]
[外链图片转存中…(img-hDPGCCAt-1715638637582)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新