(一) 定义
通过前面的 数据结构与算法之二分搜索树 的学习我们知道, 二分搜索树的性能跟树的高度(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;
}