2024年【数据结构】平衡二叉树的插入、删除_平衡二叉树插入怎么插(1),2024年最新12道Golang高级面试题

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

开始时,k2节点左孩子是小x,整棵树还是平衡树,然后在小x的子节点插入一个数,小x变成大X,此时大X高度为2,k1节点不平衡,为使树恢复平衡,我们把k2变成这棵树的根节点,因为k2大于k1,把k1置于k2的右子树上,而原本在k2右子树的Y大于k1,小于k2,就把Y置于k1的左子树上,这样既满足了二叉查找树的性质,又满足了平衡二叉树的性质。

这样的操作只需要一部分指针改变,结果我们得到另外一颗二叉查找树,它是一棵AVL树,因为X向上一移动了一层,Y还停留在原来的层面上,Z向下移动了一层。整棵树的新高度和之前没有在左子树上插入的高度相同,插入操作使得X高度长高了。因此,由于这颗子树高度没有变化,所以通往根节点的路径就不需要继续旋转了。

右旋转代码:

/* 对以T为根的二叉排序树作右旋处理 */
	/* 处理之后T的父节点指向T的左节点 */
	//右旋-顺时针旋转(如LL型就得对根结点做该旋转)
	private void R_Rotate(Node T)
	{ 
	    Node L,P;
	    P=T.parent;
	    L=T.lchild;                      /*  L指向node的左子树根结点 */
	    T.lchild=L.rchild;               /*  L的右子树挂接为node的左子树 */ 
	    
	    if(L.rchild!=null)
	    	L.rchild.parent=T;
	    L.rchild=T;
	    L.parent=P;
	    T.parent=L;
	    
	    if(P==null)
	    	root=L;
	    else if(P.rchild==T)
	    	P.rchild=L;
	    else
	    	P.lchild=L;
	    
	}

右旋转原理:获取失去平衡结点以及左结点,为了让lchild作为根节点,将lchild的rchild挂接到之前左结点上,然后在挂接到s->rchild.

左旋转代码

/* 对以T为根的二叉排序树作左旋处理, */
	/* 处理之后T的父节点指向T的右节点 */
	//左旋-逆时针旋转(如RR型就得对根结点做该旋转)
	private void L_Rotate(Node T)
	{ 
	    Node R,P;
	    P=T.parent;
	    R = T.rchild;                    /* R指向T的右子树根结点 */
	    T.rchild = R.lchild;         /* R的左子树挂接为T的右子树 */
	   
	    if(R.lchild!=null)
	    	R.lchild.parent=T;
	    R.lchild = T;
	    R.parent=P;
	    T.parent=R;
	    
	    if(P==null)
	    	root=R;
	    else if(P.rchild==T)
	    	P.rchild=R;
	    else
	    	P.lchild=R; 
	                       
	}

双旋转

对于左右和右左这两种情况,单旋转不能使它达到一个平衡状态,要经过两次旋转。双旋转是针对于这两种情况的解决方案,同样的,这样两种情况也是对称的,只要解决了左右这种情况,右左就很好办了。图4是左右情况的解决方案,节点k3不满足平衡特性,因为它的左子树k1比右子树D深2层,而且k1子树中,更深的一层的是k1的右子树k2子树,所以属于左右情况。

为使树恢复平衡,我们需要进行两步,第一步,把k1作为根,进行一次左旋转,旋转之后就变成了左左情况,所以第二步再进行一次右旋转,最后得到了一棵以k2为根的平衡二叉树树。

双旋转代码

