您的位置:首页 > 理论基础 > 数据结构算法

全面剖析红黑树

2017-01-12 16:02 281 查看

一、红黑树简介

    1 定义

红黑树(Red Black Tree)是一种自平衡二叉查找树,与 AVL 树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。

    2 性质

            (1)红黑树的每个结点或是红色的,或是黑色的;
            (2)根结点是黑色的;
            (3)每个叶结点(NIL)均为黑色的;
            (4)如果一个结点是红色的,那么它的两个子结点都是黑色的;
            (5)对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。

    3 数据结构

             红黑树TNode有左子结点、右子结点、父结点、结点颜色和结点权值五个属性。
class TNode {

private TNode leftNode;
private TNode rightNode;
private TNode parentNode;
private int colorNode;	//	RED=0,BLACK=1
private int value;
}
           红黑树的一个结点如下所示:



二、红黑树基本操作

    1 旋转

              红黑树指针结构的修改是通过旋转来完成得,这是一种保持二叉搜索树性质的搜索树局部操作,旋转可以分为两种情况:左旋与右旋。如下图所示:



            其中,右旋转(以P为转轴)和左旋转(以Q为转轴),两种旋转呈镜像,而且互为逆操作。在旋转过程中只有指针改变,其他所有的属性均不会改变。
左旋操作 leftRotate(TNode) 实现:
private void leftRotate(TNode node) {

TNode rightNode = node.getRightNode();

node.setRightNode(rightNode.getLeftNode());
if (rightNode.getLeftNode() != NIL) {
rightNode.getLeftNode().setParentNode(node);
}
rightNode.setParentNode(node.getParentNode());

if (node.getParentNode() == NIL) {
rootNode = rightNode;
} else if (node == node.getParentNode().getLeftNode()) {
node.getParentNode().setLeftNode(rightNode);
} else {
node.getParentNode().setRightNode(rightNode);
}

rightNode.setLeftNode(node);
node.setParentNode(rightNode);
}
右旋操作 rightRotate(TNode) 实现:
private void rightRotate(TNode node) {

TNode leftNode = node.getLeftNode();
node.setLeftNode(leftNode.getRightNode());

if (leftNode.getRightNode() != null) {
leftNode.getRightNode().setParentNode(node);
}

leftNode.setParentNode(node.getParentNode());

if (node.getParentNode() == NIL) {
rootNode = leftNode;
} else if (node == node.getParentNode().getLeftNode()) {
node.getParentNode().setLeftNode(leftNode);
} else {
node.getParentNode().setRightNode(leftNode);
}

leftNode.setRightNode(node);
node.setParentNode(leftNode);
}
由于旋转只改变了了结点指针的指向,所以左旋和右旋操作均能在O(1)的时间内完成。

2 插入

对于每个插入的结点,都有可能破坏红黑树的五个性质,当插入结点为黑色时,性质5可能遭到破坏,且调整该红黑树所需求的代价比较大;当插入结点为红色时,若不为根结点,只需保证性质4即可。所以我们约定每次插入的结点为红色,再根据红黑树的特性进行变更和调整。
插入操作可以分为以下几种情形。
(1)插入结点为根结点:
直接将插入的结点变更为黑色即可,满足性质2。
(2)插入结点的父结点为黑色结点:
该操作不会违反性质2和性质4,红黑树没有被破坏。所以不用做任何操作。

(3)插入结点的父结点为红色结点,且叔结点为红色结点:
叔结点定义为当前结点的祖父结点的另一个子结点,叔结点和父结点互为兄弟结点。此时该结点的祖父结点一定存在,否则父结点不可能为红色结点。
情况(3)同时可以分为父结点是祖父结点的左子结点还是右子结点的两种情况,且两种情况具有对称性。

这里我们考虑父结点为祖父结点左子结点的情况:将当前节点的父节点和叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点, 从新的当前节点重新开始算法。





(4)插入结点的父结点为红色结点,且叔结点为黑色结点,当前结点是其父结点的右子结点:
将当前结点的父节点做为新的当前结点,以新当前结点为支点进行左旋操作。





(5)插入结点的父结点为红色结点,且叔结点为黑色结点,当前结点是其父结点的左子结点:
将父结点变为黑色结点,祖父结点变为红色结点,在祖父结点为支点进行右旋操作。





