平衡二叉树是一种特殊的二叉查找树,其中每个节点的左右子树的高度差不超过1。这种树的平衡性质使其在多种操作下保持较高的效率。
平衡二叉树的定义与性质
严格定义:在平衡二叉树中,任一节点的两个子树的高度最大差别为一,这使得树保持一定程度的平衡,进而保证操作的效率。
查找效率:平衡二叉树的查找、插入和删除操作的时间复杂度均为O(log n),其中n为树中节点的数量。这是因为树保持了相对平衡,避免了最坏情况下的链式存储结构。
代码实现
改造之前的二叉树代码,增加高度属性(二叉树的高度是根节点到叶子节点的最长简单路径边的条数),以便后面进行高度判断:
AVLNode类:
public class AVLNode {int value; // 节点的值int height; // 节点的高度AVLNode left; // 左子节点AVLNode right; // 右子节点public AVLNode(int value) {this.value = value;this.height = 1;}
}
AVL类:
public class AVL {private AVLNode root; // 根节点// 获取节点的高度public int getHeight(AVLNode node) {return node == null ? 0 : node.height;}public AVLNode insert(AVLNode node, int value) {if (node == null) {return new AVLNode(value);}if (value < node.value) {node.left = insert(node.left, value);} else if (value > node.value) {node.right = insert(node.right, value);} else {return node;}//增加当前节点高度node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));return node;}public void insert(int value) {root = insert(root, value);}}
平衡因子
AVL树中的平衡因子定义为某个节点的左子树高度与右子树高度之差的绝对值。具体来说:
如果一个节点的左子树高度大于右子树高度,那么该节点的平衡因子为左子树高度减去右子树高度,结果为正数。
如果一个节点的右子树高度大于左子树高度,那么该节点的平衡因子为右子树高度减去左子树高度,结果为负数。
如果左右子树高度相等,平衡因子为0。
在AVL树中,为了保持树的平衡性,任何节点的平衡因子只能是-1、0或1。这意味着AVL树是严格平衡的二叉搜索树,其中任意节点的两个子树的高度差最多为1。当通过插入或删除操作导致某个节点的平衡因子不满足这一条件时,AVL树会通过一系列的旋转操作来重新平衡树。这些旋转包括单旋(左旋或右旋)和双旋(先左后右或先右后左),以确保树的高度平衡特性得到恢复。
public int getBalanceFactor(AVLNode node) {return node == null ? 0 : getHeight(node.left) - getHeight(node.right);}
avl的四种失衡
- LL型失衡
定义:当在某个节点的左孩子的左子树上插入或删除一个节点后,如果这个左子树仍然有其他非空节点存在,导致该节点的左子树的高度比右子树高2,则发生LL型失衡。
调整方法:通过一次右旋操作来修复这种失衡。 将当前节点的左孩子作为新的根节点,新的根节点的右孩子作为原根节点的左孩子,原根节点更新为新根节点的右孩子。
if (balance > 1 && value < node.left.value) {return rightRotate(node);
}
public AVLNode rightRotate(AVLNode y) {AVLNode x = y.left;AVLNode T2 = x.right;x.right = y;y.left = T2;y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;return x;
}
- RR型失衡
定义:当在某个节点的右孩子的右子树上插入或删除一个节点后,如果这个右子树仍然有其他非空节点存在,导致该节点的右子树的高度比左子树高2,则发生RR型失衡。
调整方法:通过一次左旋操作来修复这种失衡。 将当前节点的右孩子作为新的根节点,新的根节点的左孩子作为原根节点的右孩子,原根节点更新为新根节点的左孩子。
if (balance < -1 && value > node.right.value) {return leftRotate(node);
}
public AVLNode leftRotate(AVLNode x) {AVLNode y = x.right;AVLNode T2 = y.left;y.left = x;x.right = T2;x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;return y;
}
双旋
- LR型失衡
定义:当在某个节点的左孩子的右子树上插入或删除一个节点后,如果这个右子树仍然有其他非空节点存在,导致该节点的左子树的高度比右子树高2,则发生LR型失衡。
调整方法:需要先进行RR旋转(围绕左孩子的右孩子),然后再进行LL旋转(围绕原节点)。这两次旋转使树重新获得平衡。
if (balance < -1 && value < node.right.value) {node.right = rightRotate(node.right);return leftRotate(node);}
- RL型失衡
定义:当在某个节点的右孩子的左子树上插入或删除一个节点后,如果这个左子树仍然有其他非空节点存在,导致该节点的右子树的高度比左子树高2,则发生RL型失衡。
调整方法:**需要先进行LL旋转(围绕右孩子的左孩子),然后再进行RR旋转(围绕原节点)。**这两次旋转使树重新获得平衡。
if (balance > 1 && value > node.left.value) {node.left = leftRotate(node.left);return rightRotate(node);}
avl 的遍历
AVL树的遍历方式主要有以下几种:
- 中序遍历(Inorder Traversal):左子树 -> 根节点 -> 右子树。这种遍历方式可以得到排序后的序列。
void inOrder(Node node) {if (node != null) {inOrder(node.left);System.out.print(node.key + " ");inOrder(node.right);}}
- 前序遍历(Preorder Traversal):根节点 -> 左子树 -> 右子树。这种遍历方式可以用于复制一棵树。
void preOrder(Node node) {if (node != null) {System.out.print(node.key + " ");preOrder(node.left);preOrder(node.right);}}
- 后序遍历(Postorder Traversal):左子树 -> 右子树 -> 根节点。这种遍历方式可以用于删除一棵树。
void postOrder(Node node) {if (node != null) {postOrder(node.left);postOrder(node.right);System.out.print(node.key + " ");}}