【C++】手撕红黑树

前言

在网络上流传着这样一张图片:image-20230328212019525

这张图片表达的侧面意思是:红黑树非常难!!!但如果认真阅读了这篇的博客,并且你有 AVL 树的基础的话 (重点是 AVL 树的旋转),其实你会发现,红黑树难只是指红黑树比较抽象,但它的逻辑其实是比 AVL 树要简单的,并且红黑树的代码也不难写。


一、红黑树的概念

什么是红黑树

红黑树是一种平衡二叉搜索树,但和 AVL 树使用高度来控制平衡不同,红黑树在每个结点上增加一个存储位来表示结点的颜色,可以是Red或Black,然后通过对任何一条从根到叶子的路径上各个结点着色方式的限制来达到接近平衡

红黑树通过对每个节点颜色的限制,从而使得整棵树的最长路径不超过最短路径的两倍 (注意是整棵树,对子树没有要求),这样红黑树就可以达到接近平衡。

注意:因为红黑树只要求整棵树中最长路径是最短路径的两倍,所以红黑树是近似平衡的,即子树中某一路径可能比另一条路径长两倍不止;但 AVL 是通过高度控制平衡的,且 AVL 能达到的平衡已经是二叉树中能够达到的最好平衡了,所以 AVL 树是绝对平衡的。

红黑树的性质

红黑树有如下性质:

  1. 每个结点不是红色就是黑色;
  2. 根节点是黑色的;
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 – 不允许出现连续的红色节点
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 – 每条路径都包含相同数量的黑色节点
  5. 每个叶子结点都是黑色的 (此处的叶子结点指的是空结点)

image-20230328215105040

特别注意第五点中的叶子节点并不是指我们平时说的叶子节点,而是指的空节点 (图中的NIL节点),它之所以要加这么一条规则是为了更好的标识每条路径 (比如认为图中的13 8 1 NIL 也是一条路径);但是我认为我们完全可以不用管第五点,而且这样也不会造成什么影响,因为如果不计算 NIL 节点,那么树中所有路径的黑色节点数量都会减一,并不会违反规则4,所以可以看到第五点我是用删除线进行修饰的。

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

我们可以在极端场景下来思考这个问题 – 极端场景下,当一条路径中的全部节点都为黑色时该路径为最短路径,当一条路径中黑色节点与红色节点交替时该路径为最长路径,此时最长路径刚好为最短路径的两倍;如下:image-20230328222420881


二、红黑树的节点结构

红黑树的节点结构和 AVL 树整体上类似,三个节点指针分别指向左孩子、右孩子和父亲,然后还包含一个 pair 键值对,和 AVL 树不同的时,红黑树不再需要 bf 变量来作为树的平衡因子,而是需要一个 col 变量来标识节点的颜色:

//节点颜色
enum Color {
    
    
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode {
    
    
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	std::pair<K, V> _kv;
	Color _col;  //节点颜色

	RBTreeNode(const std::pair<K, V>& kv)
		: _kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)  //默认给成红色
	{
    
    }
};

可以看到,在构造函数的初始化列表中,我们将节点的颜色默认初始化为 RED,这是因为新增节点的颜色默认给成红色更合适,原因如下:

  1. 如果新增节点的颜色为红色,那么这里存在三种情况:一是新增节点为根节点,此时我们需要将节点颜色改为黑色;二是新增节点的父节点颜色为红色,由于红黑树中不能出现连续的红色节点,所以我们需要进行调整;三是新增节点的父节点颜色为黑色,此时我们不需要做任何事,因为新增节点没有违反红黑树的性质。
  2. 而如果新增节点的颜色为黑色,那我们一定需要对该节点进行调整,因为新增节点会导致这条路径的黑色节点数量比其他所有路径的黑色节点数量多一,违反了红黑树的性质4。
  3. 综合考量上面两种情况,我们认为将新增节点颜色默认设置为红色更合适 (黑色必调整且很难调整,红色可能调整且调整较易)。

