AVL树全称是平衡二叉搜索树,相比于红黑树,它是一种高度平衡的二叉搜索树,所有节点的左右子树高度差不超过1。
完整代码在最下面
/**
* parent 父节点
* left 左子树
* right 右子树
* height 高度,叶子结点高度为1
*/
class TreeNode {
int key; // 结点值、键值
TreeNode parent;
TreeNode left;
TreeNode right;
int height;
// int size; 树的大小,若有此属性,可以在o(logN)时间内找到排第k的元素
}
1.可以初始化一棵空树;2.也可以用一个已经排好序的数组来初始化一棵树(leetCode108题)
/**
* nums 已排好序的数组
*/
public TreeNode bulidTree(int[] nums) {
return recursive(nums, 0, nums.length);
}
// 取[left, right)区间的中点mid作为根结点
// [left,mid)构建左子树,[mid+1, right)构建右子树
private TreeNode recursive(int[] nums, int left, int right) {
if (left == right) {
return null;
}
int mid = (left + right) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = recursive(nums, left, mid);
root.right = recursive(nums, mid + 1, right);
return root;
}
一个具有 N N N 个节点的AVL树,其高度是 log N + 1 \log N+1 logN+1,因此二分查找的复杂度是 o ( log N ) o(\log N) o(logN) 。这里举5种查找的应用场景:
查找确定值:给定一个待查找的值 key
,在根节点 root
中的二分查找的流程如下:
若根结点值偏大,则向左走,偏大则向右走,相等就返回;没有找到就返回null
public TreeNode getNode(int key) {
TreeNode node = root;
while (node != null) {
int cmp = node.key - key;
if (cmp > 0) {
// node偏大,要缩小
node = node.left;
} else if (cmp < 0) {
// node偏小,要变大
node = node.right;
} else {
// 相等就是找到了
return node;
}
}
// 没有找到
return null;
}
查找给定结点的前驱、后继,实际上就是找出中序遍历的前驱后继。以查找 node
结点的后继为例,若 node.right
不为空,则后继结点为右子树的最左侧结点;若 node.right
为空,则后继结点在上面,向上找,第一个具有左子树的祖先就是后继结点,可以用下图的“对称性”来辅助记忆,会发现后继结点的两种情况是轴对称的。
// 前驱
private TreeNode precursor(TreeNode node) {
if (node == null) {
return null;
} else if (node.left != null) {
// 左子节点不为空,前驱在左子树的最右侧节点上
TreeNode p = node.left;
while (p.right != null) {
p = p.right;
}
return p;
} else {
// 左子节点为空,前驱在上面
TreeNode p = node.parent;
TreeNode ch = node;
while (p != null && p.left == ch) {
ch = p;
p = p.parent;
}
// p可能为null
return p;
}
}
// 后继
private TreeNode successor(TreeNode node) {
if (node == null) {
return null;
} else if (node.right != null) {
TreeNode p = node.right;
while (p != null) {
p = p.left;
}
return p;
} else {
// 后继在上面
TreeNode p = node.parent;
TreeNode ch = p;
while (p != null && p.right == ch) {
ch = p;
p = p.parent;
}
return p;
}
}
查找下确界、上确界:给定一个key值,查找它的系确界
public TreeNode floorEntry(int key) {
TreeNode node = root;
while (node != null) {
int cmp = node.key - key;
if (cmp < 0) {
// node.key比key小,说明node就是一个下界
if (node.right != null)
node = node.right;
else
return node;
} else if (cmp > 0) {
if (node.left != null) {
node = node.left;
} else {
// 向上找到,第一个右转的结点就是下确界
TreeNode p = node.parent;
TreeNode ch = node;
while (p != null && p.left == ch) {
ch = p;
p = p.parent;
}
return p;
}
} else {
return node;
}
}
return null;
}
查找排第k的结点(k从0开始计数):查找排第k的结点,TreeNode的定义中需要多维护一个变量size,表示树的结点个数。
// 调用方法前,k保证满足:0 <= k < root.size
public TreeNode getKthEntry(int k) {
TreeNode node = root;
while (node != null) {
int rank = getRank(node);
if (rank > k) {
node = node.left;
} else if (rank < k) {
node = node.right;
k -= rank + 1;
} else {
return node;
}
}
return null;
}
// node结点的排名
private int getRank(TreeNode node) {
return node.left == null ? 0 : node.left.size;
}
在树中插入一个新的结点,设结点值为key,有三个步骤
height
。在更新过程中,若发现某个祖先节点的高度不平衡,则需要通过旋转来调整。public void insert(int key) {
// 1.查找插入位置
TreeNode node = root;
if (node == null) {
// 空树
root = new TreeNode(key);
root.height = 1;
// 更新树的属性,例如size,height等
return;
}
// 查找插入的位置
TreeNode parent;
while (node != null) {
parent = node;
int cmp = node.key - key;
if (cmp > 0) {
node = node.left;
} else if (cmp < 0) {
node = node.right;
} else {
// 已有相同的节点,无需插入
return;
}
}
// 2.判断插入位置
TreeNode e = new TreeNode(key);
e.parent = parent;
e.height = 1;
if (parent.key > key) parent.left = e;
else parent.right = e;
// 3.更新祖先节点的高度,并修复平衡性
fixStructure(parent);
// 更新树的属性,例如树的size,height等
size++;
}
删除树中的一个节点,设要删除的是key,有三个步骤:
查找到待删除的节点,记为 node
对 node
分情况讨论:
① node
是叶子结点,或 node
只有左子树,或 node
只有右子树
② node
同时有左、右子树,即 node.left
和 node.right
都不为空。
处理方法:对于①采用**“独子继承”**策略,用node的子树去取代 node
;对于②,由于 node.right
不为空,那么在中序遍历中, node
的后继结点必然在 node.right
最左侧的子树上,将这个后继结点 succesor
的值赋给node,然后删除这个后继结点 successor
,由于这个 successor
没有左子树的,这时就变成了第①种情形。
public boolean remove(int key) {
// 1.查找出要删除的结点
TreeNode node = getNode(key);
if (node == null) {
// 没有找到,返回false,表示无需执行删除
return false;
}
// 2.删除node结点
if (node.left != null && node.right != null) {
// node同时存在左右子树,则用后继结点代替
TreeNode successor = successorOf(node);
node.key = successor.key;
node = successor;
}
// 独子继承:
TreeNode replacement = node.left != null ? node.left : node.right;
TreeNode parent = node.parent;
if (replacement != null) {
replacement.parent = parent;
if (parent == null)
// parent为空,说明node为root结点,继承root的位置
root = replacement;
else if (parent.left == node)
parent.left = replacement;
else
parent.right = replacement;
} else {
// replacement为空,说明node是叶子结点
if (parent == null)
root = null; // 变成空树
else if (parent.left == node)
parent.left = null;
else
parent.right = null;
}
// null out处理node
node.left = node.right = node.parent = null;
// 3.更新祖先节点的高度,并修复平衡性
fixStructure(parent);
// 更新树的属性,例如树的size,height等
size--;
return ture;
}
上述提到的插入、删除操作的最后一个步骤是更新高度并修复平衡性。先引入平衡因子的概念,并定义空结点 null
的平衡因子为0,定义非空结点的平衡因子为左子树高度减去右子树高度,如下式。
B F ( n o d e ) = h e i g h t ( n o d e . l e f t ) − h e i g h t ( n o d e . r i g h t ) BF(node) = height(node.left) - height(node.right) BF(node)=height(node.left)−height(node.right)
所以,一棵平衡二叉查找树的平衡因子是(1,0,-1),其左、右子树的平衡因子也是(1,0,-1)。
往平衡因子为1或-1的子树中插入或删除一个结点后,有可能出现以下四种不同类型的不平衡情况,及其处理方法分别是:
// 从node结点开始,逐级向下更新并修复所有祖先节点的平衡性
private void fixStructure(TreeNode node) {
while (node != null) {
// 判断node的不平衡的类型
int bf = getBF(node);
if (bf == 2) {
int bfLeftSon = getBF(node.left);
if (bfLeftSon < 0) {
// (2,-1)不平衡要先左旋变成(2,1)不平衡
rotateLeft(node.left);
}
// 右旋处理(2,1)不平衡
rotateRight(node);
// 更新高度:先更新node,再更新p
calHelght(node.parent.left);
calHelght(node.parent.right);
calHelght(node.parent);
node = node.parent.parent;
} else if (bf == -2) {
int bfRightSon = getBF(node.right);
// (-2,1)不平衡要先右旋变成(-2,-1)不平衡
if (bfRightSon > 0) {
rotateRight(rightSon);
}
// 左旋处理(-2,-1)不平衡
rotateLeft(node);
// 更新高度:先更新node,再更新p
calHelght(node.parent.left);
calHelght(node.parent.right);
calHelght(node.parent);
node = node.parent.parent;
} else {
// node结点平衡
calHeight(node);
node = node.parent;
}
}
}
private int calHeight(TreeNode node) {
if (node == null) {
return 0;
}
return 1 + Math.max(heightOf(node.left), heightOf(node.right));
}
private int heightOf(TreeNode node) {
return node == null ? 0 : node.height;
}
上述修复过程中,涉及到结点的两种旋转:右旋、左旋。
什么样的结点才能右旋/左旋呢?
一个结点,只要它的左子树不为null,那么它就能右旋;同理,一个结点,只要它的右子树不为null,那么它就能左旋。
对结点进行旋转操作能达到什么样的效果?
对一个结点进行右旋,会使得左子树的高度减一,右子树的高度加一,最终树的平衡因子减2;相反,对一个结点进行左旋,会使得左子树的高度加一,右子树的高度减一,最终树的平衡因子加2。
右旋 node
结点,共三个步骤:① node
结点的左子结点 p
继承 node
结点的位置,成为 node.parent
的子结点,②而 node
结点成为 p
的右子结点,③原来的 p.right
结点成为 node
结点的左子结点,用代码描述如下:
// 调用右旋方法前,保证node及node.left不为null
private void rotateRight(TreeNode node) {
TreeNode p = node.left;
TreeNode parent = node.parent;
TreeNode lrSon = p.right;
// 1.p继承node的位置
p.parent = parent;
if (parent == null)
root = p;
else if (parent.left == node)
parent.left = p;
else
parent.right = p;
// 2.node成为p的右子
p.right = node;
node.parent = p;
// 3.lrSon成为node的左子
node.left = lrSon;
if (lrSon != null)
lrSon.parent = node;
}
// 调用左旋方法前,保证node及node.right不为null
private void rotateLeft(TreeNode node) {
TreeNode p = node.right;
TreeNode parent = node.parent;
TreeNode rlSon = p.left;
// 1.p继承node的位置
p.parent = parent;
if (parent == null)
root = p;
else if (parent.left == node)
parent.left = p;
else
parent.right = p;
// 2.node成为p的左子
p.left = node;
node.parent = p;
// 3.rlSon成为node的右子
node.right = rlSon;
if (rlSon != null)
rlSon.parent = node;
}
/**
* AVL树
*
* @author wjw
* @version 1.0
* @date 2021/10/31 10:33
*/
public class AVLTree {
private TreeNode root;
private int size;
public AVLTree() {
}
/**
* 用已排好序的数组来初始化树
*
* @param sortedArr
*/
public AVLTree(int[] sortedArr) {
root = buildTree(sortedArr, 0, sortedArr.length);
size = sortedArr.length;
}
/**
* 使用sortedArr[left,right)区间递归构建AVL树
*
* @param sortedArr
* @param left
* @param right
* @return
*/
private TreeNode buildTree(int[] sortedArr, int left, int right) {
if (left == right) {
return null;
}
int mid = left + (right - left) / 2;
TreeNode root = new TreeNode(sortedArr[mid]);
root.left = buildTree(sortedArr, left, mid);
root.right = buildTree(sortedArr, mid + 1, right);
if (root.left != null) {
root.left.parent = root;
}
if (root.right != null) {
root.right.parent = root;
}
calHeight(root);
return root;
}
/*
* 查询部分的代码块:
* 1.查找特定值
* 2.查找特定结点的上、下确界
* 3.前驱、后继
*
* */
/**
* 1.查找特定值
*
* @param key
* @return
*/
public boolean contains(int key) {
return getEntry(key) != null;
}
/**
* 2 a查找下确界
*
* @param key
* @return
*/
public Integer floorKey(int key) {
TreeNode floor = floorEntry(key);
return floor == null ? null : floor.key;
}
/**
* 2 b查找上确界
*
* @param key
* @return
*/
public Integer ceilingKey(int key) {
TreeNode ceil = ceilingEntry(key);
return ceil == null ? null : ceil.key;
}
/**
* 3 a前驱
*
* @param node
* @return
*/
private TreeNode precursor(TreeNode node) {
if (node == null) {
return null;
}
if (node.left != null) {
// 左子结点不为空,前驱在左子树的最右侧
TreeNode p = node.left;
while (p.right != null) {
p = p.right;
}
return p;
} else {
// 在上面,找到第一次右转的点
TreeNode p = node.parent;
TreeNode ch = node;
while (p != null && p.left == ch) {
ch = p;
p = p.parent;
}
return p;
}
}
/**
* 3 b后继
*
* @param node
* @return
*/
private TreeNode successor(TreeNode node) {
if (node == null) {
return null;
}
if (node.right != null) {
TreeNode p = node.right;
while (p.left != null) {
p = p.left;
}
return p;
} else {
// 在上面找到第一次左转的点
TreeNode p = node.parent;
TreeNode ch = node;
while (p != null && p.right == ch) {
ch = p;
p = p.parent;
}
return p;
}
}
// 查找下确界结点
private TreeNode floorEntry(int key) {
TreeNode node = root;
while (node != null) {
int cmp = node.key - key;
if (cmp < 0) {
// node是一个下界:可以向右就向右,否则直接返回
if (node.right != null) {
node = node.right;
} else {
return node;
}
} else if (cmp > 0) {
if (node.left != null) {
node = node.left;
} else {
// 不能向左走了,就回头向上找到第一次右转的点并返回
TreeNode p = node.parent;
TreeNode ch = node;
while (p != null && p.left == ch) {
ch = p;
p = p.parent;
}
return p;
}
} else {
return node;
}
}
return null;
}
private TreeNode ceilingEntry(int key) {
TreeNode node = root;
while (node != null) {
int cmp = node.key - key;
if (cmp > 0) {
// node就是一个上界
if (node.left != null) {
node = node.left;
} else {
return node;
}
} else if (cmp < 0) {
if (node.right != null) {
node = node.right;
} else {
// 向上找到第一次左转的结点
TreeNode p = node.parent;
TreeNode ch = node;
while (p != null && p.right == ch) {
ch = p;
p = p.parent;
}
return p;
}
} else {
return node;
}
}
return null;
}
// 二分查找特定结点
private TreeNode getEntry(int key) {
TreeNode node = root;
while (node != null) {
int cmp = node.key - key;
if (cmp > 0) {
node = node.left;
} else if (cmp < 0) {
node = node.right;
} else {
return node;
}
}
return null;
}
/*
* 插入部分的代码块:
* 0.先判断root是否空树
* 1.找到插入位置
* 2.判断是否重复后再插入
* 3.修复插入点所有祖先节点的平衡性及更新高度
*
* */
/**
* 插入新元素
*
* @param key
*/
public void insert(int key) {
// 判断是否为空树
if (root == null) {
root = new TreeNode(key);
root.height = 1;
size = 1;
return;
}
// 找到插入位置
TreeNode node = root;
TreeNode parent = null;
while (node != null) {
parent = node;
int cmp = node.key - key;
if (cmp > 0) {
node = node.left;
} else if (cmp < 0) {
node = node.right;
} else {
// 已存在key,无需插入
return;
}
}
// 插入
TreeNode ch = new TreeNode(key);
ch.height = 1;
ch.parent = parent;
if (key < parent.key) {
parent.left = ch;
} else {
parent.right = ch;
}
// 修复祖先节点的平衡性并更新高度
fixStructure(parent);
// 维护全局变量
size++;
}
/**
* 删除元素
*
* @param key
* @return
*/
public boolean remove(int key) {
// 1.查找是否有该元素
TreeNode node = getEntry(key);
if (node == null) {
// 不存在,无需执行删除操作
return false;
}
// 2.执行删除
// node同时存在左右子,用后继代替node
if (node.left != null && node.right != null) {
TreeNode successor = successor(node);
node.key = successor.key;
node = successor;
}
// 独子继承:子结点取代node的位置
TreeNode replacement = node.left != null ? node.left : node.right;
TreeNode parent = node.parent;
if (replacement != null) {
replacement.parent = parent;
if (parent == null) {
// node原来是根结点
root = replacement;
} else if (parent.left == node) {
parent.left = replacement;
} else {
parent.right = replacement;
}
} else {
// replacement为空,说明node是叶子结点
if (parent == null) {
root = null; // 变为空树
} else if (parent.left == node) {
parent.left = null;
} else {
parent.right = null;
}
}
// 删除:null out处理node结点
node.left = node.right = node.parent = null;
// 3.修复平衡性及更新高度
fixStructure(parent);
// 维护全局变量
size--;
return true;
}
/**
* 修复node路径上所有祖先节点的平衡性并更新高度
*
* @param node
*/
private void fixStructure(TreeNode node) {
while (node != null) {
// 判断不平衡类型
int bf = getBF(node);
if (bf == 2) {
int leftSonBF = getBF(node.left);
if (leftSonBF < 0) {
// (2,-1)不平衡要先左旋变成(2,1)不平衡
rotateLeft(node.left);
}
// 右旋处理(2,1)不平衡
rotateRight(node);
// node.parent取代了原来node的位置
calHeight(node.parent.left);
calHeight(node.parent.right);
calHeight(node.parent);
node = node.parent.parent;
} else if (bf == -2) {
int rightSonBF = getBF(node.right);
if (rightSonBF > 0) {
// (-2,1)不平衡要先右旋变成(-2,-1)不平衡
rotateRight(node.right);
}
// 左旋处理(-2,-1)不平衡
rotateLeft(node);
// 更新高度
calHeight(node.parent.left);
calHeight(node.parent.right);
calHeight(node.parent);
node = node.parent.parent;
} else {
// 已平衡,只更新高度
calHeight(node);
node = node.parent;
}
}
}
// 左旋:保证node.right不为null
private void rotateLeft(TreeNode node) {
TreeNode parent = node.parent;
TreeNode p = node.right;
TreeNode rlSon = p.left;
// 1.p取代node
p.parent = parent;
if (parent == null) {
// 说明node原来为root结点
root = p;
} else if (parent.left == node) {
parent.left = p;
} else {
parent.right = p;
}
// 2.node成为p的左子
node.parent = p;
p.left = node;
// 3.rlSon成为node的右子
node.right = rlSon;
if (rlSon != null) {
rlSon.parent = node;
}
}
// 右旋:保证node.left不为null
private void rotateRight(TreeNode node) {
TreeNode parent = node.parent;
TreeNode p = node.left;
TreeNode lrSon = p.right;
// 1.p取代node的位置
p.parent = parent;
if (parent == null) {
root = p;
} else if (parent.left == node) {
parent.left = p;
} else {
parent.right = p;
}
// 2.node成为p的右子
node.parent = p;
p.right = node;
// 3.lrSon成为node的左子
node.left = lrSon;
if (lrSon != null) {
lrSon.parent = node;
}
}
/**
* 树的结点个数
*
* @return
*/
public int size() {
return size;
}
/**
* 计算结点的平衡因子
*
* @param node
* @return
*/
private int getBF(TreeNode node) {
return node == null ? 0 : heightOf(node.left) - heightOf(node.right);
}
/**
* 计算结点高度
*
* @param node
* @return
*/
private void calHeight(TreeNode node) {
if (node == null) {
return;
}
node.height = 1 + Math.max(heightOf(node.left), heightOf(node.right));
}
private int heightOf(TreeNode node) {
return node == null ? 0 : node.height;
}
/**
* 结点
*/
private class TreeNode {
int key;
TreeNode parent;
TreeNode left;
TreeNode right;
int height; // 叶子结点的高度为1
TreeNode() {
}
TreeNode(int key) {
this.key = key;
}
}
}