平衡二叉树定义
AVL树也就是所谓的平衡二叉树,查找、删除、插入最坏情况下为O(log n),维持树的平衡只需要几次旋转即可。保证任意一个节点的左子树高度与右子树高度相差不大于1,默认树为空时高度为0。
节点结构
为了后续操作简洁性,先给出节点结构
final class node{
int value=0;//键值
int height=1;//节点高度
node left=null;//左右孩子
node right=null;
node(int value){
this.value=value;
}
}
旋转类型
维持平衡的操作就是树的旋转,旋转类型可以分为四种:
1.右旋LL类型
单个右旋,将k1的右子树作为k2的左子树,k2作为k1的右子树。该操作只是对于k1,k2进行,所以X,Y,X子树的高度不变,在旋转完成后,只需更新k1,k2高度即可
node l_l_rotate(node root){//LL旋转
node temp=root.left;//将左节点拿出来
root.left=temp.right;
temp.right=root;
root.height=Math.max(get_height(root.left),get_height(root.right))+1;//更新原root的height
root=temp;
root.height=Math.max(get_height(root.left),get_height(root.right))+1;//更新新root的height
return root;
}
简单说就是,将节点的左节点的右子树作为节点的左子树,节点作为其原来左节点的右子树。
2.左旋RR性
跟右转刚好反过来,k2的左子树作为k1的右子树,k1作为k2的左子树。跟上面一样,更改完成后,更新k1,k2的高度即可
node r_r_rotate(node root){//RR旋转
node temp=root.right;//将右节点拿出来
root.right=temp.left;
temp.left=root;
root.height=Math.max(get_height(root.left),get_height(root.right))+1;//更新原root的height
root=temp;
root.height=Math.max(get_height(root.left),get_height(root.right))+1;//更新新root的height
return root;
}
节点的右节点的左子树作为节点的右子树,节点作为其原来右节点的左子树。
3.先左旋再右旋LR
两次操作而已,k1,k2执行RR旋转,然后对k3执行LL右旋操作。
node l_r_rotate(node root){//LR旋转
root.left=r_r_rotate(root.left);//先RR旋转
return l_l_rotate(root);//再LL旋转
}
4.先右旋再左旋RL
先对k3,k2执行LL右旋,再对k1执行RR左旋操作。
node r_l_rotate(node root){//RL旋转
root.right=l_l_rotate(root.right);//先LL旋转
return r_r_rotate(root);//再LL旋转
}
(用LL表示右旋操作,是因为LL表示的是结构上是个“左边形”,所以要向右旋转)
判断旋转操作
node check(node root){//检查高度,判断是否需要旋转操作
root.height=(Math.max(get_height(root.left),get_height(root.right))+1);//更新节点高度
if(get_height(root.left)-get_height(root.right)==2){//左边增高了
if(get_height(root.left.left)>=get_height(root.left.right)){//LL型
root=l_l_rotate(root);//LL旋转
}else{//LR型
root=l_r_rotate(root);//LR旋转
}
}else if(get_height(root.right)-get_height(root.left)==2){//右边增高了
if(get_height(root.right.left)>get_height(root.right.right)){//RL型
root=r_l_rotate(root);//RL旋转
}else{//RR型
root=r_r_rotate(root);//RR旋转
}
}
return root;
}
判断很简单,在插入或者删除操作之后,在从根节点到操作的(插入或删除)的那个节点的这条路径上,更新该路径上的节点的高度,并判断是否需要旋转操作来维持平衡。
插入节点操作
从根节点开始到插入位置的这条路径上,在插入节点操作完成之后之后,需要依次从下(插入节点)向上,更新每个该路径上的节点的高度(height属性)信息,借助上面的check函数,在需要旋转的情况下判断条件,进行旋转操作。
由此可知,插入操作需要以递归函数的方式进行。原因如下:
1.建立新插入的节点与原有节点(父节点)之间的引用关系
例如,待插入节点的value值小于root节点时:root.left=insert(root.left,value);将新建立的节点作为其父节点的左子节点,建立联系。
2.回退时更新父节点的高度(height属性)信息
在完成最底层,带插入位置的插入操作之后,即root.left=new node(value);进行root节点的高度更新
参考代码:
node insert(node root,int value){//插入节点
if(root==null){//找到插入位置
node t=new node(value);//创建该节点
return t;
}
if(root.value==value){//相等就算了,不过此处没有优化,相等的节点上层仍需要check检查
System.out.println("already exists");
return root;
}
if(value<root.value){//左子树上创建节点
root.left=insert(root.left,value);
}else{//右子树上创建节点
root.right=insert(root.right,value);
}
root=check(root);//检查高度,判断是否需要旋转操作
return root;
}
删除节点操作
删除节点有两个情况
1.待删除的节点具有左子树和右子树
这种情况下待删除的节点可以看做是个官二代,要把他删除了,但有权有势的人家直接找了个替罪羊,自己改个名字照样活的自在。真实情况应该是,将节点删除,在其左子树和右子树中,选择较高的一个子树的极值,放于删除节点出,无图无真相
要删除节点5,则在节点5左子树和右子树中选择较高的一个,这里一样高就随便选了,比如选择右子树,则选择右子树的最小值,即6,放到节点5的位置上;如果选择左子树,则选择最大值,即3,放到5的位置上。然后删除选择的6或者3节点。所以可以看出来,这操作其实根本就不是删除5,而是删除左子树的最大值或者右子树的最小值,5节点一直存在,不过就是换个value值而已,对象不变。
这样的好处,一个是操作简单,不用创建新对象,直接在原对象上修改即可,另外就是可以在删除操作中维持二叉树的平衡,不需要进行旋转操作(当然更新高度还是要有的)
2.待删除的节点不同时具有左子树和右子树
当然不可能每个节点都是官二代,待删除的节点可能是叶子节点或者只有一个子树不为空的节点,此时就需要放弃节点对象了。
删除操作仍然是以递归的形式进行的,原理相同,从根到待删除的节点,在该影响路径上,由下向上依次递归更新。在从根节点找到官二代节点的情形中,同样如此,不过就是换个方向继续向下而已,所以方式一致。
参考代码:
node delete(node root,int value){//根据值来删除节点
if(root==null){//即使没找到也会check检查height
System.out.println("not found");
return null;
}
if(value<root.value){//左子树上去找
root.left=delete(root.left,value);
}else if(value>root.value){//右子树上去找
root.right=delete(root.right,value);
}else{//找到了
if(root.left!=null&&root.right!=null){//左右都不等于null,删除之后仍然平衡
if(root.left.height>root.right.height){//只是需要更新height
root.value=max(root.left);//取左子树最大值
root.left=delete(root.left,root.value);//删除该最大值
}else{
root.value=min(root.right);//取右子树最小值
root.right=delete(root.right,root.value);//删除该最小值
}
}else{//左右子树部分或者全部为null
root=(root.left!=null?root.left:root.right);
return root;
}
}
root=check(root);
return root;
}
参考示例:
建立平衡二叉树:1,2,3,7,8,9,4,5,6
删除:4,6
插入3节点时,RR旋转
插入8节点时,RR旋转
插入9节点,RR旋转
添加6节点后,RR左旋
删除4,6操作
删除6节点,LL右旋
参考代码:
public class t{
public static void main(String[] args){
int[] arr=new int[]{1,2,3,7,8,9,4,5,6};
tree tr=new tree();
for(int i:arr){
tr.insert(i);
}
tr.delete(4);
tr.delete(6);
tree.pre_show(tr);
}
}
final class node{
int value=0;//键值
int height=1;//节点高度
node left=null;//左右孩子
node right=null;
node(int value){
this.value=value;
}
}
final class tree{
node root=null;
tree(){}
void insert(int value){
if(root==null){
root=new node(value);
}else{
root=insert(root,value);
}
}
node insert(node root,int value){//插入节点
if(root==null){//找到插入位置
node t=new node(value);//创建该节点
return t;
}
if(root.value==value){//相等就算了,不过此处没有优化,相等的节点上层仍需要check检查
System.out.println("already exists");
return root;
}
if(value<root.value){//左子树上创建节点
root.left=insert(root.left,value);
}else{//右子树上创建节点
root.right=insert(root.right,value);
}
root=check(root);//检查高度,判断是否需要旋转操作
return root;
}
int get_height(node t){//节点高度,空树高度为0
return t==null?0:t.height;
}
node check(node root){//检查高度,判断是否需要旋转操作
root.height=(Math.max(get_height(root.left),get_height(root.right))+1);//更新节点高度
if(get_height(root.left)-get_height(root.right)==2){//左边增高了
if(get_height(root.left.left)>=get_height(root.left.right)){//LL型
root=l_l_rotate(root);//LL旋转
}else{//LR型
root=l_r_rotate(root);//LR旋转
}
}else if(get_height(root.right)-get_height(root.left)==2){//右边增高了
if(get_height(root.right.left)>get_height(root.right.right)){//RL型
root=r_l_rotate(root);//RL旋转
}else{//RR型
root=r_r_rotate(root);//RR旋转
}
}
return root;
}
void delete(int value){
root=delete(root,value);
}
node delete(node root,int value){//根据值来删除节点
if(root==null){//即使没找到也会check检查height
System.out.println("not found");
return null;
}
if(value<root.value){//左子树上去找
root.left=delete(root.left,value);
}else if(value>root.value){//右子树上去找
root.right=delete(root.right,value);
}else{//找到了
if(root.left!=null&&root.right!=null){//左右都不等于null,删除之后仍然平衡
if(root.left.height>root.right.height){//只是需要更新height
root.value=max(root.left);//取左子树最大值
root.left=delete(root.left,root.value);//删除该最大值
}else{
root.value=min(root.right);//取右子树最小值
root.right=delete(root.right,root.value);//删除该最小值
}
}else{//左右子树部分或者全部为null
root=(root.left!=null?root.left:root.right);
return root;
}
}
root=check(root);
return root;
}
int max(node root){
if(root==null){
System.out.println("search and not found");
return -1;
}
while(root.right!=null){
root=root.right;
}
return root.value;
}
int min(node root){
if(root==null){
System.out.println("search and not found");
return -1;
}
while(root.left!=null){
root=root.left;
}
return root.value;
}
node l_l_rotate(node root){//LL旋转
node temp=root.left;//将左节点拿出来
root.left=temp.right;
temp.right=root;
root.height=Math.max(get_height(root.left),get_height(root.right))+1;//更新原root的height
root=temp;
root.height=Math.max(get_height(root.left),get_height(root.right))+1;//更新新root的height
return root;
}
node r_r_rotate(node root){//RR旋转
node temp=root.right;//将右节点拿出来
root.right=temp.left;
temp.left=root;
root.height=Math.max(get_height(root.left),get_height(root.right))+1;//更新原root的height
root=temp;
root.height=Math.max(get_height(root.left),get_height(root.right))+1;//更新新root的height
return root;
}
node l_r_rotate(node root){//LR旋转
root.left=r_r_rotate(root.left);//先RR旋转
return l_l_rotate(root);//再LL旋转
}
node r_l_rotate(node root){//RL旋转
root.right=l_l_rotate(root.right);//先LL旋转
return r_r_rotate(root);//再LL旋转
}
static void pre_show(tree tr){//前序遍历树
pre_show(tr.root);
}
static void pre_show(node root){//前序遍历树
if(root!=null){
System.out.println("value:"+root.value+",height:"+root.height);
pre_show(root.left);
pre_show(root.right);
}
}
}
结果:
E:\>java t
value:7,height:4
value:2,height:3
value:1,height:1
value:5,height:2
value:3,height:1
value:8,height:2
value:9,height:1
总结
平衡二叉树提供复杂度为O(Log n)的查询、修改操作,通过对树的旋转来保持树的平衡性质。