首页 » 软件编程 » C语言

红黑树(C++实现)

C语言 2022-01-12

文章目录

  • 一、红黑树的概念
    • 1.1. 为什么要有红黑树
  • 二、红黑树的性质
    • 2.1. 为什么根节点必须为黑色
  • 三、红黑树结点的定义
    • 3.1. 在节点的定义中,为什么要将节点的默认颜色给成红色的
  • 四、红黑树的插入
    • 4.1. 调整的三种情况
      • 4.1.1. 插入结点的叔叔存在,且叔叔的颜色是红色。
      • 4.1.2. 插入结点的叔叔存在,且叔叔的颜色是黑色。
      • 4.1.3. 插入结点的叔叔不存在
    • 4.2. 代码实现
  • 五、红黑树的删除
    • 5.1. 待删除的结点只有一个孩子
    • 5.2. 待删除的节点没有孩子
      • 5.2.1. 如果兄弟是黑色
      • 5.2.2. 如果兄弟是红色,那么可以先转换成兄弟是黑色的情况
    • 5.3. 代码实现
  • 六、判断是否为红黑树
  • 附录

一、红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树(C++实现)

1.1. 为什么要有红黑树

普通的二叉搜索树在一般情况下查找效率为O(logN),但是在极端情况下会退化为单链表,比如向二叉搜索树中依次插入(1,2,3,4,5,6):
红黑树(C++实现)

平衡二叉树(AVL树)很好的解决退化成链表的问题,但是这又会带来一个问题,那就是平衡二叉树的定义过于严格,导致每次插入或者删除一个元素之后,都要去维护二叉树整体的平衡,这样产生额外的代价又太大了。
二叉搜索树可能退化成链表,而平衡二叉树维护平衡的代价开销又太大了(旋转次数过多),那怎么办呢?红黑树就是一种折中的办法。红黑树把平衡的定义适当放宽,不那么严格,这样红黑树既不会退化成链表,维护平衡的开销也可以接受


二、红黑树的性质

红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须除了满足二叉搜索树的性质外,还要满足下面的五条性质:

  1. 每个节点要么是黑色,要么是红色。
  2. 根节点是黑色。
  3. 每个叶子节点(NIL)是黑色(此处的叶子结点指定是两个空结点)。
  4. 如果一个节点是红色,则它的子节点必须是黑色,即两个红色节点不能直接相连
  5. 任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

根据红黑树的性质4可以得出,红黑树当中不会出现连续的红色结点,而根据性质5又可以得出,任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
因此,最短路径的情况为路径上全是黑节点,此时路径长为黑色节点的数量N:
红黑树(C++实现)

最长路径为路径上是黑色结点与红色结点的数目相同,此时路径长为2N:
红黑树(C++实现)

综上可以看出,上面的性质保证了红黑树从根到叶子的最长路径不会超过最短可能路径的两倍
这样红黑树既不会退化成链表,也不会像AVL树一样平衡过于严格从而造成很大的平衡维护的开销。

2.1. 为什么根节点必须为黑色

性质4和性质5保证最长路径不超过最短路径的两倍,而如果根节点为红色,此时要插入一个新节点,那这个新节点必须为黑色,否则就会破坏性质4,但是这样性质5就不满足了。
红黑树(C++实现)
此时需要进行变色或旋转:
红黑树(C++实现)

既然加入第二个节点后就会必须要把根节点变成黑色的,那还不如一开始根节点就为黑。


三、红黑树结点的定义

红黑树取消了AVL树中平衡因子的设定,取而代之的是颜色的设定。这里以三叉链的红黑树为例:

//使用枚举来定义颜色 enum Color { 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; //红黑树的颜色,通过枚举来实现 Color _col; //每个节点默认为红色 RBTreeNode(const pair<K, V>& kv) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _kv(kv) , _col(RED) {} }; 

3.1. 在节点的定义中,为什么要将节点的默认颜色给成红色的

节点不是黑色就是红色。
当我们向红黑树插入结点时,若我们插入的是黑色结点,那么插入路径上黑色结点的数目就比其他路径上黑色结点的数目多了一个,即破坏了红黑树的性质5,此时需要对红黑树进行调整。
而如果插入的是红色,插入节点的父节点是黑色,则不会破坏红黑树的五条性质;而如果父节点是红色,那就会破坏性质4。

也就是说,插入黑色节点必然会破坏性质5,从而需要对红黑树进行调整;插入红节点可能破坏性质4,只有在破坏性质4时才需要对红黑树进行调整,因此调整的次数少一些。


四、红黑树的插入

红黑树插入结点的逻辑分为三步:

  1. 按二叉搜索树的插入方法,找到待插入位置。
  2. 将待插入结点插入到树中。
  3. 若插入结点的父结点是红色的,破坏性质4,,需要对红黑树进行调整。

4.1. 调整的三种情况

