平衡二叉排序 / 搜索树
基本介绍
1、平衡二叉(排序/搜索)树:又被称为AVL树,改进BST结构不平衡的缺点,查询效率较高
2、它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
3、平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等
4、利用旋转实现调整树的平衡,以插入/删除节点
5、前驱节点:值小于当前节点的最大值节点
6、后继节点:值大于当前节点的最小值节点
7、平衡因子(Balance Factor)
(1)某节点的左子树与右子树的高度(深度)差即为该节点的平衡因子,平衡二叉树中不存在平衡因子大于 1 的节点
(2)在一棵平衡二叉树中,节点的平衡因子只能取 0、1 或 -1,分别对应着左右子树等高,左子树比较高,右子树比较高
对于高度为 n 的平衡二叉树
1、最少需 h(n) 个结点
(1)h(n) = h(n - 1) + h(n - 2) + 1
(2)h(0) = 0
(3)h(1) = 1
(4)h(2) = 2
2、最多需要 2n - 1 个结点
旋转
1、基本思想:检查是否因插入/删除而破坏了平衡,若破坏,则找出其中的最小不平衡二叉树,在保持二叉排序树特性的情况下,调整最小不平衡子树中节点之间的关系,以达 到新的平衡
2、最小不平衡子树:指离插入/删除节点最近(向上查找),且以平衡因子的绝对值大于1的节点作为根的子树
3、单旋转前判断是否需要双旋转
(1)左旋转之前判断:若当前节点的右子节点不为空,且其右子节点的左子树高度大于右子树高度,则为 RL
(2)右旋转之前判断:若当前节点的左子节点不为空,且其左子节点的右子树高度大于左子树高度,则为 LR
绕某元素左旋转
1、右子树高度 - 左子树高度 > 1
2、假设,旋转元素以自身为 root,旋转前,newRoot = root.right,旋转后,新的根节点为 newRoot
3、左旋转(LL):将 newRoot 的左子树移动到 root.right 作为 root 的新右子树,将 root 移动到 newRoot.left 作为 newRoot 的新左子树
绕某元素右旋转
1、左子树高度 - 右子树高度 > 1
2、假设,旋转元素以自身为 root,旋转前,newRoot = root.left,旋转后,新的根节点为 newRoot
3、右旋转(RR):将 newRoot 的右子树移动到 root.left 作为 root 的新左子树,将 root 移动到 newRoot.right 作为 newRoot 的新右子树
双旋转
1、LR:绕某元素的左子节点左旋转,接着再绕该元素自己右旋转
2、RL:绕某元素的右子节点右旋转,接着再绕该元素自己左旋转
代码实现
1、以下的插入节点、删除节点是以根节点进行旋转,而不是根据最小不平衡树的根节点进行旋转
public class AVLTree {
public Node root;
//添加节点,若有相同值的节点,则插入相同值节点的尾部
public void add(Node node) {
if (root == null) {
root = node;
} else {
root.add(node);
root = root.rotate();
}
}
//查找节点
public Node search(int value) {
if (root == null) {
return null;
} else {
return root.search(value);
}
}
//查找节点的父节点
public Node searchParent(int value) {
if (root == null) {
return null;
} else {
return root.searchParent(value);
}
}
//删除节点,若有相同值的节点,删除找到的第一个节点
public void delete(int value) {
if (root == null) {
return;
}
Node target = search(value);//找待删除的结点target
if (target == null) {//没有找到
return;
}
Node parent = searchParent(value);//找到target的父结点parent
if (parent == null) {//说明待删除节点是root
deleteRoot();
} else if (target.left == null && target.right == null) {//说明待删除节点是叶子节点
deleteLeaf(parent, value);
} else {//说明删除非根、非叶子节点
deleteNonLeaf(target, parent, value);
}
root = root.rotate();
}
//删除根节点
public void deleteRoot() {
if (root.left != null && root.right != null) {
//删除root右子树的最小值节点,并用其值覆盖root的值
root.value = deleteRightTreeMin(root);
//删除root左子树的最大值节点,并用其值覆盖root的值
//root.value = deleteLeftTreeMax(root);
} else if (root.left != null) {
root = root.left;
} else if (root.right != null) {//
root = root.right;
} else {//root.left == null && root.right == null
root = null;
}
}
//删除叶子节点
public void deleteLeaf(Node parent, int value) {
//确定target是parent的左子节点还是右子节点
if (parent.left != null && parent.left.value == value) {
parent.left = null;
} else if (parent.right != null && parent.right.value == value) {
parent.right = null;
}
}
//删除非根、非叶子节点
public void deleteNonLeaf(Node target, Node parent, int value) {
//要删除的节点有两个子节点
if (target.left != null && target.right != null) {
//删除target后继节点,并用其值覆盖target的值
target.value = deleteRightTreeMin(target);
//或删除target前驱节点,并用其值覆盖target的值
//target.value = deleteLeftTreeMax(target);
} else if (target.left != null) {//要删除的节点只有一个左子节点
if (parent.left.value == value) {//要删除的节点是父节点的左子节点
parent.left = target.left;
} else {//parent.right.value == value,要删除的节点是父节点的右子节点
parent.right = target.left;
}
} else {//target.right != null,要删除的节点只有一个右子节点
if (parent.left.value == value) {//要删除的节点是父节点的左子节点
parent.left = target.right;
} else {//parent.right.value == value,要删除的节点是父节点的右子节点
parent.right = target.right;
}
}
}
//返回parent后继节点的值,并删除该后继节点
public int deleteRightTreeMin(Node parent) {
Node target = parent.right;
if (target.left != null) {//parent的右子节点有左子节点
parent = parent.right;
target = target.left;
while (target.left != null) {//循环的查找左节点,直到找到最小值
parent = parent.left;
target = target.left;
}
parent.left = target.right;//删除该最小值节点,此时target没有左右子节点或只有右节点
} else {//target.left == null,parent的右子节点没有有左子节点
parent.right = target.right;//删除该最小值节点,此时target没有左右子节点或只有右节点
}
return target.value;
}
//返回parent前驱节点的值,并删除该前驱节点
public int deleteLeftTreeMax(Node parent) {
Node target = parent.left;
if (target.right != null) {//parent的左子节点有右子节点
parent = parent.left;
target = target.right;
while (target.right != null) {//循环的查找右节点 直到找到最大值
parent = parent.right;
target = target.right;
}
parent.right = target.left;//删除该最大值节点,此时target没有左右子节点或只有左节点
} else {//target.right == null,parent的左子节点没有有右子节点
parent.left = target.left;//删除该最大值节点,此时target没有左右子节点或只有左节点
}
return target.value;
}
}
class Node {
public int value;
public Node left;
public Node right;
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//返回左子树的高度
public int leftHeight() {
return left == null ? 0 : left.height();
}
//返回右子树的高度
public int rightHeight() {
return right == null ? 0 : right.height();
}
// 返回以该结点为根结点的树的高度
public int height() {
return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
}
//左旋转
public Node leftRotate(Node root) {
Node newRoot = root.right;
root.right = newRoot.left;
newRoot.left = root;
return newRoot;
}
//右旋转
public Node rightRotate(Node root) {
Node newRoot = root.left;
root.left = newRoot.right;
newRoot.right = root;
return newRoot;
}
//封装旋转方法,root调用
public Node rotate() {
if (rightHeight() - leftHeight() > 1) {//判断是否要左旋转
if (right != null && right.leftHeight() > right.rightHeight()) {//判断左旋转前是否要右旋转
right = rightRotate(right);//对右子结点进行右旋转
}
return leftRotate(this);//对当前结点进行左旋转
} else if (leftHeight() - rightHeight() > 1) {//判断是否要右旋转
if (left != null && left.rightHeight() > left.leftHeight()) {//判断右旋转前是否要左旋转
left = leftRotate(left);//对左子结点进行左旋转
}
return rightRotate(this);//对当前结点进行右旋转
}
return this;
}
//添加节点
public void add(Node node) {
if (node == null) {
return;
}
if (node.value < this.value) {
if (this.left == null) {
this.left = node;
} else {
this.left.add(node);
}
} else if (node.value > this.value){
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
} else {//若是相同节点,则不变
return;
}
}
//查找节点,value:节点的值;找到则返回该节点,否则返回null
public Node search(int value) {
if (value == this.value) {//找到该节点
return this;
} else if (value < this.value) {
if (this.left == null) {
return null;
} else {
return this.left.search(value);//向左子树递归查找
}
} else {//value >= this.value
if (this.right == null) {
return null;
} else {
return this.right.search(value);//向右子树递归查找
}
}
}
//查找节点的父节点,value:节点的值;找到返回节点的父节点,否则返回null
public Node searchParent(int value) {
if (this.left != null && this.left.value == value || this.right != null && this.right.value == value) {
return this;//当前节点即为要删除节点的父节点
} else {
if (value < this.value && this.left != null) {
return this.left.searchParent(value);//递归的向左子树查找
} else if (value >= this.value && this.right != null) {
return this.right.searchParent(value);//递归的向右子树查找
} else {
return null;//没有父节点
}
}
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战