三、红黑树的插入

红黑树插入的前面部分很简单,就是一般二叉搜索树的插入逻辑,需要注意的是如果插入的节点是根节点,则我们需要将节点的颜色改为黑色,因为新增节点默认是被初始化为红色的;

红黑树插入的难点在于检测插入后红黑树的性质是否被破坏,其一共可以分为两种情况:

  1. 父节点的颜色为黑色,此时没有违反红黑树任何性质,不需要调整;
  2. 父节点的颜色为红色,此时出现了连续红色节点,需要进行调整。
bool insert(const std::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 (kv.first < cur->_kv.first) {
    
    
            parent = cur;
            cur = cur->_left;
        }
        else if (kv.first > cur->_kv.first) {
    
    
            parent = cur;
            cur = cur->_right;
        }
        else {
    
    
            return false;  //不允许重复节点
        }
    }

    //走到空开始插入,注意这里还需要修改父节点的指向
    //新增节点的颜色为默认被初始化为红色,所以这里不需要再显式赋值
    Node* newNode = new Node(kv);
    if (kv.first < parent->_kv.first)
        parent->_left = newNode;
    else
        parent->_right = newNode;
    newNode->_parent = parent;  //修改父节点指向

    //如果父节点颜色为红色,则需要进行调整
    //红黑树的调整
}

红黑树的调整又分为两种情况:(约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点)

  1. 叔叔存在且为红,此时我们只需要进行变色;
  2. 叔叔不存在或者叔叔存在且为黑,此时我们需要进行旋转+变色。

下面我们来具体探讨这两种情况。


四、红黑树的调整

1、叔叔存在且为红

如果cur为红,p为红,g为黑,u存在且为红,此时我们只需要将p和u变黑,再将g变红即可 (约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点):image-20230330120556517

可以看到,当p为红时,由于g是p的父亲,所以g一定为黑,如果此时叔叔存在且为红,那么我们只需要将p和u变黑,再将g变红即可;这样做不会改变g这棵子树黑色节点的数量,所以不会影响到其他路径,同时g的子树中每条路径的黑色节点数量也是相同的,符合红黑树的性质。

节点颜色修改完毕之后,这里又分为两种情况

  1. g是根节点,由于根节点必须为黑,所以我们需要将g的颜色置为黑;
  2. g不是根节点,由于我们将g变成了红色,而我们并不知道g的父亲是不是红色,所以我们需要将g作为新增节点 继续向上调整,然后再下一层循环中再进行判断与调整。image-20230330140447302

另外,和 AVL 树中旋转的图一样,这里给出的也是抽象图,其中 a/b/c/d/e 代表的是高度为 h 的红黑树子树,h 可以取任何非负整数,所以这张图代表的是一类情况,我们以 h == 1 为例 (图中给出的组合数并不一定是准确的):image-20230328235713979

注意:上面我们是以左左为例子进行讨论的,但其实上面的处理方式也适用于 右右、左右、右左 的情况 (注意:这里的左左、右右、左右、右左和上一节 AVL 树里面的其实是差不多的,只是红黑树不通过平衡因子来控制平衡,所以进行 if 条件需要进行相应的调整而已);

也就是说,只要叔叔存在且为红,那么无论是插入节点的父亲在祖父节点的左边还是右边,插入节点在父亲的左边还是右边都没有关系,统一将父节点和叔叔节点变黑,将祖父节点变红

2、叔叔不存在或存在且为黑