因为插入结点的父结点是红色的,说明父结点不是根结点(根结点是黑色的),因此插入结点的祖父结点(父结点的父结点)就一定存在。
红黑树调整时具体应该如何调整,主要是看插入结点的叔叔(插入结点的父结点的兄弟结点),根据插入结点叔叔的不同,可将红黑树的调整分为三种情况。

调整的目的是为了让当前子树的每条路径的黑色节点数量保持不变(因为当前子树黑色节点数量改变,就会破坏性质5),同时不会出现连续的红色节点。

4.1.1. 插入结点的叔叔存在,且叔叔的颜色是红色。

如果父节点p为红色,则根据性质4,祖父节点g一定为黑色。

为了避免出现连续的红色结点,我们可以将父结点变黑,但为了保持每条路径黑色结点的数目不变,因此我们还需要将祖父结点变红,再将叔叔变黑。这样一来既保持了每条路径黑色结点的数目不变,也解决了连续红色结点的问题。

此时祖父结点变成了红色,如果祖父结点是根结点,那我们直接再将祖父结点变成黑色即可,此时相当于每条路径黑色结点的数目都增加了一个。

但如果祖父结点不是根结点的话,我们就需要将祖父结点当作新插入的结点,再判断其父结点是否为红色,若其父结点也是红色,那么又需要根据其叔叔的不同,进而进行不同的调整操作。

红黑树(C++实现)

4.1.2. 插入结点的叔叔存在,且叔叔的颜色是黑色。

这种情况一定是在情况一继续往上调整的过程中出现的,即这种情况下的cur结点一定不是新插入的结点,而是上一次情况一调整过程中的祖父结点。因为父节点为红(父节点左右子树有一个为空),叔叔节点为黑这种情况本身就不符合性质5。
红黑树(C++实现)

出现这种情况,需要进行旋转处理,因为单纯通过改变颜色来控制平衡,必然会破坏性质5,比如下面这种情况:
红黑树(C++实现)

不管是将cur还是p改成黑,都会破坏性质5。

旋转调整:
当cur,p,g为直线关系时,p是g的左孩子,cur是p的左孩子时,就需要对g进行右单旋操作,再将p的颜色改为黑,g的颜色改为红。
同理如果p是g的右孩子,cur是p的右孩子,需要对g进行左单旋,再将p的颜色改为黑,g的颜色改为红。
红黑树(C++实现)

当cur,p,g为折线关系时,即p是g的左孩子,cur是p的右孩子时,此时进行左右双旋。需要先对p进行左单旋操作,再对g进行右单旋,然后将cur的颜色改为黑,g的颜色改为红。
同理p是g的右孩子,cur是p的左孩子时,此时进行右左双旋。需要先对p进行右单旋操作,再对g进行左单旋,然后将cur的颜色改为黑,g的颜色改为红。
红黑树(C++实现)

注意: 当这种情况进行旋转处理完成以后,不需要再进行向上调整,因为cur作为该子树的根节点已经为黑,即使cur上还有节点(红色或黑色),也不会破坏五条性质。

4.1.3. 插入结点的叔叔不存在

在这种情况下的cur结点一定是新插入的结点,而不可能是由情况一变化而来的,因为叔叔不存在说明在p的下面不可能再挂黑色结点,否者会破坏性质5.
红黑树(C++实现)

此时这种情况也需要进行旋转调整,因为最短路径为1(g->NIL),最长路径为3(g->p->cur->NIL),已经不符合红黑树的性质,无论如何变色都会破坏性质4或5。

旋转调整:
当cur,p,g为直线关系时,p是g的左孩子,cur是p的左孩子时,就需要对g进行右单旋操作,再将p的颜色改为黑,g的颜色改为红。
同理如果p是g的右孩子,cur是p的右孩子,需要对g进行左单旋,再将p的颜色改为黑,g的颜色改为红。
红黑树(C++实现)

当cur,p,g为折线关系时,即p是g的左孩子,cur是p的右孩子时,此时进行左右双旋。需要先对p进行左单旋操作,再对g进行右单旋,然后将cur的颜色改为黑,g的颜色改为红。
同理p是g的右孩子,cur是p的左孩子时,此时进行右左双旋。需要先对p进行右单旋操作,再对g进行左单旋,然后将cur的颜色改为黑,g的颜色改为红。
红黑树(C++实现)

不难看出,情况2和情况3的处理方式相同,因此在代码实现时可以合并为一种方式。

4.2. 代码实现

