PS:本文章的理论知识来自《数据结构和算法 java描述》
平衡的补救
红黑树的平衡是在插入、删除的过程中取得的。对一个要插入的数据项,插入例程要检查不会破坏树一定的特征。如果破坏了,程序就会进行纠正,根据需要更改树的结构。通过维持树的特征,保持了树的平衡。
红-黑规则
-
每一个节点不是红色的就是黑色的
-
根总是黑色的
-
如果节点是红色的,则它的子节点必须是黑色的(反之倒不一定必须为真)
-
从根到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点。(从根到叶节点的路径上的黑色节点的数目称为黑色高度,所以这条规则的另一种陈述方法是:所有从根到叶节点路径上的黑色高度必须相同。)
空子节点
举例,如果节点只有右子节点,那么它空缺的左子节点就是空子节点,反之亦然。
这颗树违背了红-黑规则中的第四条
旋转
注意利用颜色规则和节点颜色变换只是有助于决定何时执行旋转。光改变单个节点的颜色不能起任何作用,旋转才是起关键作用的操作。
选择一个节点作为旋转的“顶端”,如果做一次右旋,这个“顶端”节点将会向下和向右移动到它右子节点的位置,它的左子节点将会上移到它原来的位置上。
必须确保,如果做右旋,顶端节点必须有一个左子节点,否则将没有节点旋转到顶端节点原来所在的位置。类似的,如果做左旋,顶端节点必须有一个右子节点。
单个节点位置的改变
移动子树
其实质就是单个节点位置的改变,只不过是要旋转的节点,其子节点是子树。
从旋转中可以看出,只要一个节点的左边有很多子孙节点,而右边没有这么多节点,就可以考虑向右旋转,反之亦然。但计算机不善于“只靠观察”这种工作方式,不过如果计算机能遵守几个简单的规则,它们也可以做好,这些规则就是在红-黑方法中提供的,表现形式为颜色码和四个红-黑规则。
插入一个新节点
会发生三种情况:
-
在下行路途中的颜色变换
-
插入节点之后的旋转
-
在向下路途上的旋转
在下行路途中的颜色变换
红-黑树的插入例程开始时所做的事,和普通的二叉树基本上一样:沿着从根向下走,在每个节点处通过比较节点的关键字相对大小,来决定向左还是向右走。但是在红-黑树中,找到插入点更复杂,因为有颜色变换和旋转。
假设插入例程沿着树向下执行,在每一个节点处向左或者向右走,查找插入新节点的位置。为了不违反颜色规则,在必要的时候需要进行颜色变换,其规则是:每当查找例程遇到一个有两个红色子节点的黑色节点时,它必须把子节点变为黑色,而把父节点变为红色(除非父节点为根节点,根总是黑色的)。这种颜色的变换,是因为使红色节点变为黑色节点,在不违背规则3(一个节点和它的父节点不能都是红色的)的情况下,插入(连接)新的红色节点更容易。另外,这种颜色变换并不会改变黑高。
插入节点之后的旋转
新节点的插入可能会使得树违背红-黑规则,因此在插入之后,必须检测是否违背规则,并采取相应的措施。
新插入的节点X总是红色的,X可能插入到相对于P(X的父节点)和G(P的父节点)不同的位置上。
内外侧子孙节点的解释:如果节点X是P的左节点并且P是G的左节点,或者X是P的右节点并且P是G的右节点,则X是一个外则子孙节点。相反,如果节点X在P的一侧,而P在G的另一侧,则X是一个内侧子孙节点。
为恢复红-黑规则所采取的措施,由颜色以及X和它亲属的布局所决定。新节点插入后的三种(已经涵盖所有情况)可能布局:
-
P是黑色的
-
P是红色的,X是G的一个外侧子孙节点
-
P是红色的,X是G的一个内侧子孙节点
P是黑色的
如果P是黑色的, 就什么事也不用做。因为刚插入的新节点总是红色的,如果它的父节点是黑色的,则没有红色节点连接红色节点的冲突,并且也不会增加黑色节点的数目,所以黑高也不会改变。
P是红色的,X是G的一个外侧子孙节点
这种情况下,需要进行一次旋转和颜色变换
P是红色的,X是G的一个内侧子孙节点
这种情况下,需要执行两次旋转,第一次旋转后,X和P的身份互换,但此时12是25的外侧子孙。所以图b已经跟“P是红色的, X是G的一个外侧子孙节点”一样,只不过对于图b,X和P的身份已经发生了互换。
对于重新为节点着色,在做任何旋转之前来做这件事,是最方便的,因为要是等旋转后再改变节点的颜色,到那时就很难知道如何称呼它们。步骤:
-
对于图a,改变G的颜色,从黑->红
-
对于图a,改变X的颜色,从红->黑
-
对于图a,以P为顶点,对X进行左旋
-
对于图b,以25为顶点,对18进行右旋
其他可能情况?
上面讨论的新节点插入后的三种可能布局,真的包含了所有的情况吗?
讨论1:
假设,X有一个兄弟节点S,这种情况会使插入X需要的旋转变得更复杂。但是如果P是黑色,插入X没有任何问题(这属于P是黑色的情况)。如果P是红色的,则X插入前,P是不可能有子节点的。因为假设P有子节点,则它的两个子节点必须是黑色的(规则3),P不能只有一个黑色的子节点,因为这样会违背黑高规则(S和空子节点的黑高不同)。
所以,如果P是红色的,在X插入之前,P是不可能有子节点的,除非P是黑色的。
讨论2:
所以,在X插入之前,若P是红色的,则只有以下两种布局情况
在向下路途上的旋转
在下行路途中查找插入点时所做的旋转,实际上它是发生在节点插入之前(即“插入节点之后的旋转”)。在插入过程中的颜色变换,可能会造成对规则3(父节点和子节点不能都是红色)的违规,所以需要旋转来纠正这种违规的情况。这种违规分别对应“违背规则的节点是一个外侧子孙节点”和“违背规则的节点是一个内侧子孙节点”。
违背规则的是一个外侧子孙节点
违背规则的是一个内侧子孙节点
删除节点
红-黑树节点的删除太复杂了,很多程序员都用不同的方法来回避它。一种方法是(和普通的二叉树一样)为删除的节点做个标记,而不是实际地删除它。任何找到该节点的查找例程都知道不用报告已找到该节点。很多情况下都应用这种方法,特别是在不经常执行删除操作时。
红-黑树的实现
在普通二叉树的Node类中,增加一个标志红-黑颜色的字段。
跟普通的二叉树相比,除了插入节点(不考虑删除节点操作),其他的操作是一样的。
对于红-黑树的插入节点例程
-
在向下到插入点的路径上,检查当前节点是否为黑色,以及它的两个节点是否都是红色,如果是,则变换这三个节点的颜色,同时注意根节点总保持黑色。
-
在颜色变换后,检查是否违背了“父子节点不能同时为红色”的规则,如果违背了,则执行适当的旋转,对于外侧子孙节点执行一次旋转,对于内侧子孙节点执行两次旋转。
-
当到达一个叶节点时,插入新节点并保证这个新节点是红色的,再次检查是否违背了“父子节点不能同时为红色”的规则,如果有则执行需要的旋转操作。
插入节点中,破坏了黑高规则?
我们在程序中没有跟踪黑高,只需要检查是否违背了“父子节点不能同时为红色”的规则,如果违背了,当时就可以解决掉(检查黑高规则需要更复杂的记录)。如果执行前面描述的颜色变换、颜色改变以及旋转,节点的黑高应当能自动调整,并且树应该保持平衡。
Java 代码实现
在二叉树的代码实现上对节点插入函数insert做了修改,Node类加了color域,节点删除暂时不对应。
package com.demo;
import java.io.BufferedReader;
import java.io.Console;
import java.io.IOException;
import java.io.InputStreamReader;
public class RBTree {
private final int SPACE = 6;
public static final int NULLKEY = Integer.MAX_VALUE;
private final int RED = 0;
private final int BLACK = 1;
private Node root;
private Stack workStack = new Stack();
private Stack backupStack = new Stack();
public static class Node {
public int key;
public int value; // This field can be any object
public int color;
private Node leftChild;
private Node rightChild;
private Node parent;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
/**
* Just for graphicDiplay method.
* @author hongjunxin
*
*/
private static class Stack {
private int maxSize = 50;
public Node[] array = new Node[maxSize];
private int nItems;
/**
*
* @param e
* @param maskNullKey true: if e is null, mask it as NullKey. false: just return.
*/
public void push(Node e, boolean maskNullKey) {
if (e == null) {
if (maskNullKey) {
array[nItems++] = new Node(NULLKEY, NULLKEY);
}
return;
}
if (nItems == maxSize) {
maxSize *= 2;
Node[] tmp = array;
array = new Node[maxSize];
for (int i = 0; i < maxSize/2; i++) {
array[i] = tmp[i];
}
}
array[nItems++] = e;
}
public Node pop() {
if (nItems == 0) {
return null;
} else {
return array[--nItems];
}
}
public boolean isEmpty() {
return (nItems == 0);
}
}
public void insert(int key, int value) {
this.insert(new Node(key, value));
}
public void insert(Node node) {
node.color = RED;
if (root == null) {
node.color = BLACK;
root = node;
return;
}
Node parent = null;
Node current = root;
boolean isLeft = false;
while (current != null) {
parent = current;
if (node.key < current.key) {
current = current.leftChild;
isLeft = true;
} else if (node.key > current.key) {
current = current.rightChild;
isLeft = false;
} else {
System.out.println("Already has " + node.key + ". Ignore insert.");
return;
}
if (current == null) break;
// In this while the following color change and rotation
// never change variable current.
Node x = parent;
if (needColorChanged(x)) {
if (x.equals(root)) {
x.color = BLACK;
} else {
x.color = RED;
}
x.leftChild.color = BLACK;
x.rightChild.color = BLACK;
if (x.parent == null) continue;
Node p = x.parent;
Node g = p.parent;
// If color changed breaks color rule,
// do rotation to fix it.
if (x.color == RED && p.color == RED) {
if ((x.equals(p.leftChild) && p.equals(g.leftChild)) ||
(x.equals(p.rightChild) && p.equals(g.rightChild))) {
doOnceRotation(x, p, g);
} else {
doTwiceRotation(x, p, g);
}
}
}
}
// Insert new node
if(isLeft) parent.leftChild = node;
else parent.rightChild = node;
node.parent = parent;
// After new node inserted we need to check color rule here again
if (parent.color == BLACK) {
// Insert done. Needn't any rotation.
} else {
Node x = node;
Node p = parent;
Node g = p.parent;
if ((x.equals(p.leftChild) && p.equals(g.leftChild)) ||
(x.equals(p.rightChild) && p.equals(g.rightChild))) {
doOnceRotation(x, p, g);
} else {
doTwiceRotation(x, p, g);
}
}
}
/**
* 如果 x 是外侧子孙节点,则只需要对 p 进行一次旋转操作
* @param x
* @param p x's parent who needs do once rotation
* @param g p's parent
*/
private void doOnceRotation(Node x, Node p, Node g) {
changeColor(p);
changeColor(g);
if (g.parent != null) {
if (g.equals(g.parent.leftChild )) {
g.parent.leftChild = p;
} else {
g.parent.rightChild = p;
}
p.parent = g.parent;
} else {
p.parent = null;
root = p;
p.color = BLACK;
}
g.parent = p;
if (x.equals(p.leftChild) && p.equals(g.leftChild)) {
if (p.rightChild != null) {
g.leftChild = p.rightChild;
g.leftChild.parent = g;
} else {
g.leftChild = null;
}
p.rightChild = g;
} else if (x.equals(p.rightChild) && p.equals(g.rightChild)) {
if (p.leftChild != null) {
g.rightChild = p.leftChild;
g.rightChild.parent = g;
} else {
g.rightChild = null;
}
p.leftChild = g;
}
}
/**
* 如果 x 是内侧子孙节点,则 x 需要进行两次旋转操作
* @param x who needs to do twice rotation
* @param p x's parent
* @param g p's parent
*/
private void doTwiceRotation(Node x, Node p, Node g) {
changeColor(x);
changeColor(g);
if (x.equals(p.rightChild) && p.equals(g.leftChild)) {
// x do first left rotation
g.leftChild = x;
x.parent = g;
p.parent = x;
if (x.leftChild != null) {
x.leftChild.parent = p;
p.rightChild = x.leftChild;
} else {
p.rightChild = null;
}
x.leftChild = p;
// x do second right rotation
if (g.parent != null) {
if (g.equals(g.parent.leftChild)) {
g.parent.leftChild = x;
} else {
g.parent.rightChild = x;
}
x.parent = g.parent;
} else {
x.parent = null;
root = x;
x.color = BLACK;
}
g.parent = x;
if (x.rightChild != null) {
g.leftChild = x.rightChild;
g.leftChild.parent = g;
} else {
g.leftChild = null;
}
x.rightChild = g;
} else if (x.equals(p.leftChild) && p.equals(g.rightChild)) {
// x do first right rotation
g.rightChild = x;
x.parent = g;
p.parent = x;
if (x.rightChild != null) {
x.rightChild.parent = p;
p.leftChild = x.rightChild;
} else {
p.leftChild = null;
}
x.rightChild = p;
// x do second left rotation
if (g.parent != null) {
if (g.equals(g.parent.leftChild)) {
g.parent.leftChild = x;
} else {
g.parent.rightChild = x;
}
x.parent = g.parent;
} else {
x.parent = null;
root = x;
x.color = BLACK;
}
g.parent = x;
if (x.leftChild != null) {
g.rightChild = x.leftChild;
g.rightChild.parent = g;
} else {
g.rightChild = null;
}
x.leftChild = g;
}
}
private boolean needColorChanged(Node parent) {
if (parent.leftChild != null && parent.rightChild != null) {
if (parent.leftChild.color == RED &&
parent.rightChild.color == RED &&
parent.color == BLACK) {
return true;
}
}
return false;
}
private void changeColor(Node node) {
if (node != null) {
node.color = (node.color == RED) ? BLACK : RED;
}
}
public void display() {
System.out.print("[ ");
recDisplay(root);
System.out.print("]");
System.out.println();
}
private void recDisplay(Node node) {
if (node != null) {
recDisplay(node.leftChild);
System.out.print(node.key + " ");
recDisplay(node.rightChild);
}
}
public void graphicDisplay() {
if (root == null) return;
int lineCount = 0;
Node node;
workStack.push(root, false);
// To count line
while ((node = workStack.pop()) != null) {
backupStack.push(node.leftChild, false);
backupStack.push(node.rightChild, false);
if (workStack.isEmpty()) {
while (!backupStack.isEmpty()) {
workStack.push(backupStack.pop(), false);
}
if (!workStack.isEmpty()) {
lineCount++;
}
}
}
// To draw every line
workStack.push(root, false);
printTab(lineCount--);
printNode(root);
System.out.println();
printTab(lineCount);
while (lineCount >= 0) {
node = workStack.pop();
printNode(node.leftChild);
printSpace((int)(SPACE * Math.pow(2, lineCount)));
printNode(node.rightChild);
backupStack.push(node.leftChild, true);
backupStack.push(node.rightChild, true);
if (workStack.isEmpty()) {
System.out.println();
printTab(--lineCount);
while (!backupStack.isEmpty()) {
workStack.push(backupStack.pop(), false);
}
} else {
printSpace((int)(SPACE * Math.pow(2, lineCount)));
}
}
}
private void printNode(Node node) {
if (node == null) {
System.out.print("nn");
} else {
System.out.print(node.key);
System.out.print(node.color == RED ? "R" : "B");
}
}
private void printSpace(int s) {
for (int i = 1; i < s-1; i++) {
System.out.print(" ");
}
}
private void printTab(int n) {
if (n == 0) {
printSpace((int)(0.5 * SPACE));
} else {
int an;
if (n == 1) an = 1;
else an = (n - 1) * 2;
printSpace(an * SPACE);
}
}
public void delete(int key) {
Node delNode = find(key);
if (delNode == null) {
System.out.println("Node " + key + " not found.");
return;
}
boolean isLeft = false;
if (!delNode.equals(root)) {
isLeft = delNode.equals(delNode.parent.leftChild) ? true : false;
}
if (delNode.leftChild == null &&
delNode.rightChild == null) {
if (delNode.equals(root)) {
root = null;
} else {
if (isLeft) {
delNode.parent.leftChild = null;
} else {
delNode.parent.rightChild = null;
}
}
} else if (delNode.leftChild != null &&
delNode.rightChild != null) {
Node successor = getSuccessor(delNode.key);
if (successor.equals(delNode.rightChild)) {
successor.leftChild = delNode.leftChild;
if (delNode.leftChild != null) {
delNode.leftChild.parent = successor;
}
} else {
successor.parent.leftChild = successor.rightChild;
if (successor.rightChild != null) {
successor.rightChild.parent = successor.parent;
}
delNode.leftChild.parent = successor;
delNode.rightChild.parent = successor;
successor.leftChild = delNode.leftChild;
successor.rightChild = delNode.rightChild;
}
successor.parent = delNode.parent;
if (delNode.equals(root)) {
root = successor;
} else {
if (isLeft) {
delNode.parent.leftChild = successor;
} else {
delNode.parent.rightChild = successor;
}
}
} else {
Node child;
child = (delNode.leftChild == null) ?
delNode.rightChild : delNode.leftChild;
child.parent = delNode.parent;
if (delNode.equals(root)) {
root = child;
} else {
if (isLeft) {
delNode.parent.leftChild = child;
} else {
delNode.parent.rightChild = child;
}
}
}
}
private Node getSuccessor(int key) {
Node delNode = find(key);
Node current = delNode.rightChild;
if (current != null) {
while (current.leftChild != null) {
current = current.leftChild;
}
}
return current;
}
public Node find(int key) {
Node current = root;
while (current != null) {
if (key == current.key) {
return current;
} else if (key < current.key) {
current = current.leftChild;
} else {
current = current.rightChild;
}
}
return null;
}
public Node getMax() {
Node right = root;
Node parent = null;
while (right != null) {
parent = right;
right = right.rightChild;
}
return parent;
}
public Node getMin() {
Node left = root;
Node parent = null;
while (left != null) {
parent = left;
left = left.leftChild;
}
return parent;
}
public static void main(String[] args) {
RBTreeApp.test();
}
}
class RBTreeApp {
static void test() {
RBTree tree = new RBTree();
int n;
tree.insert(1, 1);
System.out.println();
tree.display();
tree.graphicDisplay();
String key;
String action;
while (true) {
System.out.println();
System.out.println("Choose action ===============");
System.out.print("i:insert d:delete f:find b:max s:min >> ");
System.out.println();
System.out.flush();
action = getInput();
System.out.print("Input key >> ");
System.out.flush();
key = getInput();
if (action.equals("i")) {
n = Integer.parseInt(key);
tree.insert(n, n);
tree.display();
tree.graphicDisplay();
} else if (action.equals("d")) {
// tree.delete(Integer.parseInt(key));
// tree.display();
tree.graphicDisplay();
System.out.println("not support now");
} else if (action.equals("f")) {
RBTree.Node found;
found = tree.find(Integer.parseInt(key));
if (found != null) {
System.out.println("Found " + key);
} else {
System.out.println("Not found " + key);
}
} else if (action.equals("b")) {
System.out.println("Max node: " + tree.getMax().key);
} else if (action.equals("s")) {
System.out.println("Min node: " + tree.getMin().key);
} else {
break;
}
}
}
static String getInput() {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader r = new BufferedReader(isr);
String s = null;
try {
s = r.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return s;
}
}
运行结果