之所以将叔叔不存在与叔叔存在且为黑分为一类情况是因为它们的处理方式是相同的 (约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点):

  1. 当u不存在时,cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p中一定有一个节点的颜色是黑色,否则不满足性质4 – 每条路径黑色节点个数相同;此时我们的操作是旋转 + 变色,其中旋转会根据父节点位置和插入节点位置的不同分为四种情况,而变色是统一将旋转后子树的根节点变为黑色,将根的左右节点变为红色。image-20230330143655594

  2. 如果u存在且为黑,那么cur节点原来的颜色一定是黑色的,现在看到其是红色是因为cur的子树之前为情况1,然后情况1调整后将祖父节点也就是cur节点变成了红色,否则g左子树的黑色节点数量会比右子树黑色节点数量少1,违反性质4;此时我们的操作和u不存在时一模一样 – 旋转 + 变色,旋转分为四种情况,变色统一将旋转后子树的根节点变为黑色,将根的左右节点变为红色;所以说我们可以将u不存在和u存在且为黑分同一类 (本质是我们不会改变u及其子树的颜色)。

    image-20230330150557904

红黑树的旋转 – 和 AVL 树一样,红黑树会根据父节点位置的不同和插入节点位置的不同选择不同的旋转方式来进行调整,旋转一共分为四类:

  1. 右单旋 – 父节点在祖父节点的左侧,cur 在父节点的左侧;
  2. 左单旋 – 父节点在祖父节点的右侧,cur 在父节点的右侧;
  3. 左右双旋 – 父节点在祖父节点的左侧,cur 在父节点的右侧;
  4. 右左双旋 – 父节点在祖父节点的右侧,cur 在父节点的左侧。

可以看到,红黑树的旋转其实就是 AVL 树中的四种旋转,只不过红黑树中不再需要更新平衡因子,而是需要更新节点颜色而已;不过红黑树中叔叔不存在或存在且为黑情况下节点颜色的更新十分简单 – 统一将旋转后子树的根节点变为黑色,将根的左右节点变为红色即可。

需要注意的是,和情况1 – 叔叔存在且为红不同,由于这里我们将祖父节点的颜色改为黑色 (旋转后子树的根节点为祖父节点),所以不用考虑祖父的父节点为红色从而违反性质4的问题,即 这里我们不需要继续向上调整

最后,由于左单旋、右单旋、左右双旋和右左双旋这四种旋转我们在上一节 AVL 树中已经讲解的十分清楚,所以这里我们不再重复讲解,而仅仅是给出左右双旋和右左双旋两种情况的例图 (由于右单旋的例图在上面我们已经给出,所以下面只给出双旋的例图),如果看不懂例图或对旋转的细节有疑问的同学可以再回顾一下上一节;

左右双旋的情况如下:image-20230330164214847

右左双旋的情况如下:image-20230330165238506

3、插入完整代码

旋转的代码如下:

bool insert(const std::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 (kv.first < cur->_kv.first) {
    
    
            parent = cur;
            cur = cur->_left;
        }
        else if (kv.first > cur->_kv.first) {
    
    
            parent = cur;
            cur = cur->_right;
        }
        else {
    
    
            return false;  //不允许重复节点
        }
    }

    //走到空开始插入,注意这里还需要修改父节点的指向
    //新增节点的颜色为默认被初始化为红色,所以这里不需要再显式赋值
    Node* newNode = new Node(kv);
    if (kv.first < parent->_kv.first)
        parent->_left = newNode;
    else
        parent->_right = newNode;
    newNode->_parent = parent;  //修改父节点指向

    cur = newNode;
    //如果父节点颜色为红色,则需要进行调整,且最多调整到根
    while (parent && parent->_col == RED) {
    
    
        Node* grandfater = parent->_parent;  //祖父节点

        if (grandfater == nullptr)
            break;

        //如果父节点在祖父节点的左边
        if (parent == grandfater->_left) {
    
    
            Node* uncle = grandfater->_right;  //叔叔节点

            //情况一:叔叔存在且为红--父和叔叔变黑,爷爷变红,继续向上调整
            if (uncle && uncle->_col == RED) {
    
    
                parent->_col = BLACK;
                uncle->_col = BLACK;
                grandfater->_col = RED;

                //继续向上调整
                cur = grandfater;
                parent = cur->_parent;
            }
            //情况二:叔叔不存在或叔叔存在且为黑
            else {
    
    
                //如果cur在parent的左边,则右单旋--注意走到这里p已经是g的左边了
                if (cur == parent->_left) {
    
    
                    rotateR(grandfater);

                    //更新节点颜色
                    parent->_col = BLACK;
                    cur->_col = grandfater->_col = RED;
                }
                else {
    
      //左右双旋
                    //先以parent为轴进行左单旋
                    rotateL(parent);
                    //再以grandfather进行右单旋
                    rotateR(grandfater);

                    //更新节点颜色--此时cur为子树的根节点
                    cur->_col = BLACK;
                    parent->_col = grandfater->_col = RED;
                }

                //最后需要跳出循环,因为此时不会再影响上层
                break;
            }
        }
        //如果父节点在祖父节点的右边
        else {
    
    
            Node* uncle = grandfater->_left;  //叔叔节点

            //情况一:叔叔存在且为红--父和叔叔变黑,爷爷变红,继续向上调整
            if (uncle && uncle->_col == RED) {
    
    
                parent->_col = BLACK;

                uncle->_col = BLACK;
                grandfater->_col = RED;

                //继续向上调整
                cur = grandfater;
                parent = cur->_parent;
            }
            //情况二:叔叔不存在或叔叔存在且为黑
            else {
    
    
                //如果cur在parent的右边,则左单旋--注意走到这里p已经是g的右边了
                if (cur == parent->_right) {
    
    
                    rotateL(grandfater);

                    //更新节点颜色
                    parent->_col = BLACK;
                    cur->_col = grandfater->_col = RED;
                }
                else {
    
      //右左双旋
                    //先以parent为轴进行右单旋
                    rotateR(parent);
                    //再以grandfather进行左单旋
                    rotateL(grandfater);

                    //更新节点颜色--此时cur为子树的根节点
                    cur->_col = BLACK;
                    parent->_col = grandfater->_col = RED;
                }

                //最后需要跳出循环,因为此时不会再影响上层
                break;
            }
        }
    }

    //在循环外面将_root的颜色重新置黑,避免循环内部将根节点的颜色变红了
    _root->_col = BLACK;

    return true;
}

4、总结

红黑树节点的调整其实很简单,一共就两种情况:(注意新增节点一定要初始化为红色)

  1. 父节点的颜色为黑色,此时没有违反红黑树任何性质,不需要调整;
  2. 父节点的颜色为红色,此时出现了连续红色节点,需要进行调整;调整又分为两种情况:
    1. 若叔叔存在且为红,则将父亲和叔叔置黑、祖父置红,然后继续往上调整;
    2. 叔叔不存在或者叔叔存在且为黑,此时我们需要进行旋转+变色 – 旋转根据父节点和cur节点位置的不同分为左单旋、右单旋、左右双旋和右左双旋;变色统一将旋转后的子树的根节点置黑、根节点的左右节点置红即可;调整完成后不需要继续往上调整。

简单来说,红黑树如何调整取决于叔叔节点


五、红黑树的验证

红黑树的验证和 AVL 树一样,验证一共分为两部分:

  1. 验证是否为二叉搜索树;
  2. 验证其是否满足红黑树的性质;

验证二叉搜索树很简单,我们向树中插入数据,然后实现一个 InOrder 函数看遍历得到的数据是否有序即可。

//中序遍历
void InOrder() {
    
    
    return _inOrder(_root);
}

void _inOrder(Node* root) {
    
    
    if (root == nullptr)
        return;

    _inOrder(root->_left);
    std::cout << root->_kv.first >> root->_kv.second >> std::endl;
    _inOrder(root->_right);
}

image-20230330200917766