void RotateL(Node* parent)//左旋 { Node* cur = parent->_right;//右变高,不可能为空 Node* curL = cur->_left; Node* pparent = parent->_parent; //cur左子树作为parent右子树 parent->_right = curL; if (curL) curL->_parent = parent; //parent作为cur左子树 cur->_left = parent; parent->_parent = cur; //cur链接到pprent上 if (pparent == nullptr)//根 { _root = cur; cur->_parent = nullptr; } else//不为根 { cur->_parent = pparent; //判断链接在哪一侧 if (pparent->_left == parent) { pparent->_left = cur; } else { pparent->_right = cur; } } } void RotateR(Node* parent) { Node* cur = parent->_left; Node* curR = cur->_right;//cur的右子树 Node* pparent = parent->_parent;//保存parent的父亲节点 //将cur右子树链接到parent的左侧 parent->_left = curR; if (curR) curR->_parent = parent; //将parent连接到cur的右侧 cur->_right = parent; parent->_parent = cur; //将cur与pparent链接起来 if (pparent == nullptr)//cur变成新的根 { _root = cur; cur->_parent = nullptr; } else//pparent不为根 { cur->_parent = pparent; if (parent == pparent->_left)//在上一级节点的左侧 { pparent->_left = cur; } else { pparent->_right = cur; } } } //左右双旋 void RotateLR(Node* parent) { RotateL(parent->_left); RotateR(parent); } //右左双旋 void RotateRL(Node* parent) { RotateR(parent->_right); RotateL(parent); } pair<Node*,bool> Insert(const pair<K, V>& kv) { //红黑树的插入与平衡二叉树相同,不过在插入完成以后需要对颜色进行调整 if (_root == nullptr) //若红黑树为空树,则插入结点直接作为根结点 { _root = new Node(kv); _root->_col = BLACK; return make_pair(_root, 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 make_pair(cur, false); } } //此时已经找到插入的位置了,判断插入在parent的左边还是右边 cur = new Node(kv); //根据所给值构造一个结点 //因为对颜色进行调整会改变cur的指向,记录cur的指向,用于最后的返回 Node* newnode = cur; if (parent->_kv.first < kv.first)//插在右边 { parent->_right = cur; cur->_parent = parent; } else//插在左边 { parent->_left = cur; cur->_parent = parent; } //对红黑树的颜色进行调整,来满足红黑树的规则 //1.parent颜色是黑色,不需要调整,插入完成 //2.parent颜色是红色,违反了规则4,需要进行调整 while (parent && parent->_col == RED) { //parent是红色,则cur的祖父结点一定存在 Node* grandfather = parent->_parent; //根据parent和grandfather的左右关系,分别进行调整 if (parent == grandfather->_left) { //找到叔叔节点,判断三种情况 Node* uncle = grandfather->_right; //情况1,uncle存在且为红 if (uncle && uncle->_col == RED) { //直接进行颜色调整即可 parent->_col = BLACK; uncle->_col = BLACK; grandfather->_col = RED; //继续往上调整 cur = grandfather; parent = cur->_parent; } //情况2和3,uncle不存在或者存在且为黑,进行旋转 else { //情况2,右单旋 if (cur == parent->_left) { RotateR(grandfather); //颜色调整 grandfather->_col = RED; parent->_col = BLACK; } //情况3,进行左右双旋 else//cur==parent->_right { RotateLR(grandfather); //颜色调整 grandfather->_col = RED; cur->_col = BLACK; } break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理 } } else//parent==grandfather->_right { //找到叔叔节点,判断三种情况 Node* uncle = grandfather->_left; //情况1:uncle存在且为红 if (uncle && uncle->_col == RED) { //直接进行颜色调整即可 parent->_col = BLACK; uncle->_col = BLACK; grandfather->_col = RED; //继续往上处理 cur = grandfather; parent = cur->_parent; } //情况2和3,uncle不存在或者存在且为黑,进行旋转 else { //情况2,左单旋 if (cur == parent->_right) { RotateL(grandfather); //左单旋 //颜色调整 grandfather->_col = RED; parent->_col = BLACK; } //情况3,进行右左双旋 else //cur == parent->_left { RotateRL(grandfather); //右左双旋 //颜色调整 cur->_col = BLACK; grandfather->_col = RED; } break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理 } } } //把根的颜色变黑 _root->_col = BLACK; return make_pair(newnode, true); } 

五、红黑树的删除

首先红黑树是二叉排序树,按排序树的方式删除一个结点,有三种情况:

  1. 如果待删除的结点是叶子结点,则直接删除即可。
  2. 如果待删除的结点只有一个孩子,则将父结点的指针指向它的孩子。
  3. 如果待删除的结点有两个孩子,则寻找待删除结点右子树当中key值最小的结点作为实际删除结点,将值覆盖过来,之后情况转变成删除该实际删除结点,就变成了情况1。

然后需要根据删除节点的颜色进行调整,下面是对情况1和情况2的分析:

5.1. 待删除的结点只有一个孩子

既然待删除节点只有一个孩子,那么该节点必定为黑色。因为根据性质4,红色节点其孩子必定为黑,既然只有一个孩子,那么就会破坏性质5。

