数据结构分析:红黑树、B+树

前言

常见的数据结构大概分为以下8种,作为一个开发人员,数据结构是内功之一。 本文参考了网络上相关知识,加之自己的理解。简单说明红黑树、B+树的特性。

1. 二叉搜索树(Binary Search Tree,简称BST)

介绍红黑树之前先介绍下二叉搜索树的特点:

  • 左子树不为空,则左子树上结点值小于根结点

  • 右子树不为空,则右子树上结点值大于根结点

  • 子树同样也要遵循以上两点

极端情况会退化成链表;时间复杂度就是树的深度O(n)。

所有就有了平衡的二叉查找树。

AVL树:平衡二叉树,它的左右子树高度之差不超过1,并且左右两个子树都是一棵平衡二叉树。时间复杂度O(logn)。

2. 红黑树(Red-Black Tree,以下简称RBTree)

红黑树是一种自平衡的二叉查找树(二叉搜索树、二叉排序树),高效的查找算法数据结构,特殊的二叉树。

性质:

  1. 每个结点不是红色就是黑色,

  2. 根结点是黑色,

  3. 每个叶子结点(NIL)都是黑色的空结点,

  4. 从根结点到叶子结点,不会出现两个连续的红色结点,

  5. 从任何一个结点出发到叶子结点,这条路径上都有相同数目的黑色结点。

为了满足这些性质,因此需要变颜色,旋转。 旋转和颜色变换规则:所有插入的点默认为红色,只有这样数据结构才会变化。

变颜色情况:

如果当前结点的父亲是红色,且它的祖父结点的另一个子结点(叔叔结点)也是红色,则:

  1. 把父结点设为黑色,

  2. 叔叔结点设为黑色,

  3. 把祖父结点设为红色,

  4. 指针指向祖父结点。

下面左旋、右旋规则前提:

新结点(当前插入结点)、父结点、祖父结点在同一条斜线上 适用下面规则。如不在一条斜线,hashmap红黑树源码是先做一次旋转,达到一条斜线,再左旋或右旋。

举例说明

左旋:

当前父结点是红色,叔叔是黑结点或空结点的时候,且当前的结点是右子树。左旋,则:

  1. 父结点变黑色;

  2. 祖父结点变红色;

  3. 以祖父结点旋转。

网图:左旋

右旋:

当前父结点是红色,叔叔是结点或空结点的时候,且当前的结点是左子树。右旋,则:

  1. 把父结点变为黑色,

  2. 把祖父结点变为红色,

  3. 以祖父结点旋转。

网图:右旋

下图是我用processon画的插入过程,需要原图的可私我拿走。

随机选了几个数字模拟插入过程;根结点是没有父结点的,入度为0。先插入20,若根结点为空,那么插入结点20作为根结点;再插入10,从根结点找,比20小,查找20的左子树,为空,则插入。查找插入位置同二叉搜索树一样,找到后插入,再看是否需要变颜色或者旋转,防止退化成链表。

原创:可点击放大

红黑树删除操作

  1. 删除叶子结点:

    • 红色结点直接删除 ①

    • 黑色叶子结点,做删除平衡操作:
      • 兄弟结点是红色,则兄弟结点必有两个黑色子结点。② [以兄弟结点这条线旋转,变色,替补删除的结点]

      • 兄弟结点是黑色:
        • 有子结点,子结点必为红色。③ [以兄弟结点这条线旋转,变色,完成平衡操作]

        • 没子结点。④ [兄弟结点变红,父结点变黑,指针指向父结点递归完成平衡]

  2. 删除非叶子结点:
    • 有一个子结点,子结点必为红色。⑤ [用子结点替换后做平衡操作]

    • 有两个子结点则找后继结点(大于当前结点的最小结点)。⑥ [找到后继结点,作为替换结点,此时就变成了删除叶子结点的情况]

图①

图②

图③

图④

图⑤

图⑥

JDK HashMap红黑树源码

