数据结构之平衡二叉树(AVL)---Java实现

一、AVL树的基础知识

1. AVL树是BST树的一种,BST的性质均在AVL树中同样适用,但在查找等方面效率不同

2.AVL树对BST树的优化: 当插入的数据是按顺序排好时,那么在BST树中形成的是一个单链表,查询等效率便会降低,而AVL树很好的解决了这一问题。

3. 所谓的平衡即是指:每个结点的两个子树的高度差值为0或者1.

4. 平衡因子:一个结点的平衡因子是指它的左子树的高度减去右子树的高度(也有说是右子树的高度减去左子树的高度,这个影响不大)。则AVL树中结点的平衡因子只能为1、0或者-1.

二、 AVL树中的操作与BST树中操作的不同点

1. AVL树可以说是BST树的一种优化类型,它的大部分实现思路源自于BST树,但是在插入和删除结点之后,树的平衡性可能发生改变,因此需要重新判断树是否满足平衡条件,若不平衡,还要采取措施让树平衡。

2. 对树进行平衡与否检测的时机:当有新的结点插入或者删除时。

3. 应该对那些结点进行平衡检测:

3.1 插入操作:从新插入结点的父结点开始,沿着到这个结点的反向路径逐个检测。(由于新插入的结点一定是叶子结点,因此它一定是平衡的,不需要进行检测)。
3.2 删除操作:从删除结点的那个子树形成的根结点开始,沿着到这个结点的方向路径进行检测。

三、AVL类设计

与BST类的内部结点类相比,AVL类内部结点类需要新增一个记录结点高度的属性
public class AVLTree<E extends Comparable<E>> {
	
	//内部结点类
	private class Node{
		public E value;     //存储结点值
 		public Node left;   //指向左孩子
		public Node right;  //指向右孩子
		int height;         //增加项:记录当前结点所处的高度(规定叶子结点高度为1)
		
		public Node(E value) {
			this.value = value;
			this.left = null;
			this.right = null;
			this.height = 1;    //所形成的结点一定会插入到AVLTree的叶子结点处,即高度为1
		}
	}
	
	private Node root;   //根结点
	private int size;    //树中结点个数
	
	public AVLTree() {
		this.root = null;   //初始时为一棵空树
		this.size = 0;
	}
}

四、辅助方法

1. 获取结点高度:

由于结点中记录有高度值,故直接返回即可。

//获取结点的高度
	public int getHeight(Node node) {
		if(node == null)
			return 0;
		return node.height;
	}
2. 获取平衡因子

平衡因子 = 左子树高度 - 右子树高度;

//获取平衡因子
public int getBalanceFactor(Node node) {
		if(node == null)
			return 0;
		return getHeight(node.left) - getHeight(node.right);
	}
3. 为了判断平衡后的树是否满足BST的条件以及AVL的条件,这里提供两个方法进行判断:
3.1 判断是否还是BST树

实现思路:使用中序遍历得到的将是一列排好序的数据,通过判断中序遍历得到的数据是否是排好序的即可判断树是否还为BST树。

//判断是否为BST树
public boolean isBST(Node node){
	ArrayList<E> list = new ArrayList<>();
	inOrder(node,list);
	for(int i = 1; i < list.size(); i++){
		if(list.get(i - 1).compareTo(list.get(i)) > 0)
			return false;
	}
	return true;
}
3.2 判断是否还是AVL树

实现思路:通过判断每个结点的平衡因子是否小于等于1进行检测。

//判断树是否为平衡的
	public boolean isBalance() {
		return isBalance(root);
	}
	
	private boolean isBalance(Node node) {
		if(node == null)
			return true;
		
		int balanceFactor = getBalanceFactor(node);
		
		if(Math.abs(balanceFactor) > 1)
			return false;
		return isBalance(node.left) && isBalance(node.right);
	}

五、 四种平衡机制

1. LL

在这里插入图片描述
如图所示:结点A的平衡因子为2,它的左孩子结点的平衡因子为1或者0,这种类型可通过将结点A向右旋转进行平衡修复。对于插入操作相当于是插入的结点在当前结点的左孩子的左子树中

代码实现:
//LL 进行右旋转
	private Node rightRotate(Node y) {
		Node x = y.left;
		y.left = x.right;
		x.right = y;
		
		//更新height 此时y为x的孩子结点,因此需要先更新y的高度,再更新x的高度(x的高度与y的高度有关)
		y.height = Math.max(getHeight(y.left), getHeight(y.right));
		x.height = Math.max(getHeight(x.left), getHeight(x.right));
		return x;
	}

2. RR

RR与LL的思路相同,它们两个是对称的
在这里插入图片描述
如图所示:结点A的平衡因子为-2,它的右孩子结点的平衡因子为-1或者0,这种类型可通过将结点A向左旋转进行平衡修复。对于插入操作相当于是插入的结点在当前结点的右孩子的右子树中