所以这种情况可以在删除时就处理掉,用红色孩子顶替待删除结点,再将其涂成黑色。
红黑树(C++实现)

5.2. 待删除的节点没有孩子

如果待删除结点为红色,则不需要进行调整,直接删除即可。

如果待删除的节点为黑色,则需要进行调整,因为破坏了性质5。
下面是调整的几种情况:

5.2.1. 如果兄弟是黑色

红黑树(C++实现)

这种情况下,一旦deleted被删除,左边就比右边少了一个黑色结点,parent红色或者黑色都有可能,那两个侄子要么为null,要么为红色。

  • 如果两个侄子都为null,parent为红色

红黑树(C++实现)
deleted结点被删除之后,此时只要将parent涂黑,brother涂红即可。
红黑树(C++实现)

  • 如果两个侄子都为null,parent为黑色
    红黑树(C++实现)
    deleted结点被删除之后红黑树(C++实现)

这时,不论如何涂色,都无法让弥补左边缺少的一个黑色,所以只能先把brother涂红,让左右黑色数量相同,但是这样parent这棵树一定会少一个黑色。
红黑树(C++实现)

为了解决这种情况,需要将parent作为deleted,parent的_parent作为deleted的parent。再次进行调整。

因为在这种情况下,这棵树每条路径上黑色节点由2变为1,相当于以parent节点为待删除节点进行调整(注意:调整不会进行节点的删除,也就是说并不会删除parent节点)。
红黑树(C++实现)

  • 左侄子为红色,右侄子不存在或者为红色(这里用白色表示)
    红黑树(C++实现)

红黑树(C++实现)

此时对brother进行右单选,再对parent进行parent进行左单旋。
将左侄子改为parent的颜色,目的是为了能够在后面顶替parent,parent改为黑色去弥补左子树缺少的黑色。

  • 右侄子为红色,左侄子不存在或为红色(这里用白色表示)
    红黑树(C++实现)

对parent进行左单旋。
brother改为parent的颜色为了顶替parent,parent的改为黑色去弥补左子树缺少的黑色,而右侄子则改为黑色,从而保持黑色数量相等。

5.2.2. 如果兄弟是红色,那么可以先转换成兄弟是黑色的情况

红黑树(C++实现)

如果兄弟为红色,则兄弟的两个孩子肯定为黑色,否则会破坏性质5。
首先对parent进行左单旋,然后parent和brother交换颜色,之后brother指向最下面那个结点。这样一来,就变成了上面的黑兄情况。
红黑树(C++实现)

5.3. 代码实现

