前言:
平衡二叉树是基于二叉排序树(二叉搜索树)形成的,二叉查找树的前提是数据是有序的。
假如我要查找7这个值,那我需要遍历3次,也就是树的深度,每遍历一层,数据就减少一半,所以查找的时间复杂度为O(logn)。
但右边种情况就让查找的时间复杂度退化到了O(n):
为了解决这个问题,平衡二叉树诞生了,也叫AVL树。。
一、基本定义和性质
1.1、定义:
平衡二叉树也叫AVL树,它或者是一颗空树,或者具有以下性质的二叉排序树:它的左子树和左子树的高度之差(平衡因子)的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树。
1.2、平衡二叉树的性质:
- 左子树和右子树的高度之差的绝对值小于等于1
- 左子树和右子树也是平衡二叉树
1.3、平衡因子(BF)与判断平衡二叉树
平衡因子 = 结点左子树的高度 - 结点右子树的高度。
以A节点为例,它的左边树的高度left_height = 1,右边树的高度right_height = 2,高度差left_height - right_height = -1,如果高度差绝对值不超过1,那么说明该树就是平衡的。
不平衡如何解决?
二、平衡二叉树的旋转
2.1 LL型(右旋)
右旋就是通过顺时针旋转来实现平衡,就是处于右边的根结点落下变为叶子节点,而中间的节点变为新的根节点,最左边的叶子节点保持不变,最后形成新的结构。
2.2 RR型(左旋)
对于RR型失衡,通过逆时针旋转,最左边的根结点落下,变成叶子节点,中间的节点变为新的根节点,最右边的叶子节点保持不变。
2.3 LR型(先左后右)
对于LR型失衡,先在根节点左子树进行左旋处理,变成LL型失衡,然后再去通过右旋来实现整体平衡。动图演示如下:
2.4 RL型(先右后左)
对于RL型失衡,在根节点的右子树先进行右旋处理,形成RR型失衡,然后整体进行左旋处理,最后达到平衡状态,动态演示如下:
三、平衡二叉树代码实现
3.1构建平衡二叉树
插入节点思路:
1、 找到插入位置:根据二叉搜索树的性质,从根节点开始,比较关键字,决定向左子树或右子树继续查找,直到找到适合插入的位置。
2、插入节点:在找到的空位置创建新节点并插入。
3、更新节点高度:在插入新节点后,更新从新节点到根节点的所有节点的高度。
4、检查平衡因子:计算当前节点的平衡因子,以确定是否需要进行旋转操作。
5、进行旋转:根据平衡因子的值(大于 1 或小于 -1)决定进行左旋、右旋、左-右旋或右-左旋,以保持 AVL 树的平衡。
1.创建节点
//创建一个树的节点类
public class AVLNode {
//保存节点的key值
int key;
//保存节点的高度
int height;
//节点的左孩子和右孩子
AVLNode left, right;
//有参构造
public AVLNode(int key) {
this.key = key;
this.height = 1; // 新节点的初始高度为 1
}
}
2.创建二叉树即方法
public class AVLTree {
//根节点
private AVLNode root;
// 插入接口
public void insert(int key) {
root = insert(root, key);
}
// 删除接口
public void delete(int key) {
root = delete(root, key);
}
// 获取树的根节点
public AVLNode getRoot() {
return root;
}
// 中序遍历接口
public void inOrder() {
inOrder(root);
}
}
3.获取节点高度
// 获取节点高度
private int height(AVLNode node) {
//当节点为空时返回0
if (node == null){
return 0;
}else{//节点不为空时返回节点高度
return node.height;
}
}
4.获取平衡因子
// 获取平衡因子
private int getBalance(AVLNode node) {
//当节点为空时,返回0
if (node == null){
return 0;
}else {//节点不为空时返回左孩子高度-右孩子高度
return (height(node.left)-height(node.right));
}
}
5.插入节点
// 插入节点
public AVLNode insert(AVLNode node, int key) {
// 1. 执行标准的 BST 插入
if (node == null) {
return new AVLNode(key);
}
if (key < node.key) {
node.left = insert(node.left, key);
} else if (key > node.key) {
node.right = insert(node.right, key);
} else { // 重复的键不允许
return node;
}
// 2. 更新节点的高度
node.height = Math.max(height(node.left), height(node.right)) + 1;
// 3. 检查节点的平衡因子
int balance = getBalance(node);
// 如果节点不平衡,则进行旋转
// 左左情况
if (balance > 1 && key < node.left.key) {
return rightRotate(node);
}
// 右右情况
if (balance < -1 && key > node.right.key) {
return leftRotate(node);
}
// 左右情况
if (balance > 1 && key > node.left.key) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
// 右左情况
if (balance < -1 && key < node.right.key) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
// 返回更新后的节点
return node;
}
6.左旋转
// 左旋转
private AVLNode leftRotate(AVLNode x) {
AVLNode y = x.right;
AVLNode T2 = y.left;
// 旋转
y.left = x;
x.right = T2;
// 更新高度
x.height = Math.max(height(x.left), height(x.right)) + 1;
y.height = Math.max(height(y.left), height(y.right)) + 1;
// 返回新的根节点
return y;
}
7.右旋转
// 右旋转
private AVLNode rightRotate(AVLNode y) {
//记录节点位置
AVLNode x = y.left;
AVLNode T2 = x.right;
// 旋转
x.right = y;
y.left = T2;
// 更新高度
y.height = Math.max(height(y.left), height(y.right)) + 1;
x.height = Math.max(height(x.left), height(x.right)) + 1;
// 返回新的根节点
return x;
}
8.中序遍历
// 中序遍历
public void inOrder(AVLNode node) {
if (node != null) {
inOrder(node.left);
System.out.print(node.key + " ");
inOrder(node.right);
}
}
3.2 删除二叉树节点
删除节点的思路:
1、找到要删除的节点:根据二叉搜索树的性质,比较关键字,决定向左子树或右子树继续查找。
2、删除节点:
2.1、如果节点没有子节点,直接删除。
2.2、如果节点只有一个子节点,将子节点连接到其父节点。
2.3、如果节点有两个子节点,找到右子树的最小值(或左子树的最大值),将其复制到当前节点,然后递归删除该最小值(或最大值)。
3、更新节点的高度:在节点删除后,更新当前节点的高度。
4、检查平衡因子:计算当前节点的平衡因子,以确定是否需要进行旋转操作。
5、进行旋转:根据平衡因子的值(大于 1 或小于 -1)决定进行左旋、右旋、左-右旋或右-左旋。
1.找到右子树最小值
// 找到右子树的最小值
private AVLNode minValueNode(AVLNode node) {
AVLNode current = node;
while (current.left != null) {
current = current.left;
}
return current;
}
2.删除节点
// 删除节点
public AVLNode delete(AVLNode root, int key) {
// 执行标准的 BST 删除
if (root == null) {
return root;
}
if (key < root.key) {
root.left = delete(root.left, key);
} else if (key > root.key) {
root.right = delete(root.right, key);
} else {
// 节点只有一个子节点或没有子节点
if ((root.left == null) || (root.right == null)) {
AVLNode temp = root.left != null ? root.left : root.right;
// 如果没有子节点
if (temp == null) {
return null;
} else { // 只有一个子节点
return temp;
}
} else {
// 节点有两个子节点,找到右子树的最小值
AVLNode temp = minValueNode(root.right);
root.key = temp.key; // 复制最小值到当前节点
root.right = delete(root.right, temp.key); // 删除最小值
}
}
// 如果树只有一个节点,则返回
if (root == null) {
return root;
}
// 更新节点的高度
root.height = Math.max(height(root.left), height(root.right)) + 1;
// 检查节点的平衡因子
int balance = getBalance(root);
// 如果节点不平衡,则进行旋转
// 左左情况
if (balance > 1 && getBalance(root.left) >= 0) {
return rightRotate(root);
}
// 左右情况
if (balance > 1 && getBalance(root.left) < 0) {
root.left = leftRotate(root.left);
return rightRotate(root);
}
// 右右情况
if (balance < -1 && getBalance(root.right) <= 0) {
return leftRotate(root);
}
// 右左情况
if (balance < -1 && getBalance(root.right) > 0) {
root.right = rightRotate(root.right);
return leftRotate(root);
}
// 返回更新后的根节点
return root;
}