/*  对以指针T所指结点为根的二叉树作左平衡旋转处理 */
	/*  本算法结束时,指针T指向新的根结点 */
	public void LeftBalance(Node T)
	{ 
	    Node L,Lr;
	    L = T.lchild;                    /*  L指向T的左子树根结点 */
	    switch(L.bf)
	    { 
	        /* 检查T的左子树的平衡度,并作相应平衡处理 */
	        case LH:                        /* 新结点插入在T的左孩子的左子树上,要作单右旋处理 */
	            T.bf=L.bf=EH;
	            R_Rotate(T);
	            break;
	        case RH:                        /* 新结点插入在T的左孩子的右子树上,要作双旋处理 */ //
	            Lr=L.rchild;                /* Lr指向T的左孩子的右子树根 */
	            switch(Lr.bf)
	            {   
	                /* 修改T及其左孩子的平衡因子 */
	                case LH: 
	                    T.bf=RH;
	                    L.bf=EH;
	                    break;
	                case EH: 
	                    T.bf=L.bf=EH;
	                    break;
	                case RH: 
	                    T.bf=EH;
	                    L.bf=LH;
	                    break;
	            }
	            Lr.bf=EH;
	            L_Rotate(T.lchild); /* 对T的左子树作左旋平衡处理 */
	            R_Rotate(T);                /* 对T作右旋平衡处理 */
	            break;
	        case EH:	  //特殊情况4,这种情况在添加时不可能出现,只在移除时可能出现,旋转之后整体树高不变
				L.bf = RH;
				T.bf = LH;
				R_Rotate(T);
				break;
	    }
	}

首先,定义三个常数变量,分别代码1、0、-1。

(1)函数被调用,传入一个需调整平衡型的子树T,根节点为k3,由于LeftBalance函数被调用时,其实是已经确认当前子树是不平衡的状态,且左子树的高度大于右子树的高度。换句话说,此时T的根结点应该是平衡因子BF的值大于1的数。k3的BF为2

(2)将T的左孩子赋值给L。L指向K1.

(3)然后是分支判断。

(4)当L(k1)的平衡因子为LH,即为1时,表明它与根结点的BF值符号相同,因此,将它们的BF值都改为0,并进行右旋(顺时针)操作,是左左情况

(5)当L的平衡因子为RH时,即为-1时,表明它与根结点的BF值符号相反,此时需要做双旋操作。针对L的右孩子k2的BF作判断,修改结点T(k3)和L(k1)的BF值。将当前的Lr的BF改为0。从图中看到K2的左结点是连接到K1的右子树上,右结点连接到K3的左子树,其中当k2结点为RH,说明K2有右结点有,左结点无,k3为0((*T)->bf=EH; ),k1就没有右结点为LH。当为Lh看程序。

(6)对根结点的左子树进行左旋,以K1为根节点进行左旋转,形成左左情况。

(7)对根结点K3进行右旋,完成平衡操作。

插入节点

插入过程也是一个二叉树查找的过程,先看图

在插入节点2之前,该树还是一颗平衡树,当插入节点2之后,节点3就成为了不平衡点,需要对节点3进行左平衡处理。

在详细的分析一下:

首先从根节点4开始搜索,发现2<4,于是搜索4的左节点3,发现2<3,就搜索1,2>1,搜索1的右节点,此时发现1的右节点为空,就执行插入操作,将2插入到1的右节点,那么怎么发现此时这棵树不平衡的呢?因为我们是通过递归寻找插入点,当找到插入点在1的右节点之后,开始往父节点回溯,回溯过程中告诉父节点,孩儿有没有长高,如果长高了,父节点就要判断左右子树高度差是否大于1,也就是处于不平衡状态,每个节点都有个平衡因子,EH=0(等高),LH=1(左边高1),RH=-1(右边高1)例如,插入2之前,节点1的BF=0,节点3BF=1,节点4BF=1,插入节点2之后,往父节点1回溯,说我长高了,1节点BF=0,现在右节点长高了,所以此时节点1BF=-1,在往父节点3回溯,告诉父节点3,我长高了,而父节点3BF=1,而现在左孩子长高了,那BF=2,此时节点3称为不平衡点,需要对节点3做左平衡处理。处理完成后,2节点变成了1,3的父节点,此时2节点的高度和没插入节点2之前3节点高度一样,于是告诉父节点4,我没有长高,此时递归结束。

代码如下:

