• 数据结构与算法——19.红黑树


    这篇文章我们来讲一下红黑树

    目录

    1.概述

    1.1红黑树的性质

    2.红黑树的实现

    3.总结


    1.概述

    首先,我们来大致了解一下什么是红黑树

    红黑树是一种自平衡的二叉查找树,是一种高效的查找树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。因此,红黑树在业界应用很广泛,比如 Java 中的 TreeMap,JDK 1.8 中的 HashMap。

    1.1红黑树的性质

    看过前面二叉查找树(即二叉搜索树)的同学都知道,普通的二叉查找树在极端情况下可退化成链表,此时的增删查效率都会比较低下。

    为了避免这种情况,就出现了一些自平衡的查找树,比如 AVL,红黑树等。这些自平衡的查找树通过定义一些性质,将任意节点的左右子树高度差控制在规定范围内,以达到平衡状态。前面讲的AVL树实现自平衡的机制是设置一个平衡因子。

    以红黑树为例,红黑树通过如下的性质定义实现自平衡:

    • 所有节点都有两种颜色:红色或黑色。
    • 根节点是黑色。
    • 所有null视为黑色。
    • 每个红色节点必须有两个黑色的子节点,即红色节点不能相邻(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
    • 从根节点到任意一个叶子节点,路径中的黑色节点数目一样(黑色完美平衡,简称黑高)。

    有了上面的几个性质作为限制,即可避免二叉查找树退化成单链表的情况。

    但是,仅仅避免这种情况还不够,这里还要考虑某个节点到其每个叶子节点路径长度的问题。

    如果某些路径长度过长,那么,在对这些路径上的节点进行增删查操作时,效率也会大大降低。

    这个时候性质4和性质5用途就凸显了,有了这两个性质作为约束,即可保证任意节点到其每个叶子节点路径最长不会超过最短路径的2倍。原因如下:

    当某条路径最短时,这条路径必然都是由黑色节点构成。当某条路径长度最长时,这条路径必然是由红色和黑色节点相间构成(性质4限定了不能出现两个连续的红色节点)。而性质5又限定了从任一节点到其每个叶子节点的所有路径必须包含相同数量的黑色节点。此时,在路径最长的情况下,路径上红色节点数量 = 黑色节点数量。该路径长度为两倍黑色节点数量,也就是最短路径长度的2倍。

    下面看几个红黑树:

    2.红黑树的实现

    下面来看一下红黑树的实现

    1. package Tree;
    2. /**红黑树的相关操作*/
    3. public class L4_RBTree {
    4. enum Color{RED,BLACK}
    5. private Node root;
    6. private static class Node {
    7. int key;
    8. Object value;
    9. Node left;
    10. Node right;
    11. Node parent;//父节点
    12. Color color = Color.RED;//颜色
    13. public Node(int key, Object value) {
    14. this.key = key;
    15. this.value = value;
    16. }
    17. //是否是左孩子
    18. boolean isLeftChild(){
    19. return parent != null && parent.left == this;
    20. }
    21. //找叔叔结点
    22. Node isUncle(){
    23. if (parent == null || parent.parent == null){
    24. return null;
    25. }
    26. if (parent.isLeftChild()){
    27. return parent.parent.right;
    28. }else {
    29. return parent.parent.left;
    30. }
    31. }
    32. //找兄弟
    33. Node sibling(){
    34. if (parent == null){return null;}
    35. if (this.isLeftChild()){
    36. return parent.right;
    37. }else {
    38. return parent.left;
    39. }
    40. }
    41. }
    42. //判断是不是红色
    43. boolean isRed(Node node){
    44. return node != null && node.color == Color.RED;
    45. }
    46. //判断是不是黑色
    47. boolean isBlack(Node node){
    48. return !isRed(node);
    49. }
    50. /**
    51. * 右旋
    52. * 要对parent进行处理
    53. * 选转后新根的父子关系
    54. * */
    55. private void rightRotate(Node node){
    56. Node nodeParent = node.parent;
    57. Node nodeLeft = node.left;
    58. Node nodeLeftRight = nodeLeft.right;
    59. if (nodeLeftRight != null){
    60. nodeLeftRight.parent = node;
    61. }
    62. nodeLeft.right = node;
    63. nodeLeft.parent = nodeParent;
    64. node.left = nodeLeftRight;
    65. node.parent = nodeLeft;
    66. if (nodeParent == null){
    67. root = nodeLeft;
    68. }else if (nodeParent.left == node){
    69. nodeParent.left = nodeLeft;
    70. }else {
    71. nodeParent.right = nodeLeft;
    72. }
    73. }
    74. //左旋
    75. private void leftRotate(Node node){
    76. Node nodeParent = node.parent;
    77. Node nodeRight = node.right;
    78. Node nodeRightLeft = nodeRight.left;
    79. if (nodeRightLeft != null){
    80. nodeRightLeft.parent = node;
    81. }
    82. nodeRight.left = node;
    83. nodeRight.parent = nodeParent;
    84. node.right = nodeRightLeft;
    85. node.parent = nodeRight;
    86. if (nodeParent == null){
    87. root = nodeRight;
    88. }else if (nodeParent.left == node){
    89. nodeParent.left = nodeRight;
    90. }else {
    91. nodeParent.right = nodeRight;
    92. }
    93. }
    94. /**新增或更新*/
    95. public void put(int key,Object value){
    96. Node p = root;
    97. Node parent = null;
    98. while (p != null){
    99. if (key < p.key){
    100. p = p.left;
    101. } else if(p.key < key){
    102. p = p.right;
    103. } else {
    104. p.value = value;
    105. return;
    106. }
    107. }
    108. Node inserted = new Node(key,value);
    109. if (parent == null){
    110. root = inserted;
    111. }else if (key < parent.key){
    112. parent.left = inserted;
    113. inserted.parent = parent;
    114. }else {
    115. parent.right = inserted;
    116. inserted.parent = parent;
    117. }
    118. fixRedRed(inserted);
    119. }
    120. /**节点调整*/
    121. private void fixRedRed(Node x){
    122. //插入节点是跟节点
    123. if (x == root){
    124. x.color = Color.BLACK;
    125. return;
    126. }
    127. //插入节点的父亲是黑色
    128. if (isBlack(x.parent)){
    129. return;
    130. }
    131. //红红相邻且叔叔为红
    132. Node parent = x.parent;
    133. Node uncle = x.isUncle();
    134. Node grandparent = parent.parent;
    135. if (isRed(uncle)){
    136. parent.color = Color.BLACK;
    137. uncle.color = Color.BLACK;
    138. grandparent.color = Color.RED;
    139. fixRedRed(grandparent);
    140. return;
    141. }
    142. //红红相邻且叔叔为黑
    143. if(parent.isLeftChild() && x.isLeftChild()){//LL
    144. parent.color = Color.BLACK;
    145. grandparent.color = Color.RED;
    146. rightRotate(grandparent);
    147. }else if (parent.isLeftChild() && !x.isLeftChild()){//LR
    148. leftRotate(parent);
    149. x.color = Color.BLACK;
    150. grandparent.color = Color.RED;
    151. rightRotate(grandparent);
    152. } else if (!parent.isLeftChild() && !x.isLeftChild()){//RR
    153. parent.color = Color.BLACK;
    154. grandparent.color = Color.RED;
    155. leftRotate(grandparent);
    156. }else {//RL
    157. rightRotate(parent);
    158. x.color = Color.BLACK;
    159. grandparent.color = Color.RED;
    160. leftRotate(grandparent);
    161. }
    162. }
    163. /**删除*/
    164. public void remove(int key){
    165. Node deleted = find(key);
    166. if (deleted == null){return;}
    167. doRemove(deleted);
    168. }
    169. private void doRemove(Node deleted){
    170. Node replaced = findReplaced(deleted);
    171. Node parent = deleted.parent;
    172. if (replaced == null){//没有孩子
    173. //删除的是根节点
    174. if (deleted == root){
    175. root = null;
    176. }else {
    177. if(isBlack(deleted)){
    178. //复杂调整
    179. fixDoubleBlack(deleted);
    180. }else {
    181. // 无需处理
    182. }
    183. if (deleted.isLeftChild()){
    184. parent.left = null;
    185. }else {
    186. parent.right = null;
    187. }
    188. deleted.parent = null;
    189. }
    190. return;
    191. }
    192. //有一个孩子
    193. if (deleted.left == null || deleted.right == null){
    194. //删除的是根节点
    195. if (deleted == null){
    196. root.key = replaced.key;
    197. root.value = replaced.value;
    198. root.left = root.right = null;
    199. }else {
    200. if (deleted.isLeftChild()){
    201. parent.left = replaced;
    202. }else {
    203. parent.right = replaced;
    204. }
    205. replaced.parent = parent;
    206. deleted.left = deleted.right = deleted.parent = null;//有助于垃圾回收
    207. if (isBlack(deleted) && isBlack(replaced)){
    208. //复杂处理
    209. fixDoubleBlack(replaced);
    210. }else {
    211. replaced.color = Color.BLACK;
    212. }
    213. }
    214. return;
    215. }
    216. //有两个孩子(是一种转换的操作)
    217. int t = deleted.key;
    218. deleted.key = replaced.key;
    219. replaced.key = t;
    220. Object v = deleted.value;
    221. deleted.value = replaced.value;
    222. replaced.value = v;
    223. doRemove(replaced);
    224. }
    225. /**遇到双黑的不平衡的复杂处理
    226. * x表示待调整的结点
    227. * */
    228. private void fixDoubleBlack(Node x){
    229. if (x == root){
    230. return;
    231. }
    232. Node parent = x.parent;
    233. Node sibling = x.sibling();
    234. //被调整节点的兄弟节点为红色
    235. if (isRed(sibling)){
    236. if (x.isLeftChild()){
    237. leftRotate(parent);
    238. }else {
    239. rightRotate(parent);
    240. }
    241. parent.color = Color.RED;
    242. sibling.color = Color.BLACK;
    243. fixDoubleBlack(x);
    244. return;
    245. }
    246. //兄弟是黑色且两个侄子都是黑色
    247. if (sibling != null){
    248. if (isBlack(sibling.left) && isBlack(sibling.right)){
    249. sibling.color = Color.RED;
    250. if (isRed(parent)){
    251. parent.color = Color.BLACK;
    252. }else {
    253. fixDoubleBlack(parent);
    254. }
    255. }
    256. //兄弟是黑色但是侄子有红色
    257. else {
    258. //LL
    259. if(sibling.isLeftChild() && isRed(sibling.left)){
    260. rightRotate(parent);
    261. sibling.left.color = Color.BLACK;
    262. sibling.color = parent.color;
    263. parent.color = Color.BLACK;
    264. }
    265. //LR
    266. else if (sibling.isLeftChild() && isRed(sibling.right)){
    267. sibling.right.color = parent.color;
    268. leftRotate(sibling);
    269. rightRotate(parent);
    270. parent.color = Color.BLACK;
    271. }
    272. //RL
    273. else if (!sibling.isLeftChild() && isRed(sibling.left)){
    274. sibling.left.color = parent.color;
    275. rightRotate(sibling);
    276. leftRotate(parent);
    277. parent.color = Color.BLACK;
    278. }
    279. //RR
    280. else {
    281. leftRotate(parent);
    282. sibling.right.color = Color.BLACK;
    283. sibling.color = parent.color;
    284. parent.color = Color.BLACK;
    285. }
    286. }
    287. }else {
    288. fixDoubleBlack(parent);
    289. }
    290. }
    291. /**找到要删除的结点*/
    292. private Node find(int key){
    293. Node p = root;
    294. while (p != null){
    295. if (key < p.key){
    296. p = p.left;
    297. }else if (key > p.key){
    298. p = p.right;
    299. }else {
    300. return p;
    301. }
    302. }
    303. return null;
    304. }
    305. /**查找剩余结点*/
    306. private Node findReplaced(Node deleted){
    307. if (deleted.left == null && deleted.right == null){
    308. return null;
    309. }else if (deleted.left == null){
    310. return deleted.right;
    311. }else if(deleted.right == null){
    312. return deleted.left;
    313. }
    314. Node s = deleted.right;
    315. while (s.left != null){
    316. s = s.left;
    317. }
    318. return s;
    319. }
    320. }

    3.总结

    有一说一,这红黑树确实比前面的几种树要难一点,主要是它的属性太多,限制太多。说实话,这篇文章中仅仅只是简单的介绍了一下红黑树,实现了一下红黑树的相关操作,但是红黑树的增加和删除中的一些操作没有细讲,这个有时间了我后面会单独再出一篇红黑树的补充文章然后细讲,这篇就这样了吧。

    最后说一点,对于这种类似于链式的结构(实际是树形结构),我们要掌握它的定义,条件,然后画图,然后对照图来进行相关的操作,然后再用代码去实现,这样好一点,而不是凭空想象着去写,那样是写不出来的。

  • 相关阅读:
    java基于springboot的插画漫画约稿网站 vue
    深度 | 车载语音群雄并起共争智能座舱新高地
    C++哈希
    【opencv】dnn示例-scene_text_detection.cpp 场景文本区域检测
    大厂超全安全测试--关于安全测试的分类及如何测试
    YoloV8改进策略:独家原创,LSKA(大可分离核注意力)改进YoloV8,比Transformer更有效,包括论文翻译和实验结果
    HC32单片机的TIMERA输出波形极性不对的问题
    函数的分文件编写
    实现微信扫码自动登陆与注册功能
    【无标题】
  • 原文地址:https://blog.csdn.net/m0_52096593/article/details/133472446