【红黑树】RB树的实现原理

定义

红黑树是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是red或者black,通过对任何一条从根节点到叶子结点上的简单路径来约束,红黑树保证最长路径不超过最短路径的两倍,因而近似平衡。

性质

1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个根节点是红色的,则它的两个叶子结点是黑色的(没有两个连续的红色结点)
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(每条路径上黑色结点的数量相等)

从上面的约束条件可以得出红黑树能保证最长路径不超过最短路径的两倍

因为第1条该树上的节点非红即黑,由于第3条该树上不允许存在两个连续的红节点,那么对于从一个节点到其叶子节点的一条最长的路径一定是红黑交错的,那么最短路径一定是纯黑色的节点;而又第4条从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点,这么来说最长路径上的黑节点的数目和最短路径上的黑节点的数目相等!而又第2条根结点为黑、第3条叶子节点是黑,那么可知:最长路径<=2*最短路径。

插入操作

由于性质的约束:插入点不能为黑节点,应插入红节点。因为你插入黑节点将破坏性质4,所以每次插入的点都是红结点,但是若他的父节点也为红,那岂不是破坏了性质3?对啊,所以要做一些“旋转”和一些节点的变色!另为叙述方便,N为当前插入的节点,父节点为P,祖父节点为G,叔节点为U。下边将一一列出所有插入时遇到的情况:

节点的设计

enum Color{ Red,Black};

template<class K,class V>
struct RBTreeNode
{
    RBTreeNode(K key, V value, Color color = Red)
    : _key(key)
    , _value(value)
    , _color(color) //默认红色,满足第四个条件:每条路径的黑色结点树相同
    , _pLeft(NULL)
    , _pRight(NULL)
    , _pParent(NULL)
    {}


    K _key;
    V _value;
    Color _color;
    RBTreeNode<K, V>* _pLeft;
    RBTreeNode<K, V>* _pRight;
    RBTreeNode<K, V>* _pParent;
};

情形1:该树为空树,直接插入根结点的位置,违反性质1,把节点颜色有红改为黑即可。

情形2:插入节点N的父节点P为黑色,不违反任何性质,无需做任何修改。

情形3:N为红,P为红,g为黑(祖节点一定存在,且为黑,下边同理)U也为红,这里不论P是G的左孩子,还是右孩子;不论N是P的左孩子,还是右孩子。

操作:将N,u改为黑,g改为红,然后把g当成cur,继续向上调整。

解析:N、P都为红,违反性质3;若把P改为黑,符合性质3,显然左边少了一个黑节点,违反性质4;所以我们把G,U都改为相反色,这样一来通过G的路径的黑节点数目没变,即符合3、4,但是G变红了,若G的父节点又是红的不就有违反了3,是这样,所以经过上边操作后未结束,需把G作为起始点,即把G看做一个插入的红节点继续向上检索—-属于哪种情况,按那种情况操作~要么中间就结束,要么知道根结点(此时根结点变红,一根结点向上检索,那木有了,那就把他变为黑色吧)。

这里写图片描述

情形4:N为红,P为红,U为黑,P为G的左孩子,N为P的左孩子(或者P为G的右孩子,N为P的左孩子;反正就是同向的)。

操作:n为g的左孩子,n为p的左孩子,则进行右单旋转;相反,p为g的右孩子,n为p的右孩子,则进行左单旋转

p、g变色–p变黑,g变红

解析:要知道经过P、G变换(旋转),变换后P的位置就是当年G的位置,所以红P变为黑,而黑G变为红都是为了不违反性质5,而维持到达叶节点所包含的黑节点的数目不变!还可以理解为:也就是相当于(只是相当于,并不是实事,只是为了更好理解;)把红N头上的红节点移到对面黑U的头上;这样即符合了性质3也不违反性质4,这样就结束了。

这里写图片描述

情形5:N为红,P为红,U为黑,P为G的左孩子,N为P的右孩子(或者P为G的右孩子,N为P的左孩子;反正两方向相反)。

操作:n为g的左孩子,n为p的右孩子,则针对p做左单旋转;相
反,n为g的右孩子,n为p的左孩子,则针对p做右单旋转,则转换成了情况4。

解析:由于P、N都为红,经变换,不违反性质5;然后就变成4的情形,此时G与G现在的左孩子变色,并变换,结束。

