数据结构与算法之AVL树

(一) 定义

通过前面的 数据结构与算法之二分搜索树 的学习我们知道, 二分搜索树的性能跟树的高度(h)存在必然联系:

1. 二分搜索树的添加操作, 删除操作, 查询操作 都需要在二分搜索树中找到合适的结点再进行逻辑操作, 找适到合结点所要经历的最多个数结点为 树的高度h.

2. 二分搜索树的高度h 与 二分搜索树元素个数n 存在 h = log2(n+1) 的关系, 在考虑时间复杂度的情况下通常忽略常数2和1, 因此二分搜索树的操作 平均 时间复杂度为 O(logn).

3. 为什么说平均呢? 因为二分搜索树结构是不平衡的, 最坏的情况有可能退化链表O(n). 因此我们需要一种结构平衡的二分搜索树,就算在最坏的情况也能保证二分搜索树的性能保持在 O(log n).

平衡二叉树: 是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树. 平衡二叉树的高度和节点数量之间的关系一定是O(logn).

常见的平衡二叉搜索树有:AVL, 红黑树, Treap

AVL: 是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树

AVL树维护自身的平衡涉及到两个概念:

  • 结点的高度: 从当前结点向下到某个叶子结点最长简单路径中边的条数 + 1(红色)
  • 平衡因子: 当前结点的左右子树的高度差, 平衡因子为1、0或 -1的节点被认为是平衡的,因为它的左右子树高度差不超过 1(绿色)
    在这里插入图片描述

(二) 自定义AVL树

AVL树基于二分搜索树映射实现

1.AVL树基础结构

public class AVLTree<K extends Comparable<K>, V>{

	
	private class Node {
		
		/**
		 * 存储映射的键 
		 */
		public K key;
		
		/**
		 * 存储映射的值 
		 */
		public V value;
		
		/**
		 * 左子树
		 */
		public Node left;
		
		/**
		 * 右子树
		 */
		public Node right;
		
		/**
		 * 当前结点的高度
		 */
		public int height;
		
		public Node(K key, V value) {
			this.key = key;
			this.value = value;
			this.left = null;
			this.right = null;
			
			// 叶子结点的height默认为 1
			this.height = 1;
		}
	}
	
	/**
	 * 根结点
	 */
	private Node root;
	
	/**
	 * 映射中键值对个数
	 */
	private int size;
	
	public AVLTree() {
		this.root = null;
		this.size = 0;
	}
	
	/**
	 * 返回以node为根的二分搜索树的最小值所在的结点
	 * 
	 * @param node
	 * @return
	 */
	private Node minimum(Node node) {
		if(node.left == null) {
            return node;
		}
		return minimum(node.left);
	}

	/**
	 * 获取node结点的高度
	 * 
	 * @param node
	 * @return
	 */
	private int getHeight(Node node) {
		if (node == null) {
			return 0;
		}
		return node.height;
	}
	
	/**
	 * 获取node结点的平衡因子: node结点的左右子树的高度差
	 * 
	 * @param node
	 * @return
	 */
	private int getBalanceFactor(Node node) {
		if (node == null) {
			return 0;
		}
		return getHeight(node.left) - getHeight(node.right);
	}
}

2.AVL添加操作

  • 下图AVL树插入新结点2后, 导致 结点8 的平衡因子由原先的1变成2, 树的平衡性被打破
    在这里插入图片描述
    这是AVL添加操作的第一种情况: 插入的元素在不平衡节点左侧的左侧(LL)
    此时应向不平衡的结点进行 右旋转操作
    在这里插入图片描述
    通用情况:在这里插入图片描述
private Node rightRotate(Node y) {
	Node x = y.left;
	Node T3 = x.right;
	
	// 向右旋转
	x.right = y;
	y.left = T3;
	
	// 更新height
	y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
	x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
	
	return x;
}
  • AVL添加操作的第二种情况: 插入的元素在不平衡节点右侧的右侧(RR)
    此时应向不平衡的结点进行 左旋转操作
    在这里插入图片描述
private Node leftRotate(Node y) {
	Node x = y.right;
	Node T3 = x.left;
	
	// 向左旋转
	x.left = y;
	y.right = T3;
	
	// 更新height
	y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
	x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
	
	return x;
}
  • AVL添加操作的第三种情况: 插入的元素在不平衡节点左侧的右侧(LR)
    在这里插入图片描述
    此时就不能单独对5结点进行左旋转, 因为 结点4和5 都比 结点3 大, 这种情况下先对结点5进行左旋转后转换为LL情况, 然后再进行右旋转.

在这里插入图片描述

  • AVL添加操作的第四种情况: 插入的元素在不平衡节点右侧的左侧(RL)
    先对结点x进行右旋转后转换为RR情况, 然后再进行左旋转.
    在这里插入图片描述
/**
 * 向AVL树中添加键值对key-value(Key键不允许重复)
 */
