当前位置:   article > 正文

【数据结构】红黑树实现详解

【数据结构】红黑树实现详解

在本篇博客中,作者将会带领你使用C++来实现一棵红黑树,此红黑树的实现是基于二叉搜索树AVLTree一块来讲的,所以在看本篇博客之前,你可以先看看下面这两篇博客

【C++】二叉搜索树-CSDN博客

【数据结构】AVLTree实现详解-CSDN博客

在这棵红黑树中,我们用到了二叉搜索树的插入规则AVLTree的旋转,所以在本篇博客中,在旋转部分可以直接看AVLTree的博客

目录

一.什么是红黑树 

 1.红黑树的性质

二.红黑树的实现 

1.红黑树结点的定义

2.红黑树类的定义

3.红黑树的插入 

①按二叉搜索树的规则进行插入 

②判断红黑树的性质是否被破坏

③如果被破坏,则进行调整 

cur为红,cur_parent为红,grandparent为黑,uncle为红

cur为红,cur_parent为红,grandparent为黑,uncle为黑\uncle不存在

uncle不存在 

uncle存在且为黑

cur为红,cur_parent为红,grandparent为红,uncle为黑\uncle不存在 

uncle不存在

uncle存在且为黑 

④ 调整代码

⑤最终插入代码 

⑥代码分析

4.红黑树的查找

5.红黑树的修改 

三.测试

四.所有源代码

blog_RBTree.h

test.cpp 

一.什么是红黑树 

红黑树是一棵二叉搜索树,它的结点不是黑的就是红的,其中它有一个非常重要的特点:

通过对任何一条从根到叶子的路径上各个结点的着色控制,保证了红黑树没有一条路径会比最短路径长出两倍,达到接近平衡的特点。 

看到这句话,可能你还云里雾里的,但是不要怕,简单的来说,红黑树的特点就是:找出树中最短的路径最长的路径,其中这条最长路径的长度不会大于最短路径长度的两倍

如下图所示:

在这棵红黑树中,最短的路径是最左边的那条,长度为3,最长的路径是最右边的那条,长度为4,即4 < 2*3,最长的路径不会大于最短路径的两倍

 1.红黑树的性质

那么红黑树是怎么做到上面的这个特点的呢?

那是因为红黑树需要遵循下面这5条性质

1.每个结点不是红的就是黑的

2.根节点是黑色的

3.如果一个结点是红色的,那么它的两个孩子一定是黑色

4.对于每个结点,从该结点到其所有后代叶结点的路径上,均包含相同数量的黑色结点

