AVL树 —— 带平衡的二叉查找树 及其实现

前面我们已经介绍了二叉查找树的性质以及二叉查找树的实现,这篇博客我们介绍带平衡的二叉查找树。在介绍之前,必须纠正一个错误。

树的高度: 对任意结点ni, ni的高度为从ni到一片树叶的最长路径的长。如:叶子结点的高度为0
树的深度: 对任意结点ni, ni的深度为从根到ni的唯一的路径的长。如:根结点的深度为0

很多人会将这两个概念混淆,甚至于我看到的一本电子书上的作者也将这个概念混淆了。

AVL 带平衡的二叉查找树

AVL是在二叉查找树的基础上升级的一种树结构,AVL除了具有二叉查找树的特性外,还有它独特的性质,即其每个结点的左子树和右子树高度最多差1 ,如下图所示为AVL树,其中红色的为各个结点的高度:AVL树
上图中如果将14结点删除则不是AVL树。在这颗树中,如果我们插入一个结点0,这就破坏了原有树的平衡,需要通过旋转来进行修正。而插入的情况分为以下四种:

  • 对结点的左儿子的左子树进行一次插入 (左左单旋转)
  • 对结点的左儿子的右子树进行一次插入 (左右双旋转)
  • 对结点的右儿子的左子树进行一次插入 (右左单旋转)
  • 对结点的右儿子的右子树进行一次插入 (右右单旋转)

如: 我们需要向上图中插入结点0,原有树的平衡被打破,结点5和结点12的高度相差2.
在这里插入图片描述
上图中的树如果想要保持平衡,需要进行一次左左单旋转,将结点3和结点5进行一次左左单旋转,得到的结果如下图所示(右右单旋转和左左单旋转是关于结点的镜像对称,这里不再举例):
在这里插入图片描述
下面给出一棵新的树:
在这里插入图片描述
对上图进行改进插入一个结点5,树的平衡条件再次发生改变。如下图:
在这里插入图片描述
此时我们需要进行一次右左双旋转(发生在结点3、结点6和结点4),而右左双旋转又可以分解为一次左左单旋转和一次右右单旋转。下图是进行左左单旋转(结点6和结点4)后的结果.
在这里插入图片描述
进行左左单旋转后我们可以看到树还是没有平衡,此时我们还需要进行一次右右单旋转(发生在结点3和结点4)。旋转后的结果如下图所示
在这里插入图片描述此时我们可以发现树又重新平衡了,类似于右左双旋转,左右双旋转可以分解为一次右右单旋转和一次左左单旋转,这里不再举例。下面给出AVL树的实现

AVL树的实现

UnderflowException.java

public class UnderflowException extends Exception {

	public UnderflowException(String message) {
		super(message);
	}
}

AVLTree.java

import java.util.Comparator;
import java.util.Objects;

public class AVLTree<T extends Comparable<? super T>> {

	private static final int ALLOWED_IMBALANCE = 1;
	private AvlNode<T> root;
	private Comparator<? super T> cmp;	// 自定义排序
	
	public AVLTree() {
		this(null);
	}
	
	public AVLTree(Comparator<? super T> comparator) {	// 向下限定
		root = null;
		this.cmp = comparator;
	}
	
	public void makeEmpty() {
		root = null;
	}
	
	public boolean isEmpty() {
		return root == null;
	}
	
	public boolean contains(T element) {
		return contains(element, root);
	}
	
	private boolean contains(T element, AvlNode<T> t) {
		if(t == null) return false;
		
		int compareResult = myCompare(element, t.element);
		
		if(compareResult > 0) {
			return contains(element,t.left);
		}else if(compareResult < 0) {
			return contains(element, t.right);
		}
		
		return true;	// 匹配到了
		
	}
	
	public T findMin() throws UnderflowException {
		if(isEmpty()) throw new UnderflowException("树为空!");
		return findMin(root).element;
	}
	
	private AvlNode<T> findMin(AvlNode<T> t){	// 最小值在最左边
		
		if( t == null ) return null;
		else if(t.left == null) return t;
		
		return findMin(t.left);
	}
	
	public T findMax() throws UnderflowException {
		if(isEmpty()) throw new UnderflowException("树为空!");
		return findMax(root).element;
	}
	
	private AvlNode<T> findMax(AvlNode<T> t){	// 最大值在最右边
		if(t == null) return null;
		else if(t.right == null) return t;
		return findMax(t.right);
	}
	
	public void remove(T element) {
		root = remove(element,root);
	}
	
	private AvlNode<T> remove(T element, AvlNode<T> t){
		
		if( t == null) return null;
		
		int compareResult = myCompare(element, t.element);
		
		if(compareResult > 0) {
			return remove(element , t.left);
		}else if(compareResult < 0) {
			return remove(element , t.right);
		}else if(t.left != null && t.right != null) {	// 判断是否有两个孩子
			t.element = findMin(t.right).element;
			t.right = remove(element,t.right);
		}else {											// 没有孩子或者只有一个孩子的情况
			t = (t.left != null) ? t.left:t.right;
		}
		
		return balance(t);	// 删除完后需要旋转,因为删除可能打破了平衡
	}

