一、概述
什么是红黑树?红黑树是一种二叉树,做开发的一定听说过红黑树,红黑树属于一种高级的数据结构了,在中高级开发的面试环节中,我们经常会遇到相关的面试题,本篇我们就来了解下这种数据结构。
二、红黑树的特征
红黑树是一种特殊的平衡二叉树,关于平衡二叉树,我们以前介绍过AVL树,不清楚的可以先看这篇数据结构之AVL树。对红黑树的操作在最坏的情况下花费O(logN)时间。它具有以下性质:
-
每一个节点要么是红色,要么是黑色。
-
根(root)节点是黑色的。
-
如果一个节点是红色的,那么它的子节点必须是黑色的。
-
从一个节点到任意叶节点的路径必须包含相同数目的黑色节点。
我们对于红黑树的概念很好理解,如下图所示,这是一棵红黑树。
难点在于红黑树的代码实现。对于增删操作如何保证新增/删的节点不破坏红黑树的基本结构,这是本篇主要讨论的内容。
三、思路分析
现在我们需要插入一个新节点,这个新节点的父节点有两种情况,要么是红色,要么是黑色的。
一、如果该父节点是黑色的,那么很显然,为了满足条件4,新插入的这个节点一定是红色的,此时我们直接插入就好了。
二、如果该父节点是红色的,为了满足条件3,新插入的节点只能是黑色,此时就会破坏条件4,所以针对这种情况,我们需要通过节点颜色的改变和节点的旋转来保证红黑树的结构不会被破坏。
具体情况可分为五种,下面我通过作图的方式来简要说明一下。
情况一:如上图所示,黑色节点下挂着红色的左子节点,左子节点下面又挂着红色的左孙子节点,这种情况需要进行右旋转,再将原来的黑色节点变成红色,原来的红色左子节点变黑,左孙子节点不变,这样便可保证红黑树结构不被破坏。
情况二:如上图所示,该情况和【情况一】成镜像对称,黑色节点下挂着红色的右子节点,右子节点下面又挂着红色的右孙子节点,这种情况需要进行左旋转,再将原黑色节点变红,原红色右子节点变黑,这样便可保证红黑树结构不被破坏。
情况三:如上图所示,黑色节点下面挂着红色的左子节点,左子节点下面挂着红色的右孙子节点,该情况需要通过两次旋转得到正确的红黑树,先以【6】节点为核心左旋转一次得到类似【情况一】的样式,再以【6】节点为核心右旋转一次得到正确的结构,这样便可保证红黑树结构不被破坏。
第4种:如上图所示,该情况和【情况三】成镜像对称,黑色节点下挂着红色左子节点,左子节点下挂着红色右孙子节点,该情况同样需要两次旋转得到正确的红黑树,第一次以【11】节点为核心右旋转得到类似【情况二】的结构,再左旋转一次,这样便可保证红黑树结构不被破坏。
第5种:如上图所示,黑色节点下的两个子节点都是红色,孙子节点也是红色的,此时应该将原来两个红色子节点变成黑色,原来的黑色节点变成红色,特别注意,如果原来的黑色节点是根节点,那么不必变换颜色。
以上红黑树结构被破坏的情况我们已经分析完毕,下面我们来通过代码实现一下红黑树这种数据结构。
四、手写红黑树
笔者花了一些时间利用递归的方式实现了红黑树(如有错误还请指正),代码如下
public class RedBlackTree<T extends Comparable<? super T>> {
// 根节点
private Node root;
RedBlackTree() {
root = null;
}
// 新增操作
public void insert(T element) {
root = insert(element, root);
}
private Node<T> insert(T element, Node<T> node) {
if (root == null)
// 根节点插入黑色
return new Node(element, false);
if (node == null)
// 默认插入红色
return new Node(element, true);
int result = element.compareTo(node.element);
if (result > 0)
node.rightNode = insert(element, node.rightNode);
if (result < 0)
node.leftNode = insert(element, node.leftNode);
return blance(node);
}
// 平衡
private Node<T> blance(Node<T> node) {
if (node == null)
return null;
// 该节点为黑色,左子节点为空或者黑色,右子节点为红色
boolean stage1 = !node.isRed && (node.leftNode == null || !node.leftNode.isRed) &&
node.rightNode != null && node.rightNode.isRed;
// 该节点为黑色,右子节点为空或者黑色,左子节点为红色
boolean stage2 = !node.isRed && (node.rightNode == null || !node.rightNode.isRed) &&
node.leftNode != null && node.leftNode.isRed;
if (stage1) {
// 在stage1的基础上右子子节点为红色,左旋和着色
if (node.rightNode.rightNode != null && node.rightNode.rightNode.isRed)
node = rotateWithLeftChildAndColoring(node);
// 在stage1的基础上右子节点的左子节点为红色,先对右子节点右旋和着色,再对该节点左旋着色
if (node.rightNode.leftNode != null && node.rightNode.leftNode.isRed) {
node.rightNode = rotateWithRightChildAndColoring(node.rightNode);
node = rotateWithLeftChildAndColoring(node);
}
}
if (stage2) {
// 在stage1的基础上左子子节点为红色,右旋和着色
if (node.leftNode.leftNode != null && node.leftNode.leftNode.isRed)
node = rotateWithRightChildAndColoring(node);
// 在stage1的基础上左子节点的右子节点为红色,先对左子节点左旋和着色,再对该节点右旋着色
if (node.leftNode.rightNode != null && node.leftNode.rightNode.isRed) {
node.leftNode = rotateWithLeftChildAndColoring(node.leftNode);
node = rotateWithRightChildAndColoring(node);
}
}
// 节点为黑色,左右子节点均为红色,孙子节点有红色
boolean stage3 = !node.isRed && node.leftNode != null && node.rightNode != null &&
node.leftNode.isRed && node.rightNode.isRed && (
node.rightNode.rightNode != null && node.rightNode.rightNode.isRed ||
node.rightNode.leftNode != null && node.rightNode.leftNode.isRed ||
node.leftNode.leftNode != null && node.leftNode.leftNode.isRed ||
node.leftNode.rightNode != null && node.leftNode.rightNode.isRed
);
if (stage3)
node = coloringChild(node);
return node;
}
private Node<T> rotateWithLeftChildAndColoring(Node<T> node) {
Node temp = node;
temp.coloring(true);
node = node.rightNode;
node.coloring(false);
temp.rightNode = node.leftNode;
node.leftNode = temp;
return node;
}
private Node<T> coloringChild(Node<T> node) {
if (node == root) {
node.rightNode.coloring(false);
node.leftNode.coloring(false);
} else {
node.coloring(true);
node.rightNode.coloring(false);
node.leftNode.coloring(false);
}
return node;
}
private Node<T> rotateWithRightChildAndColoring(Node<T> node) {
Node temp = node;
temp.coloring(true);
node = node.leftNode;
node.coloring(false);
temp.leftNode = node.rightNode;
node.rightNode = temp;
return node;
}
public void printTree() {
System.out.println(root.toString());
}
class Node<T> {
// 元素
T element;
// 左子节点
Node<T> leftNode;
// 右子节点
Node<T> rightNode;
// 是否为红色, true:红色,false:黑色
boolean isRed;
Node(T element, boolean red) {
this(element, null, null, red);
}
Node(T element, Node<T> left, Node<T> right, boolean red) {
this.element = element;
this.leftNode = left;
this.rightNode = right;
this.isRed = red;
}
public Node<T> coloring(boolean red) {
this.isRed = red;
return this;
}
@Override
public String toString() {
return "Node{" +
"element=" + element +
", isRed=" + isRed +
", leftNode=" + leftNode +
", rightNode=" + rightNode +
'}';
}
}
}
以上代码,笔者只实现了新增的业务逻辑,感兴趣的朋友可以在此基础上添加删除和查找的方法……
五、小结
本篇我们简单的分析了下红黑树这种数据结构,并且利用递归的方式将之实现,感兴趣的朋友也可以尝试着用非递归方式实现它。
对程序员来说数据结构的重要性不言而喻,在源码中我们也经常能看到红黑树这种数据结构,比如:在Java8中HashMap的链表结构会在一定情况下转换成红黑树的结构,不清楚的朋友请转战HashMap源码剖析。
感兴趣的读者朋友可以 关注本公众号,和我们一起学习探究。
本人因所学有限,如有错误之处,望请各位指正!