二叉平衡树

目录

 

一、定义

二、二叉平衡树调整

三、二叉平衡树的插入

四、二叉平衡树的删除


一、定义

平衡二叉树或则是空树,或者是具有如下特征的二叉排序树

(1)左子树和右子树的深度之差的绝对值不超过1;

(2)左子树和右子树也是平衡二叉树

平衡二叉树的查找的时间复杂度是O(log n)。

某结点的平衡因子:该结点左右子树深度之差。

示例:

二、二叉平衡树调整

       在创建平衡二叉树时需要插入结点,插入节点时按照二叉排序树处理,若插入的结点破坏了二叉平衡树的平衡性,则需要对二叉平衡树进行调整。调整的方法是:找到离插入节点最近且平衡因子绝对值超过1的祖先结点,以该结点为根的子树称为最小不平衡二叉树(如上图右边值为15的结点为最小不平衡二叉树的根结点),可将重新平衡的范围局限在这颗子树。 

    假设最小不平衡二叉树的根结点为A,那么失去平衡后调整的规律可以归纳为以下四种。

(1)LL型:由于在A左子树根节点的左子树上插入节点,使A的平衡因子由1增至2而失去平衡。则需要进行一次向右旋转的操作。

两个LL型调整的例子,调整代码都是A.left = B.right; B.right = A:

(2)RR型:由于在A右子树根节点的右子树上插入节点,A的平衡因子由-1变为-2而失去平衡。则需要进行一次向左旋转的调整。

RR型调整的例子,调整代码是 A.left = B.right; B.left = A:

(3)LR型:由于在A的左子树根节点的右子树上插入节点,导致A的平衡因子由1增至2,而使A失去平衡,则需要进行两次操作。第一次对B及起右子树进行左旋使之变成LL型,然后对A进行右旋,左旋右旋的代码同上面。(插入结点在C的左子树和在C的右子树上操作相同)

LR调整的例子:

(4)RL型:由于在A的右子树根节点的左子树上插入节点,A的平衡因子由-1变成-2,致使以A为根的子树失去平衡,则旋转方法是先对A的右子树右旋,再对A树左旋。

RL调整示例:

调整的实现:

//以p为根的树的右旋转
	public AVLNode rightRotation(AVLNode p){
		AVLNode b = p.left;		//p的左子树
		p.left = b.right;	//旋转后令b的右孩子作为p的左孩子
		p.height = Math.max(Height(b.right), Height(p.left))+1; //重新计算p的树深
		b.right = p;		//b的右孩子变成p
		b.height = Math.max(Height(b.left),Height(b.right))+1; //重新计算b的树深
		return b;	//旋转之后b为新的根节点
	}
	
	//以p为根的树的左旋转
	public AVLNode leftRotation(AVLNode p){
		AVLNode b = p.right;	//p的右子树
		p.right = b.left;	//旋转后令b的左孩子为p的右孩子
		p.height = Math.max(Height(b.left), Height(p.left))+1;	//重新计算p的树深
		b.left = p;			//b的左孩子变成p
		b.height = Math.max(Height(b.left), Height(b.right))+1;	//重新计算b的树深
		return b;	//旋转之后b为新的根节点
	}
	
	//先左旋再右旋,先把p的左子树左旋,再对p右旋
	private AVLNode LRRotation(AVLNode p){
		p.left = leftRotation(p.left);
		return rightRotation(p);
	}
	//先右旋再左旋,先把p的右子树右旋,再对p左旋
	private AVLNode RLRotation(AVLNode p){
		p.right = rightRotation(p.right);
		return leftRotation(p);
	}

三、二叉平衡树的插入

通过观察:对于一颗平衡二叉树,假设要插入的结点为P,插入后P的双亲结点为F。

           ①当插入的结点没有导致子树的深度的增加时,一定不会影响平衡。(F本来有右子树或左子树,P插入后成为F的左子树                 或右子树,树的深度不变,不会影响平衡)

           ②当插入的结点导致子树的深度增加时:

                  1.如果影响了平衡,那么经过调整后不会改变最小不平衡二叉树根结点的祖先的平衡因子

                  2.如果没有影响平衡,那么会影响插入结点点祖先的平衡因子。

           ③如何确定是否影响了平衡

                  如果子树的深度增加了,并且某个祖先结点的平衡因子绝对值超过了2,说明影响了平衡。

