紅黑樹詳解

紅黑樹

紅黑樹是一棵二叉搜索樹,它在每一個節點上增加了一個表示顏色的指針,紅色或者黑色。
一棵紅黑樹是滿足下面性質的二叉搜索樹:

  1. 每個節點非紅即黑
  2. 根結點是黑色的
  3. 每個葉節點(這裏的葉節點是指NULL節點)是黑色的
  4. 如果一個節點是紅的,那麼兩個子節點都是黑的
  5. 對於任一節點,從該節點到所有後代葉節點的路徑上,黑色節點的數目相等

由於這些性質的約定,紅黑樹沒有一條路徑會比其他路徑長出兩倍,因而紅黑樹是近似平衡的。


紅黑樹在插入和刪除時會對樹進行修改,可能會違反上面列出的紅黑樹的性質,因此需要對樹作出相應的調整。

插入

說明:爲了滿足性質5,我們默認新增節點的顏色爲紅色
並且約定:cur爲當前插入節點、p爲插入節點父節點、g爲祖父節點,u爲叔叔節點。

情況一
若樹爲空,爲了滿足性質2,需將新增節點改成黑色

情況二
插入節點的父節點爲黑色,直接插入

情況三
插入節點的父節點爲紅色

這裏根據叔叔節點的不同又分兩種情況討論:

1)叔叔節點存在且爲紅
調整方法:將p和u改成黑,g改成紅,然後把p當成cur繼續向上迭代調整。
這裏寫圖片描述
2)叔叔節點不存在或着爲黑
根據p和cur的位置的不同分爲四種情況:

①p爲g的左孩子,cur爲p的左孩子
調整方法:進行右單旋,並且p變爲黑色,g變爲紅色,此時經過調整後的根結點已經爲黑色,故不需要向上調整
這裏寫圖片描述

②p爲g的右孩子,cur爲p的右孩子
調整方法:進行左單旋,並且p變爲黑色,g變爲紅色,此時經過調整後的根結點已經爲黑色,故不需要向上調整
這裏寫圖片描述

③p爲g的左孩子,cur爲p的右孩子
調整方法:針對p進行左單旋,從而轉化爲情況①
這裏寫圖片描述

④p爲g的右孩子,cur爲p的左孩子
調整方法:針對p進行右單旋,從而轉化爲情況②
這裏寫圖片描述

刪除

紅黑樹的刪除和二叉搜索樹的刪除操作類似,但是紅黑樹在刪除節點後需要進行相應的調整,使其仍然滿足紅黑樹的性質。當被刪除節點左右節點都不爲空的情況下,我們需要在其右子樹尋找最左節點或者在其左子樹尋找最右邊的節點作爲真正要被刪除的替換節點(金蟬脫殼),被刪節點與替換節點交換後,把替換節點刪除即可。
注意,此時待刪除的節點要麼沒有孩子節點,要麼只有左子樹或者只有右子樹

下面主要說明在刪除替換節點後,怎樣重新調整紅黑樹

情況一
刪除的節點爲紅色節點,直接刪除

情況二
刪除的節點爲黑色,顯然刪除一個黑色節點後會違反性質5,需要進行調整,

1)有孩子節點並且孩子節點爲紅色
直接刪除節點並把孩子節點變爲黑色
注意要刪除節點爲根結點的情況:
這裏寫圖片描述
2)無孩子節或者孩子節點爲黑色(事實上孩子節點爲黑的情況一定不存在)

分爲以下五種情況:
注:約定N爲待刪節點,P爲父節點,S爲兄弟節點
①S爲紅色(p和S的子節點一定爲黑),N是P的左孩子(或者右孩子)
操作:以S爲軸心左旋,並且把S變爲黑,P變爲紅
這裏寫圖片描述
此時刪除N節點後,在N節點的路徑上少了一個黑節點,因此需要以N爲開始重新向上調整

②P和S都爲黑,且S的孩子們也都爲黑
操作:S變爲紅色
這裏寫圖片描述
此時刪除S變色後,並且刪除N後,以P爲根結點的子樹的黑節點減少了兩個,因此需要以P爲開始重新向上調整

③P爲紅(S一定爲黑),S的孩子們爲黑
操作:P改爲黑,S改爲紅
這裏寫圖片描述
此時,在刪除N節點後黑節點的數目無變化,無需調整

④P爲任意顏色,S爲黑,N是P的左孩子,S的右孩子SR爲紅(或者是N是P的右孩子,S的左孩子爲紅,S的右孩子任意)
操作:以S爲中心左旋,SR(SL)改爲黑,P改爲黑,S改爲P的顏色
這裏寫圖片描述
此時,刪除N後,黑節點數目無變化,無需調整

