c++ 红黑树(带头结点)(k,k型)

简介: 想必在看到这篇文章的时候,你一定是带着问题去搜索的,一定是对红黑树已经有了初步大致的认识,已经知道了红黑树的性质与普通红黑树的功能与如何代码实现,但是莫一天突然看到了带头结点的红黑树,肯定是对此有一些疑惑的,或者来说在代码的实现上自己存在着某些疑惑。那么话不多说,就先给出红黑树(带头结点)的完整实现代码。然后再给出相应的详细解释。


想必在看到这篇文章的时候,你一定是带着问题去搜索的,一定是对红黑树已经有了初步大致的认识,已经知道了红黑树的性质与普通红黑树的功能与如何代码实现,但是莫一天突然看到了带头结点的红黑树,肯定是对此有一些疑惑的,或者来说在代码的实现上自己存在着某些疑惑。那么话不多说,就先给出红黑树(带头结点)的完整实现代码。然后再给出相应的详细解释。

代码的实现

include

include

using namespace std;
enum Colour
{
RED,
BLACK,
};

template
struct RBTreeNode
{
RBTreeNode _pLeft;
RBTreeNode
_pRight;
RBTreeNode* _pParent;
T _data;
Colour _col;

RBTreeNode(const T& data = T(), Colour col = RED)
    : _pLeft(nullptr)
    , _pRight(nullptr)
    , _pParent(nullptr)
    , _data(data)
    , _col(col)

{}

};

