• 数据结构-----红黑树的插入


    目录

    前言

    红黑树的储存结构

     一、节点旋转操作

    左旋(Left Rotation)

    右旋(Right Rotation) 

    二、插入节点

    1.插入的是空树

    2.插入节点的key重新重复

    3.插入节点的父节点是黑色

    4.插入节点的父节点是红色

    4.1父节点是祖父节点的左子节点

    4.1.1叔叔节点是红色 

     4.1.2叔叔节点是黑色

    4.1.2-1 插入节点是作左子节点

    4.1.2-2插入节点是作右子节点

     4.2父节点是祖父节点的右子节点

    4.2.1叔叔节点是红色

    4.2.2 叔叔节点是黑色

    4.2.1-1 插入节点是作左子节点

    4.2.1-2 插入节点是作右子节点

    三、完整代码展示


    前言

            上一期我们初步学习了红黑树的基本概念和特性(上一期链接:数据结构-----红黑树简介_Gretel Tade的博客-CSDN博客 如果不了解红黑树相关性质的话建议看看这个),那么从这一期开始,我们就进入到了红黑树的深入学习,首先我通过这一期来详细介绍红黑树的插入操作实现,下面就看看怎么去把数据插入到红黑树吧!

    红黑树的储存结构

     根据红黑树的要求,我们可以去定义红黑树节点和树的结构体,如下所示:

    1. //宏定义颜色
    2. #define red 0
    3. #define black 1
    4. //数据类型Datatype
    5. typedef char Datatype;
    6. //红黑树节点存储结构
    7. typedef struct node {
    8. Datatype data;
    9. int color;
    10. int key;//排序键值,根据key大小排序
    11. struct node* par;//父节点指针
    12. struct node* left, * right;//左右子节点指针
    13. }Node;
    14. //红黑树的定义rbtree
    15. typedef struct tree {
    16. Node* root;//指向根节点指针
    17. Node* nil;//叶子节点(哨兵)
    18. }rbtree;

     一、节点旋转操作

    数据结构当中,旋转操作是一种很常见的操作,可能去实现数据结构平衡或者其他相关特性的要求,同样的的AVL树和红黑树里边也是要进行旋转操作的,通过旋转来满足平衡的特性。旋转分两种:左旋(Left Rotation)右旋(Right Rotation)

    左旋(Left Rotation)

    左旋是一种将某个节点的右子节点旋转上来的操作。也就是说当前节点的右子节点顶替了自己,然后自己变为右子节点的左子节点,以保持树的平衡。

    操作如下:

    1. 将当前节点的右子节点设为新的父节点。
    2. 将新的父节点的左子节点设为当前节点的右子节点。
    3. 如果当前节点有父节点,将新的父节点替代当前节点的位置。
    4. 将当前节点设为新的父节点的左子节点。

     代码实现:

    1. //左旋(以x为旋转点,向左旋转)
    2. void left_rotate(rbtree* T, Node* x) {
    3. Node* y = x->right;//标记到右子节点
    4. x->right = y->left;//y的左子节点代替x的右子节点
    5. if (x->right != T->nil)
    6. x->right->par = x;//如果不为空(nil)其父节点指向x
    7. y->par = x->par;//把y的父节点指向x的父节点,此时x与y没有直接联系了
    8. if (x->par == T->nil) {//判断x的父节点是否为根结点
    9. T->root = y;//如果是的话,y就变为根结点
    10. }
    11. else {
    12. //y顶替x的位置
    13. if (x == x->par->left)
    14. x->par->left = y;//如果x是父节点的左边,那y就代替x成为左子节点
    15. else
    16. x->par->right = y;//如果x是父节点的右边,那y就代替x成为右子节点
    17. }
    18. //y的左子节点指向x,x的父节点指向y
    19. y->left = x;
    20. x->par = y;
    21. }

    右旋(Right Rotation) 

    同样的右旋也是将左子节点顶替自己成为父节点, 然后自己成为左子节点的右子节点。

    操作如下:

    1. 将当前节点的左子节点设为新的父节点
    2. 将新的父节点的右子节点设为当前节点的左子节点
    3. 如果当前节点有父节点,将新的父节点替代当前节点的位置
    4. 将当前节点设为新的父节点的右子节点

     代码实现:

    1. //右旋(以x为旋转点,向右旋转)
    2. void right_rotate(rbtree* T, Node* x) {
    3. Node* y = x->left;//标记到左子节点y
    4. x->left = y->right;//y的右子节点代替x的左子节点
    5. if (x->left != T->nil)
    6. x->left->par = x;
    7. y->par = x->par;//y的父节点指向x的父节点
    8. if (x->par == T->nil)
    9. T->root = y;//如果x是根结点的话,那么y代替x成为根结点
    10. else {
    11. if (x == x->par->left)
    12. x->par->left = y;
    13. else
    14. x->par->right = y;
    15. }
    16. //y的右子节点指向x,x的父节点为y
    17. y->right = x;
    18. x->par = y;
    19. }

    二、插入节点

    再讲之前,我分享一个网址给大家(链接:Red/Black Tree Visualization),这个是一个红黑树模拟器的网址,你们可以去进行红黑树插入删除遍历等操作,可以自己试试看。如下图所示:

     废话不多说了,上正文!

    红黑树的插入操作分两步走:

    • 找到插入位置
    • 进行自平衡调整

     注意:插入节点初始为红色

    原因分析:因为红黑树中任意一个节点到叶子节点路径所含黑色节点数量相同,也就是说如果我插入的节点为黑色的话,那么就会破坏红黑树的要求,所以插入的节点必须是红色节点,才能保证红黑树的性质。

    下面就开始讨论红黑树的几种插入情况:

    1.插入的是空树

    这是最简单的插入情况,当插入第一个节点的时候,红黑树为空我们只需要让根节点指向这个节点即可。操作如下:

    1. 根节点指向此节点
    2. 把根节点染黑

    2.插入节点的key重新重复

    这种情况的话我们可以根据自己喜好去处理,如果出现了重复的key,那么就把这个key里面的值进行更新;或者我们不进行插入操作,因为key不可以重复,直接退出插入操作。

    3.插入节点的父节点是黑色

    这很好处理,直接插入就行了,因为父节点为黑色,插入节点为红色,所以不会影响红黑树的平衡性。

    1. 直接插入即可

    4.插入节点的父节点是红色

    这种情况是最为复杂的,由于父节点颜色是红色,所以要进行平衡调整,所以要去进一步的讨论才行。那具体根据什么去调整呢?是看叔叔节点的颜色来调整(父节点的兄弟节点),具体分以下几种情况:

     大的有两种情况,要看父节点是祖父节点的左边还是右边,下面我就以父节点为左子节点为例子:

     下文图标说明:

    t 表示插入的节点

    P表示父节点

    B表示叔叔节点

    PP表示祖父节点

    4.1父节点是祖父节点的左子节点
    4.1.1叔叔节点是红色 

    如果叔叔节点的颜色是红色的话,这里不需要进行旋转操作,只需要让父节点和叔叔节点颜色变为黑色,祖父节点颜色变为红色即可。流程如下:

    • 把P 和B 节点染黑
    • 把PP节点染红

     4.1.2叔叔节点是黑色

    这里的话又要去分两种情况:

    1. 插入节点是父节点的左子节点
    2. 插入节点是父节点的右子节点
    4.1.2-1 插入节点是作左子节点

     如果插入的节点是父节点的左子节点的话,那么要进行以下操作:

    • 把P染黑
    • 把PP染红
    • 对PP进行右旋

    4.1.2-2插入节点是作右子节点

     如果插入节点是作为父节点的右子节点的话,要进行以下操作:

    • 对P进行左旋
    • 把t 染黑
    • 把PP染红
    • 对PP进行右旋

     4.2父节点是祖父节点的右子节点

    这里的操作跟4.1基本上是一模一样的,只是对称过去是了,但是我还是想详细列出来吧,下面接着看。

    4.2.1叔叔节点是红色

    操作步骤如下:

    • 把B(叔叔节点)和P(父节点)然黑
    • 把PP(祖父节点)染红

    4.2.2 叔叔节点是黑色

    同样的也是分以下两种情况讨论: 

    4.2.1-1 插入节点是作左子节点
    •  对P 进行右旋
    • 将t 染黑
    • 将PP 然红
    • 对PP 进行左旋

    4.2.1-2 插入节点是作右子节点
    •  将P 染黑
    • 将PP 然红
    • 对PP进行左旋

     以上这些就是红黑树的插入全部可能了,是不是很多啊,其实还好啦!只要我们把这些情况一个一个分类,然后思路捋一捋很容易弄明白的,后面讲到红黑树的删除还有更多种情况呢!还有就是,这些图片是我自己画的,呃画得不太好,不好意思哈。

    三、完整代码展示

    1. #include
    2. #include
    3. #include
    4. #include
    5. //宏定义颜色
    6. #define red 0
    7. #define black 1
    8. //数据类型Datatype
    9. typedef char Datatype;
    10. //红黑树节点存储结构
    11. typedef struct node {
    12. Datatype data;
    13. int color;
    14. int key;
    15. struct node* par;//父节点指针
    16. struct node* left, * right;//左右子节点指针
    17. }Node;
    18. //红黑树的定义rbtree
    19. typedef struct tree {
    20. Node* root;//指向根节点指针
    21. Node* nil;//叶子节点(哨兵)
    22. }rbtree;
    23. //创建初始化红黑树
    24. rbtree* Create_inittree() {
    25. rbtree* T = (rbtree*)malloc(sizeof(rbtree));
    26. assert(T);
    27. T->nil = (Node*)malloc(sizeof(Node));
    28. assert(T->nil);
    29. //T->nil是不储存数据的节点,作为空节点代替NULL,也就是哨兵节点(表示空)
    30. T->nil->color = black;
    31. T->nil->par = NULL;
    32. T->nil->left = T->nil->right = NULL;
    33. T->root = T->nil;
    34. return T;
    35. }
    36. //创建一个节点
    37. Node* Create_node(rbtree*T ,Datatype data, int key) {
    38. Node* new_node = (Node*)malloc(sizeof(Node));
    39. assert(new_node);
    40. new_node->data = data;
    41. new_node->color = red;//初始化颜色红色
    42. //左右父节点为nil哨兵节点
    43. new_node->left=new_node->right = T->nil;
    44. new_node->par = T->nil;
    45. new_node->key = key;
    46. return new_node;
    47. }
    48. //左旋(以x为旋转点,向左旋转)
    49. void left_rotate(rbtree* T, Node* x) {
    50. Node* y = x->right;//标记到右子节点
    51. x->right = y->left;//y的左子节点代替x的右子节点
    52. if (x->right != T->nil)
    53. x->right->par = x;//如果不为空(nil)其父节点指向x
    54. y->par = x->par;//把y的父节点指向x的父节点,此时x与y没有直接联系了
    55. if (x->par == T->nil) {//判断x的父节点是否为根结点
    56. T->root = y;//如果是的话,y就变为根结点
    57. }
    58. else {
    59. //y顶替x的位置
    60. if (x == x->par->left)
    61. x->par->left = y;//如果x是父节点的左边,那y就代替x成为左子节点
    62. else
    63. x->par->right = y;//如果x是父节点的右边,那y就代替x成为右子节点
    64. }
    65. //y的左子节点指向x,x的父节点指向y
    66. y->left = x;
    67. x->par = y;
    68. }
    69. //右旋(以x为旋转点,向右旋转)
    70. void right_rotate(rbtree* T, Node* x) {
    71. Node* y = x->left;//标记到左子节点y
    72. x->left = y->right;//y的右子节点代替x的左子节点
    73. if (x->left != T->nil)
    74. x->left->par = x;
    75. y->par = x->par;//y的父节点指向x的父节点
    76. if (x->par == T->nil)
    77. T->root = y;//如果x是根结点的话,那么y代替x成为根结点
    78. else {
    79. if (x == x->par->left)
    80. x->par->left = y;
    81. else
    82. x->par->right = y;
    83. }
    84. //y的右子节点指向x,x的父节点为y
    85. y->right = x;
    86. x->par = y;
    87. }
    88. //插入后平衡调整
    89. void Insert_adjust(rbtree* T, Node* t) {
    90. //如果父节点的颜色是红色那就进行调整操作了
    91. if (t->par->color == red) {
    92. Node* p = t->par;
    93. Node* pp = p->par;
    94. //01 p节点是pp左子节点
    95. if (p == pp->left) {
    96. Node* uncle = pp->right;
    97. //01-1 叔叔节点颜色是红色
    98. if (uncle->color == red) {
    99. p->color = black;
    100. uncle->color = black;
    101. pp->color = red;
    102. t = pp;
    103. }
    104. //01-2 叔叔节点颜色是黑色
    105. else {
    106. //01-2-1 插入节点t是p的左子节点
    107. if (t == p->left) {
    108. p->color = black;
    109. pp->color = red;
    110. right_rotate(T, pp);
    111. t = p;
    112. }
    113. //01-2-2 插入节点t是p的右子节点
    114. else if(t==p->right){
    115. left_rotate(T, p);
    116. t->color = black;
    117. pp->color = red;
    118. right_rotate(T, pp);
    119. }
    120. }
    121. }
    122. //02 p节点是pp的右子节点
    123. else {
    124. Node* uncle = pp->left;
    125. //02-1 叔叔节点颜色是红色
    126. if (uncle->color == red) {
    127. pp->color = red;
    128. p->color = black;
    129. uncle->color = black;
    130. t = pp;
    131. }
    132. //02-2 叔叔节点颜色是黑色
    133. else {
    134. //02-2-1 插入节点t是p的右子节点
    135. if (t == p->right) {
    136. p->color = black;
    137. pp->color = red;
    138. left_rotate(T,pp);
    139. t = p;
    140. }
    141. //02-2-2 插入节点t是p的左子节点
    142. else {
    143. right_rotate(T, p);
    144. t->color = black;
    145. pp->color = red;
    146. left_rotate(T, pp);
    147. }
    148. }
    149. }
    150. }
    151. //根节点标记黑色
    152. T->root->color = black;
    153. }
    154. //插入节点
    155. void Insert_node(rbtree* T, Datatype data,int key) {
    156. assert(T);
    157. Node* t = Create_node(T ,data, key);
    158. Node* root = T->root;//快指针
    159. Node* cur=T->nil;//慢指针
    160. //1.如果根节点为空
    161. if (T->root==T->nil) {
    162. T->root = t;//根结点指向新创建的节点
    163. }
    164. else {
    165. while (root != T->nil) {
    166. cur = root;//cur标记为root的上一个节点(父节点)
    167. if (t->key > root->key)
    168. root = root->right;
    169. else if (t->key < root->key)
    170. root = root->left;
    171. //如果出现插入重复的key值,就退出,不进行插入操作
    172. else {
    173. printf("Don't insert the same key!\n");
    174. free(t);
    175. t = NULL;
    176. return;
    177. }
    178. }
    179. }
    180. //判断插入的位置
    181. if (key < cur->key)
    182. cur->left = t;//小的话就插入左边
    183. else
    184. cur->right = t;//大的话就插入右边
    185. t->par = cur;//新插入的父节点指针指向cur
    186. Insert_adjust(T, t);//平衡调整
    187. }

     单单值考虑插入操作就有两百多行代码,后面还有删除操作,查找操作,总共的话大概400行代码,这里就先发今天所讲的插入操作内容的代码,注释很详细,慢慢看哈,我相信你一点看得懂的!

    以上就是本期的全部内容了,我们下一期讲红黑树的删除操作,下次见!

    分享一张壁纸: 

  • 相关阅读:
    About-Flink
    2022年下半年信息系统项目管理师综合知识真题及答案1-20
    JDK21的虚拟线程是什么?和平台线程什么关系?
    Java File分隔符和 Path分隔符
    七、使用kubeadm搭建生产环境单master多node节点的k8s集群
    NLP领域可以投稿的期刊(2022整理)
    latex左侧大括号 latex中大括号多行公式
    图像去雾易语言代码
    双非本23秋招之路-从考研跑路到某安全大厂(无实习、项目)
    如何使用Python实现发送邮件功能
  • 原文地址:https://blog.csdn.net/m0_73633088/article/details/133863827