浅谈平衡搜索树之红黑树

上篇我们讲到了平衡搜索树的一种AVL树,它的时间复杂度是O(lgN)。这是一种相对来说很严格的平衡搜索树,它的平衡条件相对苛刻,在很多情况下都需要旋转调整,我们可以理解为它更容易发生旋转。今天我们要介绍的是红黑树,相比于AVL树,它更不容易发生旋转。

红黑树的特点
1.每个节点,只有红或黑两种颜色。
2.根节点是黑色的。
3.不会有两个连续的红节点出现。
4.对每个节点,从该节点到其所有后代叶子节点的简单路径上,都包含相同数目的黑节点。

通过如上的规律我们可以读出一个结论:最长搜索路径不超过最短搜索路径的两倍。很简单,因为最短路径是该路径全都是黑节点,最长路径是一黑一红的路径。
如下图
这里写图片描述
最长搜索路径是6,最短搜索路径是3。因为最短搜索路径的时间复杂度是O(lgN),所以最长搜索路径的时间复杂度是O(2lgN),所以最坏情况下,相比于AVL树,它的时间复杂度也不过是2倍而已,例如找10亿个数,AVL数需要将近30次,红黑树也只需要60次,对于计算机来说,这点差值根本不算什么,但是红黑树却比AVL树更难发生旋转,达到一种近似平衡就可以了。

红黑树的增
与AVL树同样的,我们只研究红黑树的增。
一. 与之前同样的是,一样也是比较插入的节点与当前节点的key值,找到一个为空的位置然后进行插入。
二. 这里我们都让插入的节点的颜色为红色,否则就会破坏红黑树的第四个性质。接下来来分析一下插入以后需要调整的情况。
①.首先当前节点cur的颜色肯定为红,如果父亲的颜色是黑色的,那么无需调整直接返回就行。
②.我们在父亲颜色为红的情况下进下分析:
第一种情况:
parent的兄弟节点uncle存在且为红,即如下图 ,这种情况我们只需要进行变色处理就行了,将g的颜色变为红,并将u和p的颜色都变为黑,再将cur给到g,继续向上调整。
这里写图片描述

第二种情况:
parent的兄弟节点uncle存在且为黑,或uncle不存在,且p是g的右孩子,cur是p的右孩子,则用左单旋;若p是g的左孩子,cur是p的左孩子,则用右单旋。旋转的原理和AVL树一样,只是最后不是改平衡因子而是换颜色了,所以这里就不单独讲旋转了,只给出图。
左单旋:
这里写图片描述
右单旋:
这里写图片描述
这里需要注意的时,此时的cur一定不是新增节点,否则这颗树之前就已经不满足每条路径黑色节点数相等的这个性质了,所以cur一定是通过第一种情况变色而来,它的左右子树一定存在且有黑节点,p的另一个子树也一定存在且有黑节点。

第三种情况:
parent的兄弟节点uncle存在且为黑,或uncle不存在,且p是g的右孩子,cur是p的左孩子,则用右左双旋;若p是g的左孩子,cur是p的右孩子,则用左右双旋。同样的,这里只给出图。
右左双旋:
这里写图片描述
左右双旋:
这里写图片描述

下面我们来看下一下红黑树的实现代码

#pragma once

#include <iostream>
using namespace std;

enum Color
{
    RED,
    BLACK,
};

template <class K,class V>
struct RBTreeNode
{
    K _key;
    V _value;
    Color _color;

    RBTreeNode<K, V>* _left;
    RBTreeNode<K, V>* _right;
    RBTreeNode<K, V>* _parent;

    RBTreeNode(const K& key, const V& value)
        : _left(NULL)
        , _right(NULL)
        , _parent(NULL)
        , _key(key)
        , _value(value)
        , _color(RED)
    {}
};

template <class K,class V>
class RBTree
{
    typedef RBTreeNode<K, V> Node;
public:
    RBTree()
        :_root(NULL)
    {}

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

