平衡二叉树和二叉查找树的关系:
平衡二叉树本质上也是一颗二叉查找树,是二叉查找树的改进,只是在其基础上增加了“平衡”的要求。
AVL树,防止出现链条状树,将时间复杂度保持为O(log n),链条状树会导致起不到使用二叉查找树来进行数据查询优化的目的。
如:使用序列1,2,3,4,5形成二叉查找树,那么将会形成一条链条,将时间复杂度提高到O(n)。
![](https://i-blog.csdnimg.cn/blog_migrate/afb4e2a5090e0528d78b8d4b7b4f7514.png)
平衡二叉树的定义和含义:
![](https://i-blog.csdnimg.cn/blog_migrate/26b53d30b6b61efff48a9f6e26391df3.png)
//因此二叉平衡树的高度为log n,如log 6 约等于2,满足要求。
//平衡指的是左右子树高度差不超过1,即node[lchild].hight - node[rchild].hight<=1
//两子树的高度差叫做“平衡因子”,结点存放的是高度,用于判断二叉树是否失衡。
//初始化新结点的时候,高度初始化为1,因为叶子结点的高度就是1,新结点生成位置是叶子结点,旧结点的高度随新结点的生成而更新。
![](https://i-blog.csdnimg.cn/blog_migrate/9d2d49f86d33997ad954f4a9d45038ee.png)
//父亲结点不能通过儿子结点的平衡因子推出自己的平衡因子,可以画个图看看明显就是错误的。
平衡二叉树的基本操作:
![](https://i-blog.csdnimg.cn/blog_migrate/dd3e57df6694fb63b6bc27f92cc00ba4.png)
//二叉查找树是按一定规律建造的,因此不能随意修改二叉查找树的值,因此基本操作不包括修改,删除操作较为复杂,这里主要讲查找,插入和建立
1.平衡二叉树的查找:
![](https://i-blog.csdnimg.cn/blog_migrate/af5930ae40971882986b9a61cebd1d03.png)
//每一递归一次走到下一层,因此递归次数和高度有关。
平衡二叉树的插入:(也较为复杂)
(1)左旋
![](https://i-blog.csdnimg.cn/blog_migrate/2dc2f553b548de0e59a285b5306ed2c0.png)
引入二叉查找树中的前驱和后继的概念,则A的前驱是五角星,A的后继是深棱形,A的后继是大于A的最小值,放到数列里面就是A后面的哪个值。
左旋的过程中,A作为B的左孩子(向左扭过去),B多出了一个孩子,A刚好缺少一个右孩子(多一个补一个)因为深棱形是A的后继,比A大,应该作为A的右孩子。
了解了过程,总结就是:B的左孩子变成了A的右孩子,B的左孩子由A来补充
![](https://i-blog.csdnimg.cn/blog_migrate/344ca6f246501865d0748893ce53044f.png)
(助记:旋转的点要扔掉原来的孩子才能拥有新的孩子)
(2)右旋
右旋和左旋是对称的过程
B的左孩子向右边扭,B作为A的右孩子,A多了一个右孩子,B少了一个左孩子,A的左孩子来到B作为其左孩子。
对于B来说黑色方格是B的前驱,是小于B的最大值,因此应该作为B的左孩子
了解了过程,总结就是:A的右孩子变成了B的左孩子,A的右孩子用B来补充
![](https://i-blog.csdnimg.cn/blog_migrate/4b708e5afc985bb44cf605cd35695530.png)
(助记:旋转的点要扔掉原来的孩子才能拥有新的孩子,扔孩子捡孩子)
![](https://i-blog.csdnimg.cn/blog_migrate/7f97ee35dc55240a7259922de836fafa.png)
左旋和右旋后,AB的相对位置发生了改变,记得旋转完后更新A,B的高度,再更新根结点
(3)一颗已平衡的二叉树中插入结点,一定会有结点的平衡因子发生改变(不一定失衡),此时有可能失衡,那么如何将失衡的二叉树进行调整?
![](https://i-blog.csdnimg.cn/blog_migrate/7fef363b2886b938e90d66cad38ff019.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2df70af23563872215e6ca737c72ff5e.jpeg)
由图所示,插入结点后B的失衡,A也跟着失衡,根结点到插入点后前两个结点都失衡了,不难发现只要将插入结点去掉或者调整好,A,B就能恢复平衡。
失衡的话只有两种情况,就是靠近插入结点,第一个失衡的结点,也就是上图的B点,对于B点来说,失衡只有两种可能就是平衡因子为2或者-2。
(1)当平衡因子为2
说明左树比右树高,那么左树高也有两种情况,那么树的类型只有可能是如下的两种:
![](https://i-blog.csdnimg.cn/blog_migrate/2efdb09d52f12fac441fd8ce0af04290.png)
//分别是LL类型和LR类型(指的是树的类型,不是指左右旋的意思),之前的哪个图是LL类型的一种情况,B的左子树的平衡因子是1。
![](https://i-blog.csdnimg.cn/blog_migrate/373e4561d293fe0a960d085a204389ff.png)
若是LL类型,只需要A右旋即可:
![](https://i-blog.csdnimg.cn/blog_migrate/bc32fe54d6494aaf102065be23db4a7d.png)
A右旋:B的右孩子变成A的左孩子,A变成B的右孩子,B成为根结点。
//右旋B成为根结点,B的平衡因子变成了0。
若是LR类型,先将B进行左旋,就可以变成LL类型,然后再将A进行右旋即可
![](https://i-blog.csdnimg.cn/blog_migrate/104977cd00512d859d90f37dfe0f9476.png)
(2)平衡因子为-2
![](https://i-blog.csdnimg.cn/blog_migrate/02d5c17d9bc30788e2f6c180b0961291.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3f52061f4374b1ae9fb2d138c91f27f7.png)
若是RR类型,只需要A左旋即可:
![](https://i-blog.csdnimg.cn/blog_migrate/adcd813f360f47eae0defaea10aac1d1.png)
若是RL类型,先将B右旋,再将A左旋
![](https://i-blog.csdnimg.cn/blog_migrate/fe88b0ad1e02b0eb4d31bffa614a14fe.png)
![](https://i-blog.csdnimg.cn/blog_migrate/52f462b99f25aee179880d408a089b6c.png)
其实平时写题目的时候像下面这样画出简化图是最好理解的(但要记得扔孩子捡孩子的细节)
![](https://i-blog.csdnimg.cn/blog_migrate/69502b2eab128c03e82720badc67feb3.jpeg)
总结一下:
要懂得左旋和右旋是什么意思,然后就是按根结点的平衡因子和根结点的左右子树的平衡因子分成LL,LR,RR,RL四种类型,要懂得这四种类型如何处理才能使得其根结点的平衡因子下降为0。
在以上基础上可以探讨如何对平衡二叉树进行插入了
void insert(node* &root, int v) {
if(root == NULL) {
root = newNode(v);
return ;
}
if(root->data > v) {
insert(root->lchild, v);
updateHeight(root);//插入结点后,前面的路径的每一个结点都要更新其高度;
if(getBalanceFactor(root) == 2) {
if(getBalanceFactor(root->lchild) == 1) {//LL类型;
R(root);
} else if(getBalanceFactor(root->lchild) == -1) {
R(root->lchild);
L(root);
}
}
} else {
insert(root->rchild, v);
updateHeight(root);
if(getBalanceFactor(root) == -2) {
if(getBalanceFactor(root) == 1) {//Rl类型;
R(root->rchild);
L(root);
} else if(getBalanceFactor(root) == -1) {
L(root);
}
}
}
}
平衡二叉树的插入和创造的全部代码:
#include<iostream>
using namespace std;
struct node {
int data;
int height;
node* lchild;
node* rchild;
};
node* newNode(int x) {
node* root = new node;
root->data = x;
root->height = 1;//平衡二叉树创造结点记得给高度初始化,叶子结点高度为1;
root->lchild = NULL;
root->rchild = NULL;
return root;
}
int getHeight(node* root) {
if(root == NULL)//记得要NULL,否则叶子结点在计算平衡因子的时候会对NULL使用->操作;
return 0;
return root->height;
}
void updateHeight(node* root) {
root->height = max(getHeight(root->lchild), getHeight(root->rchild)) + 1;
return ;
}
int getBalanceFactor(node* root) {
return getHeight(root->lchild) - getHeight(root->rchild);
}
void R(node* &root) {
node* next = root->lchild;
root->lchild = next->rchild;
next->rchild = root;
updateHeight(root);
updateHeight(next);
root = next;
}
void L(node* &root) {//左旋和右旋记得都要使用引用,因为树的结构都发生了改变;
node* next = root->rchild;
root->rchild = next->lchild;
next->lchild = root;
updateHeight(root);
updateHeight(next);
root = next;//由此处可以体现要使用引用;
}
void insert(node* &root, int x) {//记得使用引用,因为这层是对上一层的根结点的左或者右结点本身进行操作;
if(root == NULL) {
root = newNode(x);
return ;
}
if(root->data > x) {
insert(root->lchild, x);//插入结点成功后,插入过程中经历的结点结点会在回溯过程中更新,并通过判断更新后孩子高度来判断是否该结点是否失衡,从下往上调整;
updateHeight(root);//插入结点后路径上的所有结点的高度记得要更新;
if(getBalanceFactor(root) == 2) {
if(getBalanceFactor(root->lchild) == 1) {
R(root);
} else if(getBalanceFactor(root->lchild) == -1) {
L(root->lchild);
R(root);
}
}
} else {
insert(root->rchild, x);
updateHeight(root);
if(getBalanceFactor(root) == -2) {
if(getBalanceFactor(root->rchild) == -1) {
L(root);
} else if(getBalanceFactor(root->rchild) == 1) {
R(root->rchild);
L(root);
}
}
}
}
const int Maxn = 10010;
int n;
int arr[Maxn] = {0};
node* create(int arr[], int n) {
node* root = NULL;
for(int i = 0; i < n; i++) {
insert(root, arr[i]);
}
return root;
}
int main() {
cin >> n;
for(int i = 0; i < n; i++)
cin >> arr[i];
node* root = create(arr, n);
cout << root->data;
return 0;
}
//复习平衡二叉树的最好办法就是练习写一颗平衡二叉树