STL源码阅读-红黑树(RB-tree)、AVL树、AA树

来源:互联网 发布:阿里云怎么挂载数据盘 编辑:程序博客网 时间:2024/06/06 09:01

STL源码剖析看到了第五章节,这部分主要是讲关联容器,关联容器中红黑树被用来作为底层数据组织结构,这里就一起将RB-tree、AVL-tree以及AA-tree做一个简单的实现和总结。

1.RB-tree

在之前的博客里面已经总结了红黑树的原理、实现,当时是参照的算法导论进行实现的,这里按照STL里面的源代码进行比照实现,看一下不同的细节实现。

红黑树的基本定义:

1.根节点是黑色的;

2.叶节点是黑色的;

3.红色节点不能有红色子节点;

4.从根节点出发,到每一叶节点的路径上,黑节点的数量相同;

5.节点可以为红色和黑色;

红黑树不是严格意义上的平衡树,但是红黑树能够保证从根节点到叶节点的最长路径小于最短路径的两倍,也就是Lmax < 2*Lmin,因此红黑树是一种相对而言比较平衡的树,在数据比较随机的情况下,能够取得比较好的平衡性。

在介绍红黑树的插入、删除之前,先确认红黑树的节点是如何组成的。STL的源代码里面对于红黑树节点的定义如下:

