红黑树插入操作的初步理解

文章目录

  • 红黑树插入操作的初步理解
    • 红黑树的特征
    • 红黑树的插入节点总是红色的
    • 红黑树的修正
      • 变色
      • 左旋
      • 右旋
    • 插入操作
    • 插入操作的代码实现
    • 红黑树和AVL树的对比
    • 参考链接

红黑树的特征

  1. 每个节点不是红色就是黑色的
  2. 根节点总是黑色的
  3. 如果节点是红色的,则它的子节点必须是黑色的
  4. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

红黑树的插入节点总是红色的

因为红黑树的特性,如果我们每次插入的节点是黑色的,那么就必然会违背规则4,因为插入一个黑色节点肯定会导致不平衡。

所以新插入的节点会选择红色,这样每次一定会满足条件4,而且只有1/2的几率会违背规则3(只有当新插入节点的父节点是红色的时候才会违背规则3)

红黑树的修正

对于不平衡的红黑树,我们需要进行修正。

修正方法主要有3种:

变色

变色就很好理解,因为违背了规则3,所以需要将节点颜色变换,以达到某种要求

左旋

比如说拿5举例进行左旋操作,会将8代替5的位置,并且会将8的左子树接到5的右子树上(如果存在的话)

右旋

右旋也是同样的道理,对8进行右旋操作,看图理解一下就行

插入操作

具体的来说,对于一个插入操作,如果红黑树不平衡,那么就会是下面6种情况之一

在下面的表述中,

node称为当前插入的节点

parent称为node的父节点

gparent称为parent的父节点

uncle称为parent的兄弟节点,也就是gparent的另外一个子节点

说明一点,如果parent是黑色的,那么必然是不用调整的,也就是说,调整的前提是父节点是红色的

  • parent是gparent的左子节点

    • uncle节点是红色的
    • node节点是parent的右子节点
    • node节点是parent的左子节点
  • parent是gparent的右子节点

    • uncle节点是红色的
    • node节点是parent的左子节点
    • node节点是parent的右子节点

可以发现,这6种情况其实可以分为2组,这里只讲述上面三种,下面三种和上面是很类似的,就不再叙述

  1. 建立一颗初始化的树

2. 准备插入节点4

  1. 发现不平衡,且当前的情况是parent是gparent的左子节点,uncle节点是红色的,也就是上面所列举的情况一。这时就会执行下述操作

将4号节点的parent节点(5)和uncle节点(8)染黑,将gparent(7)染红,并将当前节点(4)跳到gparent(7)

那么,情况就会变成这样,可以发现这就是情况2。

注意,node节点现在已经是7了,不再是4。


4. 对于情况2,会进行如下的调整

将当前node节点的值变为parent的值,也就是node变为(2),并对新的当前节点node(2)进行左旋操作.

​ 树的情况就会变成下图,注意,此时node为2,会发现,这就是情况3


5. 对于情况三,执行如下步骤

将parent节点(7)染黑,gparent节点(11)染红,最后对gparent(11)执行右旋操作即可


6. 经过以上三步,红黑树又重新平衡了。

总的来说,上述的情况一,二,三是连续的,也就是至多3步,就能使红黑树平衡

插入操作的代码实现

