JAVA实现AVL树的重构,解决因为添加和删除导致的失衡问题_avl树节点删除引发失衡,旋转重新平衡后子树高度(1)

img
img
img

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

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

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

}

##### (2)解决失衡:RR左旋转(单旋)


和LL右旋转 类似 RR左旋转 意味着,导致某个节点失衡的的原因是,它右孩子的右孩子添加了一个元素。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200808105744347.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNTA3MTA0,size_16,color_FFFFFF,t_70)  
   
 g.right = p.left  
 p.left = g  
 p 成为此树的根节点。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200808105834682.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNTA3MTA0,size_16,color_FFFFFF,t_70)  
 平衡达到了,而且还是一颗搜索二叉树,但是我们还要注意一下两点  
   
 ① 改变T1,p,g的父节点  
 ②更新 p ,g 的高度。  
 **注意!!!平衡后树的高度较未添加红色节点前 没有变化**  
   
 **代码**



private void rotateRight(Node<E> grand) {
	Node<E> parent = grand.left;
	Node<E> child = parent.right;
	
	grand.left = child;
	parent.right = grand;
	
	parent.parent = grand.parent;
	if (grand.isLeftChild()) {
		grand.parent.left = parent;
	}else if (grand.isRightChild()) {
		grand.parent.right = parent;
	}else {
		root = parent;
	}
	
	grand.parent = parent;
	
	if (child != null) {
		child.parent = grand;
	}
	updateHeight(grand);
	updateHeight(parent);
	
}

##### (3)解决失衡:LR 先左旋转再右旋转(双旋)


为什么叫LR呢,因为某个节点的左孩子的右孩子添加节点导致它的失衡。  
 双旋的意思时旋转两次  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200808110317832.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNTA3MTA0,size_16,color_FFFFFF,t_70)  
   
 对 p 左旋转  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200808110444284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNTA3MTA0,size_16,color_FFFFFF,t_70)  
   
 对 g 右旋转  
   
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200808110725198.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNTA3MTA0,size_16,color_FFFFFF,t_70)  
 代码很简单



			rotateLeft(parent);
			rotateRight(grand);

**注意!!!平衡后树的高度较未添加红色节点前 没有变化**


##### (4)解决失衡:RL 先右旋转再左旋转(双旋)


这个大家可以参照LR  
   



#### 五 .afterAdd 函数设计


我们之前讲到,添加导致失衡的最坏情况是所有的祖父节点都失衡,那是不是所有的失衡节点都要去去一个一个平衡呢?  
  **并不是这样的.只要让高度最低的失衡节点恢复平衡,整棵树就可以恢复平和。(仅需要调整O(1)次),  
 而且失衡结点恢复平衡后,结点的高度和未添加结点平衡时的高度一样。这意味这,我们更新结点的高度这个操作到最低的失衡结点处就可以停止了。** 


##### 1. 设计思路


因为只要平衡最低的失衡节点,树就可以恢复平衡,所以在恢复平衡函数rebalance(node); 后直接break 退出循环。  
 这是一种设计的思路:我们先把逻辑写下来,差什么函数,写完逻辑后再补。比如 rebalance(node); 现在只有逻辑意义:平衡一个节点。但是没有实现的代码。



protected void afterAdd(Node<E> node) {
		while((node = node.parent) != null) {
			if (isBalanced(node)) {
				//更新高度
				updateHeight(node);
			}else {
				//恢复平衡
				rebalance(node);
				break;
			}
		}
}

##### 2.恢复平衡 rebalance 方法设计


我们已经了解到了,失衡的四种情况,现在我们可以把四种情况整合一下,写一个无论哪种情况都能恢复平衡的 rebalance 函数,需要注意的是我们这个函数需要传入的是失衡节点,我们上述四种情况都是grandparent(也就是g节点)失衡。所以我们我们传入的型参就写grand。  
 注意!此函数是结局失衡问题的,无论是添加导致的失衡还是删除导致的失衡



private void rebalance(Node grand) {
//找高度比较高的子节点
Node parent = ((AVLNode)grand).tallerChild();
Node node = ((AVLNode)parent).tallerChild();
if (parent.isLeftChild()) {// L
if (node.isLeftChild()) {//LL
rotateRight(grand);
}else { //LR
rotateLeft(parent);
rotateRight(grand);
}
}else { //R
if (node.isRightChild()) {//RR
rotateLeft(grand);
}else {//RL
rotateRight(parent);
rotateLeft(grand);
}
}
}


##### 3.tallerChild 函数的设计意义


大家可以看到,AVLNode中有一个tallerChild 函数。而且我在rebalance中也用到了。为什么要写这个函数呢?