验证平衡则比较麻烦,因为我们需要分别验证红黑树的几个性质 – 根节点为黑色,不允许出现连续的红色,每条路径黑色节点的数量是红色节点数量的两倍;同时,在验证的过程中我们可以顺便将不符合要求的地方的节点的key值打印出来,方便发生错误时进行调试

bool Check(Node* root, int blackCount, int baseCount) {
    
    
    //如果走到空,说明这条路径结束了,此时看count与baseCount是否相等
    if (root == nullptr) {
    
    
        if (blackCount != baseCount) {
    
    
            std::cout << "此路径黑色节点数量有误,节点key值:" << root->_kv.first << std::endl;
            return false;
        }
        return true;
    }

    //检查是否出现连续红色节点
    if (root->_col == RED) {
    
    
        if (root->_parent->_col == RED) {
    
      //红色节点一定有父亲,因为根节点为黑色
            std::cout << "出现连续红色节点,节点key值:" << root->_kv.first << std::endl;
        }
    }

    if (root->_col == BLACK)
        blackCount++;

    return Check(root->_left, blackCount, baseCount) && Check(root->_right, blackCount, baseCount);
}

//验证红黑树的性质
bool IsBanlance() {
    
    
    if (_root == nullptr)
        return true;

    //检查根节点
    if (_root->_col == RED) {
    
    
        std::cout << "根节点为红" << std::endl;
        return false;
    }

    //以最左路径黑色节点数量为基准,同其他路径的黑色节点数量相比较,看是否相等
    int baseCount = 0;
    Node* cur = _root;
    while (cur) {
    
    
        if (cur->_col == BLACK)
            baseCount++;
        cur = cur->_left;
    }

    //检查红黑树的其他性质
    return Check(_root, 0, baseCount);
}

image-20230330203126187

image-20230330203410404

如果我们用几千上万甚至几十上百万个随机数测试出来的结果都是真,那么我们的 红黑树 就基本上没问题了,当然前提是你的 IsBanlance 函数没写错。


六、红黑树的删除

和 AVL 树一样,红黑树的删除我们作为了解内容即可,如果对其特别感兴趣的同学可以参考一下《算法导论》或者《STL源码剖析》,也可以看看下面这位大佬的博客:红黑树 - Never - 博客园 (cnblogs.com)


七、红黑树与 AVL 树的比较

红黑树和 AVL 树都是高效的平衡二叉树,增删改查的时间复杂度都是 O(logN),但由于红黑树不追求绝对平衡,只需要保证最长路径不超过最短路径的2倍,所以在某些极端情况下红黑树的查询效率相较于 AVL 树要低一点点;例如当树左子树全部为黑色节点,右子树全部为一黑一红交替时,红黑树的高度差不多是 AVL 树的两倍,但是此时红黑树的查询效率仍然属于 O(logN) 这个量级;

不过也正是由于红黑树不要求左右子树绝对平衡,所以红黑树相较于 AVL 树在插入和删除时的旋转次数要少一些,所以在经常进行增删的结构中红黑树的性能比 AVL 树更优,而且红黑树实现比较简单,所以在实际业务中一般都使用红黑树,而不使用 AVL 树


八、红黑树的代码实现

RBTree.h:

#pragma once

//节点颜色
enum Color {
    
    
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode {
    
    
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	std::pair<K, V> _kv;
	Color _col;  //节点颜色

	RBTreeNode(const std::pair<K, V>& kv)
		: _kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)  //默认给成红色
	{
    
    }
};

template<class K, class V>
class RBTree {
    
    
	typedef RBTreeNode<K, V> Node;
public:
	bool insert(const std::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 (kv.first < cur->_kv.first) {
    
    
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first) {
    
    
				parent = cur;
				cur = cur->_right;
			}
			else {
    
    
				return false;  //不允许重复节点
			}
		}

