十-7,平衡二叉树概述及其Java实现
文章目录
十四, 平衡二叉树(AVL)
10.1 平衡二叉树概述
[基本定义]
- 平衡二叉树又称为AVL树, 是一种特殊的二叉排序树.
- 其左右子树都是平衡二叉树
- 以树中所有节点为根的树的左右子树高度之差的绝对值不超过1.
[平衡因子]
- 为了判断一棵二叉树是否是平衡二叉树, 引入了平衡因子的概念. 平衡因子是针对树中所有的结点来说的, 一个结点的平衡因子为其左子树的高度减去右子树高度的差.
- 对于平衡二叉树, 树中所有节点的平衡因子的取值只能是 -1, 0, 1 三个值;
[平衡调整]
- 假如向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性, 则首先要找出插入新结点后失去平衡的最小子树, 然后再调整这棵子树, 使之成为平衡子树;
- 当后, 无需调整原有其他所有的不平衡子树, 整个二叉排序树就会成为一棵平衡二叉树;
- 所谓失去平衡的最小子树是以距离插入结点最近, 且以平衡因子绝对值大于1的结点作为根的子树, 又称为最小不平衡子树;
失去平衡的最小子树被调整为平衡子树
[平衡二叉树存在的意义]
前面我们复习了二叉排序树, 二叉排序树是结合了数组查找速度快和链表的增删方便的一种数据结构, 它在增删结点效率还不错的同时,也具有良好的查询效率.
但是对于下面的二叉排序树:
在最坏情况下, 二叉排序树会退化成上图链表的形式, 查找时间复杂度变为o(height)=o(n), 为了改进这一缺点, 我们引入了平衡二叉树, 如下图所示, 平衡二叉树查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(logn)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。
10.2 求平衡二叉树的平衡因子(判断AVL树是否平衡)
平衡因子: AVL树中左子树的高度减去右子树高度的差值的绝对值;
//求二叉树的高度
public int height(){
return Math.max(leftNode==null? 0: leftNode.height(),
rightNode == null? 0: rightNode.height())+1;
}
//求左,右子树的高度
public int leftHeight(){
return leftNode.height();
}
public int rightHeight(){
return rightNode.height();
}
10.3 二叉树的左旋及其Java实现
平衡二叉树左旋过程和代码实现思路:
左旋的代码实现和解读
public void leftRotate(){
//1.创建一个新节点,这个结点的值是当前根节点this的值
//这个新节点的作用是安排根节点旋转后的新位置
TreeNode newNode = new TreeNode(this.value);
//2. 安排新节点的位置(为当前根节点在左旋后的位置,以及当前根节点的左右子树)
newNode.leftNode = this.leftNode;
newNode.rightNode = this.rightNode.leftNode;
//3. 设置新的根节点, 其实就是当前根节点的值改成其右子节点的值
this.value = this.rightNode.value;
//把新的根节点的左子节点连接到newNode上
this.leftNode = newNode;
// 新的根节点的右子节点为当前根节点的右子节点的右子节点
this.rightNode = this.rightNode.rightNode;
}
10.4 二叉树的右旋
平衡二叉树右旋过程和代码实现思路:
左旋的代码实现和解读
public void rightRotate(){
//1. 创建一个新结点newNode, 这个结点用来指代旋转前的根节点
TreeNode newNode = new TreeNode(this.value);
///2. 我们把被旋转的根节点的安排好新的左右子节点
newNode.leftNode = this.leftNode.rightNode;
newNode.rightNode = this.rightNode;
//3. 处理好旋转后的根节点的左右子树的连接关系
this.value = this.leftNode.value;
this.leftNode = this.leftNode.leftNode;
this.rightNode = newNode;
}
10.5 二叉树的双旋转
二叉树双旋转(LR, RL)的过程图解:
-
- LR-先对平衡二叉树的根节点的左子树进行左旋转,然后对二叉树的根节点进行右旋转;
-
- RL-先对平衡二叉树的根节点的右子树进行右旋转,然后对二叉树的根节点进行左旋转;
代码实现思路:
双旋转是基于单旋转(左旋、右旋)的,其只是对单旋转的代码调用,通过代码来理解双旋转即可。
双旋转代码的核心思想在于:在创建二叉树的过程中,每次添加新的节点,都要判断一下该二叉树是否需要旋转。
- 如果是双旋转中的LR情况,则先判断根节点的左子节点的右子树高度是否大于左子树的高度,如果大于则先对根节点的左子树进行左旋,最后再对原二叉树进行右旋
- 如果是双旋转中的RL情况,则先判断根节点的右子节点的左子树高度是否大于右子树的高度,如果大于则先对根节点的右子树进行右旋,最后再对原二叉树进行左旋;
核心代码如下:
//添加结点到二叉树
public void add(AVLTreeNode node) {
if (root == null) {
root = node;
} else {
root.addNode(node);
//左旋转
if (root.leftHeight() - root.rightHeight() > 1) {
if (root.leftNode.rightHeight() - root.leftNode.leftHeight() > 0) {
root.leftNode.leftRotation();
}
//右旋转
root.rightRotation();
return;
}
if (root.rightHeight() - root.leftHeight() > 1) {
if (root.rightNode.leftHeight() - root.rightNode.rightHeight() > 0) {
root.rightNode.rightRotation();
}
root.leftRotation();
return;
}
}
}