数据结构之红黑树(C++实现)

数据结构之红黑树

我之前的博客已经介绍过了二叉树和AVL树的基本概念和简单实现,具体参考数据结构-树(C语言实现篇)数据结构之AVL树(C++实现)

1. 红黑树

1.1 红黑树的概念

红黑树也是一种二叉搜索树,在此基础上每个结点增加一个存储位表示结点的颜色,可以是红色或者黑色,通过对任何一条从根节点到叶子结点的路径上每个结点颜色的限制,红黑树确保没有一条路径会比其他路径大一倍,因此,是接近平衡的一颗二叉搜索树

在这里插入图片描述

1.2 红黑树的性质
  1. 每个结点的颜色不是红色就是黑色。
  2. 根节点是黑色的。
  3. 如果一个节点是红色的,那么它的两个孩子结点是黑色的。
  4. 对于每个结点,从该结点到其所有后代的叶子结点的路径上,均包含相同数目的黑色结点。
  5. 每个叶子结点都是黑色的**(指的是空节点)**。

2. 红黑树的实现

2.1 红黑树结点的定义
// 结点的颜色
enum Colour {
    RED,
    BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;     // 左孩子
	RBTreeNode<K, V>* _right;   // 右孩子
	RBTreeNode<K, V>* _parent; // 父节点

	pair<K, V> _kv;  // 存储对
	Colour _col;      // 存储颜色

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
	{}
};
2.2 红黑树的插入操作

红黑树是在二叉搜索树的基础上加上了平衡限制条件,故分为两步:

  1. 按照二叉搜索树的规则插入新节点。

    typedef RBTreeNode<K, V> Node;
    bool Insert(const pair<K, V>& kv)
    {
        if (_root == nullptr)
        {
            _root = new Node(kv);
            _root->_col = BLACK; // 根节点必须为黑色
            return true;
        }
    	
        // 按照二叉排序树的思路进行查找
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_kv.first < kv.first)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_kv.first > kv.first)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                return false;
            }
        }
    
        cur = new Node(kv);
        if (parent->_kv.first < kv.first)
        {
            parent->_right = cur;
        }
        else
        {
            parent->_left = cur;
        }
    
        cur->_parent = parent;
    }
    
  2. 检测新结点插入之后,红黑树的性质是否被破坏。

    因为新插入的结点默认是红色的,因此,如果双亲结点是黑色,则没有违反红黑树任何性质。如果插入结点的双亲结点是红色的,就违背了红色结点的两个孩子结点是黑色的的性质。针对不同的情况进行分析:

    • 情况一:cur为红色,parent为红色,grandfather为黑色,uncle为红色。

在这里插入图片描述

 设置双亲结点和双亲的兄弟结点为黑色。
 如果g结点为根节点,那么设置为黑色。因为根节点必须是黑色。
 如果g结点不为根节点,那么设置为红色,然后继续向上调整。
  • 情况二:cur为红色,parent为红色,grandfather为黑色,uncle不存在/存在且为黑色。

在这里插入图片描述
说明:(u结点分两种情况)

 如果u结点不存在,则cur一定是新插入的结点,因为cur和p都为红色,故冲突发生在这里,一定是有一个结点不满足,才会导致性质失效。
 如果u结点存在,则其一定是黑色的,那么cur结点原来的颜色也是黑色的,因为如果cur原来不是黑色的,就不满足从根节点到叶子结点的黑色节点数量相同。现在看到是黑色的原因在于cur的子树在调整过程中将cur结点的颜色从黑色改为红色。
 p结点为g结点的左孩子,cur结点为p结点的左孩子,进行右单旋。
 p结点为g结点的右孩子,cur结点为p结点的右孩子,进行左单旋。
 p结点变成黑色,g结点变成红色。
  • 情况三:cur为红色,parent为红色,grandfather为黑色,uncle不存在/存在且为黑色。
    在这里插入图片描述
p结点为g结点的左孩子,cur结点为p的右孩子,则对p做左单旋。
p结点为g结点的右孩子,cur结点为p的左孩子,则对p做右单旋。
最终变成了情况二,按照情况二接着处理即可。
  1. 代码实现:

    // parent为红色
    while (parent && parent->_col == RED)
    {
        Node* grandfater = parent->_parent;
        assert(grandfater);
        assert(grandfater->_col == BLACK); // 如果爷爷的颜色不为黑色,那么前面的操作有误
        // 关键看叔叔
        // 若p为g左子树
        if (parent == grandfater->_left)
        {
            Node* uncle = grandfater->_right;
            // 情况一 : uncle存在且为红,变色+继续往上处理
            // 情况一只用变色,不需要旋转
            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandfater->_col = RED;
                // 继续往上处理
                cur = grandfater;
                parent = cur->_parent;
            }// 情况二+三:uncle不存在 + 存在且为黑
            else
            {
                // 情况二:右单旋+变色
                //     g 
                //   p   u
                // c
                if (cur == parent->_left)
                {
                    RotateR(grandfater); // 采用AVL树的旋转策略
                    parent->_col = BLACK;
                    grandfater->_col = RED;
                }
                else
                {
                    // 情况三:左右单旋+变色
                    //     g 
                    //   p   u
                    //     c
                    RotateL(parent);  // 这一步结束后变成了情况二
                    RotateR(grandfater); 
                    cur->_col = BLACK;
                    grandfater->_col = RED;
                }
    			// 不需要在往上调整
                break;
            }
        }
        else // (parent == grandfater->_right)
        {
            Node* uncle = grandfater->_left;
            // 情况一
            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandfater->_col = RED;
                // 继续往上处理
                cur = grandfater;
                parent = cur->_parent;
            }
            else
            {
                // 情况二:左单旋+变色
                //     g 
                //   u   p
                //         c
                if (cur == parent->_right)
                {
                    RotateL(grandfater);
                    parent->_col = BLACK;
                    grandfater->_col = RED;
                }
                else
                {
                    // 情况三:右左单旋+变色
                    //     g 
                    //   u   p
                    //     c
                    RotateR(parent);
                    RotateL(grandfater);
                    cur->_col = BLACK;
                    grandfater->_col = RED;
                }
                break;
            }
        }
    }
    
    // 记得最后需要将根节点设置为黑色
    _root->_col = BLACK;
    
