平衡二叉树
平衡二叉树的引入
案例:给定数组{1,2,3,4,5,6},要求创建一棵二叉排序树(BST)。
按照此数组创建的二叉排序树如图所示:
分析上图BST树可发现如下问题所在:
- 左子树全部为空,从形式上看,更像一个单链表
- 查询速度明显降低,不能发挥BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢
基于这些问题,我们的解决方案就是引入平衡二叉树
平衡二叉树基本介绍
- 平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树,可以保证查询效率较高。
- 平衡二叉树具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
- 平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。
- 平衡二叉树一定是二叉排序树,反之不一定成立。
平衡二叉树的案例分析
情况一:单旋转(右旋转)
给定数组{15,9,18,7,12,5},分析如下图所示:
步骤分析
- 创建一个与当前节点值相同的新节点
- 新节点的右子节点指向当前节点的右子节点
- 新节点的左子节点指向当前节点的左子节点的右子节点
- 将当前节点的左子节点的值赋给当前节点
- 当前节点的左子节点指向当前节点的左子节点的左子节点
- 当前节点的右子节点指向新节点
情况二:单旋转(左旋转)
给定数组{5,3,8,7,10,11},分析如下图所示:
步骤分析
- 首先new一个新节点,新节点的值与当前节点相同
- 新节点的左子节点指向当前节点的左子节点
- 新节点的右子节点指向当前节点的右子节点的左子节点
- 将当前节点的右子节点的值赋给当前节点
- 当前节点的右子节点指向当前节点的右子节点的右子节点
- 当前节点的左子节点指向新节点
情况三:双旋转
给定数组{5,3,8,7,10,6},分析如下图
发现无法用左旋转解决问题,此时应采用双旋转。
即先将以根节点的右子节点为根节点树先右旋转,在将整棵树左旋转,旋转方法与上面分析的一样。(注:还有一种情况这里不做分析,请动动脑筋自行分析)
代码实现
先创建一个节点类,封装数据。(注:主要实现方法都写在这个类中,请仔细分析。)
/**
* 封装节点的类
*/
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
/**
* 添加方法
*
* @param node 待添加的节点
*/
public void add(Node node) {
if (node.value < this.value) {
if (this.left == null) {
this.left = node;
} else {
this.left.add(node);
}
} else {
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}
if (getRightHeight() - getLeftHeight() > 1) {
//进到这里说明当前节点的右子树高度与左子树高度的差值大于1
if (this.right != null &&
this.right.getLeftHeight() > this.right.getRightHeight()) {
//进入这里说明当前节点的右子节点的左子树高度大于右子树高度
//需要先将右子节点进行右旋转
this.right.rightRotation();
}
//左旋转
leftRotation();
//执行到这里已经将二叉树调整为平衡二叉树,切记结束方法
return;
}
if (getLeftHeight() - getRightHeight() > 1) {
//进到这里说明当前节点的左子树高度与右子树高度的差值大于1
if (this.left != null &&
this.left.getRightHeight() > this.left.getLeftHeight()) {
//进入这里说明当前节点的左子节点的右子树高度大于左子树高度
//需要先将左子节点进行左旋转
this.left.leftRotation();
}
//右旋转
rightRotation();
}
}
/**
* 中序遍历
*/
public void inorder() {
if (this.left != null) {
this.left.inorder();
}
System.out.println(this);
if (this.right != null) {
this.right.inorder();
}
}
/**
* 获取高度的方法
*
* @return 返回当前以当前节点为父节点的树的高度
*/
public int getHeight() {
return Math.max(this.left == null ? 0 : this.left.getHeight(),
this.right == null ? 0 : this.right.getHeight()) + 1;
}
/**
* 获取左子树高度
*
* @return 返回以当前节点的左子节点为父节点的树的高度
*/
public int getLeftHeight() {
if (this.left == null) {
return 0;
}
return this.left.getHeight();
}
/**
* 获取右子树高度
*
* @return 返回以当前节点的右子节点为父节点的树的高度
*/
public int getRightHeight() {
if (this.right == null) {
return 0;
}
return this.right.getHeight();
}
/**
* 左旋转
*/
public void leftRotation() {
//1.首先new一个新节点,新节点的值与当前节点相同
Node newNode = new Node(this.value);
//2.新节点的左子节点指向当前节点的左子节点
newNode.left = this.left;
//3.新节点的右子节点指向当前节点的右子节点的左子节点
newNode.right = this.right.left;
//4.将当前节点的右子节点的值赋给当前节点
this.value = this.right.value;
//5.当前节点的右子节点指向当前节点的右子节点的右子节点
this.right = this.right.right;
//6.当前节点的左子节点指向新节点
this.left = newNode;
}
/**
* 右旋转
*/
public void rightRotation() {
//1.首先new一个新节点
Node newNode = new Node(this.value);
//2.新节点的右子节点指向当前节点的右子节点
newNode.right = this.right;
//3.新节点的左子节点指向当前节点的左子节点的右子节点
newNode.left = this.left.right;
//4.将当前节点的左子节点的值赋给当前节点
this.value = this.left.value;
//5.当前节点的左子节点指向当前节点的左子节点的左子节点
this.left = this.left.left;
//6.当前节点的右子节点指向新节点
this.right = newNode;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
}
在编写一个管理平衡二叉树的类
/**
* 管理平衡二叉树的类
*/
class AVLTree {
Node root;
/**
* 添加的方法
*
* @param node 待添加的节点
*/
public void add(Node node) {
if (node == null) {
return;
}
if (root == null) {
root = node;
} else {
root.add(node);
}
}
public void inorder() {
if (root == null) {
System.out.println("平衡二叉树为空,无法遍历");
return;
}
root.inorder();
}
}
主方法中进行测试
package com.atschool.avltree;
/**
* Description:
* Author:江洋大盗
* Date:2021/1/15 11:58
*/
public class AVLTreeDemo {
public static void main(String[] args) {
int[] arr = {5, 3, 8, 7, 10, 6};
AVLTree avlTree = new AVLTree();
for (int i : arr) {
avlTree.add(new Node(i));
}
avlTree.inorder();
System.out.println("树的高度:" + avlTree.root.getHeight());
System.out.println("左子树的高度:" + avlTree.root.left.getHeight());
System.out.println("右子树的高度:" + avlTree.root.right.getHeight());
}
}
测试结果:
(注:因为情况三最为复杂,这里只测试案例中给的情况三。)
结语
只要能收获甜蜜,荆棘丛中也有蜜蜂忙碌的身影,未来的你一定会感谢现在努力的自己。