查找二叉树,AVL树,234树,红黑树详解

普通的查找二叉树

查找二叉树结构规定每个节点的key在同一层中要比左侧节点大,右侧节点小,要比字节点的左侧节点大,右侧节点小,如下图(无规律,满足条件即可)。

在这样的排布方式下二叉树有其独特的遍历方式

前序遍历:

先访问根节点,在访问左节点,在访问右节点,其中从跟节点开始左侧和右侧都看成一个整体,整理内部在根据这个条件进行遍历,如下图(过程方便大家理解,实际代码实现可能会有不同)

中序遍历:

先访问左节点,在访问根节点,在访问右节点,同理依旧看成整体展开,这里就不画图了

后续遍历:

先访问左节点,在访问右节点,在访问根节点

代码实现:

// 定义二叉树节点
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
​
    TreeNode(int val) {
        this.val = val;
    }
}
​
public class BinaryTreePreorderTraversal {
    
    // 前序遍历方法
    public void preorderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        // 打印当前节点
        System.out.print(root.val + " ");
​
        // 遍历当前节点子节点的左侧节点,相当于左侧整体展开
        preorderTraversal(root.left);
​
        // 遍历当前节点子节点的左=右侧节点,相当于右侧整体展开
        preorderTraversal(root.right);
    }
    // 中序遍历方法,实际上就是变换遍历位置,核心思想依旧是整体展开
    public void preorderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        // 遍历当前节点子节点的左侧节点,相当于左侧整体展开
        preorderTraversal(root.left);
        // 打印当前节点
        System.out.print(root.val + " ");
        // 遍历当前节点子节点的左=右侧节点,相当于右侧整体展开
        preorderTraversal(root.right);
    }
     // 后序遍历方法,同上
    public void preorderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        // 遍历当前节点子节点的左侧节点,相当于左侧整体展开
        preorderTraversal(root.left);
        // 遍历当前节点子节点的左=右侧节点,相当于右侧整体展开
        preorderTraversal(root.right);
        // 打印当前节点
        System.out.print(root.val + " ");
    }
}

前驱查找:

查找小于当前节点的最大值,实际上就是以当前节点的左侧字节点为当前节点,在当前节点开始一直找右侧子节点一直找到尽头(因为在二叉树的添加逻辑中,比当前节点大的都会送到右侧,小的送到左侧,所以左侧节点都是小的,只需要在其中找最大值,而最大值则需要沿着右侧找)

后继查找:

查找大于当前节点的最小值,实际上就是以当前节点的左侧字节点为当前节点,在当前节点开始一直找右侧子节点一直找到尽头(原理同上)

删除操作:

二叉树的删除操作就是通过前驱查找或后继查找,查找一个节点来替换当前节点,这样可以保证二叉树结构不被破坏。

二叉树无论是查找还是增删都要进行查找而不能马上锁定,但又都有规律可循(左侧小,右侧大),查找效率高,所以他的增删查效率是数组和链表的折中表现。

AVL树

当一直为二叉树添加比根节点大或者小的节点时,会造成二叉树一侧深度过深,结构接近链表,为了防止这种情况,AVL树通过比较节点两侧深度(差距不能超过1)来折叠节点,来防止这种情况,当左侧节点过深右侧节点过浅时,AVL树会找到左右两侧中间的节点(可以将当前节点左侧所有节点和右侧所有节点连起来看成一个链表,然后拿到中间的节点),然后以此节点旋转来作为父节点,如果折叠后的中间节点原来有右侧子节点,那么则将右侧子节点挂到中间节点的新的右侧字节点(也就是当看成链表时,中间节点的右侧姐带你)的左侧字节点以保证二叉树结构不变(每个节点都要进行这种判断,以保证二叉树平衡,为了不重复判断,应该先从叶子节点开始判断),当右侧过深时同理

AVL树虽然能解决分布不均的问题,然是性能损耗过大,并不完美。

234树

234树是指每个节点可以最多拥有三个元素,其中用有n个元素的节点被允许拥有n+1个字节点,如下图。

与众不同的是其储存的方式:

首先他会将接受的元素全部存入一个节点,当一个节点存到四个元素是会进行分裂,第二个元素为父节点,左侧一个元素为子节点,右侧两个元素为子节点。

