一、红黑树介绍
引用算法导论里面的话:
一颗红黑树是满足下面红黑性质的二叉搜索树:
1、每个结点是红色,或是黑色的
2、根结点是黑色的
3、每个叶结点NIL是黑色的
4、如果一个结点是红色的,则它的两个子结点都是黑色的
5、对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
根据红黑树的这些性质,易证:一颗有n个内部结点的红黑树的高度至多为2lg(n+1)
通过这条定理我们可以知道,红黑树的动态集合操作:查找,添加,删除等时间复杂度均为O(lgn)。
二、旋转
在介绍红黑树的动态集和操作前,我们首先介绍二叉搜索树里的旋转操作,这是一种能保持二叉搜索树性质的搜索树局部操作,分为左旋和右旋操作,首先对左旋操作进行介绍。
如图所示,对X结点进行左旋,左旋后X结点成为Y结点的子结点,Y结点接替X结点原来的位置,C++代码如下:
template <class T>
void RBTree<T>::leftRotate(Node<T> *&root,Node<T> *x)
{
Node<T> *y=x->right;
y->parent=x->parent;
x->right=y->left;
if (y->left!=nullptr)
y->left->parent=x;
if (x->parent!=nullptr)
{
if(x->parent->left==x)
x->parent->left=y;
else
x->parent->right=y;
}
else
root=y;
x->parent=y;
y->left=x;
}
右旋操作如下图所示
右旋后Y结点成为X结点的子结点,X结点接替Y结点原来的位置,C++代码如下:
template <class T>
void RBTree<T>::rightRotate(Node<T> *&root, Node<T> *x)
{
Node<T> *y=x->left;
y->parent=x->parent;
x->left=y->right;
if (y->right!=nullptr)
y->right->parent=x;
if (x->parent!=nullptr)
{
if(x->parent->left==x)
x->parent->left=y;
else
x->parent->right=y;
}
else
root=y;
x->parent=y;
y->right=x;
}
三、添加操作
介绍完左旋与右旋操作后,就可以开始介绍添加操作。
添加操作一般可以分为三步:
1、将红黑树作为一颗二叉查找树,需要插入结点的位置
2、将该结点的颜色赋为红色。
(为什么要赋为红色,根据红黑树性质5:对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点,将结点赋值为红色,就可以不违反性质5。但是这样又会遇到一个问题,假如加入结点的父结点也为红色,那么就会违反性质4:如果一个结点是红色的,则它的两个子结点都是黑色的。所以这时候我们就要执行步骤三)
3、对插入后的红黑树执行一系列的着色与旋转操作,使其重新成为一颗红黑树。
(这一系列操作一般我们都会定义为insertFixUp函数,将在后面讲到)
添加操作的C++实现代码如下
template <class T>
void RBTree<T>::insert(Node<T> *&root,Node<T> *node)
{
Node<T> *y=nullptr;
Node<T> *x=root;
while(x!=nullptr)
{
y=x;
if(node->key<x->key)
x=x->left;
else
x=x->right;
}
node->parent=y;
if(y!=nullptr)
{
if(node->key<y->key)
y->left=node;
else
y->right=node;
}
else
root=node;
node->color=RED;
insertFixUp(root,node);
}
红黑树的插入修复操作,主要目的就是要将待处理结点分支上的一个红色属性(一般就是父结点的红色属性)往上或往兄弟分支移动,从而使红黑树再次满足性质4。
我们可以将红黑树的修复操作看作是一个魔方复原的过程,对待处理结点不同的局部情况执行不同的操作以得到其他情况或是完成红黑树的修复。
一般插入操作修复分为3种情况:
case1:叔叔结点为红色(叔叔结点就是父结点的兄弟结点,父结点在插入修复时都为红色),如下图
这时候我们要执行的操作就是将父结点与叔叔结点同时赋为黑色,祖父结点赋为红色,将处理结点转为祖父结点,对祖父结点继续处理。(目的就是将父结点与叔叔结点的红色属性同时上移,这样能在不破坏红黑树性质的情况下将处理结点分支的红色属性上移)如果祖父结点为根结点,将根结点赋黑色,操作结束。
case2:叔叔结点为黑色,待处理结点是近叔叔结点,如下图所示
这时候我们要进行的操作就是将待处理结点转化为父结点,并对父结点左旋,这样就能把问题处理的情况转化为case3。
case3:叔叔结点为黑色,待处理结点是远叔叔结点。如下图所示
这时候我们要执行的操作是将父结点转化为黑色,祖父结点转化为红色,并对祖父结点右旋(这时候相当于把处理结点分支上的一个红色属性给了兄弟分支),至此红黑树修复完成。
插入修复的代码如下
template <class T>
void RBTree<T>::insertFixUp(Node<T> *&root,Node<T> *node)
{
Node<T> *parent,*grdparent,*uncle;
while ((node->parent!=nullptr)&&(node->parent->color==RED))
{
parent=node->parent;
grdparent=parent->parent;
if (parent==grdparent->left)
{
uncle=grdparent->right;
if(uncle!=nullptr&&uncle->color==RED)
{
//case1:叔叔结点为红色,这时候将父结点与叔叔结点的红色属性同时往上移到祖父结点,
//处理结点转到祖父结点
uncle->color=BLACK;
parent->color=BLACK;
grdparent->color=RED;
node=grdparent;
continue;
}
else if (parent->right==node)
{
//case2:叔叔结点为黑色且待处理结点靠近叔叔结点,这时候将待处理结点转化为父结点,并对父结点左旋
//这样就能把问题处理的情况转化为case3
node=node->parent;
leftRotate(root,node);
}
else if (parent->left==node)
{
//case3:叔叔结点为黑色且待处理结点远离叔叔结点,这时候将父结点转化为黑色,祖父结点转化为红色,
//并对祖父结点右旋(这时候相当于把处理结点分支上的一个红色属性给了兄弟分支),红黑树修复完成。
parent->color=BLACK;
grdparent->color=RED;
rightRotate(root,grdparent);
}
}
else
{
uncle=grdparent->left;
if(uncle!=nullptr&&uncle->color==RED)
{
uncle->color=BLACK;
parent->color=BLACK;
grdparent->color=RED;
node=grdparent;
continue;
}
else if (parent->left==node)
{
node=node->parent;
rightRotate(root,node);
}
else if (parent->right==node)
{
parent->color=BLACK;
grdparent->color=RED;
leftRotate(root,grdparent);
}
}
}
root->color=BLACK;
}
四、删除操作
删除操作比较复杂,如果直接在找到删除结点的位置对结点进行删除会大量破坏红黑树的性质,故这里采用的办法是找到待删除结点的前驱(左结点的最大子结点)或后继(右结点的最小结点)。将其与待删除结点替换,我们对前驱或后继进行处理。
一般也分为三步:
1、找到待删除结点在红黑树中的位置
2、找到待删除结点的前驱或后继,将其与待删除结点交换,删除前驱或后继,连接删除位置的父子结点并标记,若删除的替换结点为红色,对红黑树性质没影响,操作结束,若删除的替换结点为黑色,则会破坏红黑树性质,这时候我们执行步骤三
3、通过一系列的旋转与着色,修复红黑树性质(一般这一系列操作命名为removeFixUp)
C++代码如下:
template<class T>
void RBTree<T>::remove(Node<T> *&root, Node<T> *node)
{
Node<T> *child,*parent;
RBTcolor color;
if(node->left!=nullptr&&node->right!=nullptr)
{
Node<T> *replace=node;
replace=replace->right;
while(replace->left!=nullptr)
replace=replace->left;
node->key=replace->key;
child=replace->right;
parent=replace->parent;
color=replace->color;
if(parent->left==replace)
parent->left=child;
else
parent->right=child;
if(child)
child->parent=parent;
if(color==BLACK)
removeFixUp(root,child,parent);
delete replace;
return;
}
if(node->left!=nullptr)
child=node->left;
else
child=node->right;
parent=node->parent;
color=node->color;
if(child)
child->parent=parent;
if(parent)
{
if(parent->left==node)
parent->left=child;
else
parent->right=child;
}
else
root=child;
if(color==BLACK)
removeFixUp(root,child,parent);
delete node;
}
template <class T>
void RBTree<T>::remove(T key)
{
Node<T> *node;
if((node=search(mRoot,key))!=nullptr)
remove(mRoot,node);
}
template <class T>
Node<T>* RBTree<T>::search(Node<T> *root,T key) const
{
if(root==nullptr||root->key==key)
return root;
if (key<root->key)
return search(root->left,key);
else
return search(root->right,key);
}
红黑树的删除修复操作,主要目的是使待处理结点(一般为删除结点的儿子结点)上的两个黑色属性(注意为什么两个黑色属性,删除结点的黑色属性暂时先放在待处理结点上,只有待处理结点与删除结点同为黑时需进行删除修复操作)
与添加修复操作类似,删除修复操作也可以分为4种情况
case1:待处理结点的兄弟结点为黑色,且兄弟结点具有两个黑色结点,如图所示
这种情况下执行的操作是,将处理结点与兄弟结点的一个黑色属性同时上移给父亲结点。父亲结点黑色属性加一,然后将处理结点转为父亲结点,问题转为case1,2,3,4处理
case2:兄弟结点颜色为红色时,如下图所示
这时候要执行的操作是,交换兄弟节点与父结点位置,并对父结点左旋,目的是使修正结点获得一个黑兄弟结点。这样处理后将问题转为case1,3,4处理。
case3:当兄弟结点为黑色,近侄子结点为红色时,如下图所示
这时候执行的操作是,将兄弟结点设为红色,近侄子结点设为黑色。对兄弟结点进行右旋,使问题转化为case4处理
case4:当兄弟结点为黑色,远侄子结点为红色时,如下图所示
这时候要执行的操作是,将远侄子结点继承兄弟结点黑色,兄弟结点继承父结点颜色,父结点获得黑色,对父结点左旋,红黑树平衡(这一步操作就是将远侄子结点的红色属性承接了处理结点的一个黑色属性)
删除修复操作的代码如下:
template <class T>
void RBTree<T>::removeFixUp(Node<T> *&root,Node<T> *node,Node<T> *parent)//node可能是空结点,故需要parent参数来确定父结点
{
Node<T> *brother;
while((!node||node->color==BLACK)&&node!=root)
{
if(node==parent->left)
{
brother=parent->right;
if(brother->color==RED)
{
//case1:当兄弟节点颜色为红色时,交换兄弟节点与父结点位置,并对父结点左旋,使修正结点获得一个黑兄弟结点。
//这样处理后将问题转为case2,3,4处理
brother->color=BLACK;
parent->color=RED;
leftRotate(root,parent);
brother=parent->right;
}
else if((!brother->left||brother->left->color==BLACK)&&(!brother->right||brother->right->color==BLACK))
{
//case2:当兄弟结点为黑色,且具有2个黑色子结点时,将处理结点与兄弟结点的一个黑色属性同时上移给父亲结点。
//父亲结点黑色属性加一,然后将处理结点转为父亲结点,问题转为case1,3,4处理
brother->color=RED;
node=parent;
parent=node->parent;
}
else
{
if(!brother->right||brother->right->color==BLACK)
{
//case3:当兄弟结点为黑色,近侄子结点为红色时,将兄弟结点设为红色,近侄子结点设为黑色。
//对兄弟结点进行右旋,使问题转化为case4处理
brother->left->color=BLACK;
brother->color=RED;
rightRotate(root,brother);
brother->parent->right;
}
//case4:当兄弟结点为黑色,远侄子结点为红色时,将远侄子结点继承兄弟结点黑色,兄弟结点继承父结点颜色,
//父结点获得黑色,对父结点左旋,红黑树平衡
brother->color=parent->color;
parent->color=BLACK;
brother->right->color=BLACK;
leftRotate(root,parent);
node=root;//脱出循环
}
}
else
{
brother=parent->left;
if(brother->color==RED)
{
brother->color=BLACK;
parent->color=RED;
rightRotate(root,parent);
brother=parent->left;
}
else if ((!brother->left||brother->left->color==BLACK)&&(!brother->right||brother->right->color==BLACK))
{
brother->color=RED;
node=parent;
parent=node->parent;
}
else
{
if(!brother->left||brother->left->color==BLACK)
{
brother->color=RED;
brother->right->color=BLACK;
leftRotate(root,brother);
brother=parent->left;
}
brother->color=parent->color;
parent->color=BLACK;
brother->left->color=BLACK;
rightRotate(root,parent);
node=root;
}
}
}
if(node!=nullptr)
node->color=BLACK;
}
至此一个有添加删除查找基本功能的红黑树就完成,下面是我写的一个红黑树类的完整代码
#ifndef RBTREE_H
#define RBTREE_H
#include "iostream"
#include "iomanip"
using namespace std;
enum RBTcolor{RED,BLACK};
template <class T>
class Node
{
public:
T key;
Node *parent;
Node *left;
Node *right;
RBTcolor color;
Node()
{
key=0;
parent=nullptr;
left=nullptr;
right=nullptr;
color=RED;
}
Node(T value,RBTcolor c,Node *p,Node *l,Node *r):
key(value),color(c),parent(p),left(l),right(r){}
};
template<class T>
class RBTree
{
private:
Node<T> *mRoot;
public:
RBTree();
~RBTree();
//前序遍历红黑树
void preOrder();
//中序遍历
void inOrder();
//后序遍历
void postOrder();
//查找红黑树某节点
Node<T>* search(T key);
//插入红黑树
void insert(T key);
//删除红黑树某节点
void remove(T key);
//销毁红黑树
void destory();
//打印红黑树
void print();
private:
void preOrder(Node<T> *root) const;
void inOrder(Node<T> *root) const;
void postOrder(Node<T> *root) const;
Node<T>* search(Node<T> *root,T key) const;
//左旋
void leftRotate(Node<T> *&root,Node<T> *x);
//右旋
void rightRotate(Node<T> *&root,Node<T> *x);
//插入函数
void insert(Node<T> *&root,Node<T> *node);
//删除函数
void remove(Node<T> *&root,Node<T> *node);
//插入修正函数
void insertFixUp(Node<T> *&root,Node<T> *node);
//删除修正函数
void removeFixUp(Node<T> *&root,Node<T> *node,Node<T> *parent);
//销毁红黑树
void destory(Node<T> *&tree);
void print(Node<T> *tree,T key,int direction);
};
template <class T>
RBTree<T>::RBTree():mRoot(nullptr)
{
mRoot=nullptr;
}
template <class T>
Node<T>* RBTree<T>::search(Node<T> *root,T key) const
{
if(root==nullptr||root->key==key)
return root;
if (key<root->key)
return search(root->left,key);
else
return search(root->right,key);
}
template <class T>
Node<T>* RBTree<T>::search(T key)
{
search(mRoot,key);
}
template <class T>
void RBTree<T>::leftRotate(Node<T> *&root,Node<T> *x)
{
Node<T> *y=x->right;
y->parent=x->parent;
x->right=y->left;
if (y->left!=nullptr)
y->left->parent=x;
if (x->parent!=nullptr)
{
if(x->parent->left==x)
x->parent->left=y;
else
x->parent->right=y;
}
else
root=y;
x->parent=y;
y->left=x;
}
template <class T>
void RBTree<T>::rightRotate(Node<T> *&root, Node<T> *x)
{
Node<T> *y=x->left;
y->parent=x->parent;
x->left=y->right;
if (y->right!=nullptr)
y->right->parent=x;
if (x->parent!=nullptr)
{
if(x->parent->left==x)
x->parent->left=y;
else
x->parent->right=y;
}
else
root=y;
x->parent=y;
y->right=x;
}
template <class T>
void RBTree<T>::insertFixUp(Node<T> *&root,Node<T> *node)
{
Node<T> *parent,*grdparent,*uncle;
while ((node->parent!=nullptr)&&(node->parent->color==RED))
{
parent=node->parent;
grdparent=parent->parent;
if (parent==grdparent->left)
{
uncle=grdparent->right;
if(uncle!=nullptr&&uncle->color==RED)
{
//case1:叔叔结点为红色,这时候将父结点与叔叔结点的红色属性同时往上移到祖父结点,
//处理结点转到祖父结点
uncle->color=BLACK;
parent->color=BLACK;
grdparent->color=RED;
node=grdparent;
continue;
}
else if (parent->right==node)
{
//case2:叔叔结点为黑色且待处理结点靠近叔叔结点,这时候将待处理结点转化为父结点,并对父结点左旋
//这样就能把问题处理的情况转化为case3
node=node->parent;
leftRotate(root,node);
}
else if (parent->left==node)
{
//case3:叔叔结点为黑色且待处理结点远离叔叔结点,这时候将父结点转化为黑色,祖父结点转化为红色,
//并对祖父结点右旋(这时候相当于把处理结点分支上的一个红色属性给了兄弟分支),红黑树修复完成。
parent->color=BLACK;
grdparent->color=RED;
rightRotate(root,grdparent);
}
}
else
{
uncle=grdparent->left;
if(uncle!=nullptr&&uncle->color==RED)
{
uncle->color=BLACK;
parent->color=BLACK;
grdparent->color=RED;
node=grdparent;
continue;
}
else if (parent->left==node)
{
node=node->parent;
rightRotate(root,node);
}
else if (parent->right==node)
{
parent->color=BLACK;
grdparent->color=RED;
leftRotate(root,grdparent);
}
}
}
root->color=BLACK;
}
template <class T>
void RBTree<T>::insert(Node<T> *&root,Node<T> *node)
{
Node<T> *y=nullptr;
Node<T> *x=root;
while(x!=nullptr)
{
y=x;
if(node->key<x->key)
x=x->left;
else
x=x->right;
}
node->parent=y;
if(y!=nullptr)
{
if(node->key<y->key)
y->left=node;
else
y->right=node;
}
else
root=node;
node->color=RED;
insertFixUp(root,node);
}
template <class T>
void RBTree<T>::insert(T key)
{
Node<T> *node=nullptr;
if((node=new Node<T>(key,BLACK,nullptr,nullptr,nullptr))==nullptr)
return;
insert(mRoot,node);
}
template<class T>
void RBTree<T>::remove(Node<T> *&root, Node<T> *node)
{
Node<T> *child,*parent;
RBTcolor color;
if(node->left!=nullptr&&node->right!=nullptr)
{
Node<T> *replace=node;
replace=replace->right;
while(replace->left!=nullptr)
replace=replace->left;
node->key=replace->key;
child=replace->right;
parent=replace->parent;
color=replace->color;
if(parent->left==replace)
parent->left=child;
else
parent->right=child;
if(child)
child->parent=parent;
if(color==BLACK)
removeFixUp(root,child,parent);
delete replace;
return;
}
if(node->left!=nullptr)
child=node->left;
else
child=node->right;
parent=node->parent;
color=node->color;
if(child)
child->parent=parent;
if(parent)
{
if(parent->left==node)
parent->left=child;
else
parent->right=child;
}
else
root=child;
if(color==BLACK)
removeFixUp(root,child,parent);
delete node;
}
template <class T>
void RBTree<T>::remove(T key)
{
Node<T> *node;
if((node=search(mRoot,key))!=nullptr)
remove(mRoot,node);
}
template <class T>
void RBTree<T>::removeFixUp(Node<T> *&root,Node<T> *node,Node<T> *parent)//node可能是空结点,故需要parent参数来确定父结点
{
Node<T> *brother;
while((!node||node->color==BLACK)&&node!=root)
{
if(node==parent->left)
{
brother=parent->right;
if(brother->color==RED)
{
//case1:当兄弟节点颜色为红色时,交换兄弟节点与父结点位置,并对父结点左旋,使修正结点获得一个黑兄弟结点。
//这样处理后将问题转为case2,3,4处理
brother->color=BLACK;
parent->color=RED;
leftRotate(root,parent);
brother=parent->right;
}
else if((!brother->left||brother->left->color==BLACK)&&(!brother->right||brother->right->color==BLACK))
{
//case2:当兄弟结点为黑色,且具有2个黑色子结点时,将处理结点与兄弟结点的一个黑色属性同时上移给父亲结点。
//父亲结点黑色属性加一,然后将处理结点转为父亲结点,问题转为case1,3,4处理
brother->color=RED;
node=parent;
parent=node->parent;
}
else
{
if(!brother->right||brother->right->color==BLACK)
{
//case3:当兄弟结点为黑色,近侄子结点为红色时,将兄弟结点设为红色,近侄子结点设为黑色。
//对兄弟结点进行右旋,使问题转化为case4处理
brother->left->color=BLACK;
brother->color=RED;
rightRotate(root,brother);
brother->parent->right;
}
//case4:当兄弟结点为黑色,远侄子结点为红色时,将远侄子结点继承兄弟结点黑色,兄弟结点继承父结点颜色,
//父结点获得黑色,对父结点左旋,红黑树平衡
brother->color=parent->color;
parent->color=BLACK;
brother->right->color=BLACK;
leftRotate(root,parent);
node=root;//脱出循环
}
}
else
{
brother=parent->left;
if(brother->color==RED)
{
brother->color=BLACK;
parent->color=RED;
rightRotate(root,parent);
brother=parent->left;
}
else if ((!brother->left||brother->left->color==BLACK)&&(!brother->right||brother->right->color==BLACK))
{
brother->color=RED;
node=parent;
parent=node->parent;
}
else
{
if(!brother->left||brother->left->color==BLACK)
{
brother->color=RED;
brother->right->color=BLACK;
leftRotate(root,brother);
brother=parent->left;
}
brother->color=parent->color;
parent->color=BLACK;
brother->left->color=BLACK;
rightRotate(root,parent);
node=root;
}
}
}
if(node!=nullptr)
node->color=BLACK;
}
template <class T>
void RBTree<T>::print(Node<T> *tree,T key,int direction)
{
if(tree!=nullptr)
{
if(direction==0)
cout<<setw(2)<<tree->key<<"(B) is root"<<endl;
else
cout << setw(2) << tree->key << ((tree->color==RED)?"(R)":"(B)") << " is " << setw(2)
<< key << "'s " << setw(12) << (direction==1?"right child" : "left child") << endl;
print(tree->left, tree->key, -1);
print(tree->right,tree->key, 1);
}
}
template <class T>
void RBTree<T>::print()
{
if(mRoot)
print(mRoot,mRoot->key,0);
}
template<class T>
void RBTree<T>::destory(Node<T> *&tree)
{
if(tree==nullptr)
return;
if(tree->left!=nullptr)
return destory(tree->left);
if(tree->right!=nullptr)
return destory(tree->right);
delete tree;
}
template<class T>
void RBTree<T>::destory()
{
destory(mRoot);
mRoot=nullptr;
}
#endif // RBTREE_H