1. 二叉树排序树的缺陷
1.1. 存在的问题
- 给定数列
arr = {1,2,3,4,5,6}创建二叉排序树 - 创建出来的二叉排序树如下图所示

1.1.1. 问题分析
- 左子树全部为空, 从形式上看更像是单链表
- 这种树对插入新结点的速度并不受影响
- 查询速度却会明显下降, 因为要依次比较, 不能发挥 BST 的优势
因为每次还要比较左子树, 其查询速度比单链表还要慢
2. 平衡二叉树的原理
2.1. 基本介绍
-
平衡二叉树也叫平衡二叉搜索树 (Self-balancing binary search tree)
又被称为 AVL 树, 可以保证有较高的查询效率 -
具有如下特点:
- 首先它本身是二叉排序树, 是二叉排序树的改进
- 它是一棵空树或它的左右两个子树的高度差绝对值好不超过 1
- 并且它的左右两个子树都是一棵平衡二叉树
-
平衡二叉树常用的实现方法有: 红黑树, AVL, 替罪羊树, Treap, 伸展树等.
3. 左旋转 AVL 树案例
- 要求: 给定数列, 创建出对应的平衡二叉树,
arr = {4,3,6,5,7,8}
3.1. 思路分析
-
首先用数列
arr = {4,3,6,5,7,8}进行构造二叉排序树, 如下图进行排列.

-
然后问题就出现于此, 此时正常构建的二叉排序树的右子树的高度为 3,
左子树的高度则为 1, 并不满足平衡二叉树的定义, 因此要进行左旋转.
3.1.1. 左旋转进行的步骤如下:
-
创建一个新结点, 权值等于当前二叉树的根结点, 即权值等于 4.

-
把新结点的左子结点设置为原根结点的左子结点

-
把新结点的右子树设置为原根结点的右子结点的左子结点

-
把原根结点的权值替换成原根结点的右子结点的权值

位置进行调整和优化

-
把原根结点的右子树设置成其右子结点的右子结点

-
把原根结点的左子树设置为新结点

-
调整后得到新的平衡二叉树

3.1.2. 注意事项:
-
在分析过程中左右子树的高度是关键判断是否旋转的标准,
当发现左右子树的高度差绝对值大于 1 的时候就进行旋转. -
即当新结点进行插入的时候, 若发现二叉树不再是 AVL 树的时候,
就根据条件情况进行判断进行左旋转或右旋转. -
因此在旋转前, 需要统计出二叉树的高度及其左右子树各自的高度
4. 右旋转 AVL 树案例
- 要求给定数列, 创建出对应的平衡二叉树,
arr = {10,12,8,9,7,6}
4.1. 思路分析
- 首先用数列创建出对应的排序二叉树, 如下图所示.

4.1.1. 右旋转步骤如下
- 创建一个新的结点, 权值等于当前二叉树根结点的权值
- 把新的结点的右子结点设置为原根结点的右子结点
- 把新的结点的左子结点设置为原根结点的左子结点的右子结点
- 把原根结点的权值换成原根结点的左子结点的权值
- 把原根结点的左子结点设置为其左子结点的左子结点
- 把原根结点的右子结点设置为新结点
- 最后得出 AVL 树

5. 双旋转 AVL 树
- 在某些情况下单旋转并不能完成平衡二叉树的转换
- 比如数列
arr = {10,11,7,6,8,9}以及数列arr = {2,1,6,5,7,3} - 如下图分析所示:
- 单旋转过后
arr = {10,11,7,6,8,9}并没有成功转换成平衡二叉树

- 单旋转过后
5.1. 双旋转情况分析
5.1.1. 情况一

- 当符合右旋转的条件时
- 若二叉树的左子树的右子树高度大于其左子树的左子树高度
- 则先对这棵二叉树的左子树进行左旋转
- 再对这棵二叉树本身进行右旋转即可
5.1.2. 情况二

- 当符合左旋转的条件时
- 若二叉树的右子树的左子树高度大于其右子树的右子树高度
- 则先对这棵二叉树的右子树进行右旋转
- 再对这棵二叉树本身进行左旋转即可
6. AVL 树代码实现
- 大部分代码与二叉排序树重合, 但结点类有很大程度更改和新增
6.1. 结点类
package com.leo9.dc29.avl_tree;
public class TreeNode {
public int value;
public TreeNode left_node;
public TreeNode right_node;
public TreeNode(int value) {
this.value = value;
}
//region 添加结点的方法
//递归形式添加结点, 注意需要满足二叉排序树的要求
public void addNode(TreeNode new_node) {
//若传入空结点则直接返回即可
if (new_node == null) {
return;
}
//region 判断传入的结点的值, 和当前调用方法的结点的值关系
//如果新结点的值小于等于当前调用结点的值
if (new_node.value <= this.value) {
//如果当前调用结点的左子结点为空, 则直接将新结点接在其左子结点即可
if (this.left_node == null) {
this.left_node = new_node;
}
//如果不为空, 递归向左添加
else {
this.left_node.addNode(new_node);
}
}
//如果新结点的值大于当前调用结点的值
else {
//如果当前调用结点的右子结点为空, 则直接将新结点接在其右子结点即可
if (this.right_node == null) {
this.right_node = new_node;
}
//如果不为空, 递归向右添加
else {
this.right_node.addNode(new_node);
}
}
//endregion
/**----------新增部分-----------*/
//region 判断是否需要左旋转
//如果 (右子树高度-左子树高度) > 1, 进行左旋转
if (getRightHeight() -<

本文介绍了二叉排序树的缺陷,特别是在查询效率上的不足,并引出了平衡二叉树的概念,如AVL树。详细阐述了AVL树的平衡性质,以及左旋、右旋和双旋的调整过程,通过具体案例演示了如何在不平衡时进行旋转操作。此外,还提供了AVL树的Java代码实现,包括节点类、二叉树类和测试类,展示了如何在插入节点后保持AVL树的平衡。
最低0.47元/天 解锁文章
2518

被折叠的 条评论
为什么被折叠?