typedef bool _Rb_tree_Color_type;const _Rb_tree_Color_type _S_rb_tree_red = false;const _Rb_tree_Color_type _S_rb_tree_black = true;struct _Rb_tree_node_base{  typedef _Rb_tree_Color_type _Color_type;  typedef _Rb_tree_node_base* _Base_ptr;  _Color_type _M_color; //颜色,红色或者黑色  _Base_ptr _M_parent;//父节点  _Base_ptr _M_left;//左儿子  _Base_ptr _M_right;//右儿子  static _Base_ptr _S_minimum(_Base_ptr __x)  {    while (__x->_M_left != 0) __x = __x->_M_left;    return __x;  }  static _Base_ptr _S_maximum(_Base_ptr __x)  {    while (__x->_M_right != 0) __x = __x->_M_right;    return __x;  }};template <class _Value>struct _Rb_tree_node : public _Rb_tree_node_base{  typedef _Rb_tree_node<_Value>* _Link_type;  _Value _M_value_field;};

从上面的节点定义可以看出来,红黑树的节点有三个指针,父节点、左儿子、右儿子,这种节点也是算法导论里面给出的红黑树的节点定义,在看到STL源代码之前,我一直想看一下怎么不使用父节点的情况下实现红黑树,这个应该是可以实现的,网上找到的资料:http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html。但是就性能而言,应该还是这种结构好一些,不然STL的作者也不会采用,不使用父节点会导致大量的递归过程,对性能损耗应该是很大的。(对于STL作者无限膜拜中。。。。)

红黑树的插入过程:

插入的节点首先会被涂成红色的,插入过程中按照二叉搜索树的插入节点的过程插入节点。然后进行平衡处理,如果节点c插入的时候,需要进行平衡处理且节点c是父节点的左儿子,那么有以下两种情况:


如果插入的节点c是父节点的右子树,对称处理就行了。这部分代码的实现如下:

typedef bool COLOR;const COLOR RED = false;const COLOR BLACK = true;struct RB_node{    RB_node * parent;    RB_node * left;    RB_node * right;    int value;    COLOR color;};class Tree{public:    RB_node * root;    RB_node * nil;    Tree()    {        nil = new RB_node();        nil->parent = nil;        nil->left = nil;        nil->right = nil;        nil->color = BLACK;        root = nil;    }};void RB_rebalance_insert(Tree *t,RB_node *z);void RB_insert(Tree *t,int value);void printT(Tree*t,RB_node *root);void RB_right_rotate(Tree *t,RB_node *r);void RB_left_rotate(Tree *t,RB_node *r);void RB_insert(Tree *t,int value){    RB_node *p = t->root;    RB_node *q = p;    while (p != t->nil)    {        q = p;        if(p->value < value)        {            p = p->right;        }        else        {            p = p->left;        }    }    RB_node *z = new RB_node();    z->color = RED;    z->value = value;    z->parent = q;    z->left = t->nil;    z->right = t->nil;    if(q == t->nil)    {        t->root = z;    }    else if(q->value <value)    {        q->right = z;    }    else    {        q->left = z;    }    RB_rebalance_insert(t,z);}void printT(Tree*t,RB_node *root){    RB_node * p = root;    if(p != t->nil)    {        cout << p->value <<" ";        printT(t,p->left);        printT(t,p->right);    }}void RB_rebalance_insert(Tree *t,RB_node *z){    while (z->parent->color == RED)    {        if(z->parent == z->parent->parent->left)//z在left树上面        {            RB_node *w = z->parent->parent->right;            if(w->color == RED)//如果叔叔W节点是红色,将叔叔节点和父节点涂黑,祖父节点涂红,递归处理祖父节点            {                z->parent->color = BLACK;                w->color = BLACK;                z->parent->parent->color = RED;                z = z->parent->parent;//递归处理祖父节点            }            else//如果叔叔节点不是红色的,通过右旋转实现红黑平衡            {                if(z == z->parent->right)//如果z在父节点的子树上,通过左旋,将红色节点换到左子树上去                {                    RB_left_rotate(t,z->parent);                    z = z->left;                }                z->parent->parent->color = RED;                z->parent->color = BLACK;                RB_right_rotate(t,z->parent->parent);//右旋            }        }        else//对称处理        {            RB_node *w = z->parent->parent->left;            if(w->color == RED)            {                z->parent->color = BLACK;                w->color = BLACK;                z->parent->parent->color = RED;                z = z->parent->parent;            }            else            {                if(z == z->parent->left)                {                     RB_right_rotate(t,z->parent);                    z = z->right;                }                z->parent->parent->color = RED;                z->parent->color = BLACK;                RB_left_rotate(t,z->parent->parent);            }        }    }    t->root->color = BLACK;//确保根节点是黑色的}void RB_right_rotate(Tree *t,RB_node *r){    RB_node *s = r->left;    r->left = s->right;    if(t->nil != s->right)    {        s->right->parent = r;    }    s->parent = r->parent;    if(t->root == r)    {        t->root = s;    }    else if(r->parent->left == r)    {        r->parent->left = s;    }    else    {        r->parent->right = s;    }    r->parent = s;    s->right = r;}void RB_left_rotate(Tree *t,RB_node *r){    RB_node * s = r->right;    r->right = s->left;    if(s->left != t->nil)    {        s->left->parent = r;    }    s->parent = r;    if(t->root == r)    {        t->root = s;    }    else if(r->parent->left == r)    {        r->parent->left = s;    }    else    {        r->parent->right = s;    }    r->parent = s;    s->left = r;}
上面这段代码就实现了红黑树在插入过程中的平衡性,主要是通过旋转和递归涂黑处理。
红黑树的删除同样容易导致红黑树的不平衡,因此需要进行调整。如果删除的节点是红色的,那么不会破坏红黑树的性质,如果删除的节点是黑色的,那么就需要进行平衡处理。假设需要删除的节点是p,那么会有三种情况,紫色表示节点的红黑性质尚不清楚:


第一种情况和第二种情况在删除的时候,直接用q替代p所在的位置就可以了,如果是第三种情况,首先找到p的后继,然后让p的后继替代p的颜色。最后判断删除的节点的颜色是否是黑色的,如果是黑色的,那么就需要进行再一次调整了。

删除节点相关代码如下:

void RB_delete(Tree *t,RB_node * r){    RB_node * y = r;    RB_node * z = t->nil;    COLOR cold = r->color;    if(r->left == t->nil)//情况2,没有左子树    {        z = r->right;        RB_replace(t,r,r->left);    }    else if(r->right == t->nil)//情况1,没有右子树    {        z = r->left;        RB_replace(t,r,r->left);    }    else//情况3,左右子树都存在    {        y = RB_next(t,r->right);//寻找后继        cold = y->color;        z = y->right;        if(y->parent == r)//判断后继是不是自己的儿子        {            z->parent = y;        }        else        {            RB_replace(t,y,y->right);            y->right = r->right;            y->right->parent = y;        }        RB_replace(t,r,y);        y->left = r->left;        y->left->parent = y;    }    if(cold == BLACK)    {        RB_delete_fixup(t,z);//进行调整    }}RB_node *RB_next(Tree*t,RB_node *r){    while (r->left != t->nil)    {        r = r->left;    }    return r;}void RB_replace(Tree *t,RB_node * s,RB_node *r)//用r替换s的位置{    if(t->root == s)    {        t->root = r;    }    else if(s == s->parent->left)    {        s->parent->left = r;    }    else    {        s->parent->right = r;    }    r->parent = s->parent;}

删除完节点之后,需要进行调整,因为删除过程中,如果删去了黑色的节点,会破坏红黑树的性质,也就是从root到叶节点黑高的一致性。如果需要调整的节点是红色的,那么直接涂黑;如果需要处理的节点是黑色的,考虑x是左儿子,调整分为以下几种情况:

情况一:

x的兄弟节点是红色的,那么x的父节点一定是黑色的,通过旋转,将情况一转换成情况二、三、四;


情况二:

x的兄弟节点w是黑色的,并且w的子节点都是黑色的,将w涂红,并且递归处理x的父节点;


情况三:x的兄弟节点w是黑色的,并且w的右儿子是红色的,那么需要进行旋转,使其变成情况四;


情况四:x的兄弟节点w是黑色的,w的左节点是红色的,通过旋转使整个平衡,x指向root,旋转结束。


调整部分的源代码如下:

void RB_delete_fixup(Tree *t,RB_node *r){    while (t->root != r && r->color == BLACK)    {        if(r == r->parent->left)        {            RB_node * s = r->parent->right;            if(s->color == RED)//case 1            {                r->parent->color = RED;                s->color = BLACK;                RB_left_rotate(t,r->parent);                s = r->parent->right;            }            //case 2            if(s->left->color == BLACK && s->right->color == BLACK)            {                s->color = RED;                r = r->parent;            }            else            {                if(s->left->color == RED)// ase 3                {                    s->color = RED;                    s->left->color = BLACK;                    RB_right_rotate(t,s);                    s = r->parent->right;                }                //case 4                s->color = r->parent->color;                r->parent->color = BLACK;                s->right->color = BLACK;                RB_left_rotate(t,r->parent);                r = t->root;            }        }        else        {            RB_node * s = r->parent->left;            if(s->color == RED)//case 1            {                r->parent->color = RED;                s->color = BLACK;                RB_right_rotate(t,r->parent);                s = r->parent->left;            }            //case 2            if(s->left->color == BLACK && s->right->color == BLACK)            {                s->color = RED;                r = r->parent;            }            else            {                if(s->right->color == RED)// ase 3                {                    s->color = RED;                    s->right->color = BLACK;                    RB_left_rotate(t,s);                    s = r->parent->left;                }                //case 4                s->color = r->parent->color;                r->parent->color = BLACK;                s->left->color = BLACK;                RB_right_rotate(t,r->parent);                r = t->root;            }        }    }    r->color = BLACK;}
有对红黑树进行测试过的说红黑树与AVL树的性能差不多,但是红黑树在组织方式更加复杂,不过STL采用红黑树作为map、set底层结构,必然是考虑到了性能、实现等等。

2.AVL树

定义:一棵空二叉树是AVL树,如果T是非空二叉树,TL和TR分别是其左子树和右子树,

则当且仅当TL和TR都为AVL树且|HL-HR|<=1时,T是AVL树。

有定义知,AVL树左右子树的高度差不超过1,因此需要尽可能的通过旋转的方式将不平衡控制在有限的范围内。AVL树应该用的不多,主要是需要进行大量的递归计算树的高度,看完红黑树之后,再看AVL树的旋转,感觉轻松许多。。相关的blog可以参照http://dongxicheng.org/structure/avl/,以及http://blog.csdn.net/flymu0808/article/details/26961861

3.AA树

AA树是Arne Andersson教授在他的论文"Balanced search trees made simple"中介绍的一个红黑树变种,设计的目的是减少RB树考虑的cases。AA树是一颗红黑树,但是规定红色结点不能作为任何结点的左孩子,也就是说红色结点只能作为右孩子。这样本质上跟2-3树类似(虽然后者属于B树)。另外AA树为实现方便,不再使用红黑两种颜色,而是用level标记结点。level实际上就相当于RB树中的black height,叶子结点的level等于1(反过来,level等于1的不一定是叶子结点,因为等于1的结点可能有一个红色的右孩子),红色结点使用它的父结点的level,黑色结点比它的父结点的level小1。另外,下面两种情况是禁止出现的:

1)连续两个水平方向链(horizontal link),所谓horizontal link是指一个结点跟它的右孩子结点的level相同(左孩子结点永远比它的父结点level小1)。这个规定其实相当于RB树中不能出现两个连续的红色结点。
2)向左的水平方向链(left horizontal link),也就是说一个结点最多只能出现一次向右的水平方向链。这是因为left horizontal link相当于左孩子能为红色结点,这在AA树的定义中是不允许的。

AA树的定义如下:

1.每个叶子的level是1

2.每个左孩子的level是其父节点的level减1

3.每个右孩子的level等于其父节点的level或者其父节点的level减1

4.每个右孙子的level一定比其祖父的level小

5.每个level大于1的节点有两个孩子

在插入和删除操作中,可能会出现上面两个禁止发生的情况,这时候就需要通过树的旋转操作来纠正。AA树中只有两个基本操作:skew和split。前者用于纠正出现向左的水平方向链,后者用于纠正出现连续两个水平方向链的情况。skew就是一个右旋转,split是一个左旋转,但两者不是互逆的。skew操作之后可能引起1)的发生(当skew之前已经有一个右孩子的level跟当前结点的level相同),这时需要配合使用split操作。split操作的特点是新的子树的根节点level增加1, 从而会在它的父结点中出现1)(当它作为父结点的左孩子)或者在它的父结点中出现2)(当它作为父结点的右孩子而且父结点跟祖父结点的level相同),这时需要通过skew和split操作纠正这两种情况。


由于split引起的新问题发生在parent一级局部结点,而skew引起的新问题只发生在当前局部结点,所以在实现时需要先skew,再split。
因为AA树也是平衡BST,它的时间复杂度跟RB树一样,即O(logn),但是旋转次数相对多一些(RB树插入操作最多旋转两次,而且旋转完毕即结束rebalancing;删除操作最多旋转三次,也是旋转完毕即结束rebalancing)。

AA树的删除还不是很懂,看着别人的代码来实现的。

#include <iostream>using namespace std;template <class Type>class AATree{private:    struct Node    {        Type data;        int level;        Node * left;        Node * right;        Node(const Type &d,Node *lt= NULL,Node*rt=NULL,int le =1 )        :data(d),left(lt),right(rt),level(le)        {        }    };    Node * root;public:    AATree(){root = NULL;}    ~AATree(){}    bool find(const Type d){return find(d,root);}    void insert(const Type d){insert(d,root);}    void remove (const Type d){remove(d,root);}    void print()    {        print(root);    }private:    bool find(const Type d,Node* root);    void insert(const Type d,Node *&root);    void remove(const Type d,Node *&root);    void makeempty(Node *&t);    void LL (Node *&t);    void RR(Node *&t);    void print(Node * t)    {        if(t != NULL)        {            print(t->left);           cout << t->data<<" ";           print(t->right);        }    }};//findtemplate <class Type>bool AATree<Type>::find(const Type d,Node*t){    if(t == NULL) return false;    else if(t->data < d) return find(d,t->right);    else if(t->data > d) return find(d,t->left);    return true;}template <class Type>void AATree<Type> :: insert(const Type d,Node *&root){    if(root == NULL) root = new Node(d);    else if(d > root->data) insert(d,root->right);    else  insert(d,root->left);    LL(root);    RR(root);}template <class Type>void AATree<Type> ::LL(Node *&t){    if(t->left != NULL &&t->left->left != NULL && t->level == t->left->left->level)    {        Node * tt = t->left;        t->left = tt->right;        tt->right = t;        t = tt;    }}template <class Type>void AATree<Type> ::RR(Node *&r){    if(r->right != NULL && r->right->right != NULL && r->right->right->level == r->level)    {        Node * tt = r->right;        r->right = tt->left;        tt->left = r;        r = tt;        r->level ++;    }}template <class Type>void AATree<Type>::remove(const Type d,Node *&root){    if (root == NULL) return ;    else if(d > root->data) remove(d,root->right);    else if(d < root->data) remove(d,root->left);    else if(root->left != NULL && root->right != NULL)    {        Node *tt = root->right;        while (tt->left != NULL) tt = tt->left;        root->data = tt->data;        remove(root->data,root->right);    }    else    {        Node *old = root;        root = (root->left == NULL) ? root->right: root->left;        delete old;        return ;    }    LL(root);    if(root->right) RR(root->right);    if(root->right && root->right->right) LL(root->right->right);    RR(root);    if(root->right) RR(root->right);}int main (){    AATree<int> iat;    iat.insert(2);    iat.insert(3);    iat.insert(1);    iat.insert(6);    iat.insert(7);    iat.insert(9);    iat.remove(2);    iat.print();}





0 0