//进行对节点的调整 void deleteFixUp(Node* replace, Node* parent) { Node* brother = nullptr; // 如果顶替结点是黑色结点,并且不是根结点。 while (replace == nullptr || replace->_col == BLACK) { //左孩子位置的所有情况 if (parent->_left == replace) { brother = parent->_right; // case1 红兄,brother涂黑,parent涂红,parent左旋,replace的兄弟改变了,变成了黑兄的情况 if (brother->_col == RED) { brother->_col = BLACK; parent->_col = RED; RotateL(parent); brother = parent->_right; } // 经过上面,不管进没进if,兄弟都成了黑色 // case2 黑兄,且兄弟的两个孩子都为黑 if ((brother->_left == nullptr || brother->_left->_col == BLACK) && (brother->_right == nullptr || brother->_right->_col== BLACK)) { // 如果parent此时为红,则把brother的黑色转移到parent上,调整结束 if (parent->_col == RED) { parent->_col = BLACK; brother->_col = RED; break; } // 如果此时parent为黑,即此时全黑了,则把brother涂红,需要对parent又进行一轮调整 else { brother->_col = RED; replace = parent; parent = replace->_parent; } } else { // case3 黑兄,兄弟的左孩子为红色 if (brother->_left != nullptr && brother->_left->_col == RED) { brother->_left->_col = parent->_col; parent->_col = BLACK; RotateR(brother); RotateL(parent); // case4 黑兄,兄弟的右孩子为红色 } else if (brother->_right != nullptr && brother->_right->_col == RED) { brother->_col = parent->_col; parent->_col = BLACK; brother->_right->_col = BLACK; RotateL(parent); } break; } } //对称位置的情况,把旋转方向反回来 else { brother = parent->_left; // case1 红兄,brother涂黑,parent涂红,parent左旋,replace的兄弟改变了,变成了黑兄的情况 if (brother->_col == RED) { brother->_col = BLACK; parent->_col = RED; RotateR(parent); brother = parent->_left; } // 经过上面,不管进没进if,兄弟都成了黑色 // case2 黑兄,且兄弟的两个孩子都为黑 if ((brother->_left == nullptr || brother->_left->_col == BLACK) && (brother->_right == nullptr || brother->_right->_col == BLACK)) { // 如果parent此时为红,则把brother的黑色转移到parent上 if (parent->_col == RED) { parent->_col = BLACK; brother->_col = RED; break; } // 如果此时parent为黑,即此时全黑了,则把brother涂红,需要对parent又进行一轮调整 else { brother->_col = RED; replace = parent; parent = replace->_parent; } } else { // case3 黑兄,兄弟的左孩子为红色,右孩子随意 if (brother->_right != nullptr && brother->_right->_col == RED) { brother->_right->_col = parent->_col; parent->_col = BLACK; RotateL(brother); RotateR(parent); } // case4 黑兄,兄弟的右孩子为红色,左孩子随意 else if (brother->_left != nullptr && brother->_left->_col == RED) { brother->_col = parent->_col; parent->_col = BLACK; brother->_left->_col = BLACK; RotateR(parent); } //调整结束 break; } } } } //删除 bool Erase(const K& key) { //用于遍历二叉树 Node* parent = nullptr; Node* cur = _root; //用于标记实际的待删除结点及其父结点 Node* delParentPos = nullptr; Node* delPos = nullptr; while (cur) { if (key < cur->_kv.first) //所给key值小于当前结点的key值 { //往该结点的左子树走 parent = cur; cur = cur->_left; } else if (key > cur->_kv.first) //所给key值大于当前结点的key值 { //往该结点的右子树走 parent = cur; cur = cur->_right; } else //找到了待删除结点 { if (cur->_left == nullptr) //待删除结点的左子树为空 { if (cur == _root) //待删除结点是根结点 { _root = _root->_right; //让根结点的右子树作为新的根结点 if (_root) { _root->_parent = nullptr; _root->_col = BLACK; //根结点为黑色 } delete cur; //删除原根结点 return true; } else { //待删除的结点只有一个孩子 if (cur->_right)//cur的右不为空,cur是黑色节点 { if (cur == parent->_left) { parent->_left = cur->_right; parent->_left->_col = BLACK; } if (cur == parent->_right) { parent->_right = cur->_right; parent->_right->_col = BLACK; } return true; } //cur为叶子节点 else { delParentPos = parent; //标记实际删除结点的父结点 delPos = cur; //标记实际删除的结点 } } break; //进行红黑树的调整以及结点的实际删除 } else if (cur->_right == nullptr) //待删除结点的右子树为空 { if (cur == _root) //待删除结点是根结点 { _root = _root->_left; //让根结点的左子树作为新的根结点 if (_root) { _root->_parent = nullptr; _root->_col = BLACK; //根结点为黑色 } delete cur; //删除原根结点 return true; } else { //待删除的结点只有一个孩子 if (cur->_left)//cur的左不为空,cur是黑色节点 { if (cur == parent->_left) { parent->_left = cur->_left; parent->_left->_col = BLACK; } if (cur == parent->_right) { parent->_right = cur->_left; parent->_right->_col = BLACK; } return true; } //cur为叶子节点 else { delParentPos = parent; //标记实际删除结点的父结点 delPos = cur; //标记实际删除的结点 } } break; //进行红黑树的调整以及结点的实际删除 } else //待删除结点的左右子树均不为空 { //替换法删除 //寻找待删除结点右子树当中key值最小的结点作为实际删除结点 Node* minParent = cur; Node* minRight = cur->_right; while (minRight->_left) { minParent = minRight; minRight = minRight->_left; } cur->_kv.first = minRight->_kv.first; //将待删除结点的key改为minRight的key cur->_kv.second = minRight->_kv.second; //将待删除结点的value改为minRight的value delParentPos = minParent; //标记实际删除结点的父结点 delPos = minRight; //标记实际删除的结点 break; //进行红黑树的调整以及结点的实际删除 } } } //如果cur走到了空,则说明没找到 if (cur == nullptr) return false; //记录待删除结点及其父结点(用于后续实际删除) Node* del = delPos; Node* delP = delParentPos; //如果待删除结点为红色,则不会进行调整 if (delPos->_col == BLACK) deleteFixUp(delPos, delParentPos); //进行实际删除 if (del == delP->_left) //实际删除结点是其父结点的左孩子 { delP->_left = del->_left; if (del->_left) del->_left->_parent = delP; } else //实际删除结点是其父结点的右孩子 { delP->_right = del->_left; if (del->_left) del->_left->_parent = delP; } delete del; //实际删除结点 return true; } 

六、判断是否为红黑树

要判断是否为红黑树,只需要验证其性质即可

