参照算法导论简单实现了一下红黑树的节点插入,对于多次static_cast的使用,我表示面向对象那块学的不好,谁有好的方法可以告诉我一下。
首先实现一棵二叉查找树,类的声明如下:
typedef int treeKeyType;
class BinSearchTreeNode
{
public:
BinSearchTreeNode(treeKeyType k):key(k)
{
left=NULL;
right=NULL;
parent=NULL;
}
BinSearchTreeNode * left;
BinSearchTreeNode * right;
BinSearchTreeNode * parent;
treeKeyType key;
};
class BinSearchTree
{
public:
BinSearchTreeNode * root;
BinSearchTree():root(NULL){}
~BinSearchTree(){}
// 中序遍历
void inOrderPrint(BinSearchTreeNode *);
// 前序遍历
void preOrderPrint(BinSearchTreeNode *);
// 后序遍历
void proOrderPrint(BinSearchTreeNode *);
// 中序遍历
void inOrder();
// 前序遍历
void preOrder();
// 后序遍历
void proOrder();
// find the node who is equal to key
BinSearchTreeNode * treeSearch(treeKeyType key);
// return the minimum value of tree which has root x
BinSearchTreeNode * minNode(BinSearchTreeNode * x);
// return the maximum value of tree which has root x
BinSearchTreeNode * maxNode(BinSearchTreeNode * x);
// return the successor of node x
BinSearchTreeNode * successor(BinSearchTreeNode * x);
// return the predessor of node x
BinSearchTreeNode * predessor(BinSearchTreeNode * x);
// insert node z into the tree
void insertNode(BinSearchTreeNode * newNode);
// delete node z from the tree
void deleteNode(BinSearchTreeNode * z);
};
因为红黑树也有孩子节点,父亲节点,关键词等成员,这点与二叉查找树的节点非常类似,所以我们红黑树的节点直接继承自二叉查找树的节点,只不过多了一个颜色成员color。
但是红黑树相比二叉查找树来说,必须满足以下五条性质:
(1)每个节点或者红,或者黑;
(2)根节点必须是黑的;
(3)每个叶子节点(或者说NIL,空节点)是黑的;
(4)每一个红的节点的父亲节点必须是黑的;
(5)对于每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点,我们将这个数目成为黑高度。
首先,介绍一下红黑树的左旋(left-rotate)和右旋(right-rotate)操作,这两个操作是互逆的。
#define RED 0
#define BLACK 1
typedef int Color;
class RedBlackTreeNode : public BinSearchTreeNode
{
public:
Color color;
RedBlackTreeNode(treeKeyType key, Color c_color=BLACK):BinSearchTreeNode(key), color(c_color)
{
}
};
首先,介绍一下红黑树的左旋(left-rotate)和右旋(right-rotate)操作,这两个操作是互逆的。
左旋操作的过程如下:
(1)首先将x设置为y的左孩子;
(2)y的左孩子Beta的父亲设置为为x;
(3)将y的父亲节点设置为x的父亲节点;
(4)判断一下x以前是否作为根节点,如果是,将当前树的跟改成y;接下来再判断x以前作为左孩子还是右孩子,如果是左孩子,将y设置为x的爷爷的左孩子,否则设置为x的爷爷的右孩子;
(5)x成为y的左孩子,y成为x的父亲。
右旋,反之。。。
左旋和右旋的实现代码如下:
// 左旋操作
void RedBlackTree::leftRotate(RedBlackTreeNode * x)
{
RedBlackTreeNode * y=static_cast<RedBlackTreeNode *>(x->right);
x->right=y->left;
if(y->left != NULL)
y->left->parent=x;
y->parent=x->parent;
if(x->parent == NULL) //then x is current root
root=y;
else if(x->parent->right == x)
x->parent->right=y;
else
x->parent->left=y;
y->left=x;
x->parent=y;
}
// 右旋操作
void RedBlackTree::rightRotate(RedBlackTreeNode * y)
{
RedBlackTreeNode * x=static_cast<RedBlackTreeNode *>(y->left);
y->left=x->right;
if(x->right != NULL)
x->right->parent=y;
if(y->parent == NULL) //then y is currrent root
root=x;
else if(y == y->parent->left)
y->parent->left=x;
else
y->parent->right=x;
x->right=y;
y->parent=x;
}
红黑树的插入,实现如下:
在调用修复函数之前的插入操作,都和二叉查找树的插入操作类似,用两个指针x和y分别代表父子节点,一级级的往下找,直到x为空,y作为插入位置(即插入节点,作为y的孩子),判断y的关键词的大小和要插入节点关键词的大小来确定是作为y的左孩子还是右孩子。同时记住将新插入的节点z的颜色置为红色,z的两个孩子节点作为黑色的NIL节点(就是NULL),这样保证不改变树的黑高度。
// 插入节点
void RedBlackTree::insertNode(RedBlackTreeNode * z)
{
// first, do the insertion like Binary Search Tree
RedBlackTreeNode * x=static_cast<RedBlackTreeNode *>(root), * y=NULL;
while(x != NULL)
{
y=x;
if(z->key < x->key)
x=static_cast<RedBlackTreeNode *>(y->left);
else
x=static_cast<RedBlackTreeNode *>(y->right);
}
z->parent=y;
if(y == NULL) //说明当前树为空
root=z;
else if(z->key < y->key)
y->left=z;
else
y->right=z;
z->left=NULL;
z->right=NULL;
z->color=RED;
//调用红黑树修复函数
insertFixUp(z);
}
红黑树的修复操作是为了保持红黑树的性质,实现如下:
这里分三种情况:case1,case2和case3,如图所示。
case 1
图中z是当前要插入的节点,只有当z的父亲为红色时,我们才有必要执行修复操作;因为如果z的父亲为黑色的话,红黑树性质(4):所有红色节点的父亲必须为黑色也会满足,从而所有红黑树的性质都满足了,直接退出算法就可以。无论何种情况,新插入的节点都要置为红色以保证黑高度不变。
case1代表的情况是z的叔叔(叔叔就是爷爷的相对于爸爸的另一个孩子)也是红色的。这时将z的父亲、叔叔和爷爷的颜色取反。让z指向新的位置,即z的爷爷。继续循环。
case 2 and case 3
如果z的叔叔是黑色的,那就进入case2或者case3的情况,可能是先执行case2后执行case3,也可能只执行case3,无论如何,case3都是要执行的。
case2代表的情况是当前要插入的节点z是其父亲A的右孩子,更一般的情况,应该说z和其父亲之间的连线l1和z的父亲和z的爷爷之间的连接线l2成一个锐角。(估计大家应该能明白是什么意思,就是B是A的右孩子,A是C的左孩子。但是我们在考虑case1,case2,case3时,仅仅考虑了一半的情况:即z的父亲A是z的爷爷z的左孩子,另一半情况当然是A是C的右孩子了!那些情况也会分case1,case2,case3,跟现在讨论的完全类似。)对于case2,对z的父亲做一次左旋操作,就会变成case3的情况。然后在z的爷爷位置做一次右旋操作,同时将z的父亲和z的爷爷的颜色取反。此时,z的父亲变为黑色了,(我们认为z的父节点如果变为黑色后就满足红黑树性质了),循环自动退出。
总的来说,case1有可能执行多次,但是case2和case3只有可能执行一次。
// 需要进行修复,保持红黑树的黑高度一致等性质不变
void RedBlackTree::insertFixUp(RedBlackTreeNode * z)
{
while( z->parent != NULL && static_cast<RedBlackTreeNode *>(z->parent)->color == RED )
{
if(z->parent->parent->left == z->parent)
{
RedBlackTreeNode * ancle =static_cast<RedBlackTreeNode *>(z->parent->parent->right);
if(ancle->color == RED) // case1
{
static_cast<RedBlackTreeNode *>(z->parent->parent)->color=RED;
static_cast<RedBlackTreeNode *>(z->parent)->color=BLACK;
ancle->color=BLACK;
z=static_cast<RedBlackTreeNode *>(z->parent->parent);
}
else
{
if(z->parent->right == z) // case2
{
z=static_cast<RedBlackTreeNode *>(z->parent);
leftRotate(z);
}
// case3
static_cast<RedBlackTreeNode *>(z->parent)->color=BLACK;
static_cast<RedBlackTreeNode *>(z->parent->parent)->color=RED;
rightRotate(static_cast<RedBlackTreeNode *>(z->parent->parent));
}
}
else // 当前节点的父亲是当前节点爷爷的右孩子
{
RedBlackTreeNode * ancle=static_cast<RedBlackTreeNode *>(z->parent->parent->left);
if(ancle->color == RED) // case1
{
static_cast<RedBlackTreeNode *>(z->parent->parent)->color=RED;
static_cast<RedBlackTreeNode *>(z->parent)->color=BLACK;
ancle->color=BLACK;
z=static_cast<RedBlackTreeNode *>(z->parent->parent);
}
else
{
if( z == z->parent->left ) // case2
{
z=static_cast<RedBlackTreeNode *>(z->parent);
rightRotate(z);
}
// case3
static_cast<RedBlackTreeNode *>(z->parent->parent)->color=RED;
static_cast<RedBlackTreeNode *>(z->parent)->color=BLACK;
leftRotate(static_cast<RedBlackTreeNode *>(z->parent->parent));
}
}
}
}