	public void insert(T element) {
		root = insert(element, root);
	}
	
	/*
	 * 插入的情况有以下四种:
	 * 			1. 根节点的左子树的左边插入(即左左,单旋转)
	 * 			2. 根结点的左子树的右边插入(即左右,双旋转)
	 * 			3. 根节点的右子树的左边插入(即右左,双旋转)
	 * 			4. 根结点的右子树的右边插入(即右右,单旋转)
	 */
	private AvlNode<T> insert(T element, AvlNode<T> t) { // 插入
		if (t == null) {
			return new AvlNode<>(element, null, null);
		}

		int compareResult = myCompare(element, t.element);

		if (compareResult < 0)
			t.left = insert(element, t.left);
		else if (compareResult > 0)
			t.right = insert(element, t.right);
		else
			;

		return balance(t);
	}
	/*
	 * 在进行树的平衡时需要考虑以下几点
	 * 		1. 树是否需要旋转
	 * 		2. 如果要旋转,是发生在根结点的左边还是右边
	 * 		3. 是单旋转还是双旋转
	 */
	private AvlNode<T> balance(AvlNode<T> t) {
		
		if (t == null)	// 基线条件
			return t;
		
		if (height(t.left) - height(t.right) > ALLOWED_IMBALANCE)	// 判断旋转是否发生在左边
			
			if (height(t.left.left) >= height(t.left.right))	// 判断是否是左左单旋转 
				t = rotateWithLeftChild(t);	// 进行单旋转
			else
				t = doubleWithLeftChild(t);	// 进行双旋转
		
		else if (height(t.right) - height(t.left) > ALLOWED_IMBALANCE)	// 判断旋转是否发生在右边
			
			if (height(t.right.right) >= height(t.right.left))	// 判断是否为右右单旋转
				t = rotateWithRightChild(t);	// 进行单旋转
			else
				t = doubleWithRightChild(t);	// 进行双旋转
		
		// 更新高度,max中的到的是子树的高度,t的高度需要在子树的高度上面加1
		t.height = Math.max(height(t.left), height(t.right)) + 1;	
		return t;
	}

	/*
	 * 左左单旋转 获取k2右结点k1 将k1的右结点作为k2的左结点 将k2作为k1的结点 更新高度 返回新的根节点k1
	 */
	private AvlNode<T> rotateWithLeftChild(AvlNode<T> k2) { // 左左单旋转
		AvlNode<T> k1 = k2.left;
		k2.left = k1.right; // 将k1的右子树变为k2的左子树
		k1.right = k2; // 将k2变为k1的右子树
		k2.height = Math.max(height(k2.left), height(k2.right)); // 更新k2的高度
		k1.height = Math.max(height(k1.left), height(k1.right)); // 更新k1的高度
		return k1;
	}

	/*
	 * 右右单旋转 获取k1的右结点k2 将k2的左结点作为k1的右结点 将k1作为k2的左结点 更新高度 返回新的根节点k2
	 */
	private AvlNode<T> rotateWithRightChild(AvlNode<T> k1) {
		AvlNode<T> k2 = k1.right;
		k1.right = k2.left;
		k2.right = k1;
		k1.height = Math.max(height(k1.left), height(k1.right));
		k2.height = Math.max(height(k2.left), height(k2.right));
		return k2;
	}

	
	/*
	 * 左右双螺旋 k3的左结点先进行右右单旋转 k3与k3的左结点进行左左单旋转
	 */
	private AvlNode<T> doubleWithLeftChild(AvlNode<T> k3) {
		k3.left = rotateWithRightChild(k3.left);
		return rotateWithLeftChild(k3);
	}

	/*
	 * 右左双螺旋 k3的右结点先进行左左单旋转 k3与k3的右结点进行右右单旋转
	 */
	private AvlNode<T> doubleWithRightChild(AvlNode<T> k1) {
		k1.right = rotateWithLeftChild(k1.right);
		return rotateWithRightChild(k1);
	}
	
	public void printTree() {
		printTree(root);
	}
	
	private void printTree(AvlNode t) {	// 前序遍历
		if(t != null) {
			printTree(t.left);
			System.out.println(t);
			printTree(t.right);
		}
	}

	private int height(AvlNode<T> t) {	// 返回指定树的高度
		return t == null ? -1 : t.height;
	}
	
	private int myCompare(T element1, T element2) {		// 比较
		
		Objects.requireNonNull(element1, "不能为空!");
		Objects.requireNonNull(element2, "不能为空!");
		
		if(cmp != null) 
			return cmp.compare(element1, element2);	// 自定义排序
		else 
			return element1.compareTo(element2);	// 自然排序
	}
	
	/*
	 * 二叉平衡树的结点
	 */
	private static class AvlNode<T> {
		T element;
		AvlNode<T> left;
		AvlNode<T> right;
		int height;

		AvlNode(T element) {
			this(element, null, null);
		}

		AvlNode(T element, AvlNode<T> lt, AvlNode<T> rt) {
			this.element = element;
		}
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值