//判断每条路径是否平衡 bool _CheckBalance(Node* root, int count, int BlackCount) { if (root == nullptr) //该路径已经走完了 { if (count != BlackCount) { cout << "error:黑色结点的数目不相等" << endl; return false; } return true; } if (root->_col == RED && root->_parent->_col == RED) { cout << "error:存在连续的红色结点" << endl; return false; } if (root->_col == BLACK) { count++; } //递归判断左右子树 return _CheckBalance(root->_left, count, BlackCount) && _CheckBalance(root->_right, count, BlackCount); } //判断是否为红黑树 bool CheckBalance() { if (_root == nullptr) { return true; } if (_root->_col == RED) { cout << "error:根结点为红色" << endl; return false; } //找最左路径作为黑色结点数目的参考值 Node* left = _root; //记录最左路径的黑色节点数 int BlackCount = 0; while (left) { if (left->_col == BLACK) BlackCount++; left = left->_left; } int count = 0; return _CheckBalance(_root, count, BlackCount); } 

中序遍历:

//中序遍历子函数 void _Inorder(Node* root) { if (root == nullptr) return; _Inorder(root->_left); cout << root->_kv.first << ":"<<root->_kv.second<<endl; _Inorder(root->_right); } //中序遍历 void Inorder() { _Inorder(_root); } 

附录