public void insertAVL(int e){
		if(root==null){
			root=new Node(e,EH,null,null,null);
			return;
		}
		TS t=new TS();
		InsertAVL(root,e,t,null);
	}
	/*  若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个 */
	/*  数据元素为e的新结点,并返回1,否则返回0。若因插入而使二叉排序树 */
	/*  失去平衡,则作平衡旋转处理,布尔变量taller反映T长高与否。 */
	private boolean InsertAVL(Node T,int e,TS tl,Node parent)
	{  
	    if(T==null)
	    { 
	        /*  插入新结点,树“长高”,置taller为TRUE */
	        Node nNode=new Node(e,EH,null,null,parent);
	        if(e<parent.value)
	        	parent.lchild=nNode;
	        else
	        	parent.rchild=nNode;
	        tl.taller=TRUE;
	    }
	    else
	    {
	        if (e==T.value)
	        { 
	            /*  树中已存在和e有相同关键字的结点则不再插入 */
	        	tl.taller=FALSE; 
	        	return false;
	        }
	        else if (e<T.value)
	        { 
	            /*  应继续在T的左子树中进行搜索 */
	            if(!InsertAVL(T.lchild,e,tl,T))
	            	return false;
	            if(tl.taller)                             /*   已插入到T的左子树中且左子树“长高” */
	                switch(T.bf)                 /*  检查T的平衡度 */
	                {
	                    case LH:                        /*  原本左子树比右子树高,需要作左平衡处理 */
	                        LeftBalance(T); 
	                        tl.taller=FALSE; 
	                        break;
	                    case EH:                        /*  原本左、右子树等高,现因左子树增高而使树增高 */
	                        T.bf=LH; 
	                        tl.taller=TRUE; 
	                        break;
	                    case RH:                        /*  原本右子树比左子树高,现左、右子树等高 */ 
	                        T.bf=EH; 
	                        tl.taller=FALSE; 
	                        break;
	                }
	        }
	        else
	        { 
	            /*  应继续在T的右子树中进行搜索 */
	            if(!InsertAVL(T.rchild,e,tl,T))
	            	return false;
	            if(tl.taller)                             /*  已插入到T的右子树且右子树“长高” */
	            {
	                switch(T.bf)                 /*  检查T的平衡度 */
	                {
	                    case LH:                        /*  原本左子树比右子树高,现左、右子树等高 */
	                        T.bf=EH; 
	                        tl.taller=FALSE;  
	                        break;
	                    case EH:                        /*  原本左、右子树等高,现因右子树增高而使树增高  */
	                        T.bf=RH; 
	                        tl.taller=TRUE; 
	                        break;
	                    case RH:                        /*  原本右子树比左子树高,需要作右平衡处理 */
	                        RightBalance(T); 
	                        tl.taller=FALSE; 
	                        break;
	                }
	            }
	        }
	    }
	    return true;
	}
删除节点

删除操作和二叉查找树删除一样,分为三种情况讨论

(1)删除节点没有左子树,这种情况直接将删除节点的父节点指向删除节点的右子树。

(2)删除节点没有右子树,这种情况直接将删除节点的父节点指向删除节点的左子树。

(3)删除节点左右子树都存在,可以采用两种方式,

1:让删除节点左子树的最右侧节点代替当前节点

2:让删除节点右子树的最左侧节点代替当前节点

如下图:

这里的难点是删除之后要判断该树是否还平衡?

还是先看图:

和插入操作一样,删除操作也是递归查找,然后删除,删除之后,该节点A要向父节点回溯,告诉父节点B我变矮了(因为删除了),父节点B此时要判断自己是否也变矮了,如果删除的节点是自己的左子树中的节点(右子树同理,这里只讨论左子树情况,右子树请看代码),就要分三种情况讨论:

(1)B.BF=EH ,也就是原来B节点左右子树高度一致,而现在左子树告诉我,左子树变矮了,则需要将B.BF设置为RH,即右边高,同时可知B的高度并没变化,所以再往B的父节点C回溯的时候,B的父节点C就会当啥都没发生。

(2)B.BF=LH,也就是原来B节点左子树比右子树高一层而现在左子树告诉我,左子树变矮了,则需要将B.BF设置为EH,同时可知B节点的高度也变矮了,于是再往B的父节点C回溯的是否,C也要分三种情况讨论。

(3)B.BF=RH,也就是原来B节点右子树比左子树高一层,而现在左子树告诉我,左子树变矮了,则需要对B进行右平衡处理

而这里又要分为两种情况讨论来判断,右平衡处理完成后,需要判断B的父节点C的左子树是否变矮了

1.B.rchild.BF=EH,也就是B节点(右平衡处理之前)的右子树的左右子树等高。那么这种情况,B的父节点C的左子树不变矮。

2.除了1情况,B的父节点C的左子树会变矮。

下面画图来理解一下这两种情况。