/*** @author yiqzq* @date 2020/3/7 11:06* 定义节点*/
public class RBNode<T extends Comparable<T>> {boolean red = true;boolean black = false;T value;RBNode<T> parent;RBNode<T> left;RBNode<T> right;public T getValue() {return value;}public RBNode(T value, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {this.value = value;this.parent = parent;this.left = left;this.right = right;}@Overridepublic String toString() {return "RBNode{" +"red=" + red +", black=" + black +", value=" + value +", parent=" + parent +", left=" + left +", right=" + right +'}';}public void setBlack() {this.black = true;this.red = false;}public void setRed() {this.black = false;this.red = true;}public boolean isRed() {return red;}public boolean isBlack() {return black;}
}
/*** @author yiqzq* @date 2020/3/7 11:22* @parm insert:新增节点* 红黑树的实现*/
public class RBTree<T extends Comparable<T>> {private RBNode<T> root;public void insert(T value) {RBNode<T> node = new RBNode<T>(value, null, null, null);RBNode<T> cur, x;cur = null;x = this.root;while (x != null) {cur = x;int cmp = node.value.compareTo(cur.value);//node 小if (cmp < 0) {x = cur.left;} else {x = cur.right;}}node.parent = cur;if (cur == null) {root = node;} else {int cmp = node.value.compareTo(cur.value);if (cmp < 0) {cur.left = node;} else {cur.right = node;}}insertFixUp(node);}private void insertFixUp(RBNode<T> node) {RBNode<T> parent, gparent; //定义父节点和祖父节点//父节点存在且父节点为红才需要调整while ((parent = node.parent) != null && parent.isRed()) {gparent = parent.parent;//总共可分为6种情况,父节点是祖父节点的左儿子if (parent == gparent.left) {RBNode<T> uncle = gparent.right;//叔叔节点是红的if (uncle != null && uncle.isRed()) {uncle.setBlack();parent.setBlack();gparent.setRed();node = gparent;continue;}//如果存在这种情况就先左旋if (node == parent.right) {leftRotate(parent);//交换当前节点RBNode<T> tmp = parent;parent = node;node = tmp;}//最后右旋调整红黑树结构parent.setBlack();gparent.setRed();rightRotate(gparent);}//还有相反的三种情况else {RBNode<T> uncle = gparent.left;//叔叔节点为红if (uncle != null && uncle.isRed()) {uncle.setBlack();parent.setBlack();gparent.setRed();node = gparent;continue;}if (node == parent.left) {rightRotate(parent);RBNode<T> tmp = parent;parent = node;node = tmp;}parent.setBlack();gparent.setRed();leftRotate(gparent);}}root.setBlack();}/** 左旋示意图:对节点x进行左旋*     p                       p*    /                       /*   x                       y*  / \                     / \* lx  y      ----->       x  ry*    / \                 / \*   ly ry               lx ly* 左旋做了三件事:* 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)* 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)* 3. 将y的左子节点设为x,将x的父节点设为y*/private void leftRotate(RBNode<T> x) {RBNode<T> y, gparent;y = x.right;//第一步x.right = y.left;if (y.left != null) {y.left.parent = x;}//第二步y.parent = x.parent;if (x.parent == null) {this.root = y;} else {if (x == x.parent.left) {x.parent.left = y;} else {x.parent.right = y;}}//第三步y.left = x;x.parent = y;}/** 右旋示意图:对节点y进行右旋*        p                   p*       /                   /*      y                   x*     / \                 / \*    x  ry   ----->      lx  y*   / \                     / \* lx  rx                   rx ry* 右旋做了三件事:* 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时)* 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)* 3. 将x的右子节点设为y,将y的父节点设为x*/private void rightRotate(RBNode<T> y) {//1. 第一步RBNode<T> x = y.left;y.left = x.right;if (x.right != null) {x.right.parent = y;}//2.第二步x.parent = y.parent;if (y.parent == null) {this.root = x;} else {if (y == y.parent.right) {y.parent.right = x;} else {y.parent.left = x;}}//3. 第三步x.right = y;y.parent = x;}/** 前序遍历"红黑树"*/private void preOrder(RBNode<T> tree) {if (tree != null) {System.out.print(tree.value + " ");preOrder(tree.left);preOrder(tree.right);}}public void preOrder() {preOrder(root);}/** 中序遍历"红黑树"*/private void inOrder(RBNode<T> tree) {if (tree != null) {inOrder(tree.left);System.out.print(tree.value + " ");inOrder(tree.right);}}public void inOrder() {inOrder(root);}/** 后序遍历"红黑树"*/private void postOrder(RBNode<T> tree) {if (tree != null) {postOrder(tree.left);postOrder(tree.right);System.out.print(tree.value + " ");}}public void postOrder() {postOrder(root);}private void print(RBNode<T> tree, T key, int direction) {if (tree != null) {// tree是根节点if (direction == 0) {System.out.printf("%2d(B) is root\n", tree.value);}// tree是分支节点else {System.out.printf("%2d(%s) is %2d's %6s child\n", tree.value, tree.red ? "R" : "B", key, direction == 1 ? "right" : "left");}print(tree.left, tree.value, -1);print(tree.right, tree.value, 1);}}public void print() {if (root != null) {print(root, root.value, 0);}}}
//测试
public class Main {public static void main(String[] args) {RBTree<Integer> rbTree = new RBTree<>();rbTree.insert(10);rbTree.insert(40);rbTree.insert(30);rbTree.insert(60);rbTree.insert(90);rbTree.insert(70);rbTree.insert(20);rbTree.insert(50);rbTree.insert(80);System.out.printf("== 前序遍历: ");rbTree.preOrder();System.out.printf("\n== 中序遍历: ");rbTree.inOrder();System.out.printf("\n== 后序遍历: ");rbTree.postOrder();System.out.printf("\n");rbTree.print();}}

红黑树和AVL树的对比

AVL是是严格的平衡树,节点高度之差最多为1,而红黑树是非严格的平衡树。

所以AVL的查询性能会优于红黑树,但是红黑树的插入删除操作会快于AVL。

因为红黑树的插入和删除的旋转操作至多进行3次,所以在插入删除的性能上,红黑树优于AVL树。

参考链接

部分内容参考自博客

一个模拟红黑树插入删除的网站

红黑树插入操作的初步理解相关推荐

  1. 底层实现红黑树_stl map底层之红黑树插入步骤详解与代码实现 | 学步园