		//走到空开始插入,注意这里还需要修改父节点的指向
		//新增节点的颜色为默认被初始化为红色,所以这里不需要再显式赋值
		Node* newNode = new Node(kv);
		if (kv.first < parent->_kv.first)
			parent->_left = newNode;
		else
			parent->_right = newNode;
		newNode->_parent = parent;  //修改父节点指向

		cur = newNode;
		//如果父节点颜色为红色,则需要进行调整,且最多调整到根
		while (parent && parent->_col == RED) {
    
    
			Node* grandfater = parent->_parent;  //祖父节点

			if (grandfater == nullptr)
				break;

			//如果父节点在祖父节点的左边
			if (parent == grandfater->_left) {
    
    
				Node* uncle = grandfater->_right;  //叔叔节点

				//情况一:叔叔存在且为红--父和叔叔变黑,爷爷变红,继续向上调整
				if (uncle && uncle->_col == RED) {
    
    
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上调整
					cur = grandfater;
					parent = cur->_parent;
				}
				//情况二:叔叔不存在或叔叔存在且为黑
				else {
    
    
					//如果cur在parent的左边,则右单旋--注意走到这里p已经是g的左边了
					if (cur == parent->_left) {
    
    
						rotateR(grandfater);

						//更新节点颜色
						parent->_col = BLACK;
						cur->_col = grandfater->_col = RED;
					}
					else {
    
      //左右双旋
						//先以parent为轴进行左单旋
						rotateL(parent);
						//再以grandfather进行右单旋
						rotateR(grandfater);

						//更新节点颜色--此时cur为子树的根节点
						cur->_col = BLACK;
						parent->_col = grandfater->_col = RED;
					}

					//最后需要跳出循环,因为此时不会再影响上层
					break;
				}
			}
			//如果父节点在祖父节点的右边
			else {
    
    
				Node* uncle = grandfater->_left;  //叔叔节点

				//情况一:叔叔存在且为红--父和叔叔变黑,爷爷变红,继续向上调整
				if (uncle && uncle->_col == RED) {
    
    
					parent->_col = BLACK;

					uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上调整
					cur = grandfater;
					parent = cur->_parent;
				}
				//情况二:叔叔不存在或叔叔存在且为黑
				else {
    
    
					//如果cur在parent的右边,则左单旋--注意走到这里p已经是g的右边了
					if (cur == parent->_right) {
    
    
						rotateL(grandfater);

						//更新节点颜色
						parent->_col = BLACK;
						cur->_col = grandfater->_col = RED;
					}
					else {
    
      //右左双旋
						//先以parent为轴进行右单旋
						rotateR(parent);
						//再以grandfather进行左单旋
						rotateL(grandfater);

						//更新节点颜色--此时cur为子树的根节点
						cur->_col = BLACK;
						parent->_col = grandfater->_col = RED;
					}

					//最后需要跳出循环,因为此时不会再影响上层
					break;
				}
			}
		}

		//在循环外面将_root的颜色重新置黑,避免循环内部将根节点的颜色变红了
		_root->_col = BLACK;