代码实现:
	//RR 左旋转
	private Node leftRotate(Node y) {
		Node x = y.right;
		y.right = x.left;
		x.left = y;
		
		//更新height
		y.height = Math.max(getHeight(y.left),getHeight(y.right));
		x.height = Math.max(getHeight(x.left),getHeight(x.right));
		return x;
	}

3. LR

在这里插入图片描述
如图所示:结点A的平衡因子为2,A的左孩子结点B的平衡因子为-1,这种类型需要通过两次旋转来实现树的平衡,先将结点B向左旋转一次,然后再将结点A向右旋转一次。对于插入操作相当于是插入的结点在当前结点的左孩子的右子树中
实现代码复用leftRotate()和rightRotate();

4. RL

在这里插入图片描述
如图所示:结点A的平衡因子为-2,A的右孩子结点B的平衡因子为1,这种类型需要通过两次旋转来实现树的平衡,先将结点B向右旋转一次,然后再将结点A向左旋转一次。对于插入操作相当于是插入的结点在当前结点的右孩子的左子树中
实现代码复用leftRotate()和rightRotate();

六、AVL树的插入操作

实现思路:大部分实现代码借用BST树的插入操作的代码,之后在插入之后需要重新计算结点的高度以及对树进行重新平衡(如何平衡在第二点中已经说明)。
代码实现:

//向树中添加结点
	public void add(E value) {
		root = add(root,value);
	}
	
	//添加结点时要满足BST的规则,增加结点会导致相关结点所处的高度的改变,
	//同时可能会破坏树的平衡性,因此需要重新更新结点高度以及平衡树。
	private Node add(Node node, E value) {
		if(node == null) {
			size++;
			return new Node(value);
		}
		
		if(value.compareTo(node.value) < 0) {
			node.left = add(node.left,value);
		}
		else if(value.compareTo(node.value) > 0){
			node.right = add(node.right,value);
		}
		else {
			throw new IllegalArgumentException("Arguments is illegal!");
		}
		
		//更新结点的高度 = 左右子树中高度的最大值 + 1
		node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
		
		//平衡因子计算
		int balanceFactor = getBalanceFactor(node);
	
		//对不平衡的结点重新进行平衡
		//1. 右旋转
			if(balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
				return rightRotate(node);
		//2. 左旋转
			if(balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
				return leftRotate(node);
		
		//3. LR
		if(balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
			node.left = leftRotate(node.left);
			return rightRotate(node);
		}
		
		//4. RL
		if(balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
			node.right = rightRotate(node.right);
			return leftRotate(node);
		}
		return node;
	}

七、AVL树的删除操作

		//获取最小值所在的结点
		private Node minimum(Node node) {
			if(node.left == null)   //node.left == null ,那么结点node即为最小结点
				return node;
			
			return minimum(node.left);  //递归遍历
		}
//删除操作
	public void remove(E value) {
		if(value == null)
			throw new IllegalArgumentException("Argument Illegal!");
		root = remove(root,value);
	}
	
	private Node remove(Node node, E value) {
		if(node == null)
			return null;
		int result = value.compareTo(node.value);   //比较当前结点与要删除的结点值的大小
		Node currentNode;
		//要删除的结点在当前结点的左子树中
		if(result <0) {
			node.left = remove(node.left,value);
			currentNode = node;
		}
		//要删除的结点在当前结点的右子树中
		else if(result > 0) {
			node.right = remove(node.right,value);
			currentNode = node;
		}
		//当前结点即为要进行删除的结点
		else {
			//左子树为空
			if(node.left == null) {
				Node nodeRight = node.right;
				node.right = null;
				
				size--;
				currentNode = nodeRight;
			}
			
			//右子树为空
			else if(node.right == null) {
				Node nodeLeft = node.left;
				node.left = null;
				size--;
				currentNode = nodeLeft;
			}
			
			//要删除结点的左右子树均不为空
			else {
				 // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
                // 用这个节点顶替待删除节点的位置
                Node minNode = minimum(node.right);
                minNode.right = remove(node.right, minNode.value);
                minNode.left = node.left;
                node.left = node.right = null;
                currentNode = minNode;
			}
			
		}
		
		if(currentNode == null) {
			return null;
		}
		
		//更新结点高度
		currentNode.height = 1 + Math.max(getHeight(currentNode.left),
				getHeight(currentNode.right));
		
		//计算平衡因子
		int balanceFactor = getBalanceFactor(currentNode);
		
		// 平衡操作
		// 1. 右旋转
		if (balanceFactor > 1 && getBalanceFactor(currentNode.left) >= 0)
			return rightRotate(currentNode);
		// 2. 左旋转
		if (balanceFactor < -1 && getBalanceFactor(currentNode.right) <= 0)
			return leftRotate(currentNode);

		// 3. LR
		if (balanceFactor > 1 && getBalanceFactor(currentNode.left) < 0) {
			currentNode.left = leftRotate(currentNode.left);
			return rightRotate(currentNode);
		}

		// 4. RL
		if (balanceFactor < -1 && getBalanceFactor(currentNode.right) > 0) {
			currentNode.right = rightRotate(currentNode.right);
			return leftRotate(currentNode);
		}
		return currentNode;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值