【数据结构与算法】第十、十一、十二章:平衡树(2-3查找树、红黑树)B树

  • 向一个父结点为3-结点的3-结点中插入新键

当插入的结点是3-结点的时候,将该结点拆分,中间元素提升至父结点,但是此时父结点是一个3-结点, 插入之后,父结点变成了4-结点,然后继续将中间元素提升至其父结点,直至遇到一个父结点是2-结点,然后将其变为3-结点,不需要继续进行拆分

在这里插入图片描述

在这里插入图片描述

  • 分解根结点

当插入结点到根结点的路径上全部是3-结点的时候,最终的根结点会变成一个临时的4-结点,此时,就需要将根结点拆分为两个2-结点,树的高度加1

在这里插入图片描述

在这里插入图片描述

4)性质

通过对2-3树插入操作的分析,发现在插入的时候,2-3树需要做一些局部的变换来保持2-3树的平衡

一棵完全平衡的2-3树具有以下性质:

  • 任意 空链接 到根结点的路径长度都是 相等

  • 4-结点变换为3-结点时,树的高度不会发生变化,只有当根结点是临时的4-结点,分解根结点时树高+1

  • 2-3树与普通二叉查找树最大的区别在于,普通的二叉查找树是自顶向下生长,而2-3树是 自底向上 生长

5)实现

  • 直接实现2-3树比较复杂,因为:

  • 需要处理不同的结点类型,非常繁琐

  • 需要多次比较操作来将结点下移

  • 需要上移来拆分4-结点

  • 拆分4-结点的情况有很多种

2-3查找树实现起来比较复杂,在某些情况插入后的平衡操作可能会使得效率降低。但是2-3查找树作为一种比较重要的概念和思路对于后面要讲到的红黑树、B树和B+树非常重要

10.2、2-3-4树 与 红黑树


在这里插入图片描述

1)与红黑树等价关系

在这里插入图片描述

在这里插入图片描述

2)转化为红黑树

在这里插入图片描述

在这里插入图片描述

一棵2-3-4树可以有多棵红黑树,一个红黑树只有一个2-3-4树

3)添加

思路:先添加、再调整

  • 新增结点都是红色
  • 都是从叶子节点新增

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

需要调整的四种情况:

  • 左左:/
*   一根斜线(此时最好用手比划一下),顶点表示祖节点,中间点表示父节点,底点表示插入节点
  • 右右: \
*   一根反斜线,表示同上
  • 左右:<
*   一个小于号,顶点表示祖节点,中间尖点表示父节点,底点表示插入节点
  • 右左:>
*   一个大于号,表示同上

在这里插入图片描述

在这里插入图片描述

4)添加后调整

在这里插入图片描述

在这里插入图片描述

TreeMap 源码

在这里插入图片描述

在这里插入图片描述

在线演示:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

5)删除

思路:先删除、再调整

在这里插入图片描述

6)删除后调整

见另一篇博文

【手撕红黑树 | 史上最详细注解】增删查改 原理剖析 代码实现

10.3、红黑树


2-3树能保证在插入元素之后,树依然保持平衡状态,它的最坏情况下所有子结点都是2-结点,树的高度为lgN,相比于普通的二叉查找树,最坏情况下树的高度为N,确实保证了最坏情况下的时间复杂度,但是2-3树实现起来过于复杂,所以介绍一种2-3树思想的简单实现:红黑树

  • 红黑树主要是对2-3树进行编码,红黑树背后的基本思想是:

  • 标准的二叉查找树 (完全由2-结点构成)和一些 额外的信息 (替换3-结点)来表示2-3树

  • 将树中的链接分为两种类型

  • 红链接:将两个2-结点连接起来构成一个3-结点

  • 黑链接:则是2-3树中的普通链接

  • 确切的说,将 3-结点 表示为由一条 左斜的红色链接 相连的两个2-结点

两个2-结点:其中一个是另一个的左子结点

  • 优点:无需修改就可以直接使用标准的二叉查找树的get方法

在这里插入图片描述

1)定义

红黑树是 含有红黑链接 并满足下列条件的 二叉查找树

  • 红链接均为 左链接

  • 没有任何一个结点同时和两条红链接相连

  • 该树是完美 黑色平衡 的,即任意空链接到根结点的路径上的黑链接数量相同

下面是红黑树与2-3树的对应关系:

在这里插入图片描述

在这里插入图片描述

以下是另一个版本解读

在这里插入图片描述

在这里插入图片描述

2)结点API

因为每个结点(根结点除外)都只会有一条指向自己的链接(从它的父结点指向它),可以在之前的Node结点中添加一个布尔类型的变量 color 来表示链接的颜色。如果指向它的链接是红色的,那么该变量的值为true,如果链接是黑色的,那么该变量的值为false

在这里插入图片描述

在这里插入图片描述

package chapter10;

/**

  • @author 土味儿

  • Date 2021/9/10

  • @version 1.0

  • 红黑树结点类

*/

