一、什么是AVL?
AVL是一种自平衡的二叉搜索树,它以其发明者Adelson-Velsky和Landis的名字命名。AVL树是一种高度平衡的二叉搜索树,它的特点是任意节点的左子树和右子树的高度差不超过1。
AVL树的平衡性是通过在插入或删除节点时进行旋转操作来维持的。旋转操作可以分为左旋和右旋两种类型,通过调整节点的位置来保持树的平衡性。当插入或删除节点导致树的平衡被破坏时,AVL树会自动进行旋转操作,使得树重新达到平衡状态。
由于AVL树的平衡性,它在最坏情况下的搜索、插入和删除操作的时间复杂度都是O(log n),其中n是树中节点的数量。这使得AVL树在实际应用中具有高效的性能。
需要注意的是,由于AVL树需要维护平衡性,所以在插入和删除操作时需要进行额外的平衡调整,这可能会导致一些性能开销。(即AVL树更适合存储之后,不再改变的数据,适合用于搜索查找,不适合插入和删除)因此,在某些特定的应用场景下,可能会选择其他类型的自平衡树,如红黑树,来权衡性能和平衡性的需求。
总结来说
AVL树是一种自平衡的二叉搜索树,具有以下性质:
-
平衡性:对于AVL树的任意节点,其左子树和右子树的高度差(平衡因子)不超过1。即对于每个节点,其左子树的高度和右子树的高度最多相差1。
-
二叉搜索树性质:AVL树是一棵二叉搜索树,即对于每个节点,其左子树中的所有节点的值都小于该节点的值,而右子树中的所有节点的值都大于该节点的值。
-
高度平衡性:AVL树的高度是最小的,也就是说,AVL树的高度是O(log n),其中n是树中节点的数量。
-
自平衡性:当向AVL树中插入或删除节点时,会通过旋转操作来保持树的平衡。旋转操作包括左旋和右旋,通过调整节点的位置来保持树的平衡性。
-
AVL树的平衡性保证了在最坏情况下,搜索、插入和删除操作的时间复杂度都是O(log n)
二、代码模板
具体的解释全部写进代码,笔者能力有限,如果不能理解,可以去下面链接🔗去理解思想,然后再回头重新看代码注释
读者能掌握韩老师的代码,自然也能理解笔者的。
class Node {
int value;// 节点值
int height;// 该节点的高度
Node left;// 左子树
Node right;// 右子树
/**
* 初始化,为 AVL的 insert()作准备
* @param value
*/
public Node(int value) {
this.value = value;
this.height = 1;
this.left = null;
this.right = null;
}
}
public class AVLTree {
private Node root;// 根节点
/**
* public
* 插入方法(供外部使用)
* @param value 待插入的值
*/
public void insert(int value) {
// 调用内部的具体实现方法
this.root = insertNode(this.root, value);
}
/**
* private
* 采用递归的形式,修改每一个分支让其达到平衡后,返回新的头结点
* @param root
* @param value
* @return
*/
private Node insertNode(Node root, int value) {
if (root == null)// 如果没有节点直接新建
return new Node(value);
if (value < root.value) // 小于当前节点,就去左边继续
root.left = insertNode(root.left, value);
else if (value > root.value)// 大于当前节点,就去右边继续
root.right = insertNode(root.right, value);
else// 等于当前节点,说明该 value已经存在,没有必要添加,直接返回该节点
return root;
// 计算当前节点的高
root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
// 当前节点的平衡因子(左树高 - 右树高 =合法= -1 || 0 || 1)
int balanceFactor = getBalanceFactor(root);
// 根据平衡因子的值,进行不同的旋转
// (一)
// 满足右单旋的条件(具体的例子在 rightRotate()的注释里)
if (balanceFactor > 1 && value < root.left.value)
return rightRotate(root);
// (二)
// 满足左单旋的条件(具体的例子在 leftRotate()的注释里)
if (balanceFactor < -1 && value > root.right.value)
return leftRotate(root);
// (三)
// 满足先左单旋 root.left,再右单旋 root
/** 例如
* 15(root) 15(root)
* / \ 先将 root.left即 10左旋 / \ 再将 root即 15右旋
* (root.left) 10 20 --------------------> 13 20 --------------------> 13
* / \ / / \
* 5 13 10 10 15
* / / \ / \ \
* 11 5 11 5 11 20
*/
if (balanceFactor > 1 && value > root.left.value) {
root.left = leftRotate(root.left);
return rightRotate(root);
}
// (四)
// 满足先右旋 root.right,再左旋 root
/** 例如
* 15(root) 15(root) 20
* / \ 先将 root.right即 25右旋 / \ 再将 root即 15左旋 / \
* 5 25(root.right) ----------------------> 5 20 --------------------> 15 25
* / \ / \ / \ \
* 20 35 18 25 5 18 35
* / \
* 18 35
*/
if (balanceFactor < -1 && value < root.right.value) {
root.right = rightRotate(root.right);
return leftRotate(root);
}
return root;
}
/**
* public
* 删除方法(供外部使用)
* @param value 删除的值
*/
public void delete(int value) {
// 调用内部具体实现的方法
this.root = deleteNode(this.root, value);
}
/**
* private
* 递归寻找 value右子节点的最小值替换它,然后删除右子树最小值节点
* @param root
* @param value
* @return
*/
private Node deleteNode(Node root, int value) {
if (root == null)
return null;
if (value < root.value)// 小于去左边找
root.left = deleteNode(root.left, value);
else if (value > root.value)// 大于去右边找
root.right = deleteNode(root.right, value);
else// 等于的情况
if (root.left == null || root.right == null)// 左右至少有一个节点为null
root = (root.left != null) ? root.left : root.right;
else {
// 找到右边节点的最小值将其替换
Node minNode = findMinNode(root.right);
root.value = minNode.value;
// 然后将替换过后的节点从二叉树中删除
root.right = deleteNode(root.right, minNode.value);
}
// 删除 minNode过后,该节点为 null,直接返回
// 或者找不到 value,也直接返回
if (root == null)
return null;
// 和插入节点相同,每次修改过后二叉树,都必须重新计算是否平衡
root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
int balanceFactor = getBalanceFactor(root);
if (balanceFactor > 1 && getBalanceFactor(root.left) >= 0)
return rightRotate(root);
if (balanceFactor > 1 && getBalanceFactor(root.left) < 0) {
root.left = leftRotate(root.left);
return rightRotate(root);
}
if (balanceFactor < -1 && getBalanceFactor(root.right) <= 0)
return leftRotate(root);
if (balanceFactor < -1 && getBalanceFactor(root.right) > 0) {
root.right = rightRotate(root.right);
return leftRotate(root);
}
return root;
}
/**
* private
* 此时左子树高 - 右子树高 == -2
* 且刚插入的位置为右子树的右子树的右子树
* 例如 本质
* 10(当前节点 root) 降 root,提 root.right
* / \ 将 10降下来,15提上去
* 5 15 (newRoot) -----------------> 15 (newRoot)
* / \ / \
* 12 20 10 20
* \ / \ \
* 25(该节点就是插入的值) 5 12 25
* @param root 为要左旋降低的节点
* @return 返回新的最后将 newRoot返回
*/
private Node leftRotate(Node root) {
// 旋转过程
Node newRoot = root.right;
root.right = newRoot.left;
newRoot.left = root;
// 更新变换过后节点的高度
root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
newRoot.height = Math.max(getHeight(newRoot.left), getHeight(newRoot.right)) + 1;
return newRoot;
}
/**
* private
* 此时左子树高 - 右子树高 == 2
* 且刚插入的位置为左子树的左子树的左子树
* 例如 本质
* 10 (当前节点root) 降 root,提 root.left
* / \ 将 10降下来,5 升上去
* (newRoot) 5 15 ------------------> 5(newRoot)
* / \ / \
* 4 7 4 10
* / / / \
* 3 (该节点就是插入的值) 3 7 15
* @param root 为要右旋降低的节点
* @return 最后将 newRoot返回
*/
private Node rightRotate(Node root) {
// 旋转过程
Node newRoot = root.left;
root.left = newRoot.right;
newRoot.right = root;
// 更新变换过后节点的高度
root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
newRoot.height = Math.max(getHeight(newRoot.left), getHeight(newRoot.right)) + 1;
return newRoot;
}
/**
* private
* @param node
* @return 返回当前节点的高度
*/
private int getHeight(Node node) {
if (node == null)
return 0;
return node.height;
}
/**
* private
* 得到当前节点的平衡因子 (合法值为 -1,0,1)
* @param node
* @return 左树的高 - 右树的高 == 平衡因子
*/
private int getBalanceFactor(Node node) {
if (node == null)
return 0;
return getHeight(node.left) - getHeight(node.right);
}
/**
* private
* 找到当前节点下的最小值(包括该节点,用于辅助 delete()方法)
* @param node
* @return
*/
private Node findMinNode(Node node) {
while (node.left != null)// 越向左越小,找到最左边的节点
node = node.left;
return node;
}
/**
* 中序遍历
* 观察是否符合 AVL的性质
*/
public void inorderTraversal() {
// 具体实现
inorderTraversal(this.root);
}
/**
* private
* @param root
*/
private void inorderTraversal(Node root) {
if (root != null) {
inorderTraversal(root.left);
System.out.print(root.value + " ");
inorderTraversal(root.right);
}
}
public static void main(String[] args) {
AVLTree avl = new AVLTree();
avl.insert(9);
avl.insert(5);
avl.insert(10);
avl.insert(0);
avl.insert(6);
avl.insert(11);
avl.insert(-1);
avl.insert(1);
avl.insert(2);
System.out.println("中序遍历结果:");
avl.inorderTraversal();
System.out.println("\n删除节点 10:");
avl.delete(10);
System.out.println("中序遍历结果:");
avl.inorderTraversal();
}
}
三、额外话
读者可能还见过另一种带 平衡因子balanceFactor的 AVL代码,
class Node {
int value;
int height;
int balanceFactor;// 平衡因子
Node left;
Node right;
public Node(int value) {
this.value = value;
this.height = 1;
this.balanceFactor = 0;
this.left = null;
this.right = null;
}
}
public class AVLTree {
private Node root;
public AVLTree() {
this.root = null;
}
public void insert(int value) {
this.root = insertNode(this.root, value);
}
private Node insertNode(Node root, int value) {
if (root == null) {
return new Node(value);
}
if (value < root.value) {
root.left = insertNode(root.left, value);
} else {
root.right = insertNode(root.right, value);
}
updateHeightAndBalanceFactor(root);
return balance(root);
}
public void delete(int value) {
this.root = deleteNode(this.root, value);
}
private Node deleteNode(Node root, int value) {
if (root == null) {
return root;
}
if (value < root.value) {
root.left = deleteNode(root.left, value);
} else if (value > root.value) {
root.right = deleteNode(root.right, value);
} else {
if (root.left == null || root.right == null) {
root = (root.left != null) ? root.left : root.right;
} else {
Node minNode = findMinNode(root.right);
root.value = minNode.value;
root.right = deleteNode(root.right, minNode.value);
}
}
if (root == null) {
return root;
}
updateHeightAndBalanceFactor(root);
return balance(root);
}
private Node balance(Node root) {
if (root.balanceFactor < -1) {
if (root.right.balanceFactor > 0) {
root.right = rotateRight(root.right);
}
root = rotateLeft(root);
} else if (root.balanceFactor > 1) {
if (root.left.balanceFactor < 0) {
root.left = rotateLeft(root.left);
}
root = rotateRight(root);
}
return root;
}
private Node rotateLeft(Node root) {
Node newRoot = root.right;
root.right = newRoot.left;
newRoot.left = root;
updateHeightAndBalanceFactor(root);
updateHeightAndBalanceFactor(newRoot);
return newRoot;
}
private Node rotateRight(Node root) {
Node newRoot = root.left;
root.left = newRoot.right;
newRoot.right = root;
updateHeightAndBalanceFactor(root);
updateHeightAndBalanceFactor(newRoot);
return newRoot;
}
private void updateHeightAndBalanceFactor(Node node) {
int leftHeight = (node.left != null) ? node.left.height : 0;
int rightHeight = (node.right != null) ? node.right.height : 0;
node.height = Math.max(leftHeight, rightHeight) + 1;
node.balanceFactor = leftHeight - rightHeight;
}
private Node findMinNode(Node node) {
while (node.left != null) {
node = node.left;
}
return node;
}
public void inorderTraversal() {
inorderTraversal(this.root);
}
private void inorderTraversal(Node root) {
if (root != null) {
inorderTraversal(root.left);
System.out.print(root.value + " ");
inorderTraversal(root.right);
}
}
public static void main(String[] args) {
AVLTree avl = new AVLTree();
avl.insert(9);
avl.insert(5);
avl.insert(10);
avl.insert(0);
avl.insert(6);
avl.insert(11);
avl.insert(-1);
avl.insert(1);
avl.insert(2);
System.out.println("中序遍历结果:");
avl.inorderTraversal();
System.out.println("\n删除节点 10:");
avl.delete(10);
System.out.println("中序遍历结果:");
avl.inorderTraversal();
}
}
其实与笔者的代码并无太大区别,仅仅是将左右子树的高度相减存入balanceFactor,然后维护balanceFactor的值合法(-1,0,1)来实现 AVL树的自平衡。二者知其一即可。