你了解了AVL树吗
首先认识几个概念
平衡二叉树的定义(AVL)定义
平衡二叉树或者是一棵空树,或者满足以下的性质:它的左子树和右子树的高度之差的绝对值不超过1,并且左子树和右子树也是一个平衡二叉树。
平衡因子
左子树高度减去右子树的高度的值或者右子树高度减去左子树高度的值。显然 -1 <=bf <= 1
AVL树的引入
平衡二叉树在二叉排序树上引入的,在二叉树中,如果插入的节点接近有序,那么二叉树就会退化为链表大大降低了查找效率,为了使二叉树无论什么情况下最大限度的接近满二叉树,从而保证它的查找效率,因此引入平衡二叉树。
旋转步骤详解
我们只需要理清需要旋转的情况,掌握旋转的要素,找到其中的规律,就能轻松地写出AVL树的代码了,下面我详细的给出旋转的情况与步骤,大家仔细观察找出其中的规律。(此例中的平衡因子bf采用的是右子树的高度减去左子树的高度)
左左(LL)(右旋):
首先理解为什么叫做右旋:按箭头方向看,从底部向上,按照顺时针方向旋转,就是右旋,可以看出右旋可以将左子树的高度变小,所以称之为右旋,类似的我们可以理解左旋的定义。
旋转维持了部分的子树结构不变,例如左孩子变为父节点,但是左孩子的左节点不变,由于父节点变为了左孩子的右节点,所以原来左孩子的右节点没地方放了(PS:这里的图示的右旋比较简单,原来的左孩子没有右节点),只能放到原来的父节点的左孩子上了。这就完成了右旋;
#include<iostream>
#include<algorithm>
using namespace std
struct Node{
int height;//节点的高度,叶子节点默认为1
int data;
Node * left;
Node * right;
Node(const int d):height(0),left(NULL),right(NULL),data(d){}
};
class AVLTree{
public:
Node* root;
int getHeight(Node* p);
void l_rotation(Node * pre);
void r_rotation(Node * pre);
void lr_rotation(Node * pre);
void rl_rotation(Node * pre);
}
AvlNode<T> *q = t->left;
89 t->left = q->right;
90 q->right = t;
91 t = q;
void AVLTree::r_rotation(Node* pre){
//右旋操作 注意过程:1、保存左孩子结点 2、将左孩子的右孩子结点挂到原父节点的左孩子结点上 3、将父结点挂到左孩子的右孩子结点上 4、跟新父节点为左孩子结点
Node* leftSub = pre->left;//保存左孩子
pre->left= leftSub->right;//将左孩子的右孩子结点挂到原父节点的左孩子结点上
leftSub->right = pre;//将父结点挂到左孩子的右孩子结点上
pre = leftSub;//更新父节点
pre->height = max(getHeight(pre->left),getHeight(pre->right))+1;
//此时leftSub==pre->right
leftSub->height=max(getHeight(leftSub->left),getHeight(leftSub->right))+1;
}
void AVLTree::r_rotation(Node* pre){
//左旋操作
//左旋操作 注意过程:1、保存右孩子结点 2、将右孩子的左孩子结点挂到原父节点的右孩子结点上 3、将父结点挂到右孩子的左孩子结点上 4、跟新父节点为右孩子结点
Node* t= pre->right;//保存右孩子
pre->right = t->left;//将右孩子的左孩子结点挂到原父节点的右孩子结点上
t->left= pre;//将父结点挂到右孩子的左孩子结点上
pre = t;
pre->height = max(getHeight(pre->left),getHeight(pre->right))+1;
t->height=max(getHeight(t->left),getHeight(t->right))+1;
}
双旋转
//插入的节点为左子树的右节点(LR)则先绕左孩子左旋,再绕父节点右旋
void AVLTree::lr_rotation(Node * pre){
l_rotation(pre->left);
r_rotation(pre);
}
//插入的节点为右子树的左节点(RL)则先绕右孩子右旋,再绕父节点左旋
void AVLTree::lr_rotation(Node * pre){
r_rotation(pre->right);
l_rotation(pre);
}
总结
1、左左插入则右旋,先存左孩再移右孙子到了左节点,然后左孩右节点挂上父节点,更新父节点;
右旋,注重看左孩子结点
2、右右插入则左旋,先存右孩再移左孙子到了右节点,然后右孩左节点挂上父节点,更新父节点;
左旋,注重看右孩子结点
3、左右插入,则双旋,先绕左孩子结点左旋,再绕父节点右旋;
4、右左插入,则双旋,先绕右孩子结点右旋,再绕父节点左旋;