		return true;
	}

	//左单旋--右右
	void rotateL(Node* parent) {
    
    
		Node* subR = parent->_right;  //右子树
		Node* subRL = subR->_left;  //右子树的左子树

		//右子树的左子树链接到parent的右边
		parent->_right = subRL;
		if (subRL)  //subR可能为空(h==0)
			subRL->_parent = parent;

		//parent链接到右子树的左边
		subR->_left = parent;
		Node* pparent = parent->_parent;  //保存parent的父节点
		parent->_parent = subR;

		//让subR成为子树的根
		if (pparent) {
    
    
			if (pparent->_left == parent) {
    
    
				pparent->_left = subR;
				subR->_parent = pparent;
			}
			else {
    
    
				pparent->_right = subR;
				subR->_parent = pparent;
			}
		}
		else {
    
      //如果parent的parent为空,说明parent是整棵树根节点,此时subR成为新的根节点
			subR->_parent = nullptr;
			_root = subR;
		}
	}

	//右单旋--左左
	void rotateR(Node* parent) {
    
    
		Node* subL = parent->_left;  //左子树
		Node* subLR = subL->_right;  //左子树的右子树

		//将左子树的右子树链接到根的左边
		parent->_left = subLR;
		if (subLR)  //应对h==0的情况
			subLR->_parent = parent;

		//将根链接到左子树的右边
		subL->_right = parent;
		Node* pparent = parent->_parent;  //记录父节点的父节点
		parent->_parent = subL;

		//让subL成为子树的根
		if (pparent) {
    
    
			if (pparent->_left == parent) {
    
    
				pparent->_left = subL;
				subL->_parent = pparent;
			}
			else {
    
    
				pparent->_right = subL;
				subL->_parent = pparent;
			}
		}
		else {
    
      //如果parent的parent为空,说明parent是整棵树的根节点,此时subL成为新的根节点
			subL->_parent = nullptr;
			_root = subL;
		}
	}

	//中序遍历
	void InOrder() {
    
    
		return _inOrder(_root);
	}

	bool Check(Node* root, int blackCount, int baseCount) {
    
    
		//如果走到空,说明这条路径结束了,此时看count与baseCount是否相等
		if (root == nullptr) {
    
    
			if (blackCount != baseCount) {
    
    
				std::cout << "此路径黑色节点数量有误,节点key值:" << root->_kv.first << std::endl;
				return false;
			}
			return true;
		}

		//检查是否出现连续红色节点
		if (root->_col == RED) {
    
    
			if (root->_parent->_col == RED) {
    
      //红色节点一定有父亲,因为根节点为黑色
				std::cout << "出现连续红色节点,节点key值:" << root->_kv.first << std::endl;
			}
		}

		if (root->_col == BLACK)
			blackCount++;

		return Check(root->_left, blackCount, baseCount) && Check(root->_right, blackCount, baseCount);
	}

	//验证红黑树的性质
	bool IsBanlance() {
    
    
		if (_root == nullptr)
			return true;

		//检查根节点
		if (_root->_col == RED) {
    
    
			std::cout << "根节点为红" << std::endl;
			return false;
		}

		//以最左路径黑色节点数量为基准,同其他路径的黑色节点数量相比较,看是否相等
		int baseCount = 0;
		Node* cur = _root;
		while (cur) {
    
    
			if (cur->_col == BLACK)
				baseCount++;
			cur = cur->_left;
		}

		//检查红黑树的其他性质
		return Check(_root, 0, baseCount);
	}

private:
	void _inOrder(Node* root) {
    
    
		if (root == nullptr)
			return;

		_inOrder(root->_left);
		std::cout << root->_kv.first << ":" << root->_kv.second << std::endl;
		_inOrder(root->_right);
	}

private:
	Node* _root = nullptr;
};

test.cpp:

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
using namespace std;
#include "RBTree.h"

//验证搜索树
void RBTree_test1() {
    
    
	int a[] = {
    
     16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTree<int, int> rb;
	for (auto e : a)
		rb.insert(std::make_pair(e, e));

	rb.InOrder();
}

//验证红黑树的性质
void RBTree_test2() {
    
    
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = {
    
     4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	RBTree<int, int> rb;
	for (auto e : a)
		rb.insert(std::make_pair(e, e));

	rb.InOrder();

	cout << rb.IsBanlance() << endl;
}

//增大测试用例--采用N个随机数来验证
void RBTree_test3() {
    
    
	srand((size_t)time(0));
	const int N = 10000;
	RBTree<int, int> rb;
	for (int i = 0; i < N; i++) {
    
    
		int x = rand();
		rb.insert(make_pair(x, x));
	}

	cout << rb.IsBanlance() << endl;
}

int main() {
    
    
	//RBTree_test1();
	//RBTree_test2();
	RBTree_test3();

	return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_62391199/article/details/129866827