红黑树
思维导图
出现的背景,初衷,要解决的问题
背景:Rudolf Bayer在1972年发明了红黑树,称为“对称二叉B树”,但红黑树这个名字来源于Leo J Guibas 和 Robert Sedgewick在1978年写的一篇论文。
Rudolf Bayer是一名慕尼黑大学的信息学教授,(信息学是一门研究信息的收集、分类、操作、存储、检索和传播的科学),他发明红黑树的初衷是为了存储和有效地检索多维数据
简介:红黑树是一棵自平衡的二叉搜索树
应用场景:
1.C++中的map,set
2.Java中的HashMap,TreeMap,TreeSet
3.用于实现Linux中的CPU调度
优势和劣势
优势:
大多数地二叉搜索树检索信息的时间复杂度为O(h),h是二叉树的高,而且如果二叉树是一颗斜树,检索的时间复杂度会达到最差情况O(n),n是树的节点个数。
而红黑树检索的时间复杂度最多是O(logn)
劣势:
在插入,删除数据后需要进行调整平衡操作(染色,左旋,右旋)。
不适用于数据较少的场景
适用的场景
适用于海量数据,且需要经常进行插入删除操作的场景
类似的树和它的对比
AVL(平衡树)比起红黑树来说更加平衡,通俗的说就是查找性能可能更优;但平衡树在插入和删除时自旋的次数可能会大于红黑树,所以如果项目中插入删除节点操作更多选择红黑树会更好,如果查询操作更多则选择AVL树更好
底层原理,关键实现
源码
组成和关键点
红黑树节点需要遵守的规则
(满足以上规则的树大致是一个平衡树)
- 1.每个节点是红的或者黑的
- 2.根节点始终是黑的
- 3.红节点均不相邻(一个节点的父节点和子节点不会为红)
- 4.从任何节点(包括根节点)到达它子孙的NULL节点,经过的黑节点数相同
红黑树的一些特点
- 1.三个节点的链在红黑树中是不存在的
- 2.从一个节点到它最远叶子节点的节点数,不超过两倍到它最近叶子节点的节点数
- 3.红黑树有n个节点,它的高h<=2log2(n+1)
黑高
- 黑高是指从根节点到叶子节点经过的黑节点数(包含黑叶子节点)
- 性质:黑高>=树高/2
保持平衡
-
变色
- 颜色由红变黑或由黑变红
-
左旋
- 以某个节点作为支点,它的右子节点变为它的父节点,右子节点的左节点变为它的左节点,右子节点的右节点不变
-
右旋
- 与左旋相反,类比即可
插入
-
操作
- 查找插入位置
- 插入后的自平衡
-
情景分析
-
1.红黑树为空
- 把插入节点作为根节点,并设置为黑
-
2.插入节点key已存在
- 用新节点value值覆盖旧的value
-
3.插入节点的父节点为黑节点
- 由于插入的节点是红色的,如果插入节点的父节点是黑色时,不会影响红黑树的平衡,直接插入即可,无需做自平衡调整
-
4.插入节点的父节点为红色
-
4.1叔叔节点存在并且为红
- 由于不能存在两个相邻的红节点,所以祖父节点为黑;因此插入节点三代的颜色为:黑红红,显然最简单的处理方法就是将之变为:红黑红
- 处理方法:
1.先将父节点和叔叔节点变黑
2.将祖父节点变红
3.将祖父节点设置为当前节点,进行后续处理 - 1.祖父节点在变色后是红色,如果祖父节点的父节点是黑色,那就不需要做任何处理
2.如果祖父节点的父节点是红色,就违背了红黑树的性质:红色节点不能相邻;需要将祖父节点设置为当前节点,继续做自平衡操作
-
4.2叔叔节点不存在或者为黑,并且插入节点的父节点是祖父节点的左子节点
-
4.2.1新插入节点,为其父节点的左子节点(LL红色情况)
- 处理:将祖父节点设为红,父节点设为黑,进行右旋
-
4.2.2新插入节点,为其父节点的右子节点(LR红色情况)
- 1.将父节点作为当前节点,进行左旋
2.此时与4.2.1的情况相同(LL双红)将左旋后的父节点继续作为当前节点,按4.2.1处理
- 1.将父节点作为当前节点,进行左旋
-
-
4.3叔叔节点不存在或者为黑,插入节点的父节点是祖父节点的右子节点,与4.2对应
- 4.3.1RR
- 4.3.2RL
-
-
红黑树手写(含打印函数):
//红黑树类
/**
* 1.创建RBTree,定义颜色
* 2.创建静态内部类 RBNode
* 3.辅助方法定义: parentOf(node), isRed(node), isBlack(node), setRed(node),setBlack(Node),InOrderPrint()
* 4.左旋方法定义: leftRotate(node)
* 5.右旋方法定义: rightRotate(node)
* 6.公开插入接口方法定义: insert(K key, V value)
* 7.内部插入接口方法定义: insert(RBNode node)
* 8.修正插入导致红黑树失衡的方法定义: insertFixUp(RBNode node)
* 9.测试红黑树的正确性
*
* @param <K>
* @param <V>
*/
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private RBNode root;
// 打印红黑树
public void padding ( String ch, int n )
{
int i;
for ( i = 0; i < n; i++ )
System.out.printf(ch);
}
void print_node (RBNode root, int level )
{
if ( root == null )
{
padding ( "\t", level );
System.out.println( "NIL" );
}
else
{
print_node ( root.right, level + 1 );
padding ( "\t", level );
if(root.color == BLACK)
{
System.out.printf("(%d)\n", root.key );
}
else
System.out.printf( "%d\n",root.key );
print_node ( root.left, level + 1 );
}
}
void print_tree()
{
print_node(this.root,0);
System.out.printf("-------------------------------------------\n");
}
/**
* 获取当前节点的父节点
* @param node
* @return
*/
private RBNode parentOf(RBNode node) {
if(node != null){
return node.parent;
}
return null;
}
/**
* 当前节点是否是红色
* @param node
* @return
*/
private boolean isRed(RBNode node){
if(node != null){
return node.color == RED;
}
return false;
}
/**
* 设置节点颜色为红色
* @param node
*/
private void setRed(RBNode node){
if(node != null){
node.color = RED;
}
}
/**
* 当前节点是否是黑色
* @param node
* @return
*/
private boolean isBlack(RBNode node){
if(node != null){
return node.color == BLACK;
}
return false;
}
/**
* 设置节点颜色为黑色
* @param node
*/
private void setBlack(RBNode node){
if(node != null){
node.color = BLACK;
}
}
/**
* 中序打印二叉树
*/
public void inOrderPrint(){
inOrderPrint(this.root);
}
private void inOrderPrint(RBNode node){
if(node != null){
inOrderPrint(node.left);
System.out.println(node);
inOrderPrint(node.right);
}
}
/**
* 左旋
* 设x为当前节点,y为x的右子节点
* @param x
*
* 步骤:
* 1.将y的左子节点设为x的右子节点, 将y的左子节点的父亲设置为x
* 2.如果x父亲存在,将x的父亲设置为y的父亲设置,将x的父亲的儿子设置为y
* 3.将x的父亲设置为y, 将x设置为y的左子节点
*
* 注意: 如果y的左子节点为空也需要赋值给x.right
*/
private void leftRotate(RBNode x){
RBNode y = x.right;
x.right = y.left;
if (y.left != null){ //等于null的时候也需要赋值给x.right
y.left.parent = x;
}
if (x.parent != null){
y.parent = x.parent;
if (x.parent.left == x){
x.parent.left = y;
}else {
x.parent.right = y;
}
}else {
this.root = y;
this.root.parent = null;
}
x.parent = y;
y.left = x;
}
private void rightRotate(RBNode x){
RBNode y = x.left;
x.left = y.right;
if (y.right != null){ //等于null的时候也需要赋值给x.right
y.right.parent = x;
}
if (x.parent != null){
y.parent = x.parent;
if (x.parent.left == x){
x.parent.left = y;
}else {
x.parent.right = y;
}
}else {
this.root = y;
this.root.parent = null;
}
x.parent = y;
y.right = x;
}
public void insert(K key, V value) {
RBNode node = new RBNode();
node.setKey(key);
node.setValue(value);
node.setColor(RED);
insert(node);
}
private void insert(RBNode node) {
RBNode parent = null;
RBNode x = this.root;
while(x != null) {
parent = x;
int cmp = node.key.compareTo(x.key);
if(cmp > 0){
x = x.right;
}else if(cmp == 0){
x.setValue(node.getValue());
return ;
}else{
x = x.left;
}
}
node.parent = parent;
if(parent != null){
int cmp = node.key.compareTo(parent.key);
if(cmp > 0){
parent.right = node;
}else{
parent.left = node;
}
}else{
this.root = node;
}
//修正红黑树平衡的方法
insertFixUp(node);
}
/**
*
* 1. 红黑树为空,处理根节点
* 2. 插入节点key已经存在,不用处理
* 3. 父节点为黑,不用处理
* 4. 需要处理
* 4.1 父节点存在且为红色
* 父节点是祖父的左子节点
* 4.1.1 叔叔节点存在且为红色 变色,然后以祖父节点进行下一轮处理
* 4.1.2 叔叔节点不存在或为黑色
* 4.1.3 当前节点是父节点的左子节点 将父节点设为黑,祖父节点设为红,以父节点进行右旋
* 4.1.4 当前节点是父节点的右子节点 以父节点左旋,然后传入父节点进行下一轮操作
*
* 父节点是祖父的右子节点
* 与上面类似
*/
private void insertFixUp(RBNode node){
this.root.color = BLACK;
RBNode parent = parentOf(node);
RBNode gparent = parentOf(parent);
if(parent != null && isRed(parent)){
RBNode uncle = null;
if(parent == gparent.left) {
uncle = gparent.right;
if (uncle != null && isRed(uncle)) {
setBlack(parent);
setBlack(uncle);
setRed(gparent);
insertFixUp(gparent);
return;
}
if (uncle == null || isBlack(uncle)) {
if (node == parent.left){
setBlack(parent);
setRed(gparent);
rightRotate(gparent);
return;
} else {
leftRotate(parent);
insertFixUp(parent);
return;
}
}
}else{
uncle = gparent.left;
if (uncle != null && isRed(uncle)) {
setBlack(parent);
setBlack(uncle);
setRed(gparent);
insertFixUp(gparent);
return;
}
if (uncle == null || isBlack(uncle)) {
if (node == parent.right){
setBlack(parent);
setRed(gparent);
leftRotate(gparent);
return;
} else {
rightRotate(parent);
insertFixUp(parent);
return;
}
}
}
}
}
static class RBNode <K extends Comparable<K>, V>{
private RBNode parent;
private RBNode left;
private RBNode right;
private boolean color;
private K key;
private V value;
@Override
public String toString() {
return "key:" + key +
" value:" + value;
}
public RBNode(){}
public RBNode(K key, V value) {
this.key = key;
this.value = value;
}
public RBNode getParent() {
return parent;
}
public RBNode getLeft() {
return left;
}
public RBNode getRight() {
return right;
}
public boolean isColor() {
return color;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public void setLeft(RBNode left) {
this.left = left;
}
public void setRight(RBNode right) {
this.right = right;
}
public void setColor(boolean color) {
this.color = color;
}
public void setKey(K key) {
this.key = key;
}
public void setValue(V value) {
this.value = value;
}
}
}
//main方法
public class TestRBTree {
public static void main(String[] args) {
RBTree<Integer, String> tree = new RBTree<>();
for(int i = 0; i < 10; i++){
String s = "a"+i;
tree.insert(i+1, s);
tree.print_tree();
System.out.println();
}
}
}
参考资料
1.Red-Black Tree | Set 1 (Introduction) - GeeksforGeeks
2.B站Up主:小刘讲源码