// 请模拟实现红黑树的插入--注意:为了后序封装map和set,本文在实现时给红黑树多增加了一个头结点
template
class RBTree
{
typedef RBTreeNode Node;
public:
RBTree()
{
_pHead = new Node;
_pHead->_pLeft = _pHead;
_pHead->_pRight = _pHead;
}

// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
// 注意:为了简单起见,本次实现红黑树不存储重复性元素
bool Insert(const T& data)
{
    Node*& pRoot = GetRoot();
    // 第一次插入结点
    if (pRoot == nullptr)
    {
        pRoot = new Node(data, BLACK);
        //////////////////////////////////////////////////////////////////////////
        pRoot->_pParent = _pHead;
        return true;
    }
    // 找待插入节点在二叉搜索树中的位置
    // 并保存其双亲节点
    else
    {
        Node* pCur = pRoot;
        Node* pParent = nullptr;
        while (pCur)
        {
            pParent = pCur;
            if (data < pCur->_data)
                pCur = pCur->_pLeft;
            else if (data > pCur->_data)
                pCur = pCur->_pRight;
            else
                return false;
        }
        // 插入新节点
        pCur = new Node(data);
        if (data < pParent->_data)
            pParent->_pLeft = pCur;
        else
            pParent->_pRight = pCur;
        pCur->_pParent = pParent;
        //调整
        // l pCur新节点默认颜色 : 红色
        // 如果pParent的颜色是黑色的,满足红黑树性质
        // 如果pParent的颜色是红色的,违反了性质三--不能有连在一起的
        // ...
        while (pParent != _pHead && pParent->_col == RED)//大前提
        {
            Node* grandfather = pParent->_pParent;
            //parent在左
            if (pParent == grandfather->_pLeft)
            {
                Node* uncle = grandfather->_pRight;
                //Node* uncle = pParent->_pRight;//错误二
                if (uncle && uncle->_col == RED)
                {
                    //情景一:pCur->红,pParent->红,grandfather->黑,uncle存在且为红
                        //     g
                        //   p   u
                        // c
                        // 
                        //解决:p,u改为黑,g改为红,最后g为红所以,要继续向上调整
                    pParent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;
                    //////////////////////////////////////
                    pCur = grandfather;
                    if (pCur == pRoot)
                    {
                        pCur->_pParent = _pHead;
                    }
                    pParent = pCur->_pParent;
                }
                else
                {
                    if (pCur == pParent->_pLeft)
                    {
                        //情景二:pCur->红,pParent->红,grandfather->黑,uncle不存在/为黑
                            //     g
                            //   p   u
                            // c
                            // 
                        // 解决:对g右单旋,p改为黑,g改为红,最后g为黑所以,直接break
                        RotateR(grandfather);
                        pParent->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    else
                    {
                        //情景三:pCur->红,pParent->红,grandfather->黑,uncle不存在/为黑
                            //       g
                            //   p      u
                            //     c
                        // 解决:对p左单旋,后变为情景二。
                        RotateL(pParent);
                        RotateR(grandfather);
                        pCur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break;
                }
            }
            else//情景大概反着来
            {
                //1  uncle
                Node* uncle = grandfather->_pLeft;//错误一
                //Node* uncle = pParent->_pRight;//错误一
                if (uncle && uncle->_col == RED)
                {
                    pParent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;
                    pCur = grandfather;
                    /////////////////////////////////////////////
                    if (pCur == pRoot)
                    {
                        pCur->_pParent = _pHead;
                    }
                    pParent = pCur->_pParent;
                }

                else
                {
                    if (pCur == pParent->_pRight)//2
                    {
                        RotateL(grandfather);
                        pParent->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    else//3
                    {
                        RotateR(pParent);
                        RotateL(grandfather);
                        pCur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break;
                }
            }
        }
        Node*& _root = GetRoot();
        //最后
        _root->_col = BLACK;

        return true;
    }
}

// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
Node* Find(const T& data)
{
    Node* pCur = GetRoot();
    while (pCur)
    {
        if (pCur->_data == data)
            break;
        else if (pCur->_data > data)
            pCur = pCur->_pLeft;
        else
            pCur = pCur->_pRight;
    }
    return pCur;
}

// 获取红黑树最左侧节点
Node* LeftMost()
{
    Node* pCur = GetRoot();
    if (nullptr == pCur)
        return _pHead;
    while (pCur->_pLeft)
    {
        pCur = pCur->_pLeft;
    }
    return pCur;
}

// 获取红黑树最右侧节点
Node* RightMost()
{
    Node* pCur = GetRoot();
    if (nullptr == pCur)
        return _pHead;
    while (pCur->_pRight)
    {
        pCur = pCur->_pRight;
    }
    return pCur;
}

// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
bool IsValidRBTRee()
{
    //1:是否存在 红-红 
    //每条路径黑色结点是否相同个数
    //最长的不超过最短的二倍
    //根,叶子为黑
    Node* _root = GetRoot();
    if (nullptr == _root)
        return true;
    if (_root->_col == RED)
        return false;
    Node* pCur = _root;
    size_t refVal = 0;
    while (pCur)
    {
        if (pCur->_col == BLACK)
            refVal++;
        pCur = pCur->_pLeft;
    }
    size_t blacknum = 0;
    return _IsValidRBTRee(_root, blacknum, refVal);
}

private:
bool _IsValidRBTRee(Node pRoot, size_t blackCount, size_t pathBlack)
{
if (pRoot == nullptr)
{
if (pathBlack != blackCount)
{
cout << "存在黑色节点数量不相等的路径" << endl;
return false;
}
return true;
}
Node
pParent = pRoot->_pParent;
if (pRoot->_col == RED)
{
if (pParent != _pHead && pRoot->_pParent->_col == RED)
{
cout << "有连续的红色节点" << endl;
return false;
}
}
if (pRoot->_col == BLACK)
{
++blackCount;
}
return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack)
&& _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
}
// 左单旋
void RotateL(Node pParent)
{
Node
& _root = GetRoot();
Node subR = pParent->_pRight;
Node
subRL = subR->_pLeft;

    pParent->_pRight = subRL;
    subR->_pLeft = pParent;

    Node* parentParent = pParent->_pParent;

    pParent->_pParent = subR;
    if (subRL)
        subRL->_pParent = pParent;

    if (_root == pParent)
    {
        _root = subR;
        subR->_pParent = nullptr;
    }
    else
    {
        if (parentParent->_pLeft == pParent)
        {
            parentParent->_pLeft = subR;
        }
        else
        {
            parentParent->_pRight = subR;
        }

        subR->_pParent = parentParent;
    }
}
// 右单旋
void RotateR(Node* pParent)
{
    Node*& _root = GetRoot();
    Node* subL = pParent->_pLeft;
    Node* subLR = subL->_pRight;

    pParent->_pLeft = subLR;
    if (subLR)
        subLR->_pParent = pParent;

    Node* parentParent = pParent->_pParent;

    subL->_pRight = pParent;
    pParent->_pParent = subL;

    if (_root == pParent)
    {
        _root = subL;
        subL->_pParent = nullptr;
    }
    else
    {
        if (parentParent->_pLeft == pParent)
        {
            parentParent->_pLeft = subL;
        }
        else
        {
            parentParent->_pRight = subL;
        }

        subL->_pParent = parentParent;
    }
}
// 为了操作树简单起见:获取根节点
Node*& GetRoot()
{
    return _pHead->_pParent;
}

private:
Node* _pHead;
};
对应的代码解释与红黑树(带头结点)的讲解
带头结点红黑树中头节点设计的解释
首先需要解释一点:本红黑树类内部只有一个私有成员:头节点。

我们知道set与map的底层就为红黑树,其就是由红黑树经过封装后得到的,那么封装这个过程其实存在着很多的细节,这些细节既复杂又多,这就使得如果直接进行封装步骤就很麻烦,那么对于此就出现了为了后序封装set与map,就出现了带头结点的红黑树。

对于带头结点的红黑树,如果对于此一点也不了解,那么其实对于一个刚学完红黑树的会存在很多的疑惑,就比如说带哨兵位的链表中,头节点是一个真是存在的一个结点,其与正常结点的区别就是其里面存储的信息是0或者空,其指针的设计与普通结点无异。如果还按照这样设计,那么红黑树的头节点也应该如此,那么这就会引出疑问

头节点的颜色如何设计?
头节点的三叉链如何设计?
头节点与根结点的具体关系如何设计?
问题1

第一个问题的答案无非是红或者黑,如果我们设计为红色,那么他要符合其中一个性质:其两个孩子全为黑色,这又怎么设计呢?因为头节点他仅有一个孩子,根节点。那么对此也只有可能设计为黑色。

问题2

对于头结点的三叉链怎么设计,我们要保证其三个指针全都设计合理,那么对此搜集了一些材料,得知其左指针与右指针全都指向自己。

其此设计的原因如下:

总之,这种设计使得红黑树的实现更加高效和易于维护。

那么对应的父亲结点也只能指向其根结点了。

代码实现的部分解释
其本代码实现的思路完全与不带头结点的红黑树的整体思路是完全一致的,跟上篇文章一样,插入总体分为两大类,第二类又分为四小情况。

针对于第一次插入,我们再进行第一次插入时,此时为特殊情况,我们要获取头节点的父亲指针指向的空间是否为空为标准进行判断是否为第一次插入,那么对于此,就要特殊设计一个函数GetRoot();

Node*& GetRoot()
{
    return _pHead->_pParent;
}

其余代码实现起来没什么难度,这里就不多说了,有疑惑的看上面的代码。

其余插入代码跟红黑树(普通)插入无疑,思路理清楚,整体实现起来还是不难的,我自己在写本代码的时候,就是先粘贴以前的注释,然后再一点一点写,不会的再去翻一翻以前的代码实现起来用了一个多小时。

代码注意事项
在写完代码的时候,在第一次测试的时候就遇到了指向空的报错,然后经过代码长达半个小时的调试与各处调整小细节,才调好了第一次解决了指向空的报错。

问题如下:

在此我们要写pParent != _pHead;

而不是写pParent这是因为根节点的pParent是空,如果还这样代码走下去,那么导致后面

这是因为我们没有把头节点当作头节点,而是把他当作根结点一样处理了,这肯定会导致错误。

这个问题结果过后,在代码看起来运行起来没有问题,但是对红黑树进行检测,还是存在着问题,他会报错

这个问题如果经过详细的一步一步进行代码调试也是可以发现的,就是没有添加如下代码部分,

这里的pRoot为:

其实就是如果跟换根节点要同时更换头节点的_pParent指针的指向。使其指向新的根节点。

我写这部分代码的时候遇见的就是这两个大问题,一些小问题就是一些代码拼写错误,还有一些书写问题,与旋转过后颜色调整错误,这些都是些小问题,经过简单调试就可以完成不用调试那么仔细。

目录
相关文章
|
4月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
115 2
|
1月前
|
Java C++
c++ 红黑树(自平衡二叉搜索树)(k,v型)
因为红黑树是一种特殊的AVL树(但少了平衡因子的存在),所以其结点的定义是在AVL树上加上新的成员变量,用于表示结点的颜色。RED,BLACK,//三叉链, _kv(kv){}首先我们在默认构造上,默认构造结点的颜色默认情况下为红色所以为什么构造结点时,默认将结点的颜色设置为红色?这是因为:当我们向红黑树插入结点时,若我们插入的是黑色结点,那么插入路径上黑色结点的数目就比其他路径上黑色结点的数目多了一个,即破坏了红黑树的性质4,此时我们就需要对红黑树进行调整。
68 0
|
7月前
|
存储 算法 安全
基于红黑树的局域网上网行为控制C++ 算法解析
在当今网络环境中,局域网上网行为控制对企业和学校至关重要。本文探讨了一种基于红黑树数据结构的高效算法,用于管理用户的上网行为,如IP地址、上网时长、访问网站类别和流量使用情况。通过红黑树的自平衡特性,确保了高效的查找、插入和删除操作。文中提供了C++代码示例,展示了如何实现该算法,并强调其在网络管理中的应用价值。
|
8月前
|
存储 C++
【C++】红黑树
红黑树是一种自平衡二叉搜索树,通过节点颜色(红或黑)及特定规则维持平衡,确保操作效率。其性质包括:每个节点非红即黑,根节点必黑,红节点的子节点必黑,从任一节点到其每个叶子的所有路径含相同数量的黑节点。实现上,通过节点结构定义、基本操作(插入、删除、旋转等)、维护平衡性质等步骤完成。代码示例展示了节点定义、插入操作及旋转调整方法。
95 2
【C++】红黑树
|
C++ 容器
【C++】红黑树模拟实现STL中的map与set
【C++】红黑树模拟实现STL中的map与set
|
Java C++ Python
【C++】手撕红黑树
【C++】手撕红黑树
72 1
|
11月前
|
关系型数据库 C++ 容器
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——红黑树
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——红黑树
85 0
|
Java C语言 C++
从C语言到C++_28(红黑树RedBlackTree)概念+插入接口实现(上)
从C语言到C++_28(红黑树RedBlackTree)概念+插入接口实现
101 4
|
C语言
从C语言到C++_29(红黑树封装set和map)红黑树迭代器的实现(下)
从C语言到C++_29(红黑树封装set和map)红黑树迭代器的实现
105 3
|
算法 关系型数据库 Java
【C++】红黑树(下)
【C++】红黑树(下)