为了简单分析,微调了部分代码,删除了部分代码:

    static final class TreeNode<K extends Comparable<K>> {K key;TreeNode<K> parent;  // red-black tree linksTreeNode<K> left;TreeNode<K> right;boolean red;TreeNode(K key) {this.key = key;}TreeNode getLeft() {return this.left;}TreeNode getRight() {return this.right;}boolean isColor() {return !this.red;}K getValue(){return key;}/*** Returns root of tree containing this node.*/final TreeNode<K> root() {for (TreeNode<K> r = this, p;;) {if ((p = r.parent) == null)return r;r = p;}}/*** 查找结点是否存在* @param key* @param p 初始传入root结点查询* @return*/final TreeNode find(TreeNode p, Object key) {if (p == null) {return null;}int k = p.key.compareTo(key);if (k > 0) {p = find(p.left, key);}else if (k < 0) {p = find(p.right, key);}return p;}/*** 插入结点* @param key* @return*/final TreeNode insert(TreeNode root, int key) {if (root == null) {return null;}TreeNode p = new TreeNode(key);while (true) {int k = root.key.compareTo(key);if (k > 0) {if (root.left == null) {p.parent = root;root = root.left = p;break;}root = root.left;}else if (k < 0) {if (root.right == null) {p.parent = root;root = root.right = p;break;}root = root.right;}else {break;}}return root;}/*** 红黑树插入平衡* @param root* @param x* @return*/static TreeNode balanceInsertion(TreeNode root, TreeNode x) {//默认插入为红色x.red = true;//xp: 当前结点父节点 xpp:当前结点爷爷结点 xppl: 当前结点左叔叔节点 xppr: 当前结点右叔叔节点for (TreeNode xp, xpp, xppl, xppr;;) {//x结点父结点为空,说明当前x结点是根结点,根节点是黑色。if ((xp = x.parent) == null) {x.red = false;return x;}//xp结点为黑色,说明插入当前结点不会破坏红黑树性质,直接返回根节点//xp结点为红色的情况,则xp的父结点xpp不可能为空,假如为空,说明xp为红色的根结点,这样不符合红黑树性质。 这里如果说的不对请指出else if (!xp.red || (xpp = xp.parent) == null) {return root;}//判断条件:当前父结点为爷爷结点的左子树,大家可以画个图,贼清晰if (xp == (xppl = xpp.left)) {//当前结点x的爷爷结点xpp 的右子树xppr不为空,并且为红色结点,则变颜色。根据我上面写的变色规则,如果当前结点父亲和叔叔结点//都是红色,则变色。父亲和叔叔结点变黑,爷爷结点变红,指针指向爷爷结点if ((xppr = xpp.right) != null && xppr.red) {xppr.red = false;xp.red = false;xpp.red = true;x = xpp;}//这里有两种情况//1.xppr结点为空,为空则xp结点两个子结点一个为空,一个为当前结点x, x可能在左子树或右子树//2.如果xppr为黑色结点,则xp结点两个子结点一个为黑色结点,一个为当前结点x, x可能在左子树或右子树else {//如果当前结点为xp父结点的右节点,则左旋 (先左旋,再右旋)//如果当前结点为xp父结点的左结点 (少一步左旋操作,直接右旋)if (x == xp.right) {//左旋root = rotateLeft(root, x = xp);//xpp = (xp = x.parent) == null ? null : xp.parent;}//两个红节点,右旋if (xp != null) {//将父结点变黑xp.red = false;if (xpp != null) {//爷爷结点变红xpp.red = true;//右旋root = rotateRight(root, xpp);}}}}else {if (xppl != null && xppl.red) {xppl.red = false;xp.red = false;xpp.red = true;x = xpp;}else {//先右旋,再左旋if (x == xp.left) {root = rotateRight(root, x = xp);xpp = (xp = x.parent) == null ? null : xp.parent;}if (xp != null) {xp.red = false;if (xpp != null) {xpp.red = true;root = rotateLeft(root, xpp);}}}}}}/*** 左旋* @param root* @param p* @return*/static TreeNode rotateLeft(TreeNode root, TreeNode p) {//当前旋转的结点为p, 定义r为当前结点的右子结点,pp为当前p结点的父结点,rl为r结点的左结点TreeNode r, pp, rl;//左旋提前是当前结点不为空,且当前结点的右子节点不为空if (p != null && (r = p.right) != null) {//r结点左子结点rl不为空,则rl的父结点指向pif ((rl = p.right = r.left) != null) {rl.parent = p;}//如果p的父结点为空,说明p是根节点,左旋后,r为根结点,则r的颜色涂黑if ((pp = r.parent = p.parent) == null) {(root = r).red = false;}//如果p的父结点不为空,且p结点为pp的左子结点,则pp的左子树指向relse if (pp.left == p) {pp.left = r;}//p结点为pp的右子结点,pp的右子树指向relse {pp.right = r;}//r的左子树指向pr.left = p;//p的父结点指向rp.parent = r;}return root;}/*** 右旋* @param root* @param p* @return*/static TreeNode rotateRight(TreeNode root, TreeNode p) {//当前旋转的结点为p, 定义l为当前结点的左子结点,pp为当前p结点的父结点,lr为l结点的右结点TreeNode l, pp, lr;//右旋提前是当前结点不为空,且当前结点的左子节点不为空if (p != null && (l = p.left) != null) {//l结点右子结点lr不为空,则lr的父结点指向pif ((lr = p.left = l.right) != null) {lr.parent = p;}//如果p的父结点为空,说明p是根节点,右旋后,l为根结点,则l的颜色涂黑if ((pp = l.parent = p.parent) == null) {(root = l).red = false;}//如果p的父结点不为空,且p结点为pp的右子结点,则pp的右子数指向lelse if (pp.right == p) {pp.right = l;}//p结点为pp的左子结点,pp的左子树指向relse {pp.left = l;}//l的右子树指向pl.right = p;//p的父结点指向lp.parent = l;}return root;}/*** 删除节点p* @param root* @param p*/final TreeNode removeTreeNode(TreeNode root, TreeNode p) {//定义当前删除结点为p, pl为p的左结点,pr为p的右结点, replacement为替代结点TreeNode<K> pl = p.left, pr = p.right, replacement;//左结点和右结点都不为空if (pl != null && pr != null) {TreeNode<K> s = pr, sl;while ((sl = s.left) != null){// find successor 找后继结点s = sl;}// swap colors 交换后继结点和p结点颜色boolean c = s.red; s.red = p.red; p.red = c;TreeNode<K> sr = s.right;TreeNode<K> pp = p.parent;if (s == pr) {//p是后继结点s的父结点,则p的父结点指向s, s右节点指向pp.parent = s;s.right = p;}else {//此时sp == pr, 改变s和p的指针TreeNode<K> sp = s.parent;if ((p.parent = sp) != null) {if (s == sp.left) {sp.left = p;} else {sp.right = p;}}if ((s.right = pr) != null) {pr.parent = s;}}//p之前关联的左结点指向空p.left = null;if ((p.right = sr) != null) {sr.parent = p;}//改变pl的父结点,从p变为sif ((s.left = pl) != null) {pl.parent = s;}//s父结点不为空,则看p是在左结点还是右节点,替换成sif ((s.parent = pp) == null) {root = s;}else if (p == pp.left) {pp.left = s;}else {pp.right = s;}//后继结点的右结点sr不为空,则sr为替代结点,否则后继结点为替代结点if (sr != null) {replacement = sr;}else {replacement = p;}}//如果左结点pl或pr不为空,则pl或pr为红色结点else if (pl != null) {replacement = pl;}else if (pr != null) {replacement = pr;}//替代结点为待删除结点p, 也就是叶子结点//叶子结点分两种情况:红色,黑色else {replacement = p;}//替代结点不等于当前结点,则分离当前结点,当前结点父结点指向替代结点if (replacement != p) {TreeNode<K> pp = replacement.parent = p.parent;if (pp == null) {root = replacement;}else if (p == pp.left) {pp.left = replacement;}else {pp.right = replacement;}p.left = p.right = p.parent = null;}//如果待删除结点为黑色结点,则进行删除平衡操作TreeNode<K> r = p.red ? root : balanceDeletion(root, replacement);// detachif (replacement == p) {//删除结点为替换结点,则分离TreeNode<K> pp = p.parent;p.parent = null;if (pp != null) {if (p == pp.left) {pp.left = null;}else if (p == pp.right) {pp.right = null;}}}return r;}/*** 红黑树删除平衡* @param root* @param x 替代结点* @return*/static TreeNode balanceDeletion(TreeNode root, TreeNode x) {//定义x为当前结点,xp为x的父结点,xpl为父结点的左子结点,xpr为父结点的右子结点for (TreeNode xp, xpl, xpr;;) {if (x == null || x == root) {return root;}else if ((xp = x.parent) == null) {x.red = false;return x;}//如果x为红色结点,涂黑else if (x.red) {x.red = false;return root;}//x为父结点的左子结点else if ((xpl = xp.left) == x) {//父结点的右结点不为空且为红色结点if ((xpr = xp.right) != null && xpr.red) {//父结点变红,父结点的右结点变黑,以父结点左旋xpr.red = false;xp.red = true;root = rotateLeft(root, xp);TreeOperation.show(root);//改变指针,上面旋转后xpr变成了xp的父结点,所以xpr的结点重新指向xpr = (xp = x.parent) == null ? null : xp.right;}if (xpr == null) {x = xp;}else {//此时xpr为x的兄弟结点,判断兄弟结点的左右子结点是否为空或为黑色结点,//如满足,则兄弟结点变红,指针指向父结点,以父结点为x结点继续做删除平衡操作TreeNode sl = xpr.left, sr = xpr.right;if ((sr == null || !sr.red) && (sl == null || !sl.red)) {xpr.red = true;x = xp;}else {if (sr == null || !sr.red) {if (sl != null) {//x兄弟结点右孩子为空或为黑色结点,且左孩子不为空,则涂黑sl.red = false;}//x兄弟结点变红,以兄弟结点右旋xpr.red = true;root = rotateRight(root, xpr);TreeOperation.show(root);//改变xp的右指针,xpr指向sl结点xpr = (xp = x.parent) == null ? null : xp.right;}//x的兄弟结点xpr修改成父结点的颜色,右子结点不为空变黑,父结点变黑,以父结点左旋if (xpr != null) {xpr.red = (xp == null) ? false : xp.red;if ((sr = xpr.right) != null) {sr.red = false;}}if (xp != null) {xp.red = false;root = rotateLeft(root, xp);TreeOperation.show(root);}x = root;}}}else {if (xpl != null && xpl.red) {xpl.red = false;xp.red = true;root = rotateRight(root, xp);TreeOperation.show(root);xpl = (xp = x.parent) == null ? null : xp.left;}if (xpl == null) {x = xp;}else {TreeNode sl = xpl.left, sr = xpl.right;if ((sl == null || !sl.red) && (sr == null || !sr.red)) {xpl.red = true;x = xp;}else {if (sl == null || !sl.red) {if (sr != null) {sr.red = false;}xpl.red = true;root = rotateLeft(root, xpl);TreeOperation.show(root);xpl = (xp = x.parent) == null ? null : xp.left;}if (xpl != null) {xpl.red = (xp == null) ? false : xp.red;if ((sl = xpl.left) != null) {sl.red = false;}}if (xp != null) {xp.red = false;root = rotateRight(root, xp);}x = root;}}}}}}

本地打印红黑树插入:

本地打印红黑树删除:

总结:

红黑树的复杂之处在于删除,删除需要变色,旋转来保持树的平衡。所以删除大概理解为:

  • 红色叶子结点直接删除

  • 黑色叶子结点则从兄弟结点借,兄弟结点有子结点,可借,通过旋转变色完成;兄弟结点没子结点,则递归父类做平衡

  • 非叶子结点则找后继结点作为替换结点,就变成了叶子结点的删除。

B+树放在下一篇分析。(文中不对之处请指出,谢谢)

数据结构分析:红黑树、B+树相关推荐

  1. 2020最新MySQL数据库面试题( MySQL引索系统+MySQL数据架构+红黑树结构图+B+树)

    视频参考一线互联网大佬一堂课教会你那些年,你未曾了解的MySQL索引优化 [建议收藏] '阿里高级架构师一个视频教会你MySql 各大知识点MySQL系列/ MySQL引索系统/MySQL数据架构/红 ...

  2. 二叉树 红黑树 B树 B+树的优缺点

    前言 在MySQL中,无论是Innodb还是MyIsam,都使用了B+树作索引结构(这里不考虑hash等其他索引).本文将从最普通的二叉查找树开始,逐步说明各种树解决的问题以及面临的新问题,从而说明M ...

  3. 讲透学烂二叉树(五):分支平衡—AVL树与红黑树伸展树自平衡

    简叙二叉树 二叉树的最大优点的就是查找效率高,在二叉排序树中查找一个结点的平均时间复杂度是O(log₂N): 在<讲透学烂二叉树(二):树与二叉/搜索/平衡等树的概念与特征>提到 二叉排序 ...

  4. 红黑树B树B+树对比分析

    红黑树 二叉查找树 学习红黑树之前先理解一下二叉查找树(BST),二叉查找树的特性: ​ 1.左子树上所有结点的值均小于或等于它的根结点的值. ​ 2.右子树上所有结点的值均大于或等于它的根结点的值. ...

  5. 04_红黑树_B树知识点_添加上溢和删除下溢

    B树(Balanced Tree) B树是一种平衡的多路搜索树,多用于文件系统.数据库的实现 眼前一亮的特点  1个节点可以存储超过两个元素,可以拥有超过两个节点  平衡,每个节点的所有子树高度一 ...

  6. 二叉查找树 平衡二叉查找树 红黑树 b树 b+树 链表 跳表 链表

    https://www.cnblogs.com/mojxtang/p/10122587.html二叉树的新增遍历查找 转载于:https://www.cnblogs.com/wangjing666/p ...

  7. B树、B+树、AVL树、红黑树

    from: http://blog.csdn.net/chlele0105/article/details/8473846 binary search tree,中文翻译为二叉搜索树.二叉查找树或者二 ...

  8. 数据结构与算法--Tree(二叉树、B±树、红黑树)

    在MySQL中,索引的的实现方式中使用的最多的就是B+Tree,那么为什么要选择B+Tree呢?我们就需要从最基本的二叉查找树说起 什么是二叉树? 二叉树 = (空树) | (节点+左右子二叉树) 解 ...

  9. MySQL数据库的红黑树优化_为什么Mysql用B+树做索引而不用B-树或红黑树

    B+树做索引而不用B-树 那么Mysql如何衡量查询效率呢?– 磁盘IO次数. 一般来说索引非常大,尤其是关系性数据库这种数据量大的索引能达到亿级别,所以为了减少内存的占用,索引也会被存储在磁盘上. ...

  10. 二叉树、平衡二叉树、红黑树、B-树、B+树、B*树、T树之间的详解和比较

    ====================================================|| 欢迎讨论技术的可以相互加微信:windgs (请备注csdn+xx职业) ======== ...

最新文章

  1. 基于三维模型的目标识别和分割在杂乱的场景中的应用
  2. python: c_char_p指向的bitmap图像数据,通过c_char_Array最终赋值给PIL的Image对象
  3. linux保存输出结果到txt
  4. 《Python3网络爬虫开发实战(第二版)》上市了!!!!
  5. oracle 查询创建的全文索引,CSS_oracle全文搜索创建与使用示例,说明:使用全文索引需要使用 - phpStudy...
  6. http-helloworld
  7. python表示矩阵
  8. SAP Analytics Cloud里的Planning model
  9. Linux系统下MySQL数据库的超级管理员root的密码忘记/忘记密码怎么办?
  10. python数据预处理——数据分箱(将值归类)
  11. Windows上使用Netbeans进行Linux C开发
  12. HDU 5348 MZL's endless loop(DFS去奇数度点+欧拉回路)
  13. ubuntu20.4 安装配置teamviewer
  14. UCDOS点阵字库提取
  15. java动态生成pdf 合并两个pdf文件功能三
  16. mysql 右连接(right join)
  17. python分组求和_如何对某一列自动分组,统计求和
  18. AWS之Glue使用方法
  19. vlookup多项匹配_Excel 怎样用VLOOKUP匹配多列数据/excle全部筛选匹配
  20. 要学计算机买macbook,我是学平面设计的,买苹果电脑需要什么样的配置以上?...

热门文章

  1. 二维列表python
  2. 应用程序迁移,电脑c盘满了怎么转移到d盘?
  3. sqlserver2012链接远程服务器,修复︰ 在 SQL Server 2012年链接服务器和远程服务器上的不同排序规则的数据更新时性能降低...
  4. RDKit|分子修改与编辑
  5. 如何启动/停止/重启MySQL + 进入MYSQL
  6. 一文吃透MySQL面试八股文
  7. 【热门】男孩取名:代表希望与志向的男孩名字
  8. 数据库系统概论整理(Part Ⅰ)
  9. teradata是MySQL吗_Teradata 数据库介绍
  10. Halcon标定板标定