二叉树细节可参考:
sheng的学习笔记-二叉树(BST)_coldstarry的博客-CSDN博客
定义
平衡二叉树,又称AVL树,用于解决二叉排序树高度不确定的情况,如果二叉排序树的子树间的高度相差太大,就会让二叉排序树操作的时间复杂度升级为O(n),为了避免这一情况,为最坏的情况做准备,就出现了平衡二叉树,使树的高度尽可能的小,其本质还是一棵二叉搜索树。
平衡二叉树的性质:
- 左子树和右子树的高度之差的绝对值小于等于1
- 左子树和右子树也是平衡二叉树
为了方便起见,给树上的每个结点附加一个数字,给出该结点左子树与右子树的高度差,这个数字称为结点的平衡因子(BF)
平衡因子=结点左子树的高度-结点右子树的高度。
左右子树的高度差(平衡因子)< 2,因此平衡二叉树所有结点的平衡因子只能是-1、0、1,如下图,是一个平衡二叉树
等价变换
等价二叉搜索树:若两棵二叉搜索树的中序遍历序列相同,则称它们彼此等价;反之亦然。
旋转调整
由于插入或者删除元素,导致的失衡。最基本的修复手段,通过围绕特定节点的旋转,实现等价前提下的局部拓扑调整。
不一定只有一个结点失去平衡,有可能插入一个结点会让多个结点失衡。这时候找 最小的失衡子树的根节点作为失衡结点。
恢复平衡
那如何去恢复平衡,使得二叉搜索树依然成立为一棵平衡树?先来看平衡调整的四种类型:
举个例子:如第一个,当平衡二叉树为AB时,插入一个C结点,使得失衡了,失衡结点为A,此时因为C结点插入的位置为失衡结点的左孩子的左孩子,所以是LL型,以此类推。
为了恢复平衡,针对每一种都不一样,但目的都是调整为平衡的,且不能违背二叉搜索树的性质:如下图是每种失衡类型的解决方法,每个都不太一样,目的都是一样的,把key的值为中等的变为树的根,最小的放在左孩子,做大的放右孩子,通过这一目的,降低树的高度,也不用死记硬背。
如书上所说,这一操作被称为树的旋转,如LL型被称为右旋,RR型称为左旋,LR型是先左旋,再右旋,RL型先右旋再左旋。
旋转协助理解图
右旋
左旋
LL型调整
如下,在实际情况中,调整的内容可能看起来更复杂,如下方块的表示省略了n个结点,调整的方式如下(右旋):
步骤为:
- B结点带左子树(α和新添加的C结点)一起上升
- A结点成为B的右子树
- 原来的B的右子树成为A的左子树,因为A的左子树是B,上升了,所以空着的
可以看成是A右旋为B的右子树
RR型
LL型和RR型是最简单的几种情况,所以放在了前面。RR型即插入的结点位置在失衡结点的右子树的右子树中,如下图:
调整的步骤和LL的差不多
步骤为:
- B结点和它的右子树(β和新添加的C结点)一起上升
- A结点变为B结点的左子树
- 原来B的左子树α变为A的右子树
可以看成是A左旋至B的左子树
LR型调整
LR型的跳转如下(左旋再右旋):
- 首先让B和它的子树(除了C)左旋至C的左子树,把C为根的树接入A的左子树
- 然后让A右旋,成为C的右子树
其过程就是把中位数的C上升,成为AB的双亲
RL型调整
RL型如下(先右旋再左旋):
- 首先让B和它的子树(除了C)右旋至C的右子树,把C为根的树接入A的右子树
- 然后让A左旋,成为C的左子树
和LR差不多
例题:输入关键字序列(16,3,7,11 ,9,26,18,14,15)给出AVL树
参考答案:
平衡树的3+4重构
平衡二叉树也是一颗BST树,那么BST的特点,平衡二叉树也应该具有,那么BST树又有什么特点呢,特点是有序,什么有序呢,中序遍历序列有序,也就是说BST树的中序序列是单调的,这个很容易就证明了,对根节点而已,其左子树的肯定比它本身要小,其右子树的值肯定比其值要大,而中序序列是左根右,所以BST树的中序序列肯定是单调的。这个是非常重要的性质,稍后我们将用到它。
我们设g(x)为最低的失衡结点,考察祖孙三代:g~p~v,按照中序遍历次序将其重命名为:a<b<c。
它们总共拥有互不相交的四颗(可能为空的)子树,按照中序遍历次序,将其重命名为:T0<T1<T2<T3。
此时,如果我们依然按照中序的遍历次序将这两个序列混合起来,就可以得到一个长度为7的序列。在这个序列中三个结点a,b,c,必然是镶嵌于这4棵子树之间
实际上无论是哪种具体的情况,经过这样的重命名之后,按照中序遍历的次序,必然是从T0到a,再从a到T1,再从T1到b,然后从b到T2,再从T2到c,最终由c到T3,这就是BST单调性的体现
因此我们可以统一的将这三个顶点abc以及这4棵子树,按照下面的拓扑关系直接的拼接起来,这样的一种拼接是针对于三个结点,以及下属的4棵子树而言的,所以也称作3+4重构
无论是插入还是删除,无论是单旋还是双旋,最终的效果都应该是这样一种形式。
优缺点:
- AVL 树同二叉搜索树相比无论查找,插入还是删除,其时间复杂度都只有
LOG(n)
; - 插入和删除都需要旋转操作,复杂度很高,成本仍然比较高,不具备推广优势
代码示例
平衡二叉树的代码示例(从别的地方拷贝过来的,没测过,慎用)
public class AVLTree {
private AVLNode root;
public class AVLNode {
public int data;
public int height;
public AVLNode parent;
public AVLNode left;
public AVLNode right;
public AVLNode(int data) {
this.data = data;
this.height = 1;
}
@Override
public String toString() {
return "AVLNode{" +
"data=" + data +
'}';
}
public void inOrder() {//中序遍历
if (this.left != null) {
this.left.inOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.inOrder();
}
}
}
private int calcHeight(AVLNode root) {
if (root.left == null && root.right == null) {
return 1;
} else if (root.right == null) {
return root.left.height + 1;
} else if (root.left == null) {
return root.right.height + 1;
} else {
return root.left.height > root.right.height ? root.left.height + 1 : root.right.height + 1;
}
}
private int calcBF(AVLNode root) {
if (root == null) {
return 0;
} else if (root.left == null && root.right == null) {
return 0;
} else if (root.right == null) {
return root.left.height;
} else if (root.left == null) {
return -root.right.height;
} else {
return root.left.height - root.right.height;
}
}
public AVLNode leftRotate(AVLNode root) {
AVLNode oldRoot = root;
AVLNode newRoot = root.right;
AVLNode parent = root.parent;
//1.newRoot 替换 oldRoot 位置
if (null != parent) {
if (oldRoot.parent.data > oldRoot.data) {
parent.left = newRoot;
} else {
parent.right = newRoot;
}
}
newRoot.parent = parent;
//2.重新组装 oldRoot (将 newRoot 的左子树 给 oldRoot 的右子树)
oldRoot.right = newRoot.left;
if (newRoot.left != null) {
newRoot.left.parent = oldRoot;
}
//3. oldRoot 为 newRoot 的左子树
newRoot.left = oldRoot;
oldRoot.parent = newRoot;
//刷新高度
oldRoot.height = calcHeight(oldRoot);
newRoot.height = calcHeight(newRoot);
return newRoot;
}
public AVLNode rightRotate(AVLNode root) {
AVLNode oldRoot = root;
AVLNode newRoot = root.left;
AVLNode parent = root.parent;
//1.newRoot 替换 oldRoot 位置
if (null != parent) {
if (oldRoot.parent.data > oldRoot.data) {
parent.left = newRoot;
} else {
parent.right = newRoot;
}
}
newRoot.parent = parent;
//2.重新组装 oldRoot (将 newRoot 的右子树 给 oldRoot 的左子树)
oldRoot.left = newRoot.right;
if (newRoot.right != null) {
newRoot.right.parent = oldRoot;
}
//3. oldRoot 为 newRoot 的左子树
newRoot.right = oldRoot;
oldRoot.parent = newRoot;
//刷新高度
oldRoot.height = calcHeight(oldRoot);
newRoot.height = calcHeight(newRoot);
return newRoot;
}
public void insert(int data) {
if (null == this.root) {
this.root = new AVLNode(data);
return;
}
this.root = insert(this.root, data);
}
public AVLNode insert(AVLNode root, int data) {
//插入左子树
if (data < root.data) {
if (null == root.left) {
root.left = new AVLNode(data);
root.left.parent = root;
} else {
insert(root.left, data);
}
}
//插入右子树
else if (data > root.data) {
if (null == root.right) {
root.right = new AVLNode(data);
root.right.parent = root;
} else {
insert(root.right, data);
}
}
//刷新高度
root.height = calcHeight(root);
//旋转
//1. LL 型 右旋转
if (calcBF(root) == 2) {
//2. LR 型 先左旋转
if (calcBF(root.left) == -1) {
root.left = leftRotate(root.left);
}
root = rightRotate(root);
}
//3. RR型 左旋转
if (calcBF(root) == -2) {
//4. RL 型 先右旋转
if (calcBF(root.right) == 1) {
root.right = rightRotate(root.right);
}
root = leftRotate(root);
}
return root;
}
public void inOrder() {
root.inOrder();
}
}
测试:
AVLTree tree = new AVLTree();
tree.insert(16);
tree.insert(3);
tree.insert(7);
tree.insert(11);
tree.insert(9);
tree.insert(26);
tree.insert(18);
tree.insert(14);
tree.insert(15);
tree.inOrder();
3+4重构函数(从别的地方拷贝过来的,没测过,慎用)
//3+4重构函数
//参数:见分析
//返回:新根
Tree connect34(Tree &a,Tree &b,Tree &c,Tree &T0,Tree &T1,Tree &T2,Tree &T3)
{
a->lchild=T0;
if (T0)
T0->parent=a;
a->rchild=T1;
if(T1)
T1->parent=a;
update_depth(a);
c->lchild=T2;
if(T2)
T2->parent=c;
c->rchild=T3;
if(T3)
T3->parent=c;
update_depth(c);
b->lchild=a;
a->parent=b;
b->rchild=c;
c->parent=b;
update_depth(b);
return b;
}
3+4重构的完整代码
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
typedef struct AVLNode *Tree;
typedef int ElementType;
struct AVLNode
{
int depth; //深度,这里计算每个结点的深度,通过深度的比较可得出是否平衡
Tree parent; //该结点的父节点,方便操作
ElementType val; //结点值
Tree lchild;
Tree rchild;
AVLNode(int val=0) //默认构造函数
{
parent=NULL;
depth=0;
lchild=rchild=NULL;
this->val=val;
}
};
Tree insert_val(Tree&,Tree,Tree);
Tree remove(Tree&,ElementType);
Tree remove_val(Tree &,Tree &);
void update_depth(Tree);
int get_balance(Tree);
int is_balance(Tree);
Tree *Find_Min(Tree&);
Tree connect34(Tree&,Tree&,Tree&,Tree&,Tree&,Tree&,Tree&);
Tree rotateAt(Tree&,Tree&);
void setchild(Tree &,Tree &,Tree &);
//向AVL树中插入val
//参数:根,插入数据value
//返回:新根结点
Tree Insert(Tree &root,ElementType val)
{
Tree temp=NULL;
Tree node=new AVLNode(val);
//插入结点
temp=insert_val(root,node,NULL); //调用真正的插入函数
if (temp)
{
update_depth(temp);
root=rotateAt(root, temp);//检查树是否该调整
}
else //无需插入,释放结点
delete temp;
return root;
}
//插入函数
//参数:根节点,待插结点,待插结点的父节点
//返回:插入结点
Tree insert_val(Tree &root,Tree node,Tree parent)
{
if (root==NULL)
{
root=node;
node->parent=parent; //设置当前结点的父结点
return root; //返回插入结点
}
if (node->val<root->val) //插左子树
return insert_val(root->lchild, node,root);
else if(node->val>root->val) //插右子树
return insert_val(root->rchild, node,root);
else //已存在该结点,停止插入操作,返回NULL
return NULL;
}
//3+4重构函数
//参数:见分析
//返回:新根
Tree connect34(Tree &a,Tree &b,Tree &c,Tree &T0,Tree &T1,Tree &T2,Tree &T3)
{
a->lchild=T0;
if (T0)
T0->parent=a;
a->rchild=T1;
if(T1)
T1->parent=a;
update_depth(a);
c->lchild=T2;
if(T2)
T2->parent=c;
c->rchild=T3;
if(T3)
T3->parent=c;
update_depth(c);
b->lchild=a;
a->parent=b;
b->rchild=c;
c->parent=b;
update_depth(b);
return b;
}
Tree rotateAt(Tree &root,Tree &node)
{
Tree son,temp;
Tree grandson;
int balance=0; //平衡因子
while (node!=NULL) //检查其祖先是否需要调整,更新
{
update_depth(node); //更新当前结点的高度信息
balance=is_balance(node); //获取当前结点的平衡因子情况
if (balance>1 || balance<-1) //平衡因子超标
{
if (balance>1) //左子树高
{
if (is_balance(node->lchild)>0) //LL型
{
//找祖孙三代,后面的类似
son=node->lchild; //找其左孩子
grandson=son->lchild; //找其左孩子的左孩子
son->parent=node->parent; //设置更新后的son的父节点
temp=node;
//重构
node=connect34(grandson, son, node, grandson->lchild, grandson->rchild, son->rchild, node->rchild);
setchild(son, temp, node);//设置son父节点的孩子为node
}
else //LR型
{
son=node->lchild;
grandson=son->rchild;
grandson->parent=node->parent;
temp=node;
node=connect34(son, grandson, node, son->lchild, grandson->lchild, grandson->rchild, node->rchild);
setchild(grandson, temp, node); //设置grandson父节点的孩子为node
}
}
else //右子树高
{
if (is_balance(node->rchild)<0) //RR型
{
son=node->rchild;
grandson=son->rchild;
son->parent=node->parent;
temp=node;
node=connect34(node, son, grandson, node->lchild, son->lchild, grandson->lchild, grandson->rchild);
setchild(son, temp, node); //设置son父节点的孩子为node
}
else //RL型
{
son=node->rchild;
grandson=son->lchild;
grandson->parent=node->parent;
temp=node;
node=connect34(node, grandson, son, node->lchild, grandson->lchild, grandson->rchild, son->rchild);
setchild(grandson, temp, node); //设置grandson父节点的孩子为node
}
}
if (node->parent==NULL) //到达根结点
{
root=node; //设置新的根结点
break; //退出
}
}
node=node->parent; //依次找到其父节点
}
return root; //返回新根
}
void setchild(Tree &g,Tree &temp,Tree &node)
{
if (g->parent)
{
if (g->parent->lchild==temp)
g->parent->lchild=node;
else
g->parent->rchild=node;
}
}
//查找最小结点
Tree *Find_Min(Tree &root)
{
if (root->lchild)
{
return Find_Min(root->lchild);
}
return &root;
}
//删除操作
//参数:根,需要删除的结点
//返回值: 返回删除结点的父节点
Tree remove_val(Tree &root,Tree &node)
{
Tree parent=node->parent;
Tree temp=NULL;
//只有左孩子
if (node->rchild==NULL && node->lchild!=NULL)
{
temp=node;
node=node->lchild; //指向左孩子
node->parent=temp->parent;
delete temp; //释放结点
update_depth(node); //更新当前结点信息
}
else if(node->lchild==NULL && node->rchild!=NULL) //只有右孩子
{
temp=node;
node=node->rchild; //指向右结点
node->parent=temp->parent;
delete temp; //释放结点
update_depth(node); //更新当前结点信息
}
else if(node->rchild==NULL && node->lchild==NULL) //叶子结点
{
parent=node->parent; //找到其父节点
if (parent) //如果父节点存在
{
delete node;
node=NULL;
update_depth(parent); //更新父节点高度信息
}
else //删除的是根
{
delete root;
root=NULL;
}
}
else //既有左孩子也有右孩子,化繁为简
{
Tree *tmp=Find_Min(node->rchild); //找到替代元素,temp为叶子结点
node->val=(*tmp)->val; //更新值
//判断当前叶子结点是左孩子还是右孩子。
parent=(*tmp)->parent;
delete *tmp;
*tmp=NULL;
update_depth(parent);
}
return parent;
}
//找到删除的结点,执行删除操作,并根据情况调整AVL树
//参数:根,需要删除的val
//返回:找到删除结点的情况则返回新根,否则返回NULL
Tree remove(Tree &root,ElementType val)
{
static Tree *temp=NULL;
if (root==NULL)
{
temp=NULL;
return NULL;
}
else if(root->val<val) //在右子树查找
remove(root->rchild, val);
else if(root->val>val) //在左子树查找
remove(root->lchild, val);
else //找到了,标记一下
temp=&root;
if (temp)
{
if (!root->parent) //如果已经返回到最后一次(也就是root是真正的树根)
{
Tree tmp=NULL;
tmp=remove_val(root,*temp); //执行删除操作
return rotateAt(root, tmp);
}
return *temp;
}
return NULL;
}
//获取当前结点的深度
int get_balance(Tree node)
{
if (node==NULL)
return 0;
return node->depth;
}
//返回当前平衡因子
int is_balance(Tree node)
{
if (node==NULL)
return 0;
else
return get_balance(node->lchild)-get_balance(node->rchild);
}
//更新当前深度
void update_depth(Tree node)
{
if (node==NULL)
return;
else
{
int depth_Lchild=get_balance(node->lchild); //左孩子深度
int depth_Rchild=get_balance(node->rchild); //右孩子深度
node->depth=max(depth_Lchild,depth_Rchild)+1;
}
}
//前序
void PreOrder(Tree root)
{
if (root==NULL)
return;
printf("%d ",root->val);
PreOrder(root->lchild);
PreOrder(root->rchild);
}
//中序
void InOrder(Tree root)
{
if (root==NULL)
return;
InOrder(root->lchild);
printf("%d ",root->val);
InOrder(root->rchild);
}
int main()
{
Tree root=NULL;
root = Insert(root, 16);
root = Insert(root, 3);
//插入7后LR调整
root = Insert(root, 7);
root = Insert(root, 11);
//插入9后LL调整
root = Insert(root, 9);
//插入26后RR调整
root = Insert(root, 26);
//插入18后RL调整
root = Insert(root, 18);
root = Insert(root, 14);
//插入15后LR调整
root = Insert(root, 15);
printf("插入:\n");
printf("前序:");
PreOrder(root); // 11 7 3 9 18 15 14 16 26
printf("\n");
printf("中序:");
InOrder(root); // 3 7 9 11 14 15 16 18 26
printf("\n");
printf("删除:\n");
//测试删除叶子结点
// remove(root, 16);
//测试删除只有左孩子的结点
// remove(root, 16);
// remove(root, 15);
//测试删除只有右孩子的结点
// remove(root, 14);
// remove(root, 15);
//测试删除有左右孩子的结点
// remove(root, 18);
//删除26后进行LR型调整
remove(root, 26);
//删除18后进行RR型
remove(root, 18);
remove(root, 3);
remove(root, 9);
//删除7过进行RL调整
remove(root, 7);
//删除11后进行LL调整
remove(root, 11);
//把结点删除完
// remove(root, 15);
// remove(root, 14);
// remove(root, 16);
printf("前序:");
PreOrder(root);
printf("\n");
printf("中序:");
InOrder(root);
printf("\n");
return 0;
}
参考文章:
平衡二叉树(AVL)图解与实现_zthgreat的博客-CSDN博客_平衡二叉树