插入操作 RBInsert(TNode) 实现:
public void RBInsert(TNode node) {

TNode previous = NIL;
TNode temp = rootNode;

while (temp != NIL) {
previous = temp;
if (temp.getValue() < node.getValue()) {
temp = temp.getRightNode();
} else {
temp = temp.getLeftNode();
}
}
node.setParentNode(previous);

if (previous == NIL) {
rootNode = node;
rootNode.se
4000
tParentNode(NIL);
} else  if (previous.getValue() > node.getValue()) {
previous.setLeftNode(node);
} else {
previous.setRightNode(node);
}

node.setLeftNode(NIL);
node.setRightNode(NIL);
node.setColorNode(0);
RBInsertFix(node);
}
插入修复操作 RBInsertFix(TNode) 实现:
private void RBInsertFix(TNode node) {

while (node.getParentNode().getColorNode() == 0) {
if (node.getParentNode() == node.getParentNode().getParentNode().getLeftNode()) {
TNode rightNuncle = node.getParentNode().getParentNode().getRightNode();
if (rightNuncle.getColorNode() == 0) {
rightNuncle.setColorNode(1);
node.getParentNode().setColorNode(1);
node.getParentNode().getParentNode().setColorNode(0);
node = node.getParentNode().getParentNode();
} else if (node == node.getParentNode().getRightNode()) {
node = node.getParentNode();
leftRotate(node);
} else {
node.getParentNode().setColorNode(1);
node.getParentNode().getParentNode().setColorNode(0);
rightRotate(node.getParentNode().getParentNode());
}
} else {
TNode leftNuncle = node.getParentNode().getParentNode().getLeftNode();
if (leftNuncle.getColorNode() == 0) {
leftNuncle.setColorNode(1);
node.getParentNode().setColorNode(1);
node.getParentNode().getParentNode().setColorNode(0);
node = node.getParentNode().getParentNode();
} else if (node == node.getParentNode().getLeftNode()) {
node = node.getParentNode();
rightRotate(node);
} else {
node.getParentNode().setColorNode(1);
node.getParentNode().getParentNode().setColorNode(0);
leftRotate(node.getParentNode().getParentNode());
}
}
}
rootNode.setColorNode(1);
}
由于一棵有n个结点的红黑树高度为O(lgn),因此在 RBInsertFix(TNode) 中仅当情况(3)发生时,当前指针会沿着树上升两层,while循环才可能会重复执行,RBInsert(TNode)因此总共花费时间O(lgn)。
同时,对于任何一棵红黑树的插入操作,其旋转不会超过2次,因为只要运行了情况(4)或情况(5),while循环就会结束。

3删除

红黑树的删除可以分为以下几种情况:
(1)当前结点是黑色结点,且兄弟结点为红色(此时父结点和兄弟结点的子结点均为黑色结点):
把父结点染成红色,把兄弟结点染成黑色,之后重新进入算法(我们只讨论当前结点是其父结点左孩子时的情况)。然后针对父结点做一次左旋操作。此变换后原红黑树性质5不变,而把问题转化为兄弟结点为黑色的情况。





(2)当前结点是黑色结点,兄弟结点是黑色结点,且兄弟结点的两个子结点均为黑色结点:
把当前结点和兄弟结点中抽取一重黑色追加到父结点上,把父结点当成新的当前结点,重新进入算法(此变换后性质5不变)。





(3)当前结点色是黑色结点,兄弟结点是黑色结点,兄弟的左子结点是红色的,右子结点是黑色的:

把兄弟结点染成红色,兄弟左子结点染成黑色,之后再在兄弟结点为支点进行右旋操作,之后重新进入算法。此是把当前的情况转化为情况(4),而性质5得以保持。





(4)当前结点是黑色结点,它的兄弟结点是黑色结点,但是兄弟结点的右子结点是红色结点,兄弟结点左子的颜色任意:

把兄弟结点染成当前结点父结点的颜色,把当前结点父结点染成黑色,兄弟结点右子结点染成黑色,之后以当前结点的父结点为支点进行左旋操作,此时算法结束,红黑树所有性质调整正确。