这是第一种情况,6的右节点8的BF=EH,那么旋转后高度不变。

这是第二种情况,6的右节点8的BF!=EH,那么旋转后高度变矮。

删除代码如下:

/* 
若在平衡的二叉排序树t中存在和e有相同关键字的结点,则删除之 
并返回TRUE,否则返回FALSE。若因删除而使二叉排序树 
失去平衡,则作平衡旋转处理,布尔变量shorter反映t变矮与否
*/
private boolean deleteAVL(Node t, int key, TS ts)
{
    if(t == null)                                      //不存在该元素 
    {
        return FALSE;                                   //删除失败 
    }
    else if(key == t.value)                           //找到元素结点
    {
        Node q = null;
        if(t.lchild == null)                     //左子树为空 
        {
            q = t.parent;
            if(q==null)
            	root=t.rchild;
            else{
            	if(key<q.value)
            		q.lchild=t.rchild;
            	else
            		q.rchild=t.rchild;
            }
            ts.shorter = TRUE;
        }
        else if(t.rchild == null)                    //右子树为空 
        {
            q = t.parent;
            if(q==null)
            	root=t.lchild;
            else{
            	if(key<q.value)
            		q.lchild=t.lchild;
            	else
            		q.rchild=t.lchild;
            }
            ts.shorter = TRUE;
        }
        else                                            //左右子树都存在,
        {
            q = t.lchild;
            while(q.rchild!=null)
            {
                q = q.rchild;
            }
            t.value = q.value;
            deleteAVL(t.lchild, q.value, ts);   //在左子树中递归删除前驱结点 
        }
    }
    else if(key < t.value)                         //左子树中继续查找 
    {
        if(!deleteAVL(t.lchild, key, ts))
        {
            return FALSE;
        }
        if(ts.shorter)
        {
            switch(t.bf)
            {
            case LH:
                t.bf = EH;
                ts.shorter = TRUE;
                break;
            case EH:
                t.bf = RH;
                ts.shorter = FALSE;
                break;
            case RH:
            	 if(t.rchild.bf == EH)    //注意这里,画图思考一下 
                     ts.shorter = FALSE;
                 else
                     ts.shorter = TRUE;
                RightBalance(t);        //右平衡处理   
                break;
            }
        }
    }
    else                                //右子树中继续查找 
    {
        if(!deleteAVL(t.rchild, key, ts))
        {
            return FALSE;
        }
        if(ts.shorter)
        {
            switch(t.bf)
            {
            case LH:
            	 if(t.lchild.bf == EH)  //注意这里,画图思考一下 
                     ts.shorter = FALSE;
                 else
                     ts.shorter = TRUE;
                LeftBalance(t);         //左平衡处理 
                break;
            case EH:
                t.bf = LH;
                ts.shorter = FALSE;
                break;
            case RH:
                t.bf = EH;
                ts.shorter = TRUE;
                break;
            }
        }
    }
    return TRUE;
}

OK,到此二叉平衡树讲完了,后面把源码附上:

package wangyi;


public class AVLTree {
	private class Node{
		int value;
		int bf;
		Node lchild;
		Node rchild;
		Node parent;
		public Node(int value,int bf,Node lchild,Node rchild,Node parent){
			this.value=value;
			this.bf=bf;
			this.lchild=lchild;
			this.rchild=rchild;
			this.parent=parent;
		}
	}
	Node root=null;
	final int LH =+1;                        /*  左高 */ 
	final int EH= 0;                            /*  等高 */ 
	final int RH=-1;                           /*  右高 */ 
	final int LC=0;                         //在左子树插入
	final int RC=1;							//在右子树插入
	final boolean FALSE=false;
	final boolean TRUE=true;
	
	/* 对以T为根的二叉排序树作右旋处理 */
	/* 处理之后T的父节点指向T的左节点 */
	//右旋-顺时针旋转(如LL型就得对根结点做该旋转)
	private void R_Rotate(Node T)
	{ 
	    Node L,P;
	    P=T.parent;
	    L=T.lchild;                      /*  L指向node的左子树根结点 */
	    T.lchild=L.rchild;               /*  L的右子树挂接为node的左子树 */ 
	    
	    if(L.rchild!=null)
	    	L.rchild.parent=T;
	    L.rchild=T;
	    L.parent=P;
	    T.parent=L;
	    
	    if(P==null)
	    	root=L;
	    else if(P.rchild==T)
	    	P.rchild=L;
	    else
	    	P.lchild=L;
	    
	}
	 