再次来到元素时会和父节点进行比较判断加入左侧节点还是右侧节点,当子节点变成四个元素时继续分裂。但与众不同的是他不会向下排布,而是上提,也就是说分类出来的父元素和当前节点的父元素合并为一个二元素节点,而分裂出的两个子节点为这个二元素节点的字节元,这就是说为什么时拥有n个元素的节点被允许拥有n+1个字节点,如下图。

而当父元素为四个元素时依旧会分裂,而其分裂后的子节点要成为其原本子节点的父节点。不过这个过程需要一定的运算,因为多元素的子节点大小不一,分配位置是需要保证二叉树左小右大的结构不变。

因为其不下沉只上提的机制,保证了深度不会发生不均匀的情况。

红黑树

红黑树是基于234树提出另一种二叉树规则,其中包括:

  • 节点颜色:每个节点要么是红色,要么是黑色。
  • 根节点:根节点必须是黑色的。
  • 红节点限制:两个红色节点不能相邻)。
  • 黑高度一致:从任一节点到其每个叶子节点的所有路径都包含相同数量的黑色节点(即黑高度相同)。

为了维持这种规律,红黑树有其独特的增加节点的方式

当添加第一个节点时,该节点会作为根节点变为黑色

当新增节点时会默认为新增节点为红色

当新增第三个节点时会有两种情况,当比根节点小时,会正常添加到左侧,当比根节点大时,添加到右侧会造成红色节点相邻,此时需要折叠变色操作,如下图。

其中1和3为黑色是因为旋转后整体第二层为黑色。而第一层为红色,但2又为根节点,所以2也要变成黑色

当添加第三个节点时,没有在右侧,而是左侧,那么则不需要进行其他操作,但是添加第四个节点时,则一定会出现红色相邻的情况,解决办法依旧是旋转变色,如下图.

依旧是红色相邻的向左侧旋转,然后第二层变色

但如果新增节点为右侧子节点的左侧子节点呢?那么则须先将两个红色节点进行反向旋转,在将整体旋转变色,如下图

以上就是红黑树节点的全部新增操作,那么这和234树有什么关系呢?为什么说是基于234树呢?当我们把红节点和其父节点(黑色节点,看成一个整体时),实际上这个整体就是最多有三个元素的整体,这样,这也是红黑树为什么只判断黑色节点深度相等即可,因为这和判断234树深度的逻辑相同,并且红黑树的各种旋转变色操作,实际上跟234树分裂造成的影响一致,所以红黑树实际上是基于234树的一个逻辑改进,但核心思想并没变化,实际上无论是红黑树还是234树,都是在尽量保证二叉树结构不会出现极端的重心偏移的前提下,放宽两侧判断深度的条件。以保证结构查询性能的同时不会造成修改时为了维持二叉树结构造成性能损耗过大。

二叉树的删除操作及其复杂:

第一种情况:删除节点没有子节点
  • 直接删除该节点。
  • 如果删除的节点是红色,不需要进一步调整。
  • 如果是黑色则需要平衡处理
第二种情况:删除的节点有一个子节点
  • 用该子节点替换删除节点,并删除该节点(拥有一个子节点的节点位于二叉树倒数第二层,因为如果再往上出现这种情况,红黑树会平衡成两个子节点一个父节点的形式)
  • 如果删除的节点是红色或替换节点是红色,不需要进一步调整。
  • 如果删除的节点和替换节点都是黑色,则需要进行平衡处理。

删除节点有两个子节点

  • 找到删除节点的后继节点(即右子节点树中的最小节点)。

  • 用后继节点替换删除节点,然后删除后继节点。

  • 处理后继节点的删除,按照上述两种情况进行调整。

综上所述,实际上所有的删除操作,本质上都是删除叶子节点

平衡处理:

删除的平衡处理实际上是要借助旁边的线路通过旋转和变色操作公用节点,并且帮正旁边的线路的黑色节点数不受影响,当旁边的线路通过旋转变色无法完成这一功能时,则需要对整个二叉树的大量公用节点进行变色处理,使所有线路的黑色节点数减一

如果删除的节点是红色
如果删除节点是黑色
如果兄弟节点是红色

如果兄弟节点是黑色,兄弟节点的子节点是红色
如果兄弟节点是黑色,并且没有子节点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不止会JS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值