红黑树删除操作 RBDelete(TNode) 实现:
public TNode RBDelete(int data) {

TNode node = search(data);
TNode temp = NIL;
TNode child = NIL;
if (node == null) {
return null;
} else {
if (node.getLeftNode() == NIL || node.getRightNode() == NIL) {
temp = node;
} else {
temp = successor(node);
}

if (temp.getLeftNode() != NIL) {
child = temp.getLeftNode();
} else {
child = temp.getRightNode();
}

child.setParentNode(temp.getParentNode());

if (temp.getParentNode() == NIL) {
rootNode = child;
} else if (temp == temp.getParentNode().getLeftNode()) {
temp.getParentNode().setLeftNode(child);
} else {
temp.getParentNode().setRightNode(child);
}

if (temp != node) {
node.setValue(temp.getValue());
}

if (temp.getColorNode() == 1) {
RBDeleteFix(child);
}
return temp;
}
}
红黑树删除修复操作 RBDeleteFix(TNode)
实现:
private void RBDeleteFix(TNode node) {

while (node != rootNode && node.getColorNode() == 1) {
if (node == node.getParentNode().getLeftNode()) {
TNode rightBrother = node.getParentNode().getRightNode();
if (rightBrother.getColorNode() == 0) {
rightBrother.setColorNode(1);
node.getParentNode().setColorNode(0);
leftRotate(node.getParentNode());
rightBrother = node.getParentNode().getRightNode();
}

if (rightBrother.getLeftNode().getColorNode() == 1 &&
rightBrother.getRightNode().getColorNode() == 1) {
rightBrother.setColorNode(0);
node = node.getParentNode();
} else if (rightBrother.getRightNode().getColorNode() == 1) {
rightBrother.getLeftNode().setColorNode(1);
rightBrother.setColorNode(0);
rightRotate(rightBrother);
rightBrother = node.getParentNode().getRightNode();
} else {
rightBrother.setColorNode(node.getParentNode().getColorNode());
node.getParentNode().setColorNode(1);
rightBrother.getRightNode().setColorNode(1);
leftRotate(node.getParentNode());
node = rootNode;
}
} else {
TNode leftBrother = node.getParentNode().getLeftNode();
if (leftBrother.getColorNode() == 0) {
leftBrother.setColorNode(1);
node.getParentNode().setColorNode(0);
rightRotate(node.getParentNode());
leftBrother = node.getParentNode().getLeftNode();
}
if (leftBrother.getLeftNode().getColorNode() == 1 &&
leftBrother.getRightNode().getColorNode() == 1) {
leftBrother.setColorNode(0);
node = node.getParentNode();
} else if (leftBrother.getLeftNode().getColorNode() == 1) {
leftBrother.setColorNode(0);
leftBrother.getRightNode().setColorNode(1);
leftRotate(leftBrother);
leftBrother = node.getParentNode().getLeftNode();
} else {
leftBrother.setColorNode(node.getParentNode().getColorNode());
node.getParentNode().setColorNode(1);
leftBrother.getLeftNode().setColorNode(1);
rightRotate(node.getParentNode());
node = rootNode;
}
}
}
node.setColorNode(1);
}
因为含n个结点的红黑树高度为O(lgn),不调用RBDeleteFix(TNode)时该过程的总时间代价为O(lgn),情况(1)(3)(4)在各执行常数次数的颜色改变和至多3次旋转后便终止。情况(2)是while循环可以重复执行的唯一情况,然后当前结点的指针沿着树上升最多O(lgn)次,且不执行旋转操作。所以删除操作的总运行时间为O(lgn),做至多3次旋转操作。

三、红黑树代码实现

package Exp4;

import java.util.Formatter;
import java.util.Scanner;