public class Node<K,V> {

/**

  • 键 key

*/

private K key;

/**

  • 值 value

*/

private V value;

/**

  • 左子结点

*/

private Node left;

/**

  • 右子结点

*/

private Node right;

/**

  • 指向该结点的父结点链接颜色

  • 红色:true

  • 黑色:false

*/

private boolean color;

/**

  • 构造器

  • @param key

  • @param value

  • @param left

  • @param right

  • @param color

*/

public Node(K key, V value, Node left, Node right, boolean color) {

this.key = key;

this.value = value;

this.left = left;

this.right = right;

this.color = color;

}

}

3)平衡化

在对红黑树进行一些增删改查的操作后,很有可能会出现红色的右链接或者两条连续红色的链接,而这些都不满足红黑树的定义,所以需要对这些情况通过旋转进行修复,让红黑树保持平衡

1、左旋

当某个结点的左子结点为黑色,右子结点为红色,此时需要左旋

左黑右红

前提:当前结点为h,它的右子结点为x

  • 左旋过程

  • 让x的左子结点变为h的右子结点:h.right = x.left

  • 让h成为x的左子结点:x.left = h

  • 把h的color属性赋给x的color属性值:x.color = h.color

  • 把 h 的color变为:RED

在这里插入图片描述


左旋初始:

在这里插入图片描述

左旋过程:

在这里插入图片描述

左旋结束:

在这里插入图片描述


2、右旋

当某个结点的左子结点是红色,且左子结点的左子结点也是红色,需要右旋

左子左孙都红

前提:当前结点为h,它的左子结点为x

  • 右旋过程

  • 让x的右子结点成为h的左子结点:h.left = x.right

  • 让h成为x的右子结点:x.right = h

  • 把h的color属性赋给x的color属性值:x.color = h.color

  • 把 h 的color变为:RED

在这里插入图片描述

右旋后x结点仍然与两条红链接相连,可以通过后续颜色反转解决


右旋初始:

在这里插入图片描述

右旋过程:

在这里插入图片描述

右旋结束:

在这里插入图片描述


4)向单个2-结点插入新键

一棵只含有一个键的红黑树只含有一个2-结点。插入另一个键后, 可能就需要旋转

  • 如果新键小于当前结点的键

只需要新增一个红色结点即可

新的红黑树和单个3-结点完全等价

在这里插入图片描述

  • 如果新键大于当前结点的键

那么新增的红色结点将会产生一条红色的右链接,此时需要通过 左旋,把红色右链接变成左链接,插入操作才算完成

形成的新的红黑树依然和3-结点等价,其中含有两个键,一条红色链接

在这里插入图片描述

5)向底部2-结点插入新键

用和二叉查找树相同的方式向一棵红黑树中插入一个新键, 会在树的底部新增一个结点(可以保证有序性),唯一区别的地方是会用红链接将新结点和它的父结点相连。如果它的父结点是一个2-结点,那么刚才上面的两种方式仍然适用

在这里插入图片描述

6)颜色反转

当一个结点的左子结点和右子结点的color都为RED时,也就是出现了临时的4-结点,此时只需要把左子结点和右子结点的颜色变为BLACK,同时让当前结点的颜色变为RED即可

左右都红

在这里插入图片描述

7)向一棵双键树插入新键

双键树:即一个3-结点的树

分为三种子情况

  • 新键大于原树中的两个键

在这里插入图片描述

  • 新键小于原树中的两个键

在这里插入图片描述

在这里插入图片描述

  • 新键介于原树中两个键之间

在这里插入图片描述

在这里插入图片描述

8)根结点颜色总是黑色

在结点Node对象中color属性表示的是父结点指向当前结点的连接的颜色,由于 根结点不存在父结点,所以每次插入操作后,都需要把根结点的颜色设置为黑色

9)向树底部3-结点插入新键

假设在树的底部的一个3-结点下加入一个新的结点

前面的3种情况都可能会出现

  • 右链接:只需要转换颜色即可

  • 左链接:需要进行右旋,然后再转换颜色

  • 中链接:需要先左旋,然后再右旋,最后转换颜色

颜色转换会使中间结点的颜色变红,相当于将它送入了父结点。这意味着父结点中继续插入一个新键,只需要使用相同的方法解决即可,直到遇到一个2-结点或者根结点为止

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

10)API

在这里插入图片描述

11)实现

package chapter10;

/**

  • @author 土味儿

  • Date 2021/9/10

  • @version 1.0

  • 红黑树

*/