⑤P任意色,S爲黑,N是P的左孩子,S的左孩子SL爲紅,S的右孩子SR爲黑(或者N是P的有孩子,S的右孩子爲紅,S的左孩子爲黑)
操作:以SL爲中心進行左旋,並且S變爲紅,SL變爲黑
這裏寫圖片描述
此時,變成了情況④
至此,刪除的所有情況已經分析完畢。

C++代碼:

#pragma once
#include<utility>
#include<algorithm>
#include<iostream>

using namespace std;

enum Color
{
    RED = 0,
    BLACK
};

template<typename K, typename V>
struct RBTreeNode
{
    RBTreeNode* _pLeft;
    RBTreeNode* _pRight;
    RBTreeNode* _pParent;
    Color _color;
    pair<K, V> _v;
    RBTreeNode(const K& key, const V& value)
        :_pLeft(NULL)
        , _pRight(NULL)
        , _pParent(NULL)
        , _color(RED)
        , _v(pair<K, V>(key, value))
    {}
};

template<typename K, typename V>
class RBTree
{
    typedef RBTreeNode<K, V> Node;
    typedef Node* pNode;
public:
    RBTree()
        :_pRoot(NULL)
    {}

    bool insert(const K& key, const V& value)
    {
        if (NULL == _pRoot)
        {
            _pRoot = new Node(key, value);
            _pRoot->_color = BLACK;
            return true;
        }

        pNode pCur = _pRoot;
        pNode pParent = NULL;
        while (pCur)
        {
            pParent = pCur;
            if (pCur->_v.first > key)
                pCur = pCur->_pLeft;
            else if (pCur->_v.first < key)
                pCur = pCur->_pRight;
            else
                return false;
        }
        pCur = new Node(key, value);
        if (pParent->_v.first > key)
            pParent->_pLeft = pCur;
        else
            pParent->_pRight = pCur;

        pCur->_pParent = pParent;

        if (BLACK == pParent->_color)
            return true;
        while (pCur != _pRoot && RED == pParent->_color)
        {
            pNode pGrdftr = pParent->_pParent;

            //根據p位於g的方向
            if (pParent == pGrdftr->_pLeft)
            {
                //叔叔是否存在,節點的顏色
                pNode pUncle = pGrdftr->_pRight;
                if (NULL != pUncle && RED == pUncle->_color)
                {
                    pParent->_color = BLACK;
                    pUncle->_color = BLACK;
                    pGrdftr->_color = RED;
                    pCur = pGrdftr;
                    pParent = pCur->_pParent;
                }
                else
                {
                    //根據cur在p的哪邊
                    if (pCur == pParent->_pLeft)
                    {
                        _RotateRight(pParent->_pParent);
                        pParent->_color = BLACK;
                        pGrdftr->_color = RED;
                    }
                    else
                    {
                        _RotateLeft(pParent);
                        swap(pParent, pCur);
                    }
                }
            }
            else
            {
                //叔叔是否存在,節點的顏色
                pNode pUncle = pGrdftr->_pLeft;
                if (NULL != pUncle && RED == pUncle->_color)
                {
                    pParent->_color = BLACK;
                    pUncle->_color = BLACK;
                    pGrdftr->_color = RED;
                    pCur = pGrdftr;
                    pParent = pCur->_pParent;
                }
                else
                {
                    //根據cur在p的哪邊
                    if (pCur == pParent->_pRight)
                    {
                        _RotateLeft(pParent->_pParent);
                        pParent->_color = BLACK;
                        pGrdftr->_color = RED;
                    }
                    else
                    {
                        _RotateRight(pParent);
                        swap(pParent, pCur);
                    }
                }
            }
        }
        _pRoot->_color = BLACK;
        return true;
    }

    bool IsRBTree()
    {
        if (NULL == _pRoot)
            return true;

        if (BLACK != _pRoot->_color)
            return false;
        int BlackNum = 0;
        int CurBlackNum = 0;
        pNode pCur = _pRoot;

        //先統計出隨便一路徑的黑色節點的個數
        while (pCur)
        {
            if (BLACK == pCur->_color)
                BlackNum++;
            pCur = pCur->_pLeft;
        }
        return _IsRBTree(_pRoot,BlackNum,CurBlackNum);
    }

    bool Find(const K& key)
    {
        pNode pCur = _pRoot;
        while (pCur)
        {
            if (key == pCur->_v.first)
                return true;
            else if (key < pCur->_v.first)
                pCur = pCur->_pRight;
            else
                pCur = pCur->_pLeft;
        }
        return false;
    }