public class RBTree {
private final TNode NIL = new TNode(null, null, null, 1, -1);
private TNode rootNode;

public RBTree() {
rootNode = NIL;
}

public RBTree(TNode rootNode) {
this.rootNode = rootNode;
}

public void RBInsert(TNode node) {

TNode previous = NIL;
TNode temp = rootNode;

while (temp != NIL) {
previous = temp;
if (temp.getValue() < node.getValue()) {
temp = temp.getRightNode();
} else {
temp = temp.getLeftNode();
}
}
node.setParentNode(previous);

if (previous == NIL) {
rootNode = node;
rootNode.setParentNode(NIL);
} else if (previous.getValue() > node.getValue()) {
previous.setLeftNode(node);
} else {
previous.setRightNode(node);
}

node.setLeftNode(NIL);
node.setRightNode(NIL);
node.setColorNode(0);
RBInsertFix(node);
}

private void RBInsertFix(TNode node) {

while (node.getParentNode().getColorNode() == 0) {
if (node.getParentNode() == node.getParentNode().getParentNode().getLeftNode()) {
TNode rightNuncle = node.getParentNode().getParentNode().getRightNode();
if (rightNuncle.getColorNode() == 0) { //Case 1
rightNuncle.setColorNode(1);
node.getParentNode().setColorNode(1);
node.getParentNode().getParentNode().setColorNode(0);
node = node.getParentNode().getParentNode();
} else if (node == node.getParentNode().getRightNode()) { //case 2
node = node.getParentNode();
leftRotate(node);
} else { //case 3
node.getParentNode().setColorNode(1);
node.getParentNode().getParentNode().setColorNode(0);
rightRotate(node.getParentNode().getParentNode());
}
} else {
TNode leftNuncle = node.getParentNode().getParentNode().getLeftNode();
if (leftNuncle.getColorNode() == 0) { //case 4
leftNuncle.setColorNode(1);
node.ge
c168
tParentNode().setColorNode(1);
node.getParentNode().getParentNode().setColorNode(0);
node = node.getParentNode().getParentNode();
} else if (node == node.getParentNode().getLeftNode()) { //case 5
node = node.getParentNode();
rightRotate(node);
} else { // case 6
node.getParentNode().setColorNode(1);
node.getParentNode().getParentNode().setColorNode(0);
leftRotate(node.getParentNode().getParentNode());
}
}
}
rootNode.setColorNode(1);
}

public TNode RBDelete(int data) { TNode node = search(data); TNode temp = NIL; TNode child = NIL; if (node == null) { return null; } else { if (node.getLeftNode() == NIL || node.getRightNode() == NIL) { temp = node; } else { temp = successor(node); } if (temp.getLeftNode() != NIL) { child = temp.getLeftNode(); } else { child = temp.getRightNode(); } child.setParentNode(temp.getParentNode()); if (temp.getParentNode() == NIL) { rootNode = child; } else if (temp == temp.getParentNode().getLeftNode()) { temp.getParentNode().setLeftNode(child); } else { temp.getParentNode().setRightNode(child); } if (temp != node) { node.setValue(temp.getValue()); } if (temp.getColorNode() == 1) { RBDeleteFix(child); } return temp; } }

private void RBDeleteFix(TNode node) { while (node != rootNode && node.getColorNode() == 1) { if (node == node.getParentNode().getLeftNode()) { TNode rightBrother = node.getParentNode().getRightNode(); if (rightBrother.getColorNode() == 0) { rightBrother.setColorNode(1); node.getParentNode().setColorNode(0); leftRotate(node.getParentNode()); rightBrother = node.getParentNode().getRightNode(); } if (rightBrother.getLeftNode().getColorNode() == 1 && rightBrother.getRightNode().getColorNode() == 1) { rightBrother.setColorNode(0); node = node.getParentNode(); } else if (rightBrother.getRightNode().getColorNode() == 1) { rightBrother.getLeftNode().setColorNode(1); rightBrother.setColorNode(0); rightRotate(rightBrother); rightBrother = node.getParentNode().getRightNode(); } else { rightBrother.setColorNode(node.getParentNode().getColorNode()); node.getParentNode().setColorNode(1); rightBrother.getRightNode().setColorNode(1); leftRotate(node.getParentNode()); node = rootNode; } } else { TNode leftBrother = node.getParentNode().getLeftNode(); if (leftBrother.getColorNode() == 0) { leftBrother.setColorNode(1); node.getParentNode().setColorNode(0); rightRotate(node.getParentNode()); leftBrother = node.getParentNode().getLeftNode(); } if (leftBrother.getLeftNode().getColorNode() == 1 && leftBrother.getRightNode().getColorNode() == 1) { leftBrother.setColorNode(0); node = node.getParentNode(); } else if (leftBrother.getLeftNode().getColorNode() == 1) { leftBrother.setColorNode(0); leftBrother.getRightNode().setColorNode(1); leftRotate(leftBrother); leftBrother = node.getParentNode().getLeftNode(); } else { leftBrother.setColorNode(node.getParentNode().getColorNode()); node.getParentNode().setColorNode(1); leftBrother.getLeftNode().setColorNode(1); rightRotate(node.getParentNode()); node = rootNode; } } } node.setColorNode(1); }

public TNode successor(TNode node) {

TNode rightChild = node.getRightNode();
if (rightChild != NIL) {
TNode previous = null;
while (rightChild != NIL) {
previous = rightChild;
rightChild = rightChild.getLeftNode();
}
return previous;
} else {
TNode parent = node.getParentNode();
while (parent != NIL && node != parent.getLeftNode()) {
node = parent;
parent = parent.getParentNode();
}
return parent;
}
}

public TNode search(int data) {

TNode temp = rootNode;

while (temp != NIL) {
if (temp.getValue() == data) {
return temp;
} else if (data < temp.getValue()) {
temp = temp.getLeftNode();
} else {
temp = temp.getRightNode();
}
}
return null;
}

private void leftRotate(TNode node) { TNode rightNode = node.getRightNode(); node.setRightNode(rightNode.getLeftNode()); if (rightNode.getLeftNode() != NIL) { rightNode.getLeftNode().setParentNode(node); } rightNode.setParentNode(node.getParentNode()); if (node.getParentNode() == NIL) { rootNode = rightNode; } else if (node == node.getParentNode().getLeftNode()) { node.getParentNode().setLeftNode(rightNode); } else { node.getParentNode().setRightNode(rightNode); } rightNode.setLeftNode(node); node.setParentNode(rightNode); }

private void rightRotate(TNode node) { TNode leftNode = node.getLeftNode(); node.setLeftNode(leftNode.getRightNode()); if (leftNode.getRightNode() != null) { leftNode.getRightNode().setParentNode(node); } leftNode.setParentNode(node.getParentNode()); if (node.getParentNode() == NIL) { rootNode = leftNode; } else if (node == node.getParentNode().getLeftNode()) { node.getParentNode().setLeftNode(leftNode); } else { node.getParentNode().setRightNode(leftNode); } leftNode.setRightNode(node); node.setParentNode(leftNode); }

public void printTree() {
inOrderTraverse(rootNode);
}

private void inOrderTraverse(TNode node) {

if (node != NIL) {
inOrderTraverse(node.getLeftNode());
if(node.getColorNode() == 0) {
Formatter formatter = new Formatter(System.out);
formatter.format("Node:%-5dColor:RED", node.getValue());
System.out.println("");
} else {
Formatter formatter = new Formatter(System.out);
formatter.format("Node:%-5dColor:BLACK", node.getValue());
System.out.println("");
}

inOrderTraverse(node.getRightNode());
}

}

public TNode getNIL() {
return NIL;
}

public static void main(String[] args) {

RBTree rbTree = new RBTree();

int n = 0;
int num = 0;
System.out.println("please input the number of nodes:");
Scanner inp = new Scanner(System.in);
num = inp.nextInt();
System.out.println("please input the value of these nodes:");
while(num-- > 0){
n = inp.nextInt();
rbTree.RBInsert(new TNode(n));
}
/*rbTree.RBInsert(new TNode(41));
rbTree.RBInsert(new TNode(38));
rbTree.RBInsert(new TNode(31));
rbTree.RBInsert(new TNode(12));
rbTree.RBInsert(new TNode(19));
rbTree.RBInsert(new TNode(8));*/

rbTree.printTree();

System.out.println("please input the nodes which you want to delete:");
n = inp.nextInt();
rbTree.RBDelete(n);
//rbTree.RBDelete(19);

rbTree.printTree();
}
}

