1.红黑树
许多“平衡”搜索树中的一种,可以保证在最坏情况下基本动态集合操作的时间复杂度为O(lgn)。一颗红黑树是满足下面红黑性质的二叉搜索树(当一个结点没有子结点或者父结点时,指针设为空,并且将空指针视为叶结点(外部结点),为黑色):
(1)每个结点或是红色的,或是黑色的。
(2)根结点是黑色的。
(3)每个叶结点是黑色的。
(4)如果一个结点是红色的,则它的两个子结点都是黑色的。
(5)对每个结点,从该结点到其所有后代叶结点的简单路劲上,均包含相同数目的黑色结点。这条简单路径上的黑色结点数目成为黑高,红黑树的黑高为根结点的黑高。
红黑树的结点包含5个属性:color, key, left, right, p。一颗有n个内部结点的红黑树的高度至多为2lg(n+1)。因此,SEARCH, MINIMUM, MAXIMUM, SUCCESSOR, PREDECESSOR(与二叉搜索树相同)可在红黑树上O(lgn)/ O(h)时间内执行。
INSERT与DELETE操作也在O(lgn)时间内执行,但可能会破坏掉红黑树的性质,因此需要旋转(ROTATE)操作进行恢复,包括左旋和右旋。对一个结点左旋时,该结点右孩子不能为叶结点;对一个结点右旋时,左孩子不为叶结点。
程序如下:
class Node
{
private:
int key;//值
public:
string color;
Node *left;//左子树
Node *right;//右子树
Node *parent;//父结点
int getkey();
void setkey(int k);
Node(int k = 0);
bool operator == (Node &z);
Node& operator = (Node &z);
};
//红黑树
class RBTree
{
public:
Node *root;
RBTree(){
root = NULL;
}
Node* leftrotate(Node *root, Node *x);
Node* rightrotate(Node *root, Node *x);
Node* treeinsert(Node *root, Node *z);//插入结点
Node* insertfixup(Node *root, Node *z);
Node* treesearch(Node *root, int k);//查询结点
void treewalk(Node *root);//遍历
Node* treemin(Node *root);//寻找结点root下最小值结点
Node* treemax(Node *root);//寻找结点root下最大值结点
Node* treesuccessor(Node *x);//寻找结点x的后继结点
Node* treeprodecessor(Node *x);//寻找结点x的前驱结点
Node* treetransplant(Node *root, Node *u, Node *v);//在根结点root下移动子树v到u
Node* treedelete(Node *root, Node *z);//删除结点
Node* deletefixup(Node *root, Node *z);
};
Node::Node(int k):key(k)
{
color = "red";
left = NULL;
right = NULL;
parent = NULL;
}
bool Node::operator == (Node &z)
{
if ((key == z.key)&&(left == z.left)&&(right == z.right)&&(parent == z.parent)&&(color==z.color))
{
return true;
}
else{
return false;
}
}
Node& Node::operator = (Node &z)
{
delete left;
delete right;
delete parent;
left = z.left;
right = z.right;
parent = z.parent;
key = z.key;
color = z.color;
return *this;
}
int Node::getkey()
{
return key;
}
void Node::setkey(int k)
{
key = k;
}
Node* RBTree::leftrotate(Node *root, Node *x){
Node *y = x->right;
//x的右孩子不为叶结点
if (y != NULL){
x->right = y->left;
if (y->left != NULL){
y->left->parent = x;
}
y->parent = x->parent;
if (x->parent == NULL){
root = y;
}
else if (x == x->parent->left){
x->parent->left = y;
}
else{
x->parent->right = y;
}
y->left = x;
x->parent = y;
}
return root;
}
Node* RBTree::rightrotate(Node *root, Node *x){
Node *y = x->left;
if (y != NULL){
x->left = y->right;
if (y->right != NULL){
y->right->parent = x;
}
y->parent = x->parent;
if (x->parent == NULL){
root = y;
}
else if (x == x->parent->left){
x->parent->left = y;
}
else{
x->parent->right = y;
}
y->right = x;
x->parent = y;
}
return root;
}
//查询、最大值、最小值、前驱、后继函数均可以在O(h)时间内完成,h为树的高度
Node* RBTree::treesearch(Node *root, int k)//查询结点
{
//与二叉搜索树相同
}
void RBTree::treewalk(Node *root)//中序遍历
{
if (root != NULL)
{
treewalk(root->left);
cout << root->getkey() << ":"<<root->color <<" ";
treewalk(root->right);
}
}
Node* RBTree::treemin(Node *root)//寻找结点root下最小值结点
{
//与二叉搜索树相同
}
Node* RBTree::treemax(Node *root)//寻找结点root下最大值结点
{
//与二叉搜索树相同
}
Node* RBTree::treesuccessor(Node *x)//寻找结点x的后继结点
{
Node *y;
//如果右孩子不空,则右子树中的最小值即为后继
if (x->right != NULL){
return treemin(x->right);
}
y = x->parent;
//如果父结点也为空,无后继结点
//父结点存在时,后继结点为它的最底层祖先,并且后继结点的左结点也是它的祖先
//因此循环的终止条件为x == y->right,当x是一个左孩子时终止,x的父结点为后继结点
while (y != NULL && x == y->right)
{
x = y;
y = y->parent;
}
if (y != NULL)
{
return y;
}
return 0;
}
Node* RBTree::treeprodecessor(Node *x)//寻找结点x的前驱结点
{
Node *y;
//如果左孩子存在,前驱结点为左子树中最大值
if (x->left != NULL){
return treemax(x->left);
}
y = x->parent;
//与后继结点类似,当x为右孩子时终止,x父结点为前驱结点
while (y != NULL && x == y->left)
{
x = y;
y = y->parent;
}
if (y != NULL)
{
return y;
}
return 0;
}
Node* RBTree::insertfixup(Node *root, Node *z){
Node *y=new Node();
//保证性质4,只有执行情况1时,循环会继续进行,执行2和3时,执行一次后循环就停止
while (z->parent != NULL&&z->parent->parent != NULL&&z->parent->color == "red"){
if (z->parent == z->parent->parent->left){
y = z->parent->parent->right;
if (y != NULL&&y->color == "red"){
//情况1:z的叔结点y是红色,无论z是左孩子还是右孩子,改变父结点、叔结点以及祖父结点的颜色来保证性质5,将z上移两层,重新进入循环
z->parent->color = "black";
y->color = "black";
z->parent->parent->color = "red";
z = z->parent->parent;
}
else{
//情况2:叔结点y是黑色,z是一个右孩子,将z指向父结点,左旋,将情况2转为情况3
if (z == z->parent->right)
{
z = z->parent;
root=leftrotate(root, z);
}
//情况3:叔结点为黑色,z是一个左孩子,改变z的父节点以及祖父结点的颜色,然后右旋
z->parent->color = "black";
z->parent->parent->color = "red";
root = rightrotate(root, z->parent->parent);
}
}
else{
//与之前过程相同,但左右对换
y = z->parent->parent->left;
if (y != NULL&&y->color == "red"){
//情况1
z->parent->color = "black";
y->color = "black";
z->parent->parent->color = "red";
z = z->parent->parent;
}
else{
//情况2
if (z == z->parent->left)
{
z = z->parent;
root = rightrotate(root, z);
}
//情况3
z->parent->color = "black";
z->parent->parent->color = "red";
root = leftrotate(root, z->parent->parent);
}
}
}
//保证性质2
root->color = "black";
return root;
}
Node* RBTree::treeinsert(Node *root, Node *z)//插入结点
{
Node *x, *y = NULL;
x = root;
//向下寻找位置
while (x != NULL)
{
y = x;
if (z->getkey() < x->getkey())
{
x = x->left;
}
else{
x = x->right;
}
}
z->parent = y;
//判断树是否为空
if (y == NULL)
{
root = z;
}
else if (z->getkey() < y->getkey())
{
y->left = z;
}
else{
y->right = z;
}
z->left = NULL;
z->right = NULL;
//插入的是红色结点,插入后在最底层,这样至多有一条性质被破坏,性质2或是性质4
z->color = "red";
root = insertfixup(root, z);
return root;
}
Node* RBTree::deletefixup(Node *root, Node *x){
//当y为黑色时,首先破坏的性质就是,原来包含y结点的路径上黑高少了1,其次就是x移动到y原来位置上后,可能出现x与x.p均为红色的情况
//书中所说y为根结点,y的红色孩子成为新的根节点,感觉这种情况不存在
//因为y的孩子替换z时,最后会把z的颜色赋值给y的颜色,假如入要删除的是根结点,这一步骤自然会保证根结点颜色不变
//假如根结点成为要删除结点的后继,要删除节点一定在根结点左子树中,而这时删除节点一定是左子树中的最大值,一定没有右孩子存在
//这时,直接拿左孩子替换要删除结点就行,不会存在根结点颜色变化的情况,最多是在修正过程中改变了根结点的颜色
//处理方案是假设x有额外一层黑色,只要将x原来的颜色去掉或者传递给其他红色的结点即可
Node *w;
//如果x是原来是红色的,直接赋值为黑色即可,因此循环条件是x为黑色
//当x为根结点时,直接赋值为黑色
while (!(*x == *root) && x->color == "black"){
//x为左孩子时,为右孩子的时候类似
if (x == x->parent->left){
w = x->parent->right;
//情况1:x的兄弟结点是红色的,经过变色以及旋转将其改变为新的结点并转换为黑色,将情况1转为情况2,3,4
if (w->color == "red"){
w->color = "black";
x->parent->color = "red";
root = leftrotate(root, x->parent);
w = x->parent->right;
}
//情况2:x的兄弟结点w是黑色的,并且w两个子结点都是黑色的
//x和w均为黑色,将x上额外的黑色以及w的黑色都去掉,加在他们的父节点上,将w变为红色即为去掉黑色,然后对新的x重新进行循环
if ( w->left->color == "black"&&w->right->color == "black"){
w->color = "red";
x = x->parent;
}
else{
//情况3:x的兄弟结点w是黑色,w左孩子为红色,右孩子为黑色,交换w与w左孩子的颜色,右旋,转为情况4
if ( w->right->color == "black"){
w->left->color = "black";
w->color = "red";
root = rightrotate(root, w);
w = x->parent->right;
}
//情况4:x的兄弟结点是黑色,w右孩子为红色,将其变为黑色,相当于给x.p右子树多加一个黑色
//右旋是为了将w的黑色转到x.p右子树,右旋后,w变为原来的x.p,保留原来x.p的颜色,而将原来的x.p变为黑色,转到了的新的x.p的左子树上,也即那一层额外的黑色
w->color = x->parent->color;
x->parent->color = "black";
w->right->color = "black";
root = leftrotate(root, x->parent);
//将x设置为根结点,退出循环
x = root;
}
}
else{
w = x->parent->left;
if (w->color == "red"){
w->color = "black";
x->parent->color = "red";
root = rightrotate(root, x->parent);
w = x->parent->left;
}
if (w->right->color == "black"&&w->left->color == "black"){
w->color = "red";
x = x->parent;
}
else{
if (w->left->color == "black"){
w->right->color = "black";
w->color = "red";
root = leftrotate(root, w);
w = x->parent->left;
}
w->color = x->parent->color;
x->parent->color = "black";
w->left->color = "black";
root = rightrotate(root, x->parent);
x = root;
}
}
}
x->color = "black";
return root;
}
Node* RBTree::treetransplant(Node *root, Node *u, Node *v)//在根结点root下移动子树v到u
{
//u为根结点
if (u->parent == NULL)
{
root = v;
}
else if (u == u->parent->left)
{
u->parent->left = v;
}
else{
u->parent->right = v;
}
//书中提到为空的时候也要赋值,但没有设置哨兵,只是将NULL当做哨兵来理解,实际上还是要判断
if (v != NULL){
v->parent = u->parent;
}
return root;
}
Node* RBTree::treedelete(Node *root, Node *z)//删除结点
{
//y用来指向要删除的结点以及将要移动到删除位置的结点
Node *y=z;
//x移动到了y的原始位置,记录该点踪迹,因为它可能破坏树的性质
Node *x;
string yOriginalColor = y->color;
if (z->left == NULL){
x = z->right;
//如果左孩子为空,利用右孩子替换,右孩子为空时,相当于直接删除
root = treetransplant(root, z, z->right);
}
else if (z->right == NULL){
x = z->left;
//执行此步时,一定只有左孩子,拿左孩子替换即可
root = treetransplant(root, z, z->left);
}
else{
//左右孩子都存在,首先需要寻找它的后继结点
y= treemin(z->right);
yOriginalColor = y->color;
//此时y没有左孩子
x = y->right;
if (!(y == z->right)){
//如果后继结点不是它的右孩子,后继结点没有左孩子,否则不是后继结点
//先拿后继结点右孩子替换后继结点,再拿后继结点替换要删除的结点
root = treetransplant(root, y, y->right);
//将z的右孩子换到y上,if外的语句把z的左孩子换到y上
y->right = z->right;
y->right->parent = y;
}
//如果后继结点就是它的右孩子,并且这个右孩子没有左孩子,否则不可能是后继结点,因此直接拿后继结点替换
treetransplant(root, z, y);
y->left = z->left;
y->left->parent = y;
y->color = z->color;
//书中设置了x->parent = y是防止x为空时,x.p不存在,进入到修正程序中会有错误,但x为空时,不能出现x->parent
//x为空也就是哨兵时依旧可能破坏了红黑树的性质,也应该进行进行恢复,后来才发现的,但目前程序由于时间关系中没有写
//如果y原来的颜色是黑色,需要重新维护红黑树的性质,理由如下:
//当y为红色时,删除或者移动,首先,树的黑高没有发生变化,y也不是树的根结点,根结点没有变化
//对于前两种情况,z无孩子或者只有一个孩子的时候,y=z,无孩子,红色删掉不影响,有一个孩子时候,y也不可能是红色,因为黑高不对
//对于第三种情况中,y为z的后继,y为z的右孩子时,y也不可能为红色,因为y此时没有左孩子,黑高就不对
//y不是z的右孩子时,y为红色,没有左孩子,其右孩子为黑色,替换它后不影响树的性质,而y移动到z,颜色变为z的颜色,也不会影响性质
if (x != NULL&&yOriginalColor == "black"){
root = deletefixup(root, x);
}
}
return root;
}
void main()
{
RBTree rbtree;
rbtree.root = new Node(14);
rbtree.root->color = "red";
Node z[15] = { 5, 49, 2, 18, 15, 30, 17, 56, 46, 10, 12, 44,35, 37, 19 };
for (int i = 0; i < 15; i++)
{
rbtree.root = rbtree.treeinsert(rbtree.root, &z[i]);
}
cout << "中序遍历二叉树:" << endl;
rbtree.treewalk(rbtree.root);
cout << endl;
Node *s;
s = rbtree.treesearch(rbtree.root, 2);
if (s == NULL){
cout << "无" << endl;
}
else{
cout << s->getkey() << endl;
}
s=rbtree.treeprodecessor(&z[2]);
if (s == NULL){
cout << "无" << endl;
}
else{
cout << s->getkey() << endl;
}
s = rbtree.treesuccessor(&z[6]);
if (s == NULL){
cout << "无" << endl;
}
else{
cout << s->getkey() << endl;
}
cout << "最大值:" << endl;
cout << rbtree.treemax(rbtree.root)->getkey() << endl;
cout << "最小值:" << endl;
cout << rbtree.treemin(rbtree.root)->getkey() << endl;
rbtree.root = rbtree.treedelete(rbtree.root, &z[12]);
cout << "中序遍历二叉树:" << endl;
rbtree.treewalk(rbtree.root);
cout << endl;
}
需要注意的就是在删除操作的修复程序中,x为空的情况,程序中没有处理,但应该进行处理,主函数中的数据示例就是x出现了为空的情况,最后黑高不正确,有时间再研究。
2.顺序统计树
在每个结点上添加附加信息的一棵红黑树。除包括红黑树的五个属性外还包括另一个属性size,表示以该结点为根的子树(包含自身)的结点数:
x.size=x.left.size+x.right.size+1
一个元素的秩定义为在中序遍历树时输出的位置。中序遍历其实就是从小到大排序,但中间可能出现key值相同的元素,秩不相同。
(1)查找以x为根结点的树中具有给定秩的元素:
OS-SELECT(x,i)
r=x.left.size+1 //计算x结点的秩
if i==r
return x
elseif i<r //在x左子树中
return OS-SELECT(x.left,i)
else //在x右子树中
return OS-SELECT(x.right,i-r)
(2)确定一个元素的秩:
OS-RANK(T,x)
r=x.left.size+1 //r为以y为根结点的子树中x结点的秩
y=x
while y!=T.root
//当y是其父结点的右孩子时,x的秩要加上y兄弟结点的size再加1,如果y是其父结点的左孩子,则在该子树中已无比它key值更小的结点
if y==y.p.right
r=r+y.p.left.size+1
y=y.p
return r
为了能准确的运用上面的函数,必须要再红黑树的基础操作中对size属性进行维护。
对于插入操作,新增加的结点size为1,从根到新增结点的路径上的每一个结点的size+1,对于旋转操作,会使两个结点的size属性失效,x及其孩子结点y,旋转操作的维护是利用下面的代码:
y.size=x.size
x.size=x.left.size+x.right.size+1
对于删除操作,遍历y结点(从树中的原始位置开始)到根结点的简单路径,路径上每个结点的size减1。
3.扩张数据结构
(1)选择一种基础数据结构
(2)确定基础数据结构中要维护的附加信息。
(3)检验基础数据结构上的基础修改操作能否维护附加信息。
(4)设计一些新操作。