	/* 对以T为根的二叉排序树作左旋处理, */
	/* 处理之后T的父节点指向T的右节点 */
	//左旋-逆时针旋转(如RR型就得对根结点做该旋转)
	private void L_Rotate(Node T)
	{ 
	    Node R,P;
	    P=T.parent;
	    R = T.rchild;                    /* R指向T的右子树根结点 */
	    T.rchild = R.lchild;         /* R的左子树挂接为T的右子树 */
	   
	    if(R.lchild!=null)
	    	R.lchild.parent=T;
	    R.lchild = T;
	    R.parent=P;
	    T.parent=R;
	    
	    if(P==null)
	    	root=R;
	    else if(P.rchild==T)
	    	P.rchild=R;
	    else
	    	P.lchild=R; 
	                       
	}

	/*  对以指针T所指结点为根的二叉树作左平衡旋转处理 */
	/*  本算法结束时,指针T指向新的根结点 */
	public void LeftBalance(Node T)
	{ 
	    Node L,Lr;
	    L = T.lchild;                    /*  L指向T的左子树根结点 */
	    switch(L.bf)
	    { 
	        /* 检查T的左子树的平衡度,并作相应平衡处理 */
	        case LH:                        /* 新结点插入在T的左孩子的左子树上,要作单右旋处理 */
	            T.bf=L.bf=EH;
	            R_Rotate(T);
	            break;
	        case RH:                        /* 新结点插入在T的左孩子的右子树上,要作双旋处理 */ //
	            Lr=L.rchild;                /* Lr指向T的左孩子的右子树根 */
	            switch(Lr.bf)
	            {   
	                /* 修改T及其左孩子的平衡因子 */
	                case LH: 
	                    T.bf=RH;
	                    L.bf=EH;
	                    break;
	                case EH: 
	                    T.bf=L.bf=EH;
	                    break;
	                case RH: 
	                    T.bf=EH;
	                    L.bf=LH;
	                    break;
	            }
	            Lr.bf=EH;
	            L_Rotate(T.lchild); /* 对T的左子树作左旋平衡处理 */
	            R_Rotate(T);                /* 对T作右旋平衡处理 */
	            break;
	        case EH:	  //特殊情况4,这种情况在添加时不可能出现,只在移除时可能出现,旋转之后整体树高不变
				L.bf = RH;
				T.bf = LH;
				R_Rotate(T);
				break;
	    }
	}
	 
	/*  对以指针T所指结点为根的二叉树作右平衡旋转处理, */
	/*  本算法结束时,指针T指向新的根结点 */
	public void RightBalance(Node T)
	{ 
	    Node R,Rl;
	    R=T.rchild;                      /*  R指向T的右子树根结点 */
	    switch(R.bf)
	    { 
	        /*  检查T的右子树的平衡度,并作相应平衡处理 */
	        case RH:                        /*  新结点插入在T的右孩子的右子树上,要作单左旋处理 */
	            T.bf=R.bf=EH;
	            L_Rotate(T);
	            break;
	        case LH:                        /*  新结点插入在T的右孩子的左子树上,要作双旋处理 */ //最小不平衡树的根结点为负,其右孩子为正


![img](https://img-blog.csdnimg.cn/img_convert/46aef2474d692f8eee04353c31797ea7.png)
![img](https://img-blog.csdnimg.cn/img_convert/24cd0359394f77937a01d2500b32f8c5.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

	    switch(R.bf)
	    { 
	        /*  检查T的右子树的平衡度,并作相应平衡处理 */
	        case RH:                        /*  新结点插入在T的右孩子的右子树上,要作单左旋处理 */
	            T.bf=R.bf=EH;
	            L_Rotate(T);
	            break;
	        case LH:                        /*  新结点插入在T的右孩子的左子树上,要作双旋处理 */ //最小不平衡树的根结点为负,其右孩子为正


[外链图片转存中...(img-hcYBxPVm-1715661426313)]
[外链图片转存中...(img-LVs8icRb-1715661426314)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值