        Node* cur = _root;
        Node* parent = NULL;
        while (cur)
        {
            if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                return false;
            }
        }

        cur = new Node(key, value);
        //让parent的左或右指向cur,cur的parent指向parent
        if (parent->_key < key)
        {
            parent->_right = cur;
            cur->_parent = parent;
        }
        else
        {
            parent->_left = cur;
            cur->_parent = parent;
        }

        //调平衡
        //父亲颜色为黑不用调整,所以不用考虑
        while (parent&&parent->_color == RED)
        //循环条件(父亲存在且父亲的颜色为红)
        {
            Node* grandparent = parent->_parent;
            //父亲是祖父的左
            if (parent == grandparent->_left)
            {
                Node* uncle = grandparent->_right;
                if (uncle&&uncle->_color == RED)
                //uncle的颜色为红,则变色再向上调整
                {
                    grandparent->_color = RED;
                    uncle->_color = parent->_color = BLACK;
                    cur = grandparent;
                    parent = cur->_parent;
                }
                else
                //uncle的颜色为黑或uncle不存在,旋转
                {
                    if (cur == parent->_right)
                    //cur是parent的右则先要进行左单旋
                    {
                        RotateL(parent);
                        swap(cur, parent);
                    }
                    RotateR(grandparent);
                    parent->_color = BLACK;
                    grandparent->_color = RED;
                    break;
                }
            }
            else
            //父亲是祖父的右
            {
                Node* uncle = grandparent->_left;
                if (uncle&&uncle->_color == RED)
                //uncle的颜色为红,则变色再向上调整
                {
                    grandparent->_color = RED;
                    uncle->_color = parent->_color = BLACK;
                    cur = grandparent;
                    parent = cur->_parent;
                }
                else
                //uncle的颜色为黑或uncle不存在,旋转
                {
                    if (cur == parent->_left)
                    //cur是parent的左则先要进行右单旋
                    {
                        RotateR(parent);
                        swap(cur, parent);
                    }
                    RotateL(grandparent);
                    parent->_color = BLACK;
                    grandparent->_color = RED;
                    break;
                }
            }
        }
        _root->_color = BLACK;
        return true;
    }

    bool IsBalance()
    {
        if (_root == NULL)
        {
            return true;
        }

        if (_root->_color == RED)
        {
            return false;
        }

        Node* cur = _root;
        int k = 0;
        while (cur)
        {
            if (cur->_color == BLACK)
            {
                k++;
            }
            cur = cur->_left;
        }
        size_t BlackNum = 0;
        return _IsBalance(_root, BlackNum, k);
    }

    void Inorder()
    {
         _Inorder(_root);
         cout << endl;
    }
protected:
    bool _IsBalance(Node* root, size_t BlackNum, const int k)
    {
        if (root == NULL)
        {
            if (BlackNum != k)
            {
                cout << "黑色节点数量不相等" << endl;
                return false;
            }
            return true;
        }

        if (root->_color == BLACK)
        {
            ++BlackNum;
        }

        if (root->_color == RED&&root->_parent->_color == RED)
        {
            cout << "有连续的红节点" << endl;
            return false;
        }
        return _IsBalance(root->_left, BlackNum, k) &&
            _IsBalance(root->_right, BlackNum, k);
    }

    bool _Inorder(Node* root)
    {
        if (root == NULL)
        {
            return false;
        }

        _Inorder(root->_left);
        cout << root->_key << " ";
        _Inorder(root->_right);

        return true;
    }

    void RotateL(Node* parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        Node* grandparent = parent->_parent;

        //subRL给parent的右,parent给subRL的parent
        parent->_right = subRL;
        if (subRL)
        {
            subRL->_parent = parent;
        }

        //parent给subR的左,subR给parent的parent
        subR->_left = parent;
        parent->_parent = subR;

        if (grandparent == NULL)
        {
            _root = subR;
            subR->_parent == NULL;
        }
        else
        {
            if (parent == grandparent->_left)
            {
                grandparent->_left = subR;
            }
            else
            {
                grandparent->_right = subR;
            }
            subR->_parent = grandparent;
        }
    }

    void RotateR(Node* parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        Node* grandparent = parent->_parent;

        parent->_left = subLR;
        if (subLR)
        {
            subLR->_parent = parent;
        }

        subL->_right = parent;
        parent->_parent = subL;

        if (grandparent == NULL)
        {
            _root = subL;
            subL->_parent == NULL;
        }
        else
        {
            if (parent == grandparent->_left)
            {
                grandparent->_left = subL;
            }
            else
            {
                grandparent->_right = subL;
            }
            subL->_parent = grandparent;
        }
    }
private:
    Node* _root;
};

void TestRBTree()
{
    int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
    RBTree<int, int> t;
    for (size_t i = 0; i < sizeof(a) / sizeof(int); ++i)
    {
        t.Insert(a[i], i);
        cout << t.IsBalance() << "   ";
        t.Inorder();
    }
}

同样的,我们写了一个判断是否平衡的函数。判断是否平衡的条件主要有两个:
1.是否有两个连续的红节点。
2.每条路径上的黑节点的数量是否相同。
这里我们先求出一条路径的黑节点数量k,再跟讲别的路径的黑节点数量BlackNum与k比较看是否相等,一旦有不相等的直接返回false,后面的路径也就不用再检查了。
上面的测试代码结果如下:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值