    bool Remove(const K& key)
    {
        pNode pCur = _pRoot;
        pNode pParent = NULL;
        pNode pDel = NULL;

        //尋找要刪除的節點
        while (pCur)
        {
            if (key == pCur->_v.first)
                break;
            else if (key < pCur->_v.first)
                pCur = pCur->_pRight;
            else
                pCur = pCur->_pLeft;
        }

        //沒找到要刪節點
        if (NULL == pCur)
            return false;
        //如果要刪除節點有兩個孩子節點,轉移要刪除節點
        if (pCur->_pLeft != NULL && pCur->_pRight != NULL)
        {
            pNode pRightMin = pCur->_pRight;//找到右子樹的最小節點
            //即右子樹的最左邊節點
            while (pRightMin->_pLeft)
                pRightMin = pRightMin->_pLeft;
            pCur->_v = pRightMin->_v;
            pCur = pRightMin;//讓pCur指向要刪節點
        }
        pParent = pCur->_pParent;
        pDel = pCur;

        //要刪除節點的左邊爲空
        //此邏輯裏面提前先處理好父節點的指向
        //在後面的操作中直接刪除即可
        if (NULL == pDel->_pLeft)
        {
            //如果要刪的是根結點
            if (_pRoot == pDel)
            {
                _pRoot = pDel->_pRight;
                //如果右孩子存在,那它一定爲紅色
                if (NULL != pDel->_pRight)
                {
                    _pRoot->_pParent = NULL;
                    _pRoot->_color = BLACK;
                }
                delete pDel;
                return true;
            }

            //讓被刪節點的父節點指向被刪節點的孩子節點
            if (pParent->_pLeft == pDel)
                pParent->_pLeft = pDel->_pRight;
            else
                pParent->_pRight = pDel->_pRight;
            if (pDel->_pRight)
                pDel->_pRight->_pParent = pParent;
            pCur = pDel->_pRight;
        }
        else
        {
            //如果要刪的是根結點
            if (_pRoot == pDel)
                _pRoot = pDel->_pLeft;
            //如果左孩子存在,那它一定爲紅色
            if (NULL != pDel->_pLeft)
            {
                _pRoot->_pParent = NULL;
                _pRoot->_color = BLACK;
            }
            delete pDel;
            return true;

            //讓被刪節點的父節點指向被刪節點的孩子節點
            if (pParent->_pLeft == pDel)
                pParent->_pLeft = pDel->_pRight;
            else
                pParent->_pRight = pDel->_pRight;
            if (pDel->_pRight)
                pDel->_pRight->_pParent = pParent;
            pCur = pDel->_pLeft;//讓pCur標記孩子節點
        }

        //如果要刪除節點是紅色,直接刪除
        if (pDel->_color == RED)
        {
            delete pDel;
            return true;
        }

        //如果待刪節點爲黑的,且其孩子是紅的,將孩子改爲黑色即可
        if (BLACK == pDel->_color && pCur && RED == pCur->_color)
        {
            pCur->_color = BLACK;
            delete pDel;
            return true;
        }

        //要刪除節點是黑色的
        while (pParent)
        {
            //要刪除節點是父親的左孩子
            if (pParent->_pLeft == pDel)
            {
                pNode pSubR = pParent->_pRight;//parent的右孩子,一定存在

                //右孩子是紅色的,對應情況①,注意這裏的pSubR在圖片中表示爲S
                if (pSubR->_color == RED)
                {
                    _RotateLeft(pParent);
                    pSubR->_color = BLACK;
                    pParent->_color = RED;
                }
                else//pSubR爲黑色
                {
                    pNode pSubRL = pSubR->_pLeft;
                    pNode pSubRR = pSubR->_pRight;

                    //parent爲黑色
                    //(SubR雙節點爲空)或者(SubR有雙節點並且雙節點爲黑)
                    //對應情況②
                    if (BLACK == pParent->_color && (NULL == pSubR->_pLeft && NULL == pSubR->_pRight)
                        || (pSubRL && pSubRR && BLACK == pSubRL->_color && BLACK == pSubRR->_color))
                    {
                        pSubR->_color = RED;
                        pCur = pParent;
                        pParent = pCur->_pParent;
                    }
                    else
                    {
                        if (RED == pParent->_color)//父節點爲紅色
                        {
                            //如果SubR的孩子們爲黑 或者沒有孩子們
                            //對應情況③
                            if ((NULL == pSubRL && NULL == pSubRR) ||
                                (pSubRL && pSubRR && BLACK == pSubRL->_color && BLACK == pSubRR->_color))
                            {
                                pParent->_color = BLACK;
                                pSubR->_color = RED;
                                break;
                            }
                        }

                        //對應情況⑤
                        if (RED == pSubRL->_color)
                        {
                            _RotateRight(pSubR);
                            pSubR = pSubRL;
                        }

                        //對應情況④
                        _RotateLeft(pParent);
                        if (RED == pParent->_color)
                            pSubR->_color = RED;
                        else
                            pSubR->_color = BLACK;

                        pParent->_color = BLACK;
                        pSubR->_pRight->_color = BLACK;
                        break;
                    }
                }
            }
            else//要刪除節點是父節點右孩子的情況,與上面相反
            {
                pNode pSubL = pParent->_pLeft;

                if (pSubL->_color == RED)
                {
                    _RotateRight(pParent);
                    pSubL->_color = BLACK;
                    pParent->_color = RED;
                }
                else
                {
                    pNode pSubLL = pSubL->_pLeft;
                    pNode pSubLR = pSubL->_pRight;

                    if (BLACK == pParent->_color && (NULL == pSubL->_pLeft && NULL == pSubL->_pRight)
                        || (pSubLL && pSubLR && BLACK == pSubLL->_color && BLACK == pSubLR->_color))
                    {
                        pSubL->_color = RED;
                        pCur = pParent;
                        pParent = pCur->_pParent;
                    }
                    else
                    {
                        if (RED == pParent->_color)//父節點爲紅色
                        {
                            if ((NULL == pSubLL && NULL == pSubLR) ||
                                (pSubLL && pSubLR && BLACK == pSubLL->_color && BLACK == pSubLR->_color))
                            {
                                pParent->_color = BLACK;
                                pSubL->_color = RED;
                                break;
                            }
                        }

                        //對應情況⑤
                        if (RED == pSubLL->_color)
                        {
                            _RotateLeft(pSubL);
                            pSubL = pSubLR;
                        }

                        //對應情況④
                        _RotateRight(pParent);
                        if (RED == pParent->_color)
                            pSubL->_color = RED;
                        else
                            pSubL->_color = BLACK;

                        pParent->_color = BLACK;
                        pSubL->_pLeft->_color = BLACK;
                        break;
                    }
                }
            }
        }
        _pRoot->_color = BLACK;
        delete pDel;
        return true;
    }

private:
    bool _IsRBTree(pNode pRoot,int BlackNum,int CurBlackNum)
    {
        if (NULL == pRoot)
            return (BlackNum == CurBlackNum);

        if (RED == pRoot->_color && RED == pRoot->_pParent->_color)
            return false;

        if (BLACK == pRoot->_color)
            CurBlackNum++;

        return (_IsRBTree(pRoot->_pLeft, BlackNum, CurBlackNum) && _IsRBTree(pRoot->_pRight, BlackNum, CurBlackNum));
    }