使用递归来实现插入。递归向下查找时找到插入位置并插入,由于递归在返回时按照查找的路径返回的,所以在返回时碰到的第一个失衡结点就是最小不平衡二叉树的根节点,返回过程中还可以更新结点的平衡因子。为了方便,结点记录以该结点为树根的深度,该结点的平衡因子就是左右子树根节点深度之差。返回时只需要重新计算一次树的深度即可。

结点的数据结构:

static final class AVLNode{
    int height;    //以该结点为树根的树的深度
    int data;    //值
    AVLNode left;    //左孩子
    AVLNode right;    //右孩子
    AVLNode(){
        data = 0;
        height =1;
        left = right = null;
    }
    AVLNode(int e){
        data = e;
        height = 1;
        left = right = null;
    }
    AVLNode(int height,int e,AVLNode left,AVLNode right){
        this.height = height;
        data = e;
        this.left = left;
        this.right = right;
    }
    String toString(){
        return data+" ";
    }
}

插入算法:

	//获取树深度
	private int Height(AVLNode p){
		if(p == null)
			return 0;
		else
			return p.height;
	}
    /*
	 * 插入操作,递归实现
	 * 在递归查找时可以找到插入位置,在返回时遇到的第一个不平衡的结点就是最小平衡
	 * 二叉树的根结点。而且可以根据是在该结点的左子树还是右子树上插入的来判断调整
	 * 的类型。
	 */
	private AVLNode insertAVL(AVLNode p,int e){
		if(p == null){	//找到插入的位置
			p = new AVLNode(e);
		}
		else if( e < p.data){	//递归查询左子树
			p.left = insertAVL(p.left,e);
			if(Math.abs(Height(p.left) - Height(p.right))== 2){	//插入后影响平衡
				if(e > p.left.data){	//说明是在p的左子树的右子树上插入,为LR型
					p = LRRotation(p);	
				}
				else{	//否则为LL型,进行右旋,右旋前根节点为p,右旋后根节点为返回的结点
					p = rightRotation(p);
				}
			}
		}
		else if(e > p.data){	//递归查询右子树
			p.right = insertAVL(p.right,e);
			if(Math.abs(Height(p.left) - Height(p.right))==2){//插入后影响了平衡
				if(e > p.right.data){ //说明是在p的右子树的右子树上插入的,为RR型
					p = leftRotation(p);
				}
				else{	//否则为RL型
					p = RLRotation(p);
				}
			}
		}
		p.height = Math.max(Height(p.left), Height(p.right))+1;	//重新计算p的树高
		return p;
	}

四、二叉平衡树的删除

假设P是要删除的结点,二叉平衡树的删除使用的是二叉排序树删除的方法,用P的直接前驱来替代P,然后将P的直接前驱删除,并从删除位置开始向上查找最小不平衡树的根节点。在左子树上删除一个结点导致失衡,可以看作是在右子树上插入一个结点导致失衡。如果删除的本来就是叶子结点,或者只有左子树或右子树,那么就直接删除P,然后将其左子树或右子树直接接到P的双亲结点,并向上查找最小不平衡树的根节点。