    本篇文章并没有详细的讲解红黑树各方面的知识,只是以图形的方式对红黑树插入节点需要进行调整的过程进行的解释. 最近在看stl源码剖析,看到map底层红黑树的实现.为了加深对于红黑树的理解就自己动手写了红 ...

  2. 【算法】红黑树插入数据(变色,左旋、右旋)(二)

    本人菜鸡一只,正在更新红黑树系列的文章. 该系列已经全部更完,有5篇文章: [算法]红黑树(二叉树)概念与查询(一):https://blog.csdn.net/lsr40/article/detai ...

  3. 红黑树插入/删除的几种情况,图论,拓扑学杂烩

    红黑树的操作复杂度为,也就是说,如果有10亿的序列查找也不会超过30次,效率非常高. 红黑树的结构具有分形结构,局部树和整个树遵守相同的红,黑设置规则. 0.父节点为黑色,不需要任何操作 1.新插入的 ...

  4. 红黑树----红黑树插入和删除结点的全程演示

    引言: 目前国内图书市场上,抑或网上讲解红黑树的资料层次不齐,混乱不清,没有一个完整而统一的阐述.而本人的红黑树系列四篇文章(详见文末的参考文献),虽然从头至尾,讲的有根有据,层次清晰,然距离读者真正 ...

  5. 红黑树的原理_红黑树插入算法实现原理分析

    ­ 引言 红黑树是在实际工程中被广泛应用的一种数据结构,比如Linux中的线程调度就是使用的红黑树来管理进程控制块,而Nginx中也是使用红黑树来管理的timer,Java中的TreeMap和Tree ...

  6. 红黑树插入时的自平衡

    红黑树插入时的自平衡 红黑树实质上是一棵自平衡的二叉查找树,引入带颜色的节点也是为了方便在进行插入或删除操作时,如果破坏了二叉查找树的平衡性能通过一系列变换保持平衡. 红黑树的性质 每个节点要么是红色 ...

  7. 数据结构-红黑树插入结点示例

    数据结构-红黑树插入结点示例 1.红黑树简介 2.在线可视化生成红黑树工具 3.红黑树插入结点性质和规则 3.1.红黑树插入结点性质 3.2.红黑树插入结点规则 4.红黑树插入结点示例 4.1.红黑树 ...

  8. Java之HashMap经典算法-红黑树(插入节点平衡调整,左旋转,右旋转)

    1. 红黑树的优势 有了二叉搜索树,为什么还需要平衡二叉树? 二叉搜索树容易退化成一条链,这时,查找的时间复杂度从 O(log2N)O(log_2N)O(log2​N) 将退化成 O(N)O(N )O ...

  9. C语言 红黑树插入/删除/查找/遍历

    1 红黑树介绍 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树. 红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子 ...

最新文章

  1. mfc获取鼠标在其他窗口中坐标_C井编程,稍加修改,将之前“会跑的按钮”改成“会跑的窗口”...
  2. linux 新用户 界面登录,如何在Linux系统登录界面加入个性化提示信息
  3. php中提示注意怎么解决,PHP中操作MySQL时一定要注意
  4. ubuntu下svn使用指南
  5. Angular应用里的@Input和@Output注解使用方法介绍
  6. activiti脚本任务_Activiti中的高级脚本:自定义配置注入
  7. Java 找到并返回一组字符串中第一个不为空的字符串
  8. linux下程序JDBC连接不到mysql数据库
  9. 云痕大数据 家长登录_智学网家长学生查分入口:www.zhixue.com
  10. 大学生就业新神器 网络电话“通”职场
  11. QT实现简单的抽奖界面
  12. javaweb网上鞋店
  13. 基于搜狗平台的微信文章爬虫
  14. Simhash算法介绍和应用内容
  15. 云计算是一种商业模式
  16. Python pgm解析和格式转换
  17. 钉钉自定义机器人提示报警信息
  18. 初学用于华为鸿蒙系统(HarmonyOS)的编程开发工具HUAWEI DevEco Studio:你好,鴻蒙~
  19. (Python)从零开始,简单快速学机器仿人视觉Opencv---运用三:物体运动跟踪
  20. python3 高效实现 最大质因数/质因数集合 方法

热门文章

  1. spark学习-BlockManager原理
  2. 详解梯度下降的原理及应用【学不会来打我啊】
  3. 英文文献(期刊/书籍)搜索及下载方法汇总
  4. 1110. 删点成林
  5. 1.3 数据库之单行函数
  6. cocos2d-x 3 0 制作横版格斗游戏
  7. Spring4.x❶ 两大核心之IOC
  8. 2013年工作项目流水总结
  9. Android Camera2 实现高帧率预览录制(附源码)
  10. python 解析域名_三、域名解析模块(dnspython)