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) 二叉树也可以用数组存储,可以和完 ...
随机推荐
- Python随机数函数
Python随机数函数: ''' choice(seq) 从序列的元素中随机选出一个元素 randrange ([start,] stop [,step]) 从指定范围内,在指定步长递增的集合中 获取 ...
- 什么是 PHP 过滤器?
PHP 过滤器 PHP 过滤器用于验证和过滤来自非安全来源的数据,比如用户的输入. 什么是 PHP 过滤器? PHP 过滤器用于验证和过滤来自非安全来源的数据. 测试.验证和过滤用户输入或自定义数据是 ...
- PHP isset() 函数
isset() 函数用于检测变量是否已设置并且非 NULL.高佣联盟 www.cgewang.com 如果已经使用 unset() 释放了一个变量之后,再通过 isset() 判断将返回 FALSE. ...
- PDOStatement::bindColumn
PDOStatement::bindColumn — 绑定一列到一个 PHP 变量(PHP 5 >= 5.1.0, PECL pdo >= 0.1.0) 说明 语法 bool PDOSta ...
- 6.3 省选模拟赛 Decompose 动态dp 树链剖分 set
LINK:Decompose 看起来很难 实际上也很难 考验选手的dp 树链剖分 矩阵乘法的能力. 容易列出dp方程 暴力dp 期望得分28. 对于链的情况 容易发现dp方程可以转矩阵乘法 然后利用线 ...
- windows:shellcode 代码远程APC注入和加载
https://www.cnblogs.com/theseventhson/p/13197776.html 上一章介绍了通用的shellcode加载器,这个加载器自己调用virtualAlloc分配 ...
- [转]Post和Get的区别
作者:zhanglinblog 来源:https://urlify.cn/FnYBbu 这个问题几乎面试的时候都会问到,是一个老生常谈的话题,然而随着不断的学习,对于以前的认识有很多误区,所以 ...
- “随手记”开发记录day19
将软件推荐给父母,先尝试使用软件,观察bug,若有啥不足的,才能及时修改.
- 标星7000+,这个 Python 艺术二维码生成器厉害了!
微信二维码,相信大家也并不陌生,为了生成美观的二维码,许多用户都会利用一些二维码生成工具. 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手 ...
- 【Python学习笔记】字符串拼接方法(5种)总结
字符串的 5 种拼接方法: “+”号 “,”号 直接连接 格式化 多行字符串拼接 第一种:“+”号 print("Hello"+"Python") 打印结果: ...