public class RedBlackTree<K extends Comparable, V> {

/**

  • 根结点

*/

private Node root;

/**

  • 元素数量

*/

private int n;

/**

  • 红色链接

*/

private static final boolean RED = true;

/**

  • 黑色链接

*/

private static final boolean BLACK = false;

/**

  • 构造器

*/

public RedBlackTree() {

//this.root = new Node(null, null, null, null, BLACK);

this.n = 0;

}

/**

  • 判断结点 x 的父指向链接是否为红色

  • @param x

  • @return

*/

private boolean isRed(Node x) {

if (x != null) {

return x.color == RED;

}

return false;

}

/**

  • 对 h 结点左旋


  •  H                       X
    
  • / \ // \

  • a X 左旋后> H c

  •  /  \               /  \
    
  • b    c             a    b
    

  • @param h

  • @return

*/

private Node rotateLeft(Node h) {

// 参数有效性检测

if (h == null || h.right == null) {

return null;

}

// 当前结点为 h,它的右子结点为 x

Node x = h.right;

// 让 x 的左子结点变为 h 的右子结点:h.right = x.left

h.right = x.left;

// 让 h 成为 x 的左子结点:x.left = h

x.left = h;

// 把 h 的color赋给 x 的color值:x.color = h.color

x.color = h.color;

// 把 h 的color变为 RED

h.color = RED;

return x;

}

/**

  • 对 h 结点右旋

  • 右旋后仍然有结点与两条红链接相连,需要颜色反转


  •     H                  X
    
  •   // \               // \\
    
  •  X    c    右旋后>   a     H
    
  • // \ / \

  • a b b c


  • @param h

  • @return

*/

private Node rotateRight(Node h) {

// 参数有效性检测

if (h == null || h.left == null) {

return null;

}

// 当前结点为 h ,它的左子结点为 x

Node x = h.left;

// 让 x 的右子结点变为 h 的左子结点

h.left = x.right;

// 让 h 成为 x 的右子结点

x.right = h;

// 把 h 的color赋给 x 的color:x.color = h.color

x.color = h.color;

// 把 h 的color变为:RED

h.color = RED;

return x;

}

/**

  • 对 h 结点颜色反转

  • 相当于拆分4-结点


  •    |               ||
    
  •    H      ===>     H
    
  •  // \\            / \
    
  • a    b           a   b
    

  • @param h

*/

private void flipColors(Node h) {

if (h == null) {

return;

}

// 让 h 的左右子结点颜色变为黑色

h.left.color = BLACK;

h.right.color = BLACK;

// 让 h 的颜色变为红色

h.color = RED;

}

/**

  • 插入/修改元素

  • @param key

  • @param value

*/

public void put(K key, V value) {

root = put(root, key, value);

// 根结点总是黑色

root.color = BLACK;

}

/**

  • 在 h 上插入元素,并返回新树

  • @param h

  • @param key

  • @param value

  • @return

*/

private Node put(Node h, K key, V value) {

// 如果 h 为 null

if (h == null) {

// 数量加1

n++;

// 新建结点并返回

return new Node(key, value, null, null, RED);

}

// 比较 key 与 h 结点的键的大小

int cmp = key.compareTo(h.key);

if (cmp < 0) {

// 小于:向左子结点添加(递归)

h.left = put(h.left, key, value);

} else if (cmp > 0) {

// 大于:向右子结点添加(递归)

h.right = put(h.right, key, value);

} else {

// 等于:值替换

h.value = value;

}

// 左旋:左黑右红

if (!isRed(h.left) && isRed(h.right)) {

h = rotateLeft(h);

}

// 右旋:左子左孙都红

if (h.left != null && isRed(h.left) && isRed(h.left.left)) {

h = rotateRight(h);

}

// 颜色反转:左右都红

if (isRed(h.left) && isRed(h.right)) {

flipColors(h);

}

// 返回新树h

return h;

}

/**

  • 得到 key 的值

  • @param key

  • @return

*/

public V get(K key) {

return get(root, key);

}

/**

  • 在 h 中得到 key 的值

  • @param h

  • @param key

  • @return

*/

private V get(Node h, K key) {

if (h == null) {

return null;

}

// 比较 key 与 h 键的大小

int cmp = key.compareTo(h.key);

if (cmp < 0) {

// 小于:递归查找左子树

return get(h.left, key);

} else if (cmp > 0) {

// 大于:递归查找右子树

return get(h.right, key);

} else {

// 等于:返回 value

return h.value;

}

}

/**

  • 元素数量

  • @return

*/

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了

image

1、JAVA面试核心知识整理(PDF):包含JVMJAVA集合JAVA多线程并发,JAVA基础,Spring原理微服务,Netty与RPC,网络,日志,ZookeeperKafkaRabbitMQ,Hbase,MongoDB,Cassandra,设计模式负载均衡数据库一致性哈希JAVA算法数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。

image

2、Redis学习笔记及学习思维脑图

image

3、数据面试必备20题+数据库性能优化的21个最佳实践

image
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-q3h845Vx-1713549937888)]

[外链图片转存中…(img-4YrAPAht-1713549937891)]

[外链图片转存中…(img-c0chr6bh-1713549937892)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了

[外链图片转存中…(img-MWBoCcW9-1713549937893)]

1、JAVA面试核心知识整理(PDF):包含JVMJAVA集合JAVA多线程并发,JAVA基础,Spring原理微服务,Netty与RPC,网络,日志,ZookeeperKafkaRabbitMQ,Hbase,MongoDB,Cassandra,设计模式负载均衡数据库一致性哈希JAVA算法数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。

[外链图片转存中…(img-Lvg1eDay-1713549937894)]

2、Redis学习笔记及学习思维脑图

[外链图片转存中…(img-0VIoYBfA-1713549937895)]

3、数据面试必备20题+数据库性能优化的21个最佳实践

[外链图片转存中…(img-1Izrd08E-1713549937901)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值