1. 红黑树吐槽
在整理之前学过的数据结构知识(主要是应对找工作)时候,动手实现了栈、队列、单链表、双链表、二叉搜索树,上述列举的这些基础的数据结构实现起来还是挺简单的,稍微思考一下即可完成常见的insert/delete/search/traverse操作。
我承认,在红黑树的时候我遇到障碍了,大概花了一天的时间完成了这个版本的红黑树插入操作的代码。后来我反思了一下,之所以这样可能有2个原因:其一,Red Black
Tree确实实现起来相对之前的几种数据结构复杂,这是客观原因,也是最主要的原因。其二,我曾试图参考理解他人博客中关于红黑树实现的代码,包括中文和英文版本的,但要么是那种动辄500-600行的代码,让人望而却步,要么是写的看似很详细,其实完全是bullshit(我不是说别人总结的不对,而是写的不清不楚,浪费别人时间去看,无异于谋财害命。什么?你觉得这篇也是狗屁不通,敬请移步,因为每个人的认知规律是不一样的,我也没办法啊)。
2. 红黑树基础
本文主要思路来源于该网址,这里将其部分内容做简要总结。
One Aim: Approximately balanced tree creation. From root to leaf, no (simple) path is more than twice as long as any other.
Two Rules: (CLRS says five, but others are trivial)
Color Constraint: Red nodes will have both of it’s children black. Black nodes can have either.
Number Constraint: For each node, simple path to all theThree Situations:
Couple of points to be noted before we see the situations Wherever I
refer Uncle, it means (same as in common life) – “sibling of parent
node”, “the other child of node’s parent’s parent”.
We assume as NIL nodes of all the leaves are black. We always insert red node. So, We are not breaking #2: Number Constraint by adding a black node, but we are breaking constaint #1: Color Constraint. Keep the root node black.
You will see, it makes life easier. It does not breaks any of the
constraints.
1. A Red uncle is a good uncle.
2. A Black uncle on opposite side wants one rotation.
3. A Black uncle on the same side needs two rotations.
- 一个目标:红黑树出现的意义:达到几乎平衡树B-树创建的时间复杂度。从root节点到Leaf节点,没有一条路径比其他路径的2倍还长。
- 两个原则(共有五个,这里择取重要的2个):
a). 颜色限制:红色节点的两个孩子必须都是黑色的,而黑色节点则没有这个限制。
b). 数量限制:对于每个节点,从该节点到所有叶子结点的路径中都有相同数量的黑色节点。 - 三种情形:
a). 叔叔节点为红色的情形:
b). 叔叔节点为黑色,且和插入节点位于异侧。
c). 叔叔节点为黑色,且和插入节点位于同侧。
3. 代码实现
//RedBlackTree.java
package com.fqyuan.red_black;
/*
* A red black tree is a binary search tree which fulfills the 5 policies:
* 1. Each node is either red or black.
* 2. The root node is painted black.
* 3. Each leaf node(nil) is painted black.
* 4. If a node is red, then both its children is black.
* 5. For each node, all simple paths from the node to the descent leaves contain the same number of black nodes.
*/
public class RedBlackTree {
private Node root;
private enum COLOR {
RED, BLACK
};
private class Node {
private Node parent;
private Node lchild;
private Node rchild;
private COLOR color;
private int data;
public Node(int item) {
this(item, COLOR.RED);
}
public Node(int item, COLOR color) {
this.data = item;
this.color = color;
}
}
public void insert(int item) {
// First, determine if the RBTree is empty.
if (root == null) {
Node newNode = new Node(item, COLOR.BLACK);
root = newNode;
return;
}
Node current = root;
Node parent = root;
while (current != null) {
parent = current;
if (item < current.data)
current = current.lchild;
else
current = current.rchild;
}
// The node 'current' is the position to insert
Node newNode = new Node(item);
newNode.parent = parent;
if (newNode.data < parent.data)
parent.lchild = newNode;
else
parent.rchild = newNode;
// Do some adjustment.
fixInsert(newNode);
}
private void fixInsert(Node node) {
// Case 0: If node.parent is Black, or node.parent is null. Then, we are
// done, wonderful!
if (node.parent == null || node.parent.color == COLOR.BLACK)
return;
// Case 1: Red Uncle is a good uncle. We just need to fix colors.
Node uncle = getUncle(node);
if (isUncleRed(uncle)) {
node.parent.color = COLOR.BLACK;
node.parent.parent.color = COLOR.RED;
uncle.color = COLOR.BLACK;
// Recursive check on the grandparent.
fixInsert(node.parent.parent);
} else if (isUncleRightUncle(node)) {
// Case 3.1: both the node to be inserted and the uncle are on the
// same side.
if (node.parent.rchild == node)
rotateLeft(node); // First rotate to convert to case 2.
// Case 2.1: the node to be inserted and the black uncle are on
// opposite side.
// Fix colors:
node.parent.color = COLOR.BLACK;
node.parent.parent.color = COLOR.RED;
// Fix trees:
rotateRight(node.parent);
} else {
// Case 3.2: both the node to be inserted and the uncle are on the
// same side.
if (node.parent.lchild == node)
rotateRight(node); // First rotate to convert to case 2.
// Case 2.2: the node to be inserted and the black uncle are on
// opposite side.
// Fix colors:
node.parent.color = COLOR.BLACK;
node.parent.parent.color = COLOR.RED;
// Fix trees:
rotateLeft(node.parent);
}
root.color = COLOR.BLACK;
}
private Node getUncle(Node node) {
if (node.parent == null || node.parent.parent == null)
return null;
if (node.parent == node.parent.parent.lchild) {
return node.parent.parent.rchild;
} else
return node.parent.parent.lchild;
}
private boolean isUncleRed(Node node) {
return node != null && node.color == COLOR.RED;
}
private boolean isUncleRightUncle(Node node) {
// return node.parent.rchild == node; can not invoke uncle as the
// parameter for uncle may be null.
return node.parent == node.parent.parent.lchild;
}
private boolean isLeftChild(Node node) {
return node.parent.lchild == node;
}
/**
* <pre>
* g g
* / /
* x Left Rotation(y) y
* / \ ----------------> / \
* alpha y x gamma
* / \ / \
* beta gamma alpha beta
* </pre>
*
* @param node
*/
@SuppressWarnings("unused")
private void rotateLeft(Node node) {
Node y = node;
Node x = node.parent;
Node g = node.parent.parent;
Node alpha = node.parent.lchild; // Not used.
Node beta = node.lchild;
Node gamma = node.rchild; // Not used.
y.lchild = x;
y.parent = g;
x.rchild = beta;
x.parent = y;
if (g != null) {
if (g.lchild == x)
g.lchild = y;
else
g.rchild = y;
} else {
root = y;
}
if (beta != null)
beta.parent = x;
}
/**
* <pre>
* g g
* / /
* y Right Rotation(x) x
* / \ ----------------> / \
* x gamma alpha y
* / \ / \
* alpha beta beta gamma
* </pre>
*/
@SuppressWarnings("unused")
private void rotateRight(Node node) {
Node x = node;
Node y = node.parent;
Node g = node.parent.parent;
Node alpha = node.lchild;
Node beta = node.rchild;
Node gamma = y.rchild;
x.rchild = y;
x.parent = g;
y.lchild = beta;
y.parent = x;
// Ever made mistake here, attention: the father of y changed because of
// the code upon.
if (g != null) {
if (g.lchild == y)
g.lchild = x;
else
g.rchild = x;
} else {
root = x;
}
if (beta != null)
beta.parent = y;
}
public boolean search(int item) {
if (root == null)
return false;
Node node = root;
while (node.data != item) {
if (item < node.data)
node = node.lchild;
else
node = node.rchild;
if (node == null)
return false;
}
return true;
}
public boolean delete(int item) {
return false;
}
public void inOrder() {
inOrder(root);
}
private void inOrder(Node node) {
if (node == null)
return;
inOrder(node.lchild);
// + node.parent != null? node.parent.data : "Root node" + "\n"
System.out.print(
node.data + "\t" + node.color + "\t" + ((node.parent == null) ? "Root" : node.parent.data) + "\n");
inOrder(node.rchild);
}
public void deleteRBTree() {
}
}
//RedBlackDemo.java
package com.fqyuan.red_black;
public class RedBlackDemo {
public static void main(String[] args) {
RedBlackTree rbTree = new RedBlackTree();
int[] arr = { 8, 11, 14, 18, 22, 23, 31, 37, 41, 47, 60, 74, 84, 87, 88, 97, 99 };
for (int val : arr) {
rbTree.insert(val);
}
rbTree.inOrder();
if (rbTree.search(4))
System.out.println("Find 45");
else
System.out.println("45 not found.");
}
}
//Running result:
8 BLACK 11
11 BLACK 18
14 BLACK 11
18 BLACK Root
22 BLACK 23
23 BLACK 37
31 BLACK 23
37 RED 18
41 BLACK 47
47 RED 74
60 BLACK 47
74 BLACK 37
84 BLACK 87
87 RED 74
88 RED 97
97 BLACK 87
99 RED 97
45 not found.