2.3 红黑树的验证

红黑树的验证分为两步:

  1. 检测其是否满足二叉搜索树(中序序列是否是有序序列)。

    void InOrder()
    {
        _InOrder(_root);
        cout << endl;
    }
    
    void _InOrder(Node* root)
    {
        if (root == nullptr)
        {
        	return;
        }
    
        _InOrder(root->_left);
        cout << root->_kv.first << ":" << root->_kv.second << endl;
        _InOrder(root->_right);
    }
    
  2. 检测是否满足红黑树的性质。

    bool IsBalance() 
    {
        /* 性质:
            1. 根节点是黑色的
            2. 如果一个节点是红色的,则它的两个孩子结点是黑色的
            3. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
            4. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
         */
        if (_root == nullptr)
            return true;
        if (_root->_col == RED) {
            cout << "根节点不是黑色" << endl;
            return false;
        }
        // 黑色节点数量基准值
        int benchmark = 0;
        return PrevCheck(_root, 0, benchmark);
    }
    
    /*
    	思路:
    	判断每条路径上的黑色节点个数是否相同。
    	blackNum为当前路径结点的个数
    	benchmark第一次为0,第二次为第一次获取路径结点的个数
    	如果benchmark不等于blackNum,表示当前路径的黑色节点个数与其他路径不相同,则不满足红黑树的条件。
    */
    bool PrevCheck(Node *root, int blackNum, int &benchmark) 
    {
        if (root == nullptr) {
            if (benchmark == 0) {
                benchmark = blackNum;
                return true;
            }
    
            if (blackNum != benchmark) {
                cout << "黑色节点数量不相等" << endl;
                return false;
            } else {
                return true;
            }
        }
        // 如果为黑色,那么黑色节点数量++
        if (root->_col == BLACK)
            ++blackNum;
    	
        // 检查当前结点与其双亲结点是否都为红色
        if (root->_col == RED && root->_parent->_col == RED) {
            cout << "出现连续的红色结点" << endl;
            return false;
        }
        return PrevCheck(root->_left, blackNum, benchmark)
            && PrevCheck(root->_right, blackNum, benchmark);
    }
    
2.4 红黑树的删除

红黑树的删除这里不做讨论,有兴趣可以参考:《算法导论》或者《STL源码剖析》

https://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

2.5 红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log_2(n)),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在C++中,std::map 是使用(RedBlack Tree)作为底层数据结构实现的。是一种自平衡二叉查找,它具有以下特点: 1. 每个节点要么色,要么是色。 2. 根节点是色的。 3. 所有叶子节点(NIL节点)都是色的。 4. 如果一个节点是色的,则它的两个子节点都是色的。 5. 对于每个节点,从该节点到其后代叶子节点的简单路径上,均包含相同数目的色节点。 这些特性保证了的平衡性,使得在最坏情况下,插入、删除和查找操作的时间复杂度都是O(log n)。 std::map 是C++标准库中提供的关联容器之一,它提供了一种键值对的存储方式,并根据键的顺序自动进行排序。通过使用作为底层数据结构,std::map 可以在插入、删除和查找操作上具有较好的性能表现。 以下是一个使用 std::map 的简单示例: ```cpp #include <iostream> #include <map> int main() { std::map<int, std::string> myMap; // 插入键值对 myMap.insert(std::make_pair(1, "Apple")); myMap.insert(std::make_pair(2, "Banana")); myMap.insert(std::make_pair(3, "Orange")); // 遍历输出 for (const auto& pair : myMap) { std::cout << pair.first << ": " << pair.second << std::endl; } // 查找键对应的值 auto it = myMap.find(2); if (it != myMap.end()) { std::cout << "Value of key 2: " << it->second << std::endl; } return 0; } ``` 上述示例中,我们使用 std::map 存储了一些水果的名称,并按照键的顺序输出。通过调用 find() 函数,我们可以查找指定键对应的值。 希望以上回答能够解决你的问题!如有更多疑问,请继续提问。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值