public void add(K key, V value) {
	this.root = add(this.root, key, value);
}

/**
 * 向以node为根的AVL树中插入键值对key-value, 返回插入新节点后AVL树的跟
 * 
 * @param node
 * @param key
 * @param value
 * @return
 */
private Node add(Node node, K key, V value) {
	if (node == null) {
		size++;
		return new Node(key, value);
	}
	
	// 二分搜索树中存在插入key, 修改key对应的value
	if (node.key.compareTo(key) == 0) {
		node.value = value;
	} else if (node.key.compareTo(key) < 0) {
		node.right = add(node.right, key, value); 
	} else if (node.key.compareTo(key) > 0) {
		node.left = add(node.left, key, value); 
	}
	
	// 维护结点的深度
	node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
	
	// 计算平衡因子
    int balanceFactor = getBalanceFactor(node);
	
	// 第一种情况: 插入的元素在不平衡节点左侧的左侧(LL)
	if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) {
		// 右旋转
		return rightRotate(node);
	}
	
	// 第二种情况: 插入的元素在不平衡节点右侧的右侧(RR)
	if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) {
		// 左旋转
		return leftRotate(node);
	}
	
	// 第三种情况: 插入的元素在不平衡节点左侧的右侧(LR)
	if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
		node.left = leftRotate(node.left);
        return rightRotate(node);
	}
	
	// 第四种情况: 插入的元素在不平衡节点右侧的左侧(RL)
	if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
		node.right = rightRotate(node.right);
        return leftRotate(node);
	}
	
	return node;
}

3.AVL树删除操作

删除操作也需要维护AVL树的平衡, 递归执行remove(node, key)函数, 每次都会返回删除节点后新的AVL树的根node, 我们只需要对返回的node维护平衡, 考虑LL, RR LR, RL四种情况.

/**
 * 从AVL中删除键为key的数据
 */
public V remove(K key) {
	Node node = getNode(root, key);
	if (node != null) {
		root = remove(root, key);
		return node.value;
	}
	return null;
}

/**
 * 删除以node为根的AVL树中键为key的结点, 返回删除节点后新的AVL树的跟
 * 
 * @param node
 * @param key
 * @return
 */
private Node remove(Node node, K key) {
	if (node == null) {
		return null;
	} 
	Node resNode;
	if (node.key.compareTo(key) < 0) {
		node.right = remove(node.right, key);
		resNode = node;
	} else if (node.key.compareTo(key) > 0) {
		node.left = remove(node.left, key);
		resNode = node;
	} else {
		if (node.left == null) {
			// 要删除结点的左子树为空, 返回 删除结点的右子树
			Node delNode = node.right;
			node.right = null;
			size--;
			resNode = delNode;
		} else if (node.right == null) {
			// 要删除结点的右子树为空, 返回 删除结点的左子树
			Node delNode = node.left;
			node.left = null;
			size--;
			resNode = delNode;
		} else {
			// 要删除结点的左右子树都不为空
			Node replaceNode = minimum(node.right);
			
			// 移除要删除结点的右子树中最小元素结点, 返回删除最小元素节点后二分搜索树的跟 作为 替代者的右子树. removeMin(node): 方法并没有维护AVL树的平衡
			replaceNode.right = remove(node.right, replaceNode.key);
			replaceNode.left = node.left;
			node.left = node.right = null;
			resNode = replaceNode;
		}
	}
	
	if (resNode == null) {
		return null;
	}
	
	// 维护结点的深度
	resNode.height = Math.max(getHeight(resNode.left), getHeight(resNode.right)) + 1;
	
	// 计算平衡因子
       int balanceFactor = getBalanceFactor(resNode);
	
	// 第一种情况: 插入的元素在不平衡节点左侧的左侧(LL)
	if (balanceFactor > 1 && getBalanceFactor(resNode.left) >= 0) {
		// 右旋转
		return rightRotate(resNode);
	}
	
	// 第二种情况: 插入的元素在不平衡节点右侧的右侧(RR)
	if (balanceFactor < -1 && getBalanceFactor(resNode.right) <= 0) {
		// 左旋转
		return leftRotate(resNode);
	}
	
	// 第三种情况: 插入的元素在不平衡节点左侧的右侧(LR)
	if (balanceFactor > 1 && getBalanceFactor(resNode.left) < 0) {
		resNode.left = leftRotate(resNode.left);
           return rightRotate(resNode);
	}
	
	// 第四种情况: 插入的元素在不平衡节点右侧的左侧(RL)
	if (balanceFactor < -1 && getBalanceFactor(resNode.right) > 0) {
		resNode.right = rightRotate(resNode.right);
           return leftRotate(resNode);
	}
	
	return resNode;
}
发布了13 篇原创文章 · 获赞 0 · 访问量 445

猜你喜欢

转载自blog.csdn.net/Admin_Lian/article/details/104924521