private AVLNode deleteAVL(AVLNode p,int e){
    if(p == null)    //不存在值等于e的结点,则什么都不做
        return p;
    else if( e < p.data){    //查找p的左子树
        p.left = deleteAVL(p.left,e);
    }
    else if( e > p.data){    //查找p的右子树
        p.right = deleteAVL(p.right,e);
    }
    else{    //找到了值等于e的结点
        if( p.left != null && p.right != null){    //如果p有左右子树
                AVLNode s = p.left;
                while( s.right != null){    //找p的前驱,即左子树最右下的结点
                    s = s.right;
                }
                p.data = s.data;    //使用前驱替代p
                p.left = deleteAVL( p.left , s.data)    //要删除的结点就成了p的前驱
        }
        else{    //如果p只有左子树或右子树或都没有
            //当要删除的是p的前驱时,最后也是在这删除的,因为p的前驱一定没有右子树

            p = p.left == null ? p.right : p.left;    //将要删除结点的非空子树挂在其双亲结点
        }
    }
    //判断是否影响了平衡并调整树
    if( p == null)
        return p;
    else if(Height(p.left) - Height(p.right) >=2){    //由于左子树比右子树高影响平衡
         if( Height(p.left.left) > Height(p.left.right) ){    //LL型
             p = leftRotation(p);
         }
         else{    //LR型
            p = LRRotation(p);
         }
    }
    else if(Height(p.right) - Height(p.left) >=2){    //由于右子树比左子树高影响平衡
        if(Height(p.left.left) > Height(p.left.right)){    //RL型
            p = RLRotation(p);
        }
        else{    //RR型
            p = rightRotation(p);
        }
    }
    p.Hieght = Math.max(Height(p.left),Height(p.right)) + 1;    //重新计算p的深度
    return p;
}
            
        
            
            
            

完整代码:

package trees;
import java.util.*;
//二叉平衡树(AVL树)
public class AVLTree {
	static final class AVLNode{
		int height;	//以该结点为根的树的深度,平衡因子为左右子树深度之差
		int data;	//值
		AVLNode left;	//左孩子
		AVLNode right;  //右孩子
		AVLNode(){
			height = 1;
			data = 0;
			left = right = null;
		}
		AVLNode(int height,int e,AVLNode left,AVLNode right){
			this.height = height;
			this.data = e;
			this.left = left;
			this.right = right;
		}
		AVLNode(int e){
			data = e;
			height = 1;
			left = right = null;
		}
		public String toString(){
			return data+" ";
		}
	}
	HashMap<String,String> s = new  HashMap<>();
	
	AVLNode root;
	public AVLTree(){
		root = null;
	}
	//获取树深度
	private int Height(AVLNode p){
		
		if(p == null)
			return 0;
		else
			return p.height;
	}
	
	//以p为根的树的右旋转
	public AVLNode rightRotation(AVLNode p){
		AVLNode b = p.left;		//p的左子树
		p.left = b.right;	//旋转后令b的右孩子作为p的左孩子
		p.height = Math.max(Height(b.right), Height(p.left))+1; //重新计算p的树深
		b.right = p;		//b的右孩子变成p
		b.height = Math.max(Height(b.left),Height(b.right))+1; //重新计算b的树深
		return b;	//旋转之后b为新的根节点
	}
	
	//以p为根的树的左旋转
	public AVLNode leftRotation(AVLNode p){
		AVLNode b = p.right;	//p的右子树
		p.right = b.left;	//旋转后令b的左孩子为p的右孩子
		p.height = Math.max(Height(b.left), Height(p.left))+1;	//重新计算p的树深
		b.left = p;			//b的左孩子变成p
		b.height = Math.max(Height(b.left), Height(b.right))+1;	//重新计算b的树深
		return b;	//旋转之后b为新的根节点
	}
	
