算法学习----红黑树
红黑树的介绍
先来看下算法导论对R-BTree的介绍:
红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。
通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
简单的说,红黑树具有以下5条性质:
1每个节点都包含 5个域:color,key ,leftNode,rightNode,parentNode.
2 根节点是黑色的。
3每个叶节点是黑色的。
4如果一个节点是红色的,那么他的两个儿子一定是黑色的。
5对于每个节点,从该节点到其他子孙节点的所有路径上包含相同数目的黑色节点。
红黑树的效率问题:
1.在一棵二叉查找树上,执行查找、插入、删除等操作,的时间复杂度为O(lgn)
因为,一棵由n个结点,随机构造的二叉查找树的高度为lgn,所以顺理成章,一般操作的执行时间为O(lgn)
2.但若是一棵具有n个结点的线性链,则此些操作最坏情况运行时间为O(n)
而红黑树,能保证在最坏情况下,基本的动态几何操作的时间均为O(lgn)。
红黑树的应用:
红黑树用在关联数组、字典的实现上。需要的空间比散列表小。
任何键值对应,需要随机存储和键有序的情况都可以用。
实例:
内存中比如缓存的(区块-数据),如编号对应内容,引索号对应数据项,日期对应日程,价格对应商品等。应用遍及,在内存中使用效率比较高。
C++ STL中的关联式容器:集合set、多重集合multiset、映射map、多重映射multimap等均采用了红黑树的变体set<int> s; map<int, string>s等
Java中的容器Set,Map的构造器TreeSet,TreeMap也使用了红黑树
在Linux内核中,用于组织虚拟区间的数据结构也是红黑树。
接下来,根据定义看看红黑树节点的实现:Java语言实现
package org.bupt.qyl;
/**
* 红黑树
* 首先是一个二叉搜索树
* 然后具有五个性质
* 性质1. 每个节点都包含 5个域 :color,key ,leftNode,rightNode,parentNode.
* 性质2. 根是黑色
* 性质3. 每个叶节点是黑色的。
* 性质4. 每个红色结点的两个子结点都是黑色 (从每个叶子到根的所有路径上不能有两个连续的红色结点)
* 性质5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点
* @author qyl
*
* @param <E>
*/
public class RBTree<E> {
//红黑树的根结点
private RBTreeNode<E> rootNode;
//构造方法
public RBTree(){
this.rootNode = null;
}
//构造方法结束
//get set方法
public RBTreeNode<E> getRootNode(){
return this.rootNode;
}
public void setRootNode(RBTreeNode<E>rootNode){
this.rootNode = rootNode;
}
//get set方法结束
public RBTreeNode<E> parentOf(RBTreeNode<E>node){
return node == null?null:node.getParentNode();
}
public RBTreeNode<E> leftOf(RBTreeNode<E>node){
return node == null?null:node.getLeftNode();
}
public RBTreeNode<E> rightOf(RBTreeNode<E>node){
return node == null?null:node.getRightNode();
}
public boolean colorOf(RBTreeNode<E>node){
return node == null?RBTreeNode.BLACK:node.getColor();
}
public void setColor(RBTreeNode<E>node,boolean color){
if(node == null) return;
node.setColor(color);
}
}
这个是C语言实现的红黑树节点的定义:
typedef int key_t;
typedef int data_t;
typedef enum color_t
{
RED = 0,
BLACK = 1
}color_t;
typedef struct rb_node_t
{
key_t key;
color_t color;
struct rb_node_t *parent;
struct rb_node_t *left;
struct rb_node_t *right,
}rb_node_t;
红黑树的旋转
当我们在对红黑树进行插入和删除等操作时,对树做了修改,那么可能会违背红黑树的性质。
为了保持红黑树的性质,我们可以通过对树进行旋转,即修改树种某些结点的颜色及指针结构,以达到对红黑树进行插入、删除结点等操作时,红黑树依然能保持它特有的性质。
左、右旋转的图示:注:旋转过程中二叉搜索树(BST)性质不变:α≤x≤β≤y≤γ
步骤解释:需要变动的是3根粗链
①y←right[x] //记录指向y节点的指针
②right[x]←left[y], p[left[y]]←x //β连到x右
③p[y]←p[x], p[x]的左或右指针指向y //y连到p[x]
④ Left[y]←x, p[x]←y //x连到y左
/**
* 围绕某个结点左旋转
* @param node
*/
private void rotateLeft(RBTreeNode<E>node){
if (node != null) {
/**
* 得到node的右子结点
*/
RBTreeNode<E> rightNode = node.getRightNode();
/**
* 把node的右子结点设为rightNode的左子结点
*/
node.setRightNode(rightNode.getLeftNode());
/**
* 如果rightNode的左子结点非空
* 就把rightNode的左子结点的父结点设为node
*/
if(rightNode.getLeftNode()!=null)
rightNode.getLeftNode().setParentNode(node);
/**
* 把rightNode的父结点设为node的父结点
*/
rightNode.setParentNode(node.getParentNode());
/**
* node的父结点为空
* 表明node是红黑树的根结点
*/
if(node.getParentNode() == null)
this.setRootNode(rightNode);
/**
* node父结点的左子结点存储的值与node相同
* 表明node是父结点的左子结点
*/
else if(node.getParentNode().getLeftNode() == node)
node.getParentNode().setLeftNode(rightNode);
/**
* node是父结点的右子结点
*/
else
node.getParentNode().setRightNode(rightNode);
/**
* rightNode的左子结点设为node
*/
rightNode.setLeftNode(node);
/**
* node的父结点设为rightNode
*/
node.setParentNode(rightNode);
}
}
右旋与左旋一样,只是方向改变了下:
/**
* 围绕某个结点右旋转
* 原理同左旋转相同
* 只不过方向换了下
* @param node
*/
private void rotateRight(RBTreeNode<E>node){
if(node != null){
/**
* 得到node的左子结点leftNode
*/
RBTreeNode<E>leftNode = node.getLeftNode();
/**
* 把node的左子结点设为leftNode的右子结点
*/
node.setLeftNode(leftNode.getRightNode());
/**
* 如果leftNode的右子结点非空
* 那么把leftNode的右子结点的父结点设为node
*/
if(leftNode.getRightNode() != null) leftNode.getRightNode().setParentNode(node);
/**
* leftNode的父结点设为node的父结点
*/
leftNode.setParentNode(node.getParentNode());
/**
* node为根结点
*/
if(node.getParentNode() == null)
this.rootNode = leftNode;
else if(node.getParentNode().getRightNode() == node)
node.getParentNode().setRightNode(leftNode);
else node.getParentNode().setLeftNode(leftNode);
leftNode.setRightNode(node);
node.setParentNode(leftNode);
}
}
红黑树的插入:
step 1:将z节点按BST树规则插入红黑树中,z是叶子节点;
step 2:将z涂红;
step 3:调整使其满足红黑树的性质;
在对红黑树进行插入操作时,我们一般总是插入红色的结点,因为这样可以在插入过程中尽量避免对树的调整。
那么,我们插入一个结点后,可能会使原树的哪些性质改变列?
由于,我们是按照二叉树的方式进行插入,因此元素的搜索性质不会改变。
如果插入的结点是根结点,性质2会被破坏,
如果插入结点的父结点是红色,则会破坏性质4。
因此,插入一个红色结点只会破坏性质2或性质4。
我们的回复策略很简单:
其一、把出现违背红黑树性质的结点向上移,如果能移到根结点,那么很容易就能通过直接修改根结点来恢复红黑树的性质。直接通过修改根结点来恢复红黑树应满足的性质。
其二、穷举所有的可能性,之后把能归于同一类方法处理的归为同一类,不能直接处理的化归到下面的几种情况,
情况1:插入的是根结点。
原树是空树,此情况只会违反性质2。
对策:直接把此结点涂为黑色。
情况2:插入的结点的父结点是黑色。
此不会违反性质2和性质4,红黑树没有被破坏。
对策:什么也不做。
情况3:当前结点的父结点是红色且祖父结点的另一个子结点(叔叔结点)是红色。
此时父结点的父结点一定存在,否则插入前就已不是红黑树。
与此同时,又分为父结点是祖父结点的左子还是右子,对于对称性,我们只要解开一个方向就可以了。
在此,我们只考虑父结点为祖父左子的情况。
同时,还可以分为当前结点是其父结点的左子还是右子,但是处理方式是一样的。我们将此归为同一类。
对策:将当前节点的父节点和叔叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点,从新的当前节点重新开始算法。
红黑树插入节点算法的Java实现:
public void insertNode(RBTreeNode<E> node) {
if (node == null) return;
if (node.getValue() == null) return;
/**
* rootnode为空,
* 空的红黑树
*/
if (this.getRootNode() == null) {
rootNode = new RBTreeNode<E>(node.getValue());
return;
}
/**
* rootnode非空
*/
RBTreeNode<E> tempNode = this.rootNode;
while (true) {
/**
* 红黑树中已经存在此结点
* 无须插入
* 直接返回
*/
if (tempNode.compareTo(node) == 0) {
return;
}else if (tempNode.compareTo(node) > 0) {
if (tempNode.getLeftNode() != null) {
tempNode = tempNode.getLeftNode();
}else {
tempNode.setLeftNode(node);
node.setParentNode(tempNode);
fixAfterInsertion(node);
return;
}
}else {
if (tempNode.getRightNode() != null) {
tempNode = tempNode.getRightNode();
}else {
tempNode.setRightNode(node);
node.setParentNode(tempNode);
fixAfterInsertion(node);
return;
}
}
}
}
private void fixAfterInsertion(RBTreeNode<E>node){
if(node == null) return;
/**
* node标记为红结点
*/
node.setColor(RBTreeNode.RED);
/**
* while循环条件
* node非空 并且
* node非根结点 并且
* node的父结点的颜色为红色
*/
while(node != null && ( node != rootNode) && node.getParentNode().getColor() == RBTreeNode.RED){
/**
* node的父结点是node祖父结点的左子结点
*/
if(parentOf(node) == leftOf(parentOf(parentOf(node)))){
/**
* y是node祖父结点的右子结点
*/
RBTreeNode<E> y = rightOf(parentOf(parentOf(node)));
/**
* 如果y的颜色是红色
*/
if(colorOf(y) == RBTreeNode.RED){
/**
* 把node父结点的颜色设为黑色
*/
setColor(parentOf(node),RBTreeNode.BLACK);
/**
* 把y的颜色设为黑色
*/
setColor(y,RBTreeNode.BLACK);
/**
* 把node祖父结点的颜色设为红色
*/
setColor(parentOf(parentOf(node)),RBTreeNode.RED);
/**
* node指向node的祖父结点
*/
node = parentOf(parentOf(node));
} else {
/**
* 这块y的颜色是黑色
*/
/**
* 如果node是父结点的右子结点
*/
if(node == rightOf(parentOf(node))){
/**
* node指向node的父结点
*/
node = parentOf(node);
/**
* 围绕node进行左旋转
*/
rotateLeft(node);
}
/**
* 把node的父结点设置为黑色
*/
setColor(parentOf(node),RBTreeNode.BLACK);
/**
* 把node的祖父结点设置为红色
*/
setColor(parentOf(parentOf(node)),RBTreeNode.RED);
/**
* 围绕node的祖父结点进行右旋转
*/
rotateRight(parentOf(parentOf(node)));
}
/**
* node的父结点是node祖父结点的右子结点
*/
} else {
/**
* y指向node祖父结点的左子结点
*/
RBTreeNode<E> y = leftOf(parentOf(parentOf(node)));
/**
* 如果y的颜色是红色
*/
//if (colorOf(y) == RED) {
if(colorOf(y) == RBTreeNode.RED){
/**
* 把node的父结点设为黑色
*/
setColor(parentOf(node),RBTreeNode.BLACK);
/**
* 把y的颜色设为黑色
*/
setColor(y,RBTreeNode.BLACK);
/**
* 把node的祖父结点设为红色
*/
setColor(parentOf(parentOf(node)),RBTreeNode.RED);
/**
* node结点指向其祖父结点
* 然后向上逐层调整
*/
node = parentOf(parentOf(node));
} else {
/**
* node是node父结点的左子结点
*/
if(node == leftOf(parentOf(node))){
/**
* node指向node的父结点
*/
node = parentOf(node);
/**
* 围绕node进行右旋转
* 其实node已经是老node的父结点了
*/
rotateRight(node);
}
/**
* 把node的父结点设为黑色
*/
setColor(parentOf(node),RBTreeNode.BLACK);
/**
* 把node的祖父结点设为红色
*/
setColor(parentOf(parentOf(node)),RBTreeNode.RED);
/**
* 围绕node的祖父结点进行左旋转
*/
rotateLeft(parentOf(parentOf(node)));
}
}
}
/**
* 把根结点设成黑色
*/
setColor(rootNode,RBTreeNode.BLACK);
}
- 找到真正的删除点:当被删除结点n存在左右孩子时,真正的删除点应该是n的中序遍在前驱(或后继)
- 所以可以推断出,在进行删除操作时,真正的删除点必定是只有一个孩子或没有孩子的结点。而根据红黑树的性质可以得出以下两个结论:
- 删除操作中真正被删除的必定是只有一个红色孩子或没有孩子的结点。
- 如果真正的删除点是一个红色结点,那么它必定是一个叶子结点。
public void deleteNode(RBTreeNode<E> p) {
//modCount++;
//size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
/**
* p的两个子结点都非空
*/
if (p.getLeftNode() != null && p.getRightNode() != null) {
RBTreeNode<E> s = successor(p);
p.setValue(s.getValue());
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
RBTreeNode<E> replacement = (p.getLeftNode() != null ? p.getLeftNode() : p.getRightNode());
if (replacement != null) {
// Link replacement to parent
//replacement.parent = p.parent;
replacement.setParentNode(p.getParentNode());
/**
* p的父结点为空
* p是根结点
*/
if (p.getParentNode() == null)
rootNode = replacement;
/**
* p是父结点的左子结点
*/
else if (p == p.getParentNode().getLeftNode())
p.getParentNode().setLeftNode(replacement);
/**
* p是父结点的右子结点
*/
else
p.getParentNode().setRightNode(replacement);
/**
* ok
* 把p的左右子结点,父结点指针全部置空
* p被删除了
*/
// Null out links so they are OK to use by fixAfterDeletion.
p.setLeftNode(null);
p.setRightNode(null);
p.setParentNode(null);
// Fix replacement
if (p.getColor() == RBTreeNode.BLACK)
fixAfterDeletion(replacement);
} else if (p.getParentNode() == null) {
rootNode = null;
} else { // No children. Use self as phantom replacement and unlink.
/**
* 在这个else里面
* replacement是null
* 表明p的左右两个子结点都为空
*/
if (p.getColor() == RBTreeNode.BLACK)
fixAfterDeletion(p);
if (p.getParentNode() != null) {
/**
* p是p的父结点的左子结点
*/
if (p == p.getParentNode().getLeftNode())
p.getParentNode().setLeftNode(null);
/**
* p是p的父结点的右子结点
*/
else if (p == p.getParentNode().getRightNode())
p.getParentNode().setRightNode(null);
/**
* 把p的父结点设为空
* 删除OK
*/
p.setParentNode(null);
}
}
}
private void fixAfterDeletion(RBTreeNode<E>node){
/**
* 循环条件:
* node不是根结点
* 而且
* node结点的颜色为黑色
*/
while(this.getRootNode() != node && colorOf(node) == RBTreeNode.BLACK){
/**
* node是node父结点的左子结点
*/
if( node == leftOf(parentOf(node))){
/**
* sib指向node父结点的右子结点
*/
RBTreeNode<E> sib = rightOf(parentOf(node));
/**
* 参见colorOf(RBTreeNode<E>)方法实现
* 有对空值的判断
*/
if(colorOf(sib) == RBTreeNode.RED){
/**
* setColor()中也有对空值的判断
*/
setColor(sib, RBTreeNode.BLACK);
/**
* 把node的父结点设为红色
*/
setColor(parentOf(node), RBTreeNode.RED);
/**
* 围绕node的父结点进行左旋转
*/
rotateLeft(parentOf(node));
/**
* sib指向node父结点的右子结点
*/
sib = rightOf(parentOf(node));
}
/**
* 如果sib的两个子结点都是黑色
*/
if(colorOf(leftOf(sib)) == RBTreeNode.BLACK &&
colorOf(rightOf(sib)) == RBTreeNode.BLACK) {
/**
* 把sib设为红色
*/
setColor(sib, RBTreeNode.RED);
/**
* node指向它的父结点
*/
node = parentOf(node);
} else {//这个else表示sib的两个子结点并非都是黑色,一红一黑或都是红色
/**
* 如果sib的右子结点是黑色
* 那它的左子结点肯定是红色
*/
if(colorOf(rightOf(sib)) == RBTreeNode.BLACK) {
/**
* 把sib的左子结点设为黑色
*/
setColor(leftOf(sib), RBTreeNode.BLACK);
/**
* 把sib的颜色设为黑色
*/
setColor(sib, RBTreeNode.RED);
/**
* 围绕sib进行右旋转
*/
rotateRight(sib);
/**
* sib指向node父结点的右子结点
*/
sib = rightOf(parentOf(node));
}
/**
* 把sib的颜色设为node父结点的颜色
*/
//setColor(sib, colorOf(parentOf(x)));
setColor(sib, colorOf(parentOf(node)));
/**
* 把node父结点设为黑色
*/
setColor(parentOf(node), RBTreeNode.BLACK);
/**
* 把sib右子结点的颜色设为黑色
*/
setColor(rightOf(sib), RBTreeNode.BLACK);
/**
* 围绕node的父结点进行左旋转
*/
rotateLeft(parentOf(node));
/**
* node结点指向红黑树的根结点
*/
node = rootNode;
}
} else { // symmetric
/**
* sib指向node父结点的左子结点
*/
RBTreeNode<E> sib = leftOf(parentOf(node));
/**
* 如果sib的颜色是红色
*/
if(colorOf(sib) == RBTreeNode.RED) {
/**
* 把sib的颜色设为黑色
*/
setColor(sib, RBTreeNode.BLACK);
/**
* 把node父结点的颜色设为红色
*/
setColor(parentOf(node), RBTreeNode.RED);
/**
* 围绕node的父结点进行右旋转
*/
rotateRight(parentOf(node));
/**
* sib指向node父结点的左子结点
*/
sib = leftOf(parentOf(node));
}
/**
* 如果sib的两个子结点都是黑色
*/
if (colorOf(rightOf(sib)) == RBTreeNode.BLACK &&
colorOf(leftOf(sib)) == RBTreeNode.BLACK) {
/**
* 把sib的颜色设为红色
*/
setColor(sib, RBTreeNode.RED);
/**
* node指向node的父结点
*/
node = parentOf(node);
} else {
/**
* 如果sib的左子结点是黑色
*/
if (colorOf(leftOf(sib)) == RBTreeNode.BLACK) {
/**
* 把sib的右子结点设为黑色
*/
setColor(rightOf(sib), RBTreeNode.BLACK);
/**
* 把sib的颜色设为红色
*/
setColor(sib, RBTreeNode.RED);
/**
* 围绕sib进行左旋转
*/
rotateLeft(sib);
/**
* sib指向node父结点的左子结点
*/
sib = leftOf(parentOf(node));
}
/**
* 把sib的颜色设为node父结点的颜色
*/
setColor(sib, colorOf(parentOf(node)));
/**
* 把node父结点的颜色设为黑色
*/
setColor(parentOf(node), RBTreeNode.BLACK);
/**
* 把sib左子结点的颜色设为黑色
*/
//setColor(leftOf(sib), BLACK);
setColor(leftOf(sib), RBTreeNode.BLACK);
/**
* 围绕node的父结点进行右旋转
*/
rotateRight(parentOf(node));
node = rootNode;
}
}
}
setColor(node, RBTreeNode.BLACK);
}