平衡二叉树是一种特殊的排序二叉树(对于每个结点来说,左子树中所有结点的值 < 结点值 < 右子树中所有结点的值),树的左右结点高度(到叶子结点需要经过的最大结点数)差小于2。对一颗平衡二叉树进行中序遍历可以顺序输出所有节点的值,对树进行查找时类似二分查找,时间复杂度为log(n)。
一、排序二叉树
因为平衡二叉树是特殊的排序二叉树,所以在了解平衡二叉树前需要了解排序二叉树的增删。相对于平衡二叉树,排序二叉树不需要考虑结点高度,相对简单。
排序二叉树的插入思想:
- 将值value插入进树T
- 判断当前结点值node.value与value大小关系
- node == null 插入发生在此处,实例化值为value的结点赋值给node
- value > node.value 插入将发生在当前结点node的右子树node.right
- value < node.value 插入将发生在当前结点node的左子树node.left
- value == node.value 树中存在value值,无需插入
排序二叉树的删除思想:
- 在树T中删除value
- 查找是否存在值为value的结点,如果没有将不操作
- 判断需要删除的结点node是否存在左右子树
- 如果没有左右子树,直接删除
- 如果左子树为空,以右子树替换当前结点;如果右子树为空,以左子树替换当前结点
- 如果左右子树都不为空,在左(右)子树中寻找值最大(小)的结点,将该结点值赋node.value,删除该结点
二、平衡二叉树
对于有序的输入,排序二叉树的查找效率较高,但是无序的输入可能使得排序二叉树退化成为普通的链表。平衡二叉树所做的就是保证该颗排序二叉树中每个结点的左右子树高度差始终小于2,这样就不会退化成为链表,保证查找效率。
平衡二叉树的增删和排序二叉树类似,不过每次进行操作后需要考虑树是否仍旧保持平衡。如果树失衡,需要进行及时修整,不然将影响查找效率。调整的方法有:单左旋(RR插入)、单右旋(LL插入)、双左旋(RL插入)、双右旋(LR插入)。旋转的旋转的详细解释已有人写出,通过相应的旋转使得树始终保持平衡。
平衡二叉树的插入思想:
- 排序二叉树的插入
- 插入后高度修改为左右最大子树高度+1
- 判断左右子树高度差是否小于2,判断结点是否失衡
- 当前结点node失衡:
- 插入发生在左子树的左结点,单右旋singleRotateRight(node)
- 插入发生在左子树的右结点,双右旋doubleRotateRight(node)
- 插入发生在右子树的右结点,单左旋singleRotateLeft(node)
- 插入发生在右子树的左结点,双左旋doubleRotateLeft(node)
平衡二叉树的删除思想:
- 排序二叉树的删除
- 判断左右子树高度差是否小于2,判断结点是否失衡
- 当前结点失衡,将删除视为另一侧的插入,依照插入的旋转思想进行旋转
平衡二叉树插入删除代码实现:
public class AVLTree {
private class Node {
private Node left;
private int value;
private Node right;
private int height = -1;
private Node(int value) {
this.value = value;
}
private Node() {
}
}
Node head;
public AVLTree() {
}
private int Height(Node root) {
if(root == null) {
return -1;
}else {
return root.height;
}
}
//LL旋转(右旋)
//x为失衡点,有元素插入至x的左子树w的左孩子
private Node singleRotateRight(Node x) {
Node w = x.left;
x.left = w.right;
w.right = x;
x.height = Math.max(Height(x.left), Height(x.right))+1;
w.height = Math.max(Height(w.left), Height(w.right))+1;
return w;
}
//RR旋转(左旋)
//有元素插入至x的右子树w的右孩子
private Node singleRotateLeft(Node x) {
Node w = x.right;
x.right = w.left;
w.left = x;
x.height = Math.max(Height(x.left), Height(x.right))+1;
w.height = Math.max(Height(w.left), Height(w.right))+1;
return w;
}
//LR旋转,先左旋x,再右旋z
//失衡点z,有元素插入其左子树x的右子树w
private Node doubleRotateRight(Node z) {
z.left = singleRotateLeft(z.left);
return singleRotateRight(z);
}
//RL旋转,先右旋x,再左旋z
//失衡点z,有元素插入其右子树x的左子树w
private Node doubleRotateLeft(Node z) {
z.right = singleRotateRight(z.right);
return singleRotateLeft(z);
}
//插入
private Node insert(Node root,int value) {
if(root == null) {
root = new Node(value);
}else if (value < root.value) {
//插入将发生在左子树
root.left = insert(root.left, value);
//失衡
if(Height(root.left)-Height(root.right) == 2) {
if(value < root.left.value) {
//插入发生在root的左子树
//LL插入,右旋
root = singleRotateRight(root);
}else {
//LR插入,先左旋再右旋
root = doubleRotateRight(root);
}
}
}else if(value > root.value) {
//插入将发生在右子树
root.right = insert(root.right, value);
if(Height(root.left)-Height(root.right)==2) {
//失衡
if(value>root.right.value) {
//失衡发生在root的右子树
//RR插入,左旋
root = singleRotateLeft(root);
}else {
//RL插入,先右旋再左旋
root = doubleRotateLeft(root);
}
}
}
root.height = Math.max(Height(root.left), Height(root.right))+1;
return root;
}
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 {
//找到值为value的结点
if(root.left!=null && root.right!=null) {
//如果当前结点左右子树不为空,
//找到右(左)子树中最小(大)值,赋值给当前结点
//将问题转换为删除右(左)子树中值最小(大)的结点
Node pre = root.right;
while(pre.left != null) {
pre = pre.left;
}
root.value = pre.value;
root.right = deleteNode(root.right, pre.value);
}else {
//左右结点仅有一个或者都为空
//实际的删除就发送在下面这条语句中,
//当执行root.right = deleteNode(root.right, pre.value)再次进入方法至此
//因为该节点是叶子节点,左右子树为空
//拿左右子树将其覆盖即为删除该节点
root = (root.left != null) ? root.left : root.right;
}
}
//删除完成,调整平衡性
if(root == null) {
return root;
}
root.height = Math.max(Height(root.left), Height(root.right))+1;
//失衡
if(Height(root.left)-Height(root.right)>=2) {
//删除发生在右子树,模拟插入发生在左子树
if(Height(root.left.left) > Height(root.left.right)) {
//插入发生在左子树,LL旋转
return singleRotateRight(root);
}else {
//LR旋转
return doubleRotateRight(root);
}
}else if(Height(root.left)-Height(root.right)<=-2){
//删除发生在左子树,模拟插入发生在右子树
if(Height(root.right.left) > Height(root.right.right)) {
//RL旋转
return doubleRotateLeft(root);
}else {
//RR旋转
return singleRotateLeft(root);
}
}
//未失衡,不做操作
return root;
}
public void delete(int value) {
head = deleteNode(head, value);
}
public void add(int value) {
head = insert(head, value);
}
// 中序遍历
private void print(Node node) {
if (node == null) {
return;
}
print(node.left);
System.out.print(node.value+" ");
print(node.right);
}
public void showTree() {
print(head);
System.out.println();
}
}
测试:
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) {
AVLTree t = new AVLTree();
System.out.println("插入顺序:");
for(int i=1;i<=10;i++){
int n = 1+new Random().nextInt(100);
System.out.print(n+" ");
t.add(n);
}
System.out.println();
System.out.println("中序遍历:");
t.showTree();
int a = new Scanner(System.in).nextInt();
System.out.println("删除结点"+a+":");
t.delete(a);
t.showTree();
}
}
输出: