Java数据结构——红黑树
红黑树介绍
红黑树(Red-Black Tree),它一种特殊的二叉查找树。执行查找、插入、删除等操作的时间复杂度为O(logn)。
红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。
红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。
红黑树的特性:
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点是黑色。 (注意:这里叶子节点,是指为空的叶子节点)
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
关于红黑树,需要注意的是:
- 特性(3)中的叶子节点,是只为空(NIL或null)的节点。
- 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。
- 红黑树是保持“黑平衡”的二叉树,严格意义上来说,它并不是真正的平衡二叉树,因为它可以不满足平衡的定义,即左右子树高度差不大于1,
- 红黑树的最大高度是2logn,它的复杂度是O(logn),与AVL树的复杂度一样。

红黑树的基本操作
红黑树的基本操作是添加、删除和旋转。在对红黑树进行添加或删除后,会用到旋转方法。因为添加或删除红黑树中的节点之后,红黑树会发生变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。
旋转包括两种:左旋 和 右旋。下面分别图解对红黑树的基本操作进行介绍。
左旋、右旋操作

对x进行左旋,意味着"将x变成一个左节点"。
对y进行右旋,意味着"将y变成一个右节点"。

// 左旋转过程
// node x
// / \ 左旋转 / \
// T1 x -----> node T3
// / \ / \
// T2 T3 T1 T2
private TreeNode leftRotate(TreeNode node) {
TreeNode x = node.rightChild;
// 左旋转
node.rightChild = x.leftChild;
x.leftChild = node;
x.color = node.color;
node.color = RED; return x;
} // 右旋转过程
// node x
// / \ 右旋转 / \
// x T1 -----> y node
// / \ / \
// y T2 T1 T2
private TreeNode rightRotate(TreeNode node) {
TreeNode x = node.leftChild;
// 右旋转
node.leftChild = x.rightChild;
x.rightChild = node;
x.color = node.color;
node.color = RED; return x;
}
颜色翻转操作


// 颜色翻转
private void flipColors(TreeNode node) {
node.color = RED;
node.leftChild.color = BLACK;
node.rightChild.color = BLACK;
}
添加操作
将一个节点插入到红黑树中的步骤:
- 将红黑树当作一颗二叉查找树,将节点插入。
- 将插入的节点着色为"红色"。
- 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。

// 向红黑树中添加节点
public void add(int data) {
root = add(root, data);
root.color = BLACK;
} // 添加节点后返回红黑树的根节点
public TreeNode add(TreeNode node, int data) {
if (node == null) {
TreeNode treeNode = new TreeNode(data);
return treeNode;
} else {
if (data < node.data) {
node.leftChild = add(node.leftChild, data);
} else if (data > node.data) {
node.rightChild = add(node.rightChild, data);
} else {
node.data = data;
}
// node
// \
// 红
if (isRED(node.rightChild) && !isRED(node.leftChild)) {
node = leftRotate(node);
}
// node
// /
// 红
// /
// 红
if (isRED(node.leftChild) && isRED(node.leftChild.leftChild)) {
node = rightRotate(node);
}
// node
// / \
// 红 红
if (isRED(node.leftChild) && isRED(node.rightChild)) {
flipColors(node);
}
return node;
}
}
查找操作

// 查找节点
public TreeNode search(int data) {
TreeNode current = root;
while (current.data != data) {
if (data < current.data) {
current = current.leftChild;
} else {
current = current.rightChild;
}
if (current == null) {
return null;
}
}
return current;
}
删除操作
将红黑树内的某一个节点删除的步骤
- 将红黑树当作一颗二叉查找树,将节点删除。
- 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
- 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
- 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。 在被删除节点有两个非空子节点的情况下,它的后继节点不可能是双子非空,意味着该节点的后继节点要么没有儿子,要么只有一个儿子。若没有儿子,则按情况①进行处理;若只有一个儿子,则按情况②进行处理。
- 通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。

/*
* 删除结点(node),并返回被删除的结点
*/
private void remove(TreeNode node) {
TreeNode child, parent;
boolean color;
// 被删除节点的左右孩子都不为空时
if ( (node.leftChild!=null) && (node.rightChild!=null) ) {
// 被删节点的后继节点取代"被删节点"的位置,然后再将被删节点去掉。
TreeNode replace = node;
// 获取后继节点
replace = replace.rightChild;
while (replace.leftChild != null)
replace = replace.leftChild;
// node不是根节点
if (parentOf(node)!=null) {
if (parentOf(node).leftChild == node)
parentOf(node).leftChild = replace;
else
parentOf(node).rightChild = replace;
} else {
// node是根节点,更新根节点。
this.root = replace;
}
// child是取代节点的右孩子,也是需要调整的节点。
// 取代节点肯定不存在左孩子,因为它是一个后继节点。
child = replace.rightChild;
parent = parentOf(replace);
// 保存取代节点的颜色
color = colorOf(replace);
// 被删除节点是它的后继节点的父节点
if (parent == node) {
parent = replace;
} else {
// child不为空
if (child!=null)
setParent(child, parent);
parent.leftChild = child; replace.rightChild = node.rightChild;
setParent(node.rightChild, replace);
}
replace.parent = node.parent;
replace.color = node.color;
replace.leftChild = node.leftChild;
node.leftChild.parent = replace;
if (color == BLACK)
removeFixUp(child, parent);
node = null;
return ;
}
if (node.leftChild !=null) {
child = node.leftChild;
} else {
child = node.rightChild;
}
parent = node.parent;
// 保存取代节点的颜色
color = node.color;
if (child!=null)
child.parent = parent;
// node不是根节点
if (parent!=null) {
if (parent.leftChild == node)
parent.leftChild = child;
else
parent.rightChild = child;
} else {
this.root = child;
}
if (color == BLACK)
removeFixUp(child, parent);
node = null;
} public void remove(int data) {
TreeNode node;
if ((node = search(data)) != null)
remove(node);
} /*
* 红黑树删除修正函数
* 在从红黑树中删除插入节点之后(红黑树失去平衡),再调用该函数;
* 目的是将它重新塑造成一颗红黑树。
*/
private void removeFixUp(TreeNode node, TreeNode parent) {
TreeNode other;
while ((node==null || isBlack(node)) && (node != this.root)) {
if (parent.leftChild == node) {
other = parent.rightChild;
if (isRed(other)) {
//x的兄弟是红色的
setBlack(other);
setRed(parent);
leftRotate(parent);
other = parent.rightChild;
} if ((other.leftChild==null || isBlack(other.leftChild)) &&
(other.rightChild==null || isBlack(other.rightChild))) {
//x的兄弟是黑色,且兄弟的两个孩子也是黑色的
setRed(other);
node = parent;
parent = parentOf(node);
} else {
if (other.rightChild==null || isBlack(other.rightChild)) {
//x的兄弟是黑色的,并且兄弟的左孩子是红色,右孩子为黑色。
setBlack(other.leftChild);
setRed(other);
rightRotate(other);
other = parent.rightChild;
}
//x的兄弟是黑色的;并且兄弟的右孩子是红色的,左孩子任意颜色。
setColor(other, colorOf(parent));
setBlack(parent);
setBlack(other.rightChild);
leftRotate(parent);
node = this.root;
break;
}
} else {
other = parent.leftChild;
if (isRed(other)) {
//x的兄弟是红色的
setBlack(other);
setRed(parent);
rightRotate(parent);
other = parent.leftChild;
}
if ((other.leftChild==null || isBlack(other.leftChild)) &&
(other.rightChild==null || isBlack(other.rightChild))) {
//x的兄弟是黑色,且兄弟的两个孩子也都是黑色的
setRed(other);
node = parent;
parent = parentOf(node);
} else {
if (other.leftChild==null || isBlack(other.leftChild)) {
// x的兄弟是黑色的,并且兄弟的左孩子是红色,右孩子为黑色。
setBlack(other.rightChild);
setRed(other);
leftRotate(other);
other = parent.leftChild;
}
// x的兄弟是黑色的;并且兄弟的右孩子是红色的,左孩子任意颜色。
setColor(other, colorOf(parent));
setBlack(parent);
setBlack(other.leftChild);
rightRotate(parent);
node = this.root;
break;
}
}
}
if (node!=null)
setBlack(node);
}
红黑树性能总结
1.对于完全随机的数据,普通的二叉搜索树效率较高,但极端下会退化成链表;
2.对于查询较多的情况,AVL树效率较高;
3.红黑树牺牲了平衡性,综合增删改查的所有操作,红黑树的统计性能较优。
Java数据结构——红黑树的更多相关文章
- java数据结构——红黑树(R-B Tree)
红黑树相比平衡二叉树(AVL)是一种弱平衡树,且具有以下特性: 1.每个节点非红即黑; 2.根节点是黑的; 3.每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的; 4.如图所示,如果一个 ...
- Java实现红黑树
转自:http://www.cnblogs.com/skywang12345/p/3624343.html 红黑树的介绍 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉 ...
- 高级数据结构---红黑树及其插入左旋右旋代码java实现
前面我们说到的二叉查找树,可以看到根结点是初始化之后就是固定了的,后续插入的数如果都比它大,或者都比它小,那么这个时候它就退化成了链表了,查询的时间复杂度就变成了O(n),而不是理想中O(logn), ...
- Java 集合 | 红黑树 | 前置知识
一.前言 0tnv1e.png 为啥要学红黑树吖? 因为笔者最近在赶项目的时候,不忘抽出时间来复习 Java 基础知识,现在准备看集合的源码啦啦.听闻,HashMap 在 jdk 1.8 的时候,底层 ...
- Java实现红黑树(平衡二叉树)
前言 在实现红黑树之前,我们先来了解一下符号表. 符号表的描述借鉴了Algorithms第四版,详情在:https://algs4.cs.princeton.edu/home/ 符号表有时候被称为字典 ...
- 基于Java实现红黑树的基本操作
首先,在阅读文章之前,我希望读者对二叉树有一定的了解,因为红黑树的本质就是一颗二叉树.所以本篇博客中不在将二叉树的增删查的基本操作了,需要了解的同学可以到我之前写的一篇关于二叉树基本操作的博客:htt ...
- 第三十三篇 玩转数据结构——红黑树(Read Black Tree)
1.. 图解2-3树维持绝对平衡的原理: 2.. 红黑树与2-3树是等价的 3.. 红黑树的特点 简要概括如下: 所有节点非黑即红:根节点为黑:NULL节点为黑:红节点孩子为黑:黑平衡 4.. 实现红 ...
- 用Java实现红黑树
红黑树是众多"平衡的"搜索树模式中的一种,在最坏情况下,它相关操作的时间复杂度为O(log n). 1.红黑树的属性 红黑树是一种二分查找树,与普通的二分查找树不同的一点是,红黑树 ...
- Java数据结构和算法(二)顺序存储的树结构
Java数据结构和算法(二)顺序存储的树结构 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 二叉树也可以用数组存储,可以和完 ...
随机推荐
- 第三章 Java面向对象(下)
3.1.抽象类 概述:在做子类共性功能抽取时,有些方法在父类中并没有具体的体现,这个时候就需要抽象类了 格式:public abstract class 类名 {} 语法特点: 抽象类和抽象方法必须使 ...
- 微服务迁移记(五):WEB层搭建(4)-简单的权限管理
一.redis搭建 二.WEB层主要依赖包 三.FeignClient通用接口 以上三项,参考<微服务迁移记(五):WEB层搭建(1)> 四.SpringSecurity集成 参考:< ...
- try{}catch的隐藏(如何优雅的实现异常块)
在项目中,我们会遇到异常处理,对于运行时异常,需要我们自己判断处理.对于受检异常,需要我们主动处理. 但是繁琐的try{}caht嵌套在代码里,看着很不舒服,这里我们不讨论性能,就代码来讲,来看看如何 ...
- Python os.isatty() 方法
概述 os.isatty() 方法用于判断如果文件描述符fd是打开的,同时与tty(-like)设备相连,则返回true, 否则False.高佣联盟 www.cgewang.com 语法 isatty ...
- MapReduce之GroupingComparator分组(辅助排序、二次排序)
指对Reduce阶段的数据根据某一个或几个字段进行分组. 案例 需求 有如下订单数据 现在需要找出每一个订单中最贵的商品,如图 需求分析 利用"订单id和成交金额"作为key,可以 ...
- 【NOI2010】超级钢琴 题解(贪心+堆+ST表)
题目链接 题目大意:求序列内长度在$[L,R]$范围内前$k$大子序列之和. ---------------------- 考略每个左端点$i$,合法的区间右端点在$[i+L,i+R]$内. 不妨暴力 ...
- 薪资高,福利好,会Python的人就是这么豪横!
很多人可能会有这样的疑问,数据分析Excel挺强大的,会Excel就行,为什么还要去学python? 是的,Excel和python对于数据分析而言,这两者都只是不同的工具而已. 很多人学习pytho ...
- XCTF-WEB-高手进阶区(1-4)笔记
1:baby_web 题目描述:想想初始页面是哪个 通过Dirsearch软件扫描发现Index.php被藏起来了,访问他便会指向1.php 于是通过Burp修改Get为index.php,然后放入R ...
- 使用Python语言通过PyQt5和socket实现UDP服务器
前言 最近做了一个小软件,记录一下相关内容. 已有条件 现在已有一个硬件设备作为客户端(暂称其为"电路"). 基于SIM卡,电路可以通过UDP协议传输数据(程序已经内置在电路中), ...
- 2020-08-02:输入ping IP 后敲回车,发包前会发生什么?
福哥答案2020-08-02: 首先根据目的IP和路由表决定走哪个网卡,再根据网卡的子网掩码地址判断目的IP是否在子网内.如果不在则会通过arp缓存查询IP的网卡地址,不存在的话会通过广播询问目的IP ...