这里写图片描述

    bool Insert(const K& key, const V& value)
    {
        if (_pRoot == NULL)
        {
            _pRoot = new Node(key, value);
            _pRoot->_color = Black;
            return true;
        }
        //找到插入的位置
        Node* pCur = _pRoot;
        Node* pParent = NULL;
        while (pCur)
        {
            if (pCur->_key < key)
            {
                pParent = pCur;
                pCur = pCur->_pRight;               
            }               
            else if (pCur->_key > key)
            {
                pParent = pCur;
                pCur = pCur->_pLeft;                
            }
            else
                return false;
        }

        //pCur肯定为NULL
        pCur = new Node(key, value);
        if (pParent->_key < key)
            pParent->_pRight = pCur;        
        else
            pParent->_pLeft = pCur;
        pCur->_pParent = pParent;

        //开始调整颜色
        while (pCur != _pRoot && pParent->_color == Red)
        {
            Node* grandParent = pParent->_pParent;

            if (grandParent->_pLeft == pParent)//叔叔结点为祖父结点的右孩子
            {
                Node* pUncle = grandParent->_pRight;
                //pUncle存在且为红色,调整方法:双亲和叔叔节点改为黑,祖父改为红色,
                //将祖父当做pCur继续向上调整
                if (pUncle != NULL && pUncle->_color == Red)//叔叔节点存在且红色
                {
                    grandParent->_color = Red;
                    pParent->_color = Black;
                    pUncle->_color = Black;

                    pCur = grandParent;
                    pParent = pCur->_pParent;
                }
                else // 叔叔节点不存在或者为黑色
                {
                    //观察情况4和情况5:可以知道当pCur为右孩子时,左旋,pCur为左孩子时右旋
                    //情况5中:pCur为右孩子时,左旋完成后,成为情况4中 pCur为左孩子

                    if (pParent->_pRight == pCur)//情况5:
                    {
                        RotateLeft(pParent);
                        //左旋后,更改双亲结点和当前结点
                        swap(pParent, pCur);
                    }
                    RotateRight(grandParent); //情况4
                    grandParent->_color = Red;
                    pParent->_color = Black;
                    break;
                }
            }
            else //叔叔节点为祖父结点的左孩子
            {
                Node* pUncle = grandParent->_pLeft;
                if (pUncle != NULL && pUncle->_color == Red)//叔叔节点存在且红色
                {
                    grandParent->_color = Red;
                    pParent->_color = Black;
                    pUncle->_color = Black;

                    pCur = grandParent;
                    pParent = pCur->_pParent;
                }
                else // 叔叔节点不存在或者为黑色
                {
                    //观察情况4和情况5:可以知道当pCur为右孩子时,左旋,pCur为左孩子时右旋
                    //情况5中:pCur为左孩子时,右旋完成后,成为情况4中 pCur为右孩子

                    if (pParent->_pLeft == pCur)//情况5:
                    {
                        RotateRight(pParent);
                        //左旋后,更改双亲结点和当前结点
                        swap(pParent, pCur);
                    }
                    RotateLeft(grandParent); //情况4
                    grandParent->_color = Red;
                    pParent->_color = Black;
                    break;
                }
            }
        }
        _pRoot->_color = Black;
        return true;
    }
    void RotateLeft(Node* parent) //左旋
    {
        Node* SubR = parent->_pRight;
        Node* SubRL = SubR->_pLeft;
        Node* pParent = parent->_pParent;

        //将SubRL挂在parent上
        parent->_pRight = SubRL;
        if (SubRL != NULL)
            SubRL->_pParent = parent;
        //将SubR向上提
        SubR->_pParent = pParent;
        if (pParent == NULL)//parent 可能为根结点
        {
            _pRoot = SubR;
        }
        else
        {
            if (pParent->_pLeft == parent)
                pParent->_pLeft = SubR;
            else
                pParent->_pRight = SubR;
        }

        //将parent向下挂到SubR上
        SubR->_pLeft = parent;
        parent->_pParent = SubR;

    }
    void RotateRight(Node* parent)//右旋
    {
        Node* SubL = parent->_pLeft;
        Node* SubLR = SubL->_pRight;
        Node* pParent = parent->_pParent;

        //将SubLR挂在parent上
        parent->_pLeft = SubLR;
        if (SubLR != NULL)
            SubLR->_pParent = parent;

        //将SubL向上提 
        SubL->_pParent = pParent;
        if (pParent == NULL)
        {
            _pRoot = SubL;          
        }
        else
        {
            if (pParent->_pLeft == parent)
                pParent->_pLeft = SubL;
            else
                pParent->_pRight = SubL;            
        }
        //将parent挂到subL上
        SubL->_pRight = parent;
        parent->_pParent = SubL;
    }

更详细的插入和删除操作,看前辈的博客
http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

红黑树和AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删查改的时间复杂度都是O(lg(N))。红黑树的不追求完全平衡,保证最长路径不超过最短路径的2倍,相对而言,降低了旋转的要求,所以性能跟AVL树差不多,但是红黑树实现更简单,所以实际运用中红黑树更多。

猜你喜欢

转载自blog.csdn.net/wenqiang1208/article/details/77366001