一、结构定义:
1.性质:在二叉排序树的基础上,左右子树的树高之差,不能超过 1 。
2.重点:在这之后的平衡树,主要学习的就是平衡条件
二、结构操作
两个平衡树调整的重要操作:左旋、右旋
1.左旋:对于下面树形结构,A,B代表的是K3的左右子树。
左旋就相当于逆时针旋转,把K3当做根节点。
然后我们要进行父子关系的修改,我们知道,在这个平衡树中,A这个子树中所有节点都是在K1右半子树中的,所以我们可以把A直接改为K1的右子树,并将K1及其子树作为K3的左子树,他们都是比K3小的节点,这样也不会破坏平衡树结构。
2.右旋
过程类似,还用上面的图来解释。
顺时针旋转,会将K2作为根节点,那么K2原来的右子树要调整到新的K1为根节点的右半子树中,而K2原来右子树中的节点,一定是比K1小的,所以调整为K1的左子树即可。
三、AVL树的失衡类型
1.失衡条件:左右子树高度差大于等于 2
我们以这张图为例,去区分四个类型。
注意:
1.这是整个平衡树中的一个子树,K1只是子树的根节点。
2.我们从下往上看,ABCD四个子树都是平衡的,从K2、K3看下去也是平衡的,只有K1看下去不平衡,所以K1是第一个不平衡的节点。
LL:K1左子树更高,出现在A
操作:一次右旋即可。
理解:我们先理清楚,从K2看下去,是平衡的,所以A与B之间高度差在1之内而且A更高。我们不妨设A的树高为a,B树高为b,右旋之后,K2为根节点,那么左半边高度是a,而右半边,是b+1。我们知道b有两种情况,a = b 或 a = b + 1,所以b+1的取值和a之差也不会超过1。
对于C D,他们的树高最开始一定比A小2以上,+1之后,也不可能和A之差达到2,所以不用考虑。
LR:K1左子树更高,出现在B
操作:小左旋+大右旋,先对K2这半个子树进行一次左旋,然后对K1这整个子树进行右旋
理解:从K2往下看,左侧子树本来是平衡的,但是右边高左边低,我们左旋之后,不就左边高右边低了。这样整体上又变成LL型了。
注意:牢记K1是第一个不平衡节点,从K2往下看是平衡的,K2两边子树高度差在1之内,所以左旋一定会使K2的两个子树左子树更高。
RL:K1右子树更高,出现在C
类比LR型。
操作:先对右半子树小右旋,再整体左旋
RR:K1右子树更高,出现在D
类比LL型
操作:整体进行一次左旋
四、代码演示
1.结构定义与初始化操作
//结构定义
typedef struct Node {
int key, h;//多了一个h存储树高
struct Node* lchild, * rchild;
}Node;
//几个方便的宏
#define H(n) (n->h)
#define L(n) (n->lchild)
#define R(n) (n->rchild)
#define K(n) (n->key)
//创建一个虚拟空节点
Node __NIL;
#define NIL (&__NIL)
//__attribute__((constructor))
//这个作用是规定下面函数的性质,可以让他先于主函数执行
//但是我这个编译器不支持,还是要放到主函数中调用。
void init_NIL() {
NIL->key = -1;
NIL->h = 0;
NIL->lchild = NIL->rchild = NULL;
return;
}
2.节点获取与销毁
//获取节点
Node* getNewNode(int key) {
Node* p = (Node*)malloc(sizeof(Node));
p->key = key;
p->lchild = p->rchild = NIL;//编程技巧,虚拟空节点
p->h = 1;
return p;
}
//销毁
void clear(Node* root) {
if (root == NIL) return;
clear(root->lchild);
clear(root->rchild);
free(root);
return;
}
3.左旋右旋以及失衡状态调整(重点)
这里的type_str这些与输出相关的语句,都是输出提示信息的,方便测试。
思路:通过获取树高,判断失衡类型,然后调用上面的左旋右旋。
//更新高度
void update_height(Node* root) {
H(root) = (H(L(root)) > H(R(root)) ? H(L(root)): H(R(root))) + 1;
return;
}
Node* left_rodate(Node* root) {
cout << "left rodate : " << K(root) << endl;
Node* new_node = root->rchild;
root->rchild = new_node->lchild;
new_node->lchild = root;
update_height(root);//先更新子节点的高度
update_height(new_node);
return new_node;
}
Node* right_rodate(Node* root) {
cout << "right rodate : " << K(root) << endl;
Node* new_node = root->lchild;
root->lchild = new_node->rchild;
new_node->rchild = root;
update_height(root);//先更新子节点的高度
update_height(new_node);
return new_node;
}
const char* type_str[5] = {
"",
"maintain type : LL",
"maintain type : LR",
"maintain type : RR",
"maintain type : RL",
};
Node* maintain(Node* root) {
if (abs(H(L(root)) - H(R(root))) <= 1) return root;//不需调整
int type = 0;//用来输出时,查看失衡类型
//判断失衡类型
if (H(L(root)) > H(R(root))) {
if (H(R(L(root))) > H(L(L(root)))) {
//LR型
root->lchild = left_rodate(root->lchild);//小左旋
type += 1;
}
//LL和LR都需要这步,所以合并一起
root = right_rodate(root);//大右旋
type += 1;
}
else {
type = 2;
if (H(L(R(root))) > H(R(R(root)))) {
//RL型
root->rchild = right_rodate(root->rchild);//小右旋
type += 1;
}
root = left_rodate(root);//大左旋
type += 1;
}
cout << type_str[type] << endl;
return root;
}
4.插入
注意更新树高和返回时要调整平衡条件就行。
//插入
//返回值是新的根节点。因为根节点会在平衡调整中变化
Node* insert(Node* root, int key) {
if (root == NIL) return getNewNode(key);
if (root->key == key) return root;//不重复插入
if (key < root->key) root->lchild = insert(root->lchild, key);
else root->rchild = insert(root->rchild, key);
update_height(root);//插入后要更新树高
return maintain(root);//调整返回
}
5.删除
这里和上一篇二叉搜素树的删除操作类似,只是出度0和1通过虚拟空节点这个技巧合并成一种情况了。
Node* predecessor(Node* root) {
Node* temp = L(root);
while (R(temp) != NIL) temp = R(temp);
return temp;
}
//删除
Node* eraser(Node* root, int key) {
if (root == NIL) return root;
if (key < K(root)) L(root) = eraser(L(root), key);
else if (key > K(root)) R(root) = eraser(R(root), key);
else {
//之前二叉搜索树采用的是分三种情况,这里用一种优化的新方法
if (L(root) == NIL || R(root) == NIL) {
//出度为1或0
Node* temp = L(root) != NIL ? L(root) : R(root);
free(root);
return temp;
//因为我们采用了NIL这个虚拟空节点,所以这里能兼容出度为0的情况
}
else {
//出度为2
Node* temp = predecessor(root);
K(root) = K(temp);
L(root) = eraser(L(root), K(temp));
}
}
update_height(root);
return maintain(root);
}
6.查找
//查找
Node* find(Node* root, int key) {
if (root == NIL) return NIL;
if (K(root) == key) return root;
if (key < K(root)) return find(L(root), key);
return find(R(root), key);
}
7.测试
void output(Node* root) {
if (root == NIL) return;
cout << "[key:" << K(root) << " h:" << H(root) << "]";
cout << "left:" << K(L(root)) << " right" << K(R(root)) << endl;
output(root->lchild);
output(root->rchild);
return;
}
int main()
{
init_NIL();//虚拟空节点的初始化
srand(time(0));
//测试
int x;
Node* root = NIL;
while (cin >> x && x != -1) {
//插入
cout << "插入" << x << "到AVL树中" << endl;
root = insert(root, x);
output(root);
}
while (cin >> x && x != -1) {
//删除
cout << "删除" << x << "从AVL树中" << endl;
root = eraser(root, x);
output(root);
}
while (cin >> x && x != -1) {
//查找
bool judge = false;
if (find(root, x) != NIL) judge = true;
if (judge) cout << "查找成功" << endl;
else cout << "查找失败" << endl;
}
return 0;
}
五、总结
1.要学习左旋、右旋这两个二叉树平衡调整的重要操作。
2.可以借鉴虚拟空节点这个编程技巧
3.测试时的输出更能体现编程能力,学习type_str这个技巧
4.要记住AVL树的平衡和失衡条件