class TNode {

private TNode leftNode;
private TNode rightNode;
private TNode parentNode;
private int colorNode; // RED=0,BLACK=1
private int value;

public TNode
(TNode leftNode, TNode rightNode, TNode parentNode, int colorNode, int value) {
this.leftNode = leftNode;
this.rightNode = rightNode;
this.parentNode = parentNode;
this.colorNode = colorNode;
this.value = value;
}

public TNode() {
}

public TNode(int value) {
this.value = value;
}

public TNode getLeftNode() {
return leftNode;
}

public void setLeftNode(TNode leftNode) {
this.leftNode = leftNode;
}

public TNode getRightNode() {
return rightNode;
}

public void setRightNode(TNode rightNode) {
this.rightNode = rightNode;
}

public TNode getParentNode() {
return parentNode;
}

public void setParentNode(TNode parentNode) {
this.parentNode = parentNode;
}

public int getColorNode() {
return colorNode;
}

public void setColorNode(int colorNode) {
this.colorNode = colorNode;
}

public int getValue() {
return value;
}

public void setValue(int value) {
this.value = value;
}
}

四、总结

       红黑树引入了“颜色”的概念。引入“颜色”的目的在于使得红黑树的平衡条件得以简化。正如著名的密码学专家Bruce Schneier所说的那样,“Being Partly balanced can be good enough”,红黑树并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。

       红黑树能够以O(log2 n)的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。当然,还有一些更好的,但实现起来更复杂的数据结构能够做到一步旋转之内达到平衡,但红黑树能够给我们一个比较“便宜”的解决方案。红黑树的算法时间复杂度和AVL树相同,但统计性能比AVL树更高。

       当然,红黑树并不适应所有应用树的领域。如果数据基本上是静态的,那么让他们待在他们能够插入,并且不影响平衡的地方会具有更好的性能。如果数据完全是静态的,例如,做一个哈希表,性能可能会更好一些。 在实际的系统中,例如,需要使用动态规则的防火墙系统,使用红黑树而不是散列表被实践证明具有更好的伸缩性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息