	//先左旋再右旋,先把p的左子树左旋,再对p右旋
	private AVLNode LRRotation(AVLNode p){
		p.left = leftRotation(p.left);
		return rightRotation(p);
	}
	//先右旋再左旋,先把p的右子树右旋,再对p左旋
	private AVLNode RLRotation(AVLNode p){
		p.right = rightRotation(p.right);
		return leftRotation(p);
	}
	/*
	 * 插入操作,递归实现
	 * 在递归查找时可以找到插入位置,在返回时遇到的第一个不平衡的结点就是最小平衡
	 * 二叉树的根结点。而且可以根据是在该结点的左子树还是右子树上插入的来判断调整
	 * 的类型。
	 * 如果插入导致失衡,假设A为失衡后的最小不平衡二叉树,经过旋转调整后树深度其实
	 * 不会改变,但是A的子树的树深度可能由于调整变化了位置,树深度可能变化。
	 * 只有在插入后树没有失衡时才可能导致树的深度增加。
	 */
	private AVLNode insertAVL(AVLNode p,int e){
		if(p == null){	//找到插入的位置
			p = new AVLNode(e);
		}
		else if( e < p.data){	//递归查询左子树
			p.left = insertAVL(p.left,e);
			if(Math.abs(Height(p.left) - Height(p.right))== 2){	//插入后影响平衡
				if(e > p.left.data){	//说明是在p的左子树的右子树上插入,为LR型
					p = LRRotation(p);	
				}
				else{	//否则为LL型,进行右旋,右旋前根节点为p,右旋后根节点为返回的结点
					p = rightRotation(p);
				}
			}
		}
		else if(e > p.data){	//递归查询右子树
			p.right = insertAVL(p.right,e);
			if(Math.abs(Height(p.left) - Height(p.right))==2){//插入后影响了平衡
				if(e > p.right.data){ //说明是在p的右子树的右子树上插入的,为RR型
					p = leftRotation(p);
				}
				else{	//否则为RL型
					p = RLRotation(p);
				}
			}
		}
		p.height = Math.max(Height(p.left), Height(p.right))+1;	//重新计算p的树高
		return p;
	}
	/*
	 * 删除操作
	 * 由二叉排序树的删除可知,假设要删除的结点为p,删除一般使用前驱后者后继来替代要删除的结点,
	 * 然后再删除掉前驱或者后继。二叉平衡树的删除也类似处理,在删除后要调整树
	 */
	private AVLNode deleteAVL(AVLNode p,int e){
		if( p == null)	//不存在值等于e的结点
			return p;
		else if( e < p.data){
			p.left = deleteAVL(p.left,e);	//在左子树中删除
		}
		else if( e > p.data ){
			p.right = deleteAVL(p.right,e);	//在右子树中删除
		}
		else{	//找到了值等于e的结点
			//如果既有左子树又有右子树,那么使用前驱替代的方法
			if( p.left != null && p.right !=null ){
				AVLNode s = p.left;
				while(s.right != null){
					s = s.right;
				}
				p.data = s.data;
				//删除前驱,即删除p的左子树中值等于s.data的结点
				p.left = deleteAVL(p.left,s.data);
			}
			else{
				/*
				 * 如果不是左右子树都为空,那么只需要用p的左右子树替代p即可
				 * 最后删除结点的语句就是这一句。。。
				 */
				p = p.left == null? p.right:p.left;
			}
		}
		
		//调整平衡,在p的右子树上删除一个结点导致失衡可以看做是在p的左子树上插入一个结点导致失衡
		if( p == null){
			return p;
		}
		if(Height(p.left) - Height(p.right) >= 2){	//由于左子树高而失衡
			//相当于在左子树的左子树上插入一个结点导致失衡,LL型
			if(Height(p.left.left)>Height(p.left.right)){
				return rightRotation(p);
			}
			else{	//LR型
				return LRRotation(p);
			}
		}
		else if(Height(p.right) - Height(p.left) >= 2){ //由于右子树高而失衡
			if(Height(p.right.right) > Height(p.right.left)){ //RR型
				return rightRotation(p);
			}
			else{	//RL型
				return RLRotation(p);
			}
		}
		p.height = Math.max(Height(p.left), Height(p.right))+1;	//调整树高
		return p;
	}
	public void inOrder(){
		inOrder(root);
	}
	public void inOrder(AVLNode p){
		if(p != null){
			inOrder(p.left);
			System.out.print(p+" ");
			inOrder(p.right);
		}
	}
	public void insertAVL(int e){
		root = insertAVL(root,e);
	}
	public void deleteAVL(int e){
		root = deleteAVL(root,e);
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Random rand = new Random();
		AVLTree t = new AVLTree();
		for(int i = 0;i < 10;i++){
			t.insertAVL(rand.nextInt(100)+1);
		}
		t.inOrder();
		System.out.println();
		int a;
		Scanner scan = new Scanner(System.in);
		a = scan.nextInt();
		t.deleteAVL(a);
		t.inOrder();
	}
}

几乎完全参考自:https://blog.csdn.net/shichimiyasatone/article/details/85231211

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值