平衡二叉树是二叉搜索树的改进版,二叉搜索树用来快速的对数据进行查找,可以将原先O(n)的复杂度降低到O(logn),但这是在理想情况下的结果,若在原序列是有序的情况下,构建出的二叉搜索树就是线性的,与原先的线性表结构没有差异,时间复杂度会降低为O(n)。因此为避免这种情况下,需要对二叉树保持平衡
(最糟糕的情况下的二叉搜索树)
(理想状态下的二叉搜索树,左右子树高度相差不超过1)
AVL树是最早发明的一种平衡二叉树。AVL树得名于其发明者Adelson-Velsky与Evgenii Landis,其通过每个节点的左右子树的高度差来判断是否失衡,因此也被称为高度平衡树。要注意的是AVL树只是平衡二叉树的一种
在最初遇到这个问题时,是不是有人与我一样一位需要用到一些精妙的数据结构,事实上并不是,平衡二叉树保持平衡的奥秘就在于在每次对节点进行插入和删除时会对失衡的节点进行调整。需要知道应当如何调整,首先需要知到如何判断失衡以及失衡的几种情况
平衡因子的定义:某节点的左子树与右子树的高度差即该节点的平衡因子
在平衡二叉树中平衡因子应当只有-1、0、1三个值,其他情况则视为失衡,需要重新平衡。在插入或删除一个节点时,若二叉树中的每一个节点的平衡因子都为-1、0、1,那么该二叉树是平衡的,否则需要重新平衡
二叉搜索树的失衡情况总结下来有四种:LL型、LR型、RL型与RR型,不同的情况适用不同的处理方法
上一节中的左旋与右旋显然分别对应着RR与LL的情况,因此LL仅需要进行一次右旋便能重新平衡,RR仅需要进行一次左旋就能重新平衡。而LR与RL的情况则较为复杂,一次旋转并不能完成重新平衡,需要进行两次旋转
LL | ![]() |
---|---|
LR | ![]() |
RL | ![]() |
RR | ![]() |
class Node {
int val;
Node left;
Node right;
int height;
public Node(int val) {
this.val = val;
height = 1;
left = null;
right = null;
}
}
public class AVLTree {
private Node rNode;
/**
* 获取节点的高度,若节点为空,返回0
*
* @param p 指定节点
* @return 节点的高度
*/
private int getHeight(Node p) {
return p == null ? 0 : p.height;
}
/**
* 右旋,适用于LL的情况
*
* @param base 旋转的基准节点(失衡的子树的根节点)
* @return 返回旋转完成后新的根节点
*/
public Node rightRotate(Node base) {
Node left = base.left;
base.left = left.right;
left.right = base;
base.height = Math.max(getHeight(base.left), getHeight(base.right)) + 1;
left.height = Math.max(getHeight(left.left), base.height) + 1;
return left;
}
/**
* 右旋,适用于RR的情况
*
* @param base 旋转的基准节点(失衡的子树的根节点)
* @return 返回旋转完成后新的根节点
*/
public Node leftRotate(Node base) {
Node right = base.right;
base.right = right.left;
right.left = base;
base.height = Math.max(getHeight(base.left), getHeight(base.right)) + 1;
right.height = Math.max(base.height, getHeight(right.right)) + 1;
return right;
}
/**
* 先左旋再右旋,适用于LR的情况
*
* @param base 旋转的基准节点(失衡的子树的根节点)
* @return 返回旋转完成后新的根节点
*/
public Node leftRightRotate(Node base) {
base.left = leftRotate(base.left);
return rightRotate(base);
}
/**
* 先右旋再左旋,适用于RL的情况
*
* @param base 旋转的基准节点(失衡的子树的根节点)
* @return 返回旋转完成后新的根节点
*/
public Node rightLeftRotate(Node base) {
base.right = rightRotate(base.right);
return leftRotate(base);
}
/**
* 插入新的值
*/
public void add(int val) {
rNode = add(rNode, val);
}
private Node add(Node root, int val) {
if (root == null) {
root = new Node(val);//添加节点
} else {
//进入左子树
if (val < root.val) {
//递归查找合适得到位置插入
root.left = add(root.left, val);
//插入结束后判断当前节点是否失衡
if (getHeight(root.left) - getHeight(root.right) > 1) {
if (val < root.left.val) {
root = rightRotate(root);
}//LL的情况,右旋
else {
root = leftRightRotate(root);
}//LR的情况,先左旋再右旋
}
}
//进入右子树
else if (val > root.val) {
//递归查找合适的位置插入
root.right = add(root.right, val);
//插入结束后判断当前节点是否失衡
if (getHeight(root.left) - getHeight(root.right) < -1) {
if (val > root.right.val) {
root = leftRotate(root);
}//RR的情况,左旋
else {
root = rightLeftRotate(root);
}//RL的情况,先右旋再左旋
}
} else {
System.out.println("插入的值已存在,请勿重复添加");
}
}
//重新获得节点的高度
root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
return root;//返回插入后新的根节点
}
/**
* 获取指定值的节点
*/
public Node get(int val) {
Node p = rNode;
while (true) {
if (p == null) {
return null;
} else {
if (p.val == val) {
return p;
} else if (val < p.val) {
p = p.left;
} else {
p = p.right;
}
}
}
}
/**
* 删除指定节点
*/
public void remove(int val) {
rNode = remove(rNode, val);
}
private Node remove(Node root, int val) {
if (root == null) {
return null;
}
//当前节点即需要删除的节点
if (val == root.val) {
if (root.left != null && root.right != null) {
if (root.left.height < root.right.height) {
int min = minimum(root.right);
root.val = min;
root.right = remove(root.right, min);
} else {
int max = maximum(root.left);
root.val = max;
root.left = remove(root.left, max);
}
}else{
Node tmp = root;
root = root.left == null ? root.right : root.left;
tmp = null;
}
}
//需要删除的节点在左子树中
else if (val < root.val) {
root.left = remove(root.left, val);
//删除了左子树的节点,因此需要检查右子树是否失衡
if (getHeight(root.left) - getHeight(root.right) < -1) {
//RR的情况,进行一次左旋
if (getHeight(root.right.left) < getHeight(root.right.right)) {
root = leftRotate(root);
}
//RL的情况,先进行右旋再进行左旋
else {
root = rightLeftRotate(root);
}
}
}
//需要删除的节点在右子树中
else {
root.right = remove(root.right, val);
//删除了右子树的节点,因此需要检查左子树是否失衡
if (getHeight(root.left) - getHeight(root.right) > 1) {
//LL的情况,进行一次右旋
if (getHeight(root.left.left) > getHeight(root.left.right)) {
root = rightRotate(root);
}
//LR的情况,先进行左旋再进行右旋
else {
root = leftRightRotate(root);
}
}
}
return root;
}
/**
* 获取左子树的最大值
*/
private int maximum(Node root) {
while (root.right != null) {
root = root.right;
}
return root.val;
}
/**
* 获取右子树的最小值
*/
private int minimum(Node root) {
while (root.left != null) {
root = root.left;
}
return root.val;
}
/**
* 中序遍历(按顺序输出所有值)
*/
public void preOrder() {
preOrder(rNode);
System.out.println();
}
private void preOrder(Node root) {
if (root == null) {
return;
}
preOrder(root.left);
System.out.print(root.val + " ");
preOrder(root.right);
}
/**
* 层序遍历
*/
public void sequeueTrav() {
Queue<Node> queue = new LinkedList<Node>();
queue.offer(rNode);
while (!queue.isEmpty()) {
Node tmp = queue.poll();
if (tmp == null) {
System.out.print("null ");
} else {
System.out.print(tmp.val + " ");
queue.offer(tmp.left);
queue.offer(tmp.right);
}
}
System.out.println();
}
}
public static void main(String[] args) {
AVLTree tree = new AVLTree();
int[] array = new int[]{2,1,0,3,4,5,6,16,15,14,13,12,11,10,8,9};
for (int i:array){
tree.add(i);
}
tree.preOrder();
tree.sequeueTrav();
tree.add(7);//插入7
tree.preOrder();
tree.sequeueTrav();
tree.remove(6);//删除6
tree.preOrder();
tree.sequeueTrav();
}
0 1 2 3 4 5 6 8 9 10 11 12 13 14 15 16
6 3 13 1 5 11 15 0 2 4 null 9 12 14 16 null null null null null null 8 10 null null null null null null null null null null
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
6 3 13 1 5 9 15 0 2 4 null 8 11 14 16 null null null null null null 7 null 10 12 null null null null null null null null null null
0 1 2 3 4 5 7 8 9 10 11 12 13 14 15 16
7 3 13 1 5 9 15 0 2 4 null 8 11 14 16 null null null null null null null null 10 12 null null null null null null null null
最初建立好的AVL树如下:
插入节点7后
删除节点6后