#include<iostream> using namespace std; //使用枚举来定义颜色 enum Color { 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; //红黑树的颜色,通过枚举来实现 Color _col; RBTreeNode(const pair<K, V>& kv) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _kv(kv) , _col(RED) {} }; template<class K, class V> class RBTree { typedef RBTreeNode<K, V> Node; public: RBTree() :_root(nullptr) {} void RotateL(Node* parent)//左旋 { Node* cur = parent->_right;//右变高,不可能为空 Node* curL = cur->_left; Node* pparent = parent->_parent; //cur左子树作为parent右子树 parent->_right = curL; if (curL) curL->_parent = parent; //parent作为cur左子树 cur->_left = parent; parent->_parent = cur; //cur链接到pprent上 if (pparent == nullptr)//根 { _root = cur; cur->_parent = nullptr; } else//不为根 { cur->_parent = pparent; //判断链接在哪一侧 if (pparent->_left == parent) { pparent->_left = cur; } else { pparent->_right = cur; } } } void RotateR(Node* parent) { Node* cur = parent->_left; Node* curR = cur->_right;//cur的右子树 Node* pparent = parent->_parent;//保存parent的父亲节点 //将cur右子树链接到parent的左侧 parent->_left = curR; if (curR) curR->_parent = parent; //将parent连接到cur的右侧 cur->_right = parent; parent->_parent = cur; //将cur与pparent链接起来 if (pparent == nullptr)//cur变成新的根 { _root = cur; cur->_parent = nullptr; } else//pparent不为根 { cur->_parent = pparent; if (parent == pparent->_left)//在上一级节点的左侧 { pparent->_left = cur; } else { pparent->_right = cur; } } } //左右双旋 void RotateLR(Node* parent) { RotateL(parent->_left); RotateR(parent); } //右左双旋 void RotateRL(Node* parent) { RotateR(parent->_right); RotateL(parent); } pair<Node*,bool> Insert(const pair<K, V>& kv) { //红黑树的插入与平衡二叉树相同,不过在插入完成以后需要对颜色进行调整 if (_root == nullptr) //若红黑树为空树,则插入结点直接作为根结点 { _root = new Node(kv); _root->_col = BLACK; return make_pair(_root, 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 make_pair(cur, false); } } //此时已经找到插入的位置了,判断插入在parent的左边还是右边 cur = new Node(kv); //根据所给值构造一个结点 //因为对颜色进行调整会改变cur的指向,记录cur的指向,用于最后的返回 Node* newnode = cur; if (parent->_kv.first < kv.first)//插在右边 { parent->_right = cur; cur->_parent = parent; } else//插在左边 { parent->_left = cur; cur->_parent = parent; } //对红黑树的颜色进行调整,来满足红黑树的规则 //1.parent颜色是黑色,不需要调整,插入完成 //2.parent颜色是红色,违反了规则4,需要进行调整 while (parent && parent->_col == RED) { //parent是红色,则cur的祖父结点一定存在 Node* grandfather = parent->_parent; //根据parent和grandfather的左右关系,分别进行调整 if (parent == grandfather->_left) { //找到叔叔节点,判断三种情况 Node* uncle = grandfather->_right; //情况1,uncle存在且为红 if (uncle && uncle->_col == RED) { //直接进行颜色调整即可 parent->_col = BLACK; uncle->_col = BLACK; grandfather->_col = RED; //继续往上调整 cur = grandfather; parent = cur->_parent; } //情况2和3,uncle不存在或者存在且为黑,进行旋转 else { //情况2,右单旋 if (cur == parent->_left) { RotateR(grandfather); //颜色调整 grandfather->_col = RED; parent->_col = BLACK; } //情况3,进行左右双旋 else//cur==parent->_right { RotateLR(grandfather); //颜色调整 grandfather->_col = RED; cur->_col = BLACK; } break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理 } } else//parent==grandfather->_right { //找到叔叔节点,判断三种情况 Node* uncle = grandfather->_left; //情况1:uncle存在且为红 if (uncle && uncle->_col == RED) { //直接进行颜色调整即可 parent->_col = BLACK; uncle->_col = BLACK; grandfather->_col = RED; //继续往上处理 cur = grandfather; parent = cur->_parent; } //情况2和3,uncle不存在或者存在且为黑,进行旋转 else { //情况2,左单旋 if (cur == parent->_right) { RotateL(grandfather); //左单旋 //颜色调整 grandfather->_col = RED; parent->_col = BLACK; } //情况3,进行右左双旋 else //cur == parent->_left { RotateRL(grandfather); //右左双旋 //颜色调整 cur->_col = BLACK; grandfather->_col = RED; } break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理 } } } //把根的颜色变黑 _root->_col = BLACK; return make_pair(newnode, true); } //查找函数,与二叉搜索树相同 Node* Find(const K& key) { Node* cur = _root; while (cur) { if (key < cur->_kv.first) //key值小于该结点的值 { cur = cur->_left; //在该结点的左子树当中查找 } else if (key > cur->_kv.first) //key值大于该结点的值 { cur = cur->_right; //在该结点的右子树当中查找 } else //找到了目标结点 { return cur; //返回该结点 } } return nullptr; //查找失败 } //进行对节点的调整 void deleteFixUp(Node* replace, Node* parent) { Node* brother = nullptr; // 如果顶替结点是黑色结点,并且不是根结点。 while (replace == nullptr || replace->_col == BLACK) { //左孩子位置的所有情况 if (parent->_left == replace) { brother = parent->_right; // case1 红兄,brother涂黑,parent涂红,parent左旋,replace的兄弟改变了,变成了黑兄的情况 if (brother->_col == RED) { brother->_col = BLACK; parent->_col = RED; RotateL(parent); brother = parent->_right; } // 经过上面,不管进没进if,兄弟都成了黑色 // case2 黑兄,且兄弟的两个孩子都为黑 if ((brother->_left == nullptr || brother->_left->_col == BLACK) && (brother->_right == nullptr || brother->_right->_col== BLACK)) { // 如果parent此时为红,则把brother的黑色转移到parent上,调整结束 if (parent->_col == RED) { parent->_col = BLACK; brother->_col = RED; break; } // 如果此时parent为黑,即此时全黑了,则把brother涂红,需要对parent又进行一轮调整 else { brother->_col = RED; replace = parent; parent = replace->_parent; } } else { // case3 黑兄,兄弟的左孩子为红色 if (brother->_left != nullptr && brother->_left->_col == RED) { brother->_left->_col = parent->_col; parent->_col = BLACK; RotateR(brother); RotateL(parent); // case4 黑兄,兄弟的右孩子为红色 } else if (brother->_right != nullptr && brother->_right->_col == RED) { brother->_col = parent->_col; parent->_col = BLACK; brother->_right->_col = BLACK; RotateL(parent); } break; } } //对称位置的情况,把旋转方向反回来 else { brother = parent->_left; // case1 红兄,brother涂黑,parent涂红,parent左旋,replace的兄弟改变了,变成了黑兄的情况 if (brother->_col == RED) { brother->_col = BLACK; parent->_col = RED; RotateR(parent); brother = parent->_left; } // 经过上面,不管进没进if,兄弟都成了黑色 // case2 黑兄,且兄弟的两个孩子都为黑 if ((brother->_left == nullptr || brother->_left->_col == BLACK) && (brother->_right == nullptr || brother->_right->_col == BLACK)) { // 如果parent此时为红,则把brother的黑色转移到parent上 if (parent->_col == RED) { parent->_col = BLACK; brother->_col = RED; break; } // 如果此时parent为黑,即此时全黑了,则把brother涂红,需要对parent又进行一轮调整 else { brother->_col = RED; replace = parent; parent = replace->_parent; } } else { // case3 黑兄,兄弟的左孩子为红色,右孩子随意 if (brother->_right != nullptr && brother->_right->_col == RED) { brother->_right->_col = parent->_col; parent->_col = BLACK; RotateL(brother); RotateR(parent); } // case4 黑兄,兄弟的右孩子为红色,左孩子随意 else if (brother->_left != nullptr && brother->_left->_col == RED) { brother->_col = parent->_col; parent->_col = BLACK; brother->_left->_col = BLACK; RotateR(parent); } //调整结束 break; } } } } //删除 bool Erase(const K& key) { //用于遍历二叉树 Node* parent = nullptr; Node* cur = _root; //用于标记实际的待删除结点及其父结点 Node* delParentPos = nullptr; Node* delPos = nullptr; while (cur) { if (key < cur->_kv.first) //所给key值小于当前结点的key值 { //往该结点的左子树走 parent = cur; cur = cur->_left; } else if (key > cur->_kv.first) //所给key值大于当前结点的key值 { //往该结点的右子树走 parent = cur; cur = cur->_right; } else //找到了待删除结点 { if (cur->_left == nullptr) //待删除结点的左子树为空 { if (cur == _root) //待删除结点是根结点 { _root = _root->_right; //让根结点的右子树作为新的根结点 if (_root) { _root->_parent = nullptr; _root->_col = BLACK; //根结点为黑色 } delete cur; //删除原根结点 return true; } else { //待删除的结点只有一个孩子 if (cur->_right)//cur的右不为空,cur是黑色节点 { if (cur == parent->_left) { parent->_left = cur->_right; parent->_left->_col = BLACK; } if (cur == parent->_right) { parent->_right = cur->_right; parent->_right->_col = BLACK; } return true; } //cur为叶子节点 else { delParentPos = parent; //标记实际删除结点的父结点 delPos = cur; //标记实际删除的结点 } } break; //进行红黑树的调整以及结点的实际删除 } else if (cur->_right == nullptr) //待删除结点的右子树为空 { if (cur == _root) //待删除结点是根结点 { _root = _root->_left; //让根结点的左子树作为新的根结点 if (_root) { _root->_parent = nullptr; _root->_col = BLACK; //根结点为黑色 } delete cur; //删除原根结点 return true; } else { //待删除的结点只有一个孩子 if (cur->_left)//cur的左不为空,cur是黑色节点 { if (cur == parent->_left) { parent->_left = cur->_left; parent->_left->_col = BLACK; } if (cur == parent->_right) { parent->_right = cur->_left; parent->_right->_col = BLACK; } return true; } //cur为叶子节点 else { delParentPos = parent; //标记实际删除结点的父结点 delPos = cur; //标记实际删除的结点 } } break; //进行红黑树的调整以及结点的实际删除 } else //待删除结点的左右子树均不为空 { //替换法删除 //寻找待删除结点右子树当中key值最小的结点作为实际删除结点 Node* minParent = cur; Node* minRight = cur->_right; while (minRight->_left) { minParent = minRight; minRight = minRight->_left; } cur->_kv.first = minRight->_kv.first; //将待删除结点的key改为minRight的key cur->_kv.second = minRight->_kv.second; //将待删除结点的value改为minRight的value delParentPos = minParent; //标记实际删除结点的父结点 delPos = minRight; //标记实际删除的结点 break; //进行红黑树的调整以及结点的实际删除 } } } //如果cur走到了空,则说明没找到 if (cur == nullptr) return false; //记录待删除结点及其父结点(用于后续实际删除) Node* del = delPos; Node* delP = delParentPos; //如果待删除结点为红色,则不会进行调整 if (delPos->_col == BLACK) deleteFixUp(delPos, delParentPos); //进行实际删除 if (del == delP->_left) //实际删除结点是其父结点的左孩子 { delP->_left = del->_left; if (del->_left) del->_left->_parent = delP; } else //实际删除结点是其父结点的右孩子 { delP->_right = del->_left; if (del->_left) del->_left->_parent = delP; } delete del; //实际删除结点 return true; } void Destory(Node* root) { if (root == nullptr) return; Destory(root->_left); Destory(root->_right); delete root; } ~RBTree() { Destory(_root); } //判断每条路径是否平衡 bool _CheckBalance(Node* root, int count, int BlackCount) { if (root == nullptr) //该路径已经走完了 { if (count != BlackCount) { cout << "error:黑色结点的数目不相等" << endl; return false; } return true; } if (root->_col == RED && root->_parent->_col == RED) { cout << "error:存在连续的红色结点" << endl; return false; } if (root->_col == BLACK) { count++; } return _CheckBalance(root->_left, count, BlackCount) && _CheckBalance(root->_right, count, BlackCount); } //判断是否为红黑树 bool CheckBalance() { if (_root == nullptr) { return true; } if (_root->_col == RED) { cout << "error:根结点为红色" << endl; return false; } //找最左路径作为黑色结点数目的参考值 Node* left = _root; int BlackCount = 0; while (left) { if (left->_col == BLACK) BlackCount++; left = left->_left; } int count = 0; return _CheckBalance(_root, count, BlackCount); } //中序遍历子函数 void _Inorder(Node* root) { if (root == nullptr) return; _Inorder(root->_left); cout << root->_kv.first << ":"<<root->_kv.second<<endl; _Inorder(root->_right); } //中序遍历 void Inorder() { _Inorder(_root); } private: Node* _root; }; 

红黑树(C++实现)

资料参考:
红黑树(C++实现)
理解红黑树(下)删除操作
为什么有红黑树?什么是红黑树?看完这篇你就明白了


上一篇:C++如何用数组模拟链表下一篇:C语言中浮点数的精度丢失问题解决
程序园_程序员的世界 Copyright © 2020- www.580doc.com. Some Rights Reserved.