5.每个叶子结点都是黑色的(此处的我叶子结点指的是空结点


对于这5条性质来说,可能有一些很难理解,但是不用怕,我们来解释一下。

对于1、2这两条性质来说,很好理解就字面意思,这里就不做过多的解释,其中最重要的是3、4这两条性质,在下面的红黑树插入讲解中,我们都将会围绕着3、4这两条性质来进行


3.如果一个结点是红色的,那么它的两个孩子一定是黑色的。

        其实这个也很好理解,但是这个很重要。其规则如下图所示。


4.对于每个结点,从该结点到其所有后代叶结点的路径上,均包含相同数量的黑色结点

         这条规则说的是,从任意一个结点开始一直往下走,走遍所有可能,这其中会有很多条可能的路径,但是这些路径都有一个特点,就是每条路径上的黑色结点的个数相同。如下图所示。


5.每个叶子结点都是黑色的(这里的叶子结点指的是空结点),如下图所示。 但是其实这个并不是那么重要,理解就行了。


那么为什么只要遵循上面这五条性质,就能做到红黑树的特点的呢?红黑树的特点是,最长路径的长度不会大于最短路径的长度的两倍

既然红黑树的特点与最短路径最长路径有关,那么我们来把这两条路径分析一下。

最短路径:通过这五条性质,我们可以知道在一棵红黑树中,最短路径上一定全是黑结点。不会再有情况比这种情况要短了。

最长路径: 对于最长路径来说,由于有性质4,所以在最长路径上,它的黑色结点的个数一定和最短路径的黑色结点个数相同。又由于有性质3,对于最长的路径来说,它一定是黑结点,红结点交替的。所以最长的路径的长度不会大于最短路径的两倍

如下图所示

二.红黑树的实现 

知道了红黑树的性质和特点,接下来我们可以来实现一棵红黑树了。 

1.红黑树结点的定义

在定义红黑树的结点时,与普通的树结点不同的是,这里多了一个_parent指针一个记录结点颜色的变量。 

  1. enum Color
  2. {
  3. BLACK,
  4. RED
  5. };
  6. template<class T>
  7. struct TreeNode
  8. {
  9. //成员变量
  10. T _data;//数据
  11. TreeNode<T>* _left;
  12. TreeNode<T>* _right;
  13. TreeNode<T>* _parent;
  14. Color _col;//保存结点的颜色
  15. //构造函数
  16. TreeNode(const T& data)
  17. :_data(data)
  18. ,_left(nullptr)
  19. ,_right(nullptr)
  20. ,_parent(nullptr)
  21. ,_col(RED)//每生成一个新结点,都将结点的颜色默认给红色,至于为什么,后面会解释
  22. {}
  23. };

2.红黑树类的定义

对于红黑树类的定义,我们先简单的给出一个构造函数,并将_root根结点赋值为nullptr即可。 

  1. template<class T>
  2. class RBTree
  3. {
  4. typedef TreeNode<T> Node;
  5. public:
  6. //构造函数
  7. RBTree()
  8. :_root(nullptr)//给根结点一个nullptr
  9. {}
  10. private:
  11. Node* _root;
  12. };

3.红黑树的插入 

接下来就是最重要的部分了,红黑树的插入,对于红黑树的插入,我们可以分为两个过程: 

1.按二叉搜索树的插入规则把新结点插入进去先

2.插入新结点后,判断红黑树的性质有没有被破坏,如果没有被破坏,则插入直接结束,如果有被破坏,则进行调整处理


由于插入结点的函数有点长,所以这里把插入函数分开来实现 。下面是函数的名称以及参数

bool insert(const T& data)

①按二叉搜索树的规则进行插入 

二叉搜索树的插入规则就是,先不管三七二十一,先把新结点插入进去再说。 

对于二叉搜索树的插入规则,这里不在多讲,不是很理解的,可以看下面这篇博客,这里面讲到了二叉搜索树的插入实现。我们在这里直接给出代码。

【C++】二叉搜索树-CSDN博客

  1. //如果树为NULL,则直接将新结点给到_root
  2. if (_root == nullptr)
  3. {
  4. _root = new Node(data);
  5. _root->_col = BLACK;//把新结点给_root后,要记得把颜色给位BLACK
  6. return true;
  7. }
  8. Node* cur = _root;
  9. Node* cur_parent = nullptr;
  10. while (cur != nullptr)
  11. {
  12. if (data > cur->_data)
  13. {
  14. cur_parent = cur;
  15. cur = cur->_right;
  16. }
  17. else if (data < cur->_data)
  18. {
  19. cur_parent = cur;
  20. cur = cur->_left;
  21. }
  22. else//说明该值已经存在,不进行插入
  23. {
  24. return false;
  25. }
  26. }
  27. //将新结点插入
  28. Node* newnode = new Node(data);
  29. if (cur_parent->_data > data)
  30. {
  31. cur_parent->_left = cur;
  32. cur->_parent = cur_parent;
  33. }
  34. else
  35. {
  36. cur_parent->_right = cur;
  37. cur->_parent = cur_parent;
  38. }

②判断红黑树的性质是否被破坏

当插入完结点后,就要判断红黑树的性质是否被破坏,如没有,则插入结束,如果有则进行调整处理。 

对于前者没有什么好讨论的,我们在着重于讨论后者。


红黑树的性质:

1.每个结点不是红的就是黑的

2.根节点是黑色的

3.如果一个结点是红色的,那么它的两个孩子一定是黑色的

4.对于每个结点,从该结点到其所有后代叶结点的路径上,均包含相同数量的黑色结点

5.每个叶子结点都是黑色的(此处的我叶子结点指的是空结点)

首先我们来思考一个问题,如果新增了一个结点,你希望这个新增的结点是红色还是黑色的呢?

其实我们希望这个新增的结点是红色的,为什么,我们来从红黑树的性质来分析。

当我们新插入一个结点的时候,性质1、2、5是绝对不会被破坏的,这应该很好理解。

被破坏的只有3或者4,当我们插入一个红结点,有可能会破坏性质3,当我们插入一个黑结点,会破坏性质4,如下图所示。

既然不管插入的是红结点还是黑结点,都会破坏红黑树的性质,那么为什么我们要选择插入红结点呢?

其实大家凭感觉来判断,应该都会感觉性质3的破坏更好的调整,而性质4的破坏不好调整,因为对于性质4的破坏,我们去给其他路径新增黑结点显然不合理,所以只有给当前路径减少一个黑结点来解决,那么我们把这个新增的结点给成红色即可。


既然这里已经确定了新增的结点一定为红色,所以对于红黑树的调整,我们都围绕着这个来讲解。 

③如果被破坏,则进行调整 

对于性质3的破坏,我们可以分为下面着三种情况。 

cur为红,cur_parent为红,grandparent为黑,uncle为红

上图是其中的一种插入的情况,其中cur刚好是叶子,但是cur也有可能不是叶子,所以我们可以给出这种情况的抽象图。

对于这种情况,我们的调整过程是这样的:

cur_parentuncle的颜色变为黑色grandparent的颜色变为红色。如下图所示。

但是这样的处理后,还会有可能发生性质破坏,例如grandparent的父结点还是红色,对于这样的情况,我们只需要把grandparent给cur继续向上处理即可。如下图所示。  

cur为红,cur_parent为红,grandparent为黑,uncle为黑\uncle不存在

在这种情况中,又可以分为两种小情况,一种是uncle存在且为黑,一种是uncle不存在

uncle不存在 

先来说说第一种情况,uncle不存在的情况,我们先来看图。 

对于这种情况来说,cur一定是新插入的结点,因为如果cur不是新插入的结点,那么说明是由情况一调整而来的,那么既然是从情况一调整而来的,那么cur的孩子肯定是黑色,但是这样就不符合性质4:从任意结点开始,每条路径的黑色结点的数量相同。 

uncle存在且为黑

接着第二种情况,uncle存在且为黑色,我们先来看图。 

对于这种情况来说,cur一定不是新插入的结点,因为如果cur是新插入的结点,那么在插入cur之前,这棵红黑树违反了性质4:从任意结点开始,每条路径的黑色结点的个数相同。 


基于这两种情况,我们可以给出这它们的抽象图,同时它们的调整方法是一样的。 

对于情况二,我们的处理是对grandparent进行一个右单旋,后调整cur_parent和grandparent的颜色即可。如下图所示。

 

cur为红,cur_parent为红,grandparent为红,uncle为黑\uncle不存在 

情况三看上去与情况二类似,其实可以说是,也可以说不是,首先同样的,情况三也可以分为两种情况,一种是uncle不存在,一种是uncle存在且为黑色。 

uncle不存在

我们直接来看图。

也情况二不同的是,它是一个折线。同样的如果uncle不存在,那么cur一定是新插入的结点。 

uncle存在且为黑 

同样的,我们直接来看图。 

对于这种情况来说,cur一定不是新插入的结点,它是由情况一调整过来的 


基于这两种情况,我们可以给出它的抽象图处理,如下图所示。  

对于情况三,我们的处理是,先对cur_parent进行一个左单旋,左单旋后,要交换cur和cur_parent,后再对grandparent进行一个右单旋,最后再调整颜色,如下图所示。

 


 

走到这里,我们所有的情况都已经讲完了,接下来可以编写我们的调整代码了。 

④ 调整代码

注意,我们上面讲到的三种情况没有覆盖到全部情况,因为还有另外三种情况是上面这三种情况的反方向,如情况一的反方向,如下图所示。 

同样的情况二情况三也有它们的反方向,这里就不在多讲了,逻辑都是一样的。接下来我们可以来编写我们的调整代码了,我们的调整代码可以分为如上图所示的正反向反方向来编写。   

  1. //调整结点代码
  2. while (cur_parent != nullptr && cur_parent->_col == RED)
  3. {
  4. Node* grandparent = cur_parent->_parent;
  5. if (grandparent->_left == cur_parent)//正方向
  6. {
  7. Node* uncle = grandparent->_right;
  8. if (uncle != nullptr && uncle->_col == RED)//情况一,uncle存在且为红
  9. {
  10. cur_parent->_col = uncle->_col = BLACK;
  11. grandparent->_col = RED;
  12. //继续向上处理
  13. cur = grandparent;
  14. cur_parent = cur->_parent;
  15. }
  16. else//情况二和情况三
  17. {
  18. //这里情况三经过第一次旋转后可以变成情况二,所以可以先判断是否是情况三
  19. if (cur_parent->_right == cur)//这种情况就是情况三
  20. {
  21. //先进行一个左单旋
  22. RotateL(cur_parent);
  23. swap(cur, cur_parent);//这里一定要进行交换,不然如果是情况二的,会出现逻辑错误
  24. }
  25. //代码走到这里就一定是情况二了
  26. RotateR(grandparent);
  27. cur_parent->_col = BLACK;
  28. grandparent->_col = RED;
  29. break;
  30. }
  31. }
  32. else if (grandparent->_right == cur_parent)//反方向
  33. {
  34. Node* uncle = grandparent->_left;
  35. if (uncle != nullptr && uncle->_col == RED)//反方向的情况一
  36. {
  37. cur_parent->_col = uncle->_col = BLACK;
  38. grandparent->_col = RED;
  39. cur = grandparent;
  40. cur_parent = cur->_parent;
  41. }
  42. else//反方向的情况二和情况三
  43. {
  44. if (cur_parent->_left == cur)
  45. {
  46. RotateR(cur_parent);
  47. swap(cur, cur_parent);
  48. }
  49. RotateL(grandparent);
  50. cur_parent->_col = BLACK;
  51. grandparent->_col = RED;
  52. break;
  53. }
  54. }
  55. }
  56. _root->_col = BLACK;

⑤最终插入代码 

  1. //插入结点
  2. bool insert(const T& data)
  3. {
  4. //如果树为NULL,则直接将新结点给到_root
  5. if (_root == nullptr)
  6. {
  7. _root = new Node(data);
  8. _root->_col = BLACK;//把新结点给_root后,要记得把颜色给位BLACK
  9. return true;
  10. }
  11. Node* cur = _root;
  12. Node* cur_parent = nullptr;
  13. while (cur != nullptr)
  14. {
  15. if (data > cur->_data)
  16. {
  17. cur_parent = cur;
  18. cur = cur->_right;
  19. }
  20. else if (data < cur->_data)
  21. {
  22. cur_parent = cur;
  23. cur = cur->_left;
  24. }
  25. else//说明该值已经存在,不进行插入
  26. {
  27. return false;
  28. }
  29. }
  30. //将新结点插入
  31. cur = new Node(data);
  32. if (cur_parent->_data > data)
  33. {
  34. cur_parent->_left = cur;
  35. cur->_parent = cur_parent;
  36. }
  37. else
  38. {
  39. cur_parent->_right = cur;
  40. cur->_parent = cur_parent;
  41. }
  42. //调整结点代码
  43. while (cur_parent != nullptr && cur_parent->_col == RED)
  44. {
  45. Node* grandparent = cur_parent->_parent;
  46. if (grandparent->_left == cur_parent)//正方向
  47. {
  48. Node* uncle = grandparent->_right;
  49. if (uncle != nullptr && uncle->_col == RED)//情况一,uncle存在且为红
  50. {
  51. cur_parent->_col = uncle->_col = BLACK;
  52. grandparent->_col = RED;
  53. //继续向上处理
  54. cur = grandparent;
  55. cur_parent = cur->_parent;
  56. }
  57. else//情况二和情况三
  58. {
  59. //这里情况三经过第一次旋转后可以变成情况二,所以可以先判断是否是情况三
  60. if (cur_parent->_right == cur)//这种情况就是情况三
  61. {
  62. //先进行一个左单旋
  63. RotateL(cur_parent);
  64. swap(cur, cur_parent);
  65. }
  66. //代码走到这里就一定是情况二了
  67. RotateR(grandparent);
  68. cur_parent->_col = BLACK;
  69. grandparent->_col = RED;
  70. break;
  71. }
  72. }
  73. else if (grandparent->_right == cur_parent)//反方向
  74. {
  75. Node* uncle = grandparent->_left;
  76. if (uncle != nullptr && uncle->_col == RED)//反方向的情况一
  77. {
  78. cur_parent->_col = uncle->_col = BLACK;
  79. grandparent->_col = RED;
  80. cur = grandparent;
  81. cur_parent = cur->_parent;
  82. }
  83. else//反方向的情况二和情况三
  84. {
  85. if (cur_parent->_left == cur)
  86. {
  87. RotateR(cur_parent);
  88. swap(cur, cur_parent);
  89. }
  90. RotateL(grandparent);
  91. cur_parent->_col = BLACK;
  92. grandparent->_col = RED;
  93. break;
  94. }
  95. }
  96. }
  97. _root->_col = BLACK;
  98. }

⑥代码分析

在我们的插入函数的调整部分中,有下面这段逻辑

  1. //调整结点代码
  2. while (cur_parent != nullptr && cur_parent->_col == RED)
  3. {
  4. Node* grandparent = cur_parent->_parent;
  5. if (grandparent->_left == cur_parent)//正方向
  6. {
  7. }

有的人可能会有个疑问,就是在这里的grandparent指针中,没有进行做判空处理不会出现grandparent是个空指针的错误吗?

答案是不会的,原因如下: 


首先我们可以确定的是,curcur_parent指针不会是空指针,不然这个while循环也不会进入,且进去这个循环后curcur_parent结点颜色一定为红色。那么既然cur_parent为红色,那么cur_parent就一定不是根结点,因为在红黑树的性质中,根结点一定是黑色的,但是这个时候cur_parent是红色的,说明cur_parent一定有父结点,所以不会出现grandparent为空指针的情况。

4.红黑树的查找

红黑树的查找就比较简单了,就是普通的二叉搜索树的查找方式,这里就不在多解释,不了解的可以去看前面的二叉搜索树的博客。 

  1. //查找结点
  2. Node* find(const T& data)
  3. {
  4. Node* cur = _root;
  5. if (data > cur->_data)
  6. {
  7. cur = cur->_right;
  8. }
  9. else if (data < cur->_data)
  10. {
  11. cur = cur->_left;
  12. }
  13. else
  14. {
  15. return cur;
  16. }
  17. return nullptr;
  18. }

5.红黑树的修改 

不是所有红黑树的结点都是可以修改的,只有<key,value>模型的红黑树才能修改,例如STL中的map,而key模型的红黑树是不能修改的,例如STL中的set,我们这里默认实现的是key模型的红黑树,所以在这里是不能修改的,但是如果想要做到修改,可以把这棵红黑树改成<key,value>模型即可,即在TreeNode结构体中,多加一个参数即可。 

三.测试

看到这里,我们的这棵红黑树除了删除都已经搞定了,那么我们如果判断我们写的代码是对的呢?即我们写的这棵红黑树到底是不是红黑树,判断这棵树是不是红黑树的方法就是用红黑树的性质去检查它即可。 


首先,我们再来看一下红黑树的五条性质: 

1.每个结点不是红的就是黑的

2.根节点是黑色的

3.如果一个结点是红色的,那么它的两个孩子一定是黑色的

4.对于每个结点,从该结点到其所有后代叶结点的路径上,均包含相同数量的黑色结点

5.每个叶子结点都是黑色的(此处的我叶子结点指的是空结点) 


通过下面这个代码,可以测试一个红黑树是否正确。 

  1. //判断是否符合红黑树的性质
  2. bool JudgeTree()
  3. {
  4. //空树也是红黑树
  5. if (_root == nullptr)
  6. {
  7. return true;
  8. }
  9. //性质1:树结点不是红的就是黑的
  10. //这条性质就没必要检查的了
  11. //性质2:根结点一定是黑色的
  12. if (_root->_col != BLACK)
  13. {
  14. cout << "违反性质2:根结点一定是黑色的" << endl;
  15. return false;
  16. }
  17. //性质5:所有叶子结点都是黑色的
  18. //这个性质也没必要检查
  19. size_t blackcount = 0;
  20. Node* cur = _root;
  21. while (cur != nullptr)//先求出一条路径中的黑色结点的个数
  22. {
  23. if (cur->_col == BLACK)
  24. {
  25. blackcount++;
  26. }
  27. cur = cur->_left;
  28. }
  29. size_t k = 0;//用来存储黑色结点的个数
  30. return _JudgeTree(_root, k, blackcount);//判断性质3和性质4
  31. //性质3:如果一个结点是红色的,那么它的孩子一定是黑色的
  32. //性质4:每条路径上的黑色结点的个数都相同
  33. }
  34. //判断是否红黑树的子函数
  35. bool _JudgeTree(Node* root, size_t k, size_t blackcount)
  36. {
  37. //当root走到NULL的时候,判断这条路径的黑色结点个数是否==blackcount
  38. if (root == nullptr)
  39. {
  40. if (k == blackcount)
  41. {
  42. return true;
  43. }
  44. else
  45. {
  46. cout << "违反性质4:每条路径上的黑结点个数相同" << endl;
  47. return false;
  48. }
  49. }
  50. //如果结点是红色的,判断它的父结点是否也为红色
  51. Node* root_parent = root->_parent;
  52. if (root_parent != nullptr && root->_col == RED)
  53. {
  54. if (root_parent->_col == RED)
  55. {
  56. cout << "违反性质3:如果一个结点是红色的,那么它的孩子一定是黑色的" << endl;
  57. return false;
  58. }
  59. }
  60. //如果结点是黑色的,则k++
  61. if (root->_col == BLACK)
  62. {
  63. k++;
  64. }
  65. return _JudgeTree(root->_left, k, blackcount) && _JudgeTree(root->_right, k, blackcount);
  66. }

四.所有源代码

blog_RBTree.h

  1. #pragma once
  2. #include<iostream>
  3. #include<time.h>
  4. using namespace std;
  5. namespace blog_RBTree
  6. {
  7. enum Color
  8. {
  9. BLACK,
  10. RED
  11. };
  12. template<class T>
  13. struct TreeNode
  14. {
  15. //成员变量
  16. T _data;//数据
  17. TreeNode<T>* _left;
  18. TreeNode<T>* _right;
  19. TreeNode<T>* _parent;
  20. Color _col;//保存结点的颜色
  21. //构造函数
  22. TreeNode(const T& data)
  23. :_data(data)
  24. ,_left(nullptr)
  25. ,_right(nullptr)
  26. ,_parent(nullptr)
  27. ,_col(RED)//每生成一个新结点,都将结点的颜色默认给红色,至于为什么,后面会解释
  28. {}
  29. };
  30. template<class T>
  31. class RBTree
  32. {
  33. typedef TreeNode<T> Node;
  34. public:
  35. //构造函数
  36. RBTree()
  37. :_root(nullptr)//给根结点一个nullptr
  38. {}
  39. //插入结点
  40. bool insert(const T& data)
  41. {
  42. //如果树为NULL,则直接将新结点给到_root
  43. if (_root == nullptr)
  44. {
  45. _root = new Node(data);
  46. _root->_col = BLACK;//把新结点给_root后,要记得把颜色给位BLACK
  47. return true;
  48. }
  49. Node* cur = _root;
  50. Node* cur_parent = nullptr;
  51. while (cur != nullptr)
  52. {
  53. if (data > cur->_data)
  54. {
  55. cur_parent = cur;
  56. cur = cur->_right;
  57. }
  58. else if (data < cur->_data)
  59. {
  60. cur_parent = cur;
  61. cur = cur->_left;
  62. }
  63. else//说明该值已经存在,不进行插入
  64. {
  65. return false;
  66. }
  67. }
  68. //将新结点插入
  69. cur = new Node(data);
  70. if (cur_parent->_data > data)
  71. {
  72. cur_parent->_left = cur;
  73. cur->_parent = cur_parent;
  74. }
  75. else
  76. {
  77. cur_parent->_right = cur;
  78. cur->_parent = cur_parent;
  79. }
  80. //调整结点代码
  81. while (cur_parent != nullptr && cur_parent->_col == RED)
  82. {
  83. Node* grandparent = cur_parent->_parent;
  84. if (grandparent->_left == cur_parent)//正方向
  85. {
  86. Node* uncle = grandparent->_right;
  87. if (uncle != nullptr && uncle->_col == RED)//情况一,uncle存在且为红
  88. {
  89. cur_parent->_col = uncle->_col = BLACK;
  90. grandparent->_col = RED;
  91. //继续向上处理
  92. cur = grandparent;
  93. cur_parent = cur->_parent;
  94. }
  95. else//情况二和情况三
  96. {
  97. //这里情况三经过第一次旋转后可以变成情况二,所以可以先判断是否是情况三
  98. if (cur_parent->_right == cur)//这种情况就是情况三
  99. {
  100. //先进行一个左单旋
  101. RotateL(cur_parent);
  102. swap(cur, cur_parent);
  103. }
  104. //代码走到这里就一定是情况二了
  105. RotateR(grandparent);
  106. cur_parent->_col = BLACK;
  107. grandparent->_col = RED;
  108. break;
  109. }
  110. }
  111. else if (grandparent->_right == cur_parent)//反方向
  112. {
  113. Node* uncle = grandparent->_left;
  114. if (uncle != nullptr && uncle->_col == RED)//反方向的情况一
  115. {
  116. cur_parent->_col = uncle->_col = BLACK;
  117. grandparent->_col = RED;
  118. cur = grandparent;
  119. cur_parent = cur->_parent;
  120. }
  121. else//反方向的情况二和情况三
  122. {
  123. if (cur_parent->_left == cur)
  124. {
  125. RotateR(cur_parent);
  126. swap(cur, cur_parent);
  127. }
  128. RotateL(grandparent);
  129. cur_parent->_col = BLACK;
  130. grandparent->_col = RED;
  131. break;
  132. }
  133. }
  134. }
  135. _root->_col = BLACK;
  136. }
  137. //查找结点
  138. Node* find(const T& data)
  139. {
  140. Node* cur = _root;
  141. if (data > cur->_data)
  142. {
  143. cur = cur->_right;
  144. }
  145. else if (data < cur->_data)
  146. {
  147. cur = cur->_left;
  148. }
  149. else
  150. {
  151. return cur;
  152. }
  153. return nullptr;
  154. }
  155. //判断是否符合红黑树的性质
  156. bool JudgeTree()
  157. {
  158. //空树也是红黑树
  159. if (_root == nullptr)
  160. {
  161. return true;
  162. }
  163. //性质1:树结点不是红的就是黑的
  164. //这条性质就没必要检查的了
  165. //性质2:根结点一定是黑色的
  166. if (_root->_col != BLACK)
  167. {
  168. cout << "违反性质2:根结点一定是黑色的" << endl;
  169. return false;
  170. }
  171. //性质5:所有叶子结点都是黑色的
  172. //这个性质也没必要检查
  173. size_t blackcount = 0;
  174. Node* cur = _root;
  175. while (cur != nullptr)//先求出一条路径中的黑色结点的个数
  176. {
  177. if (cur->_col == BLACK)
  178. {
  179. blackcount++;
  180. }
  181. cur = cur->_left;
  182. }
  183. size_t k = 0;//用来存储黑色结点的个数
  184. return _JudgeTree(_root, k, blackcount);//判断性质3和性质4
  185. //性质3:如果一个结点是红色的,那么它的孩子一定是黑色的
  186. //性质4:每条路径上的黑色结点的个数都相同
  187. }
  188. private:
  189. //左单旋
  190. void RotateL(Node* cur_parent)
  191. {
  192. Node* cur = cur_parent->_right;
  193. Node* cur_left = cur->_left;
  194. //改变指针的链接关系
  195. cur_parent->_right = cur_left;
  196. if (cur_left != nullptr)
  197. {
  198. cur_left->_parent = cur_parent;
  199. }
  200. cur->_left = cur_parent;
  201. Node* cur_parent_parent = cur_parent->_parent;
  202. cur_parent->_parent = cur;
  203. //旋转完成后要判断cur_parent是否为根
  204. if (cur_parent_parent != nullptr)//说明cur_parent不是根
  205. {
  206. if (cur_parent_parent->_data < cur_parent->_data)
  207. {
  208. cur_parent_parent->_right = cur;
  209. cur->_parent = cur_parent_parent;
  210. }
  211. else
  212. {
  213. cur_parent_parent->_left = cur;
  214. cur->_parent = cur_parent_parent;
  215. }
  216. }
  217. else//说明cur_parent是根
  218. {
  219. _root = cur;
  220. cur->_parent = nullptr;
  221. }
  222. }
  223. //右单旋
  224. void RotateR(Node* cur_parent)
  225. {
  226. Node* cur = cur_parent->_left;
  227. Node* cur_right = cur->_right;
  228. cur_parent->_left = cur_right;
  229. if (cur_right != nullptr)
  230. {
  231. cur_right->_parent = cur_parent;
  232. }
  233. cur->_right = cur_parent;
  234. Node* cur_parent_parent = cur_parent->_parent;
  235. cur_parent->_parent = cur;
  236. if (cur_parent_parent != nullptr)
  237. {
  238. if (cur_parent_parent->_data > cur_parent->_data)
  239. {
  240. cur_parent_parent->_left = cur;
  241. cur->_parent = cur_parent_parent;
  242. }
  243. else
  244. {
  245. cur_parent_parent->_right = cur;
  246. cur->_parent = cur_parent_parent;
  247. }
  248. }
  249. else
  250. {
  251. _root = cur;
  252. cur->_parent = nullptr;
  253. }
  254. }
  255. //获取根节点
  256. Node* GetRoot()
  257. {
  258. return _root;
  259. }
  260. //判断是否红黑树的子函数
  261. bool _JudgeTree(Node* root, size_t k, size_t blackcount)
  262. {
  263. //当root走到NULL的时候,判断这条路径的黑色结点个数是否==blackcount
  264. if (root == nullptr)
  265. {
  266. if (k == blackcount)
  267. {
  268. return true;
  269. }
  270. else
  271. {
  272. cout << "违反性质4:每条路径上的黑结点个数相同" << endl;
  273. return false;
  274. }
  275. }
  276. //如果结点是红色的,判断它的父结点是否也为红色
  277. Node* root_parent = root->_parent;
  278. if (root_parent != nullptr && root->_col == RED)
  279. {
  280. if (root_parent->_col == RED)
  281. {
  282. cout << "违反性质3:如果一个结点是红色的,那么它的孩子一定是黑色的" << endl;
  283. return false;
  284. }
  285. }
  286. //如果结点是黑色的,则k++
  287. if (root->_col == BLACK)
  288. {
  289. k++;
  290. }
  291. return _JudgeTree(root->_left, k, blackcount) && _JudgeTree(root->_right, k, blackcount);
  292. }
  293. private:
  294. Node* _root;
  295. };
  296. void Test1()
  297. {
  298. srand(time(0));
  299. RBTree<int> root;
  300. const int n = 10000;
  301. for (int i = 0; i < n; i++)
  302. {
  303. int input = rand();
  304. root.insert(input);
  305. }
  306. cout << root.JudgeTree() << endl;
  307. }
  308. }

test.cpp  

  1. #include"blog_RBTree.h"
  2. int main()
  3. {
  4. blog_RBTree::Test1();
  5. return 0;
  6. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/741845
推荐阅读
相关标签
  

闽ICP备14008679号