![在这里插入图片描述](https://img-blog.csdnimg.cn/20200808160313344.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNTA3MTA0,size_16,color_FFFFFF,t_70)  
   
 大家看这个图,我传入rebalance的是 g 节点。我们看这个图得以知道,导致他失衡的是他左孩子的左孩子。但是没图的话,用代码如何去找是哪个子节点的 子节点让他失衡呢?  
   
 g节点失衡,导致他失衡的节点一定在他左右子树中高度最高的子树中,所以我们编写tallerchild 函数 ,来找高度最高的子树


#### 五 .删除导致的失衡问题,afterRemove函数设计


首先你需要知道的是  
 1 删除节点只可能导致父节结点失衡(有且只有一个节点失衡)  
 为什么呢?在删除导致失衡的情况下,被删除的结点不会改变失衡点的高度。所以祖先结点的平衡因子不会发生改变  
 2 让父节点恢复平衡之后可能会导致更高层的祖先节点失衡(最多需要O(logn)次调整)  
 


##### 1.afterRemove函数设计


我们根据上述的绿字来设计afterRemove函数。



protected void afterRemove(Node<E> node) {
	while((node = node.parent) != null) {
		if (isBalanced(node)) {
			//更新高度
			updateHeight(node);
		}else {
			//恢复平衡
			rebalance(node);
		}
	}
}

afterRemove和afterAdd的区别在于,比如删除结点的父节点失衡,我们平衡了父节点之后,祖父结点也有可能失衡,所以我们要一直循环,直到根结点。  
 相同点在于,更新高度这个操作都到最低的失衡节点处(删除只导致一个失衡节点,我们把它看做最低),但是他们的原因不同:  
 afterRemove是因为删除不影响失衡节点以上(包括失衡节点)的祖先节点高度。  
 afterAdd是因为失衡结点恢复平衡后,结点的高度和未添加结点平衡时的高度一样。即添加影响高度,但是平衡后恢复原先高度


#### 重构代码


AVL树是BST(二叉搜索树)的子类,BST代码在之前的文章中



import java.util.Comparator;

public class AVLTree extends BST{

//构造方法
public AVLTree(){
	this(null);
}

public AVLTree(Comparator<E> comparator) {
	super(comparator);
}


/*
 一棵树是否平衡
 */
private boolean isBalanced(Node<E> node) {
	return Math.abs(((AVLNode<E>)node).balanceFactor()) <= 1;
}

/*
 高度更新方法的封装
 */
private void updateHeight(Node<E> node) {
	((AVLNode<E>)node).updateHeight();
}

/*
 AVL数需要有高度属性
 AVL特有的节点
 */
private static class AVLNode<E> extends Node<E>{
	int height = 1;
	
	public AVLNode(E element, Node<E> parent) {
		super(element, parent);
		// TODO 自动生成的构造函数存根
	}
	
	/*
	 求平衡因子
	 */
	public int balanceFactor() {
		int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
		int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
		return leftHeight - rightHeight;
	}
	
	/*
	 更新高度的方法
	 */
	public void updateHeight() {
		int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
		int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
		height = 1 + Math.max(leftHeight, rightHeight);
	}
	/*
	 * 找比较高的子节点
	 */
	public Node<E> tallerChild(){
		int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
		int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
		if (leftHeight > rightHeight) {return left;}
		if (leftHeight < rightHeight) {return right;}
		//相等返回同边的
		return isLeftChild() ? left : right;
	}
}






/*
 * 重写方法恢复平衡的逻辑
 */
@Override
protected void afterAdd(Node<E> node) {
		while((node = node.parent) != null) {
			if (isBalanced(node)) {
				//更新高度
				updateHeight(node);
			}else {
				//恢复平衡
				rebalance(node);
				break;
			}
		}
}


/*
 删除后恢复平衡的方法
 */
@Override
protected void afterRemove(Node<E> node) {
	while((node = node.parent) != null) {
		if (isBalanced(node)) {
			//更新高度
			updateHeight(node);
		}else {
			//恢复平衡
			rebalance(node);
		}
	}
}



/*
 恢复树平衡的逻辑
 */

private void rebalance(Node<E> grand) {
	//找高度比较高的子节点
	Node<E> parent = ((AVLNode<E>)grand).tallerChild();
	Node<E> node = ((AVLNode<E>)parent).tallerChild();
	if (parent.isLeftChild()) {// L
		if (node.isLeftChild()) {//LL
			rotateRight(grand);
		}else { //LR
			rotateLeft(parent);
			rotateRight(grand);
		}
	}else { //R
		if (node.isRightChild()) {//RR
			rotateLeft(grand);
		}else {//RL
			rotateRight(parent);
			rotateLeft(grand);
		}
	}
}


/*
 统一的恢复平衡的方法
 */
private void rebalance2(Node<E> grand) {
	//找高度比较高的子节点
	Node<E> parent = ((AVLNode<E>)grand).tallerChild();
	Node<E> node = ((AVLNode<E>)parent).tallerChild();
	if (parent.isLeftChild()) {// L
		if (node.isLeftChild()) {//LL
			rotate(grand, node.left, node, node.right, parent, parent.right, grand, grand.right);
		}else { //LR
			rotate(grand, parent.left, parent, node.left, node, node.right, grand, grand.right);
		}
	}else { //R
		if (node.isRightChild()) {//RR
			rotate(grand, grand.left, grand, parent, parent.left, node, node.left, node.right);
		}else {//RL
			rotate(grand, grand.left, grand, node.left, node, node.right, parent, parent.right);
		}
	}
}

/*
 统一的旋转方法
 */
private void rotate(
		Node<E> r, //子树根节点
		Node<E> a,Node<E> b,Node<E> c,
		Node<E> d,
		Node<E> e,Node<E> f,Node<E> g) {
	//让d成为这个子树的根节点
	d.parent = r.parent;
	if (r.isLeftChild()) {

img
img
img

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

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

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

成为这个子树的根节点
d.parent = r.parent;
if (r.isLeftChild()) {

[外链图片转存中…(img-ayhFdLpx-1715527545354)]
[外链图片转存中…(img-vQDYWgbF-1715527545354)]
[外链图片转存中…(img-B2ZLYNip-1715527545354)]

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值