    void _RotateRight(pNode& pParent)
    {
        pNode pSubL = pParent->_pLeft;
        pNode ppParent = pParent->_pParent;

        pParent->_pLeft = pSubL->_pRight;
        if (pSubL->_pRight != NULL)
            pSubL->_pRight->_pParent = pParent;
        pSubL->_pRight = pParent;
        pParent->_pParent = pSubL;

        if (NULL == ppParent)
        {
            _pRoot = pSubL;
            return;
        }
        else if (pParent == ppParent->_pLeft)
            ppParent->_pLeft = pSubL;
        else
            ppParent->_pRight = pSubL;

        pSubL->_pParent = ppParent;
    }

    void _RotateLeft(pNode& pParent)
    {
        pNode pSubR = pParent->_pRight;
        pNode ppParent = pParent->_pParent;

        pParent->_pRight = pSubR->_pLeft;
        if (pSubR->_pLeft != NULL)
            pSubR->_pLeft->_pParent = pParent;
        pSubR->_pLeft = pParent;
        pParent->_pParent = pSubR;
        pSubR->_pParent = ppParent;
        if (NULL== ppParent)
        {
            _pRoot = pSubR;
            return;
        }
        else if (pParent == ppParent->_pLeft)
            ppParent->_pLeft = pSubR;
        else
            ppParent->_pRight = pSubR;
    }
private:
    pNode _pRoot;
};

void TestInsert()
{
    int arr[] = { 10, 20, 30, 70, 90, 40, 5,6 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    RBTree<int, int> rb;
    for (int i = 0; i < sz; ++i)
    {
        rb.insert(arr[i], i);
    }

    rb.Remove(20);

    if (rb.IsRBTree())
        cout << "是紅黑樹" << endl;
    else
        cout << "非紅黑樹" << endl;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章