資料結構與演算法(十三)——紅黑樹1

L發表於2022-03-19

一、概述

1、介紹

  紅黑樹是一種自平衡的排序二叉樹,常用於關聯陣列、字典,在各種語言的底層實現中被廣泛應用,Java 的 TreeMap 和 TreeSet 就是基於紅黑樹實現的,在 JDK 8 的HashMap中也有應用。
  紅黑樹是在排序二叉樹的基礎上定義的,且還要滿足以下性質(重要):(請務必先學習排序二叉樹,排序二叉樹先看這篇 二叉樹
  (1)每個結點要麼是黑色,要麼是紅色。
  (2)根結點是黑色。
  (3)所有葉子結點都是黑色(這裡說的葉子結點指 NIL 結點,空結點,即:空結點為黑色)。
  (4)從任意一個結點到其所有葉子結點,所經過的黑色結點數目必須相等。
  (5)所有紅色結點的兩個孩子結點必須是黑色(即,紅色結點不能連續)。
  程式碼示例:紅黑樹-樹結點結構

1 protected static class RBNode<T extends Comparable<T>> {
2     private T value;
3     // 預設為 紅色 結點
4     private boolean red = true;
5 
6     private RBNode<T> left;
7     private RBNode<T> right;
8     private RBNode<T> parent;
9 }

  樹結點關係

2、左旋(重要)

  動圖:

  程式碼示例:對 node 左旋

 1 // 左旋
 2 private void leftRotate(RBNode<T> node) {
 3     if (node == null) {
 4         return;
 5     }
 6     final RBNode<T> p = node.parent;
 7 
 8     // 左旋. 應該認為 temp 不為null
 9     final RBNode<T> temp = node.right;
10     node.right = temp.left;
11     if (temp.left != null) {
12         // 該結點可能不存在
13         temp.left.parent = node;
14     }
15 
16     temp.left = node;
17     node.parent = temp;
18 
19     // 旋轉完成.修正根結點與父結點
20     // 1.node為根結點
21     if (node == root) {
22         root = temp;
23         temp.parent = null;
24         return;
25     }
26 
27     // 2.node不為根結點
28     // node 為父結點的右孩子
29     if (node == p.right) {
30         p.right = temp;
31     } else {
32         p.left = temp;
33     }
34     temp.parent = p;
35 }

3、右旋(重要)

  動圖:

  程式碼示例:對 node 右旋

 1 // 右旋
 2 private void rightRotate(RBNode<T> node) {
 3     if (node == null) {
 4         return;
 5     }
 6 
 7     final RBNode<T> p = node.parent;
 8 
 9     // 右旋. 應該認為 temp 不為null
10     final RBNode<T> temp = node.left;
11     node.left = temp.right;
12     if (temp.right != null) {
13         // 該結點可能不存在
14         temp.right.parent = node;
15     }
16 
17     temp.right = node;
18     node.parent = temp;
19 
20     // 旋轉完成.修正根結點與父結點
21     // 1.node為根結點
22     if (node == root) {
23         root = temp;
24         temp.parent = null;
25         return;
26     }
27 
28     // 2.node不為根結點
29     // node 為父結點的右孩子
30     if (node == p.right) {
31         p.right = temp;
32     } else {
33         p.left = temp;
34     }
35     temp.parent = p;
36 }

二、插入

  由於樹的左子樹和右子樹是對稱的,所以只討論一邊的情況即可。插入的原則滿足排序二叉樹的規則。而紅黑樹的插入還要滿足紅黑樹的性質,所以插入完成後,還要對紅黑樹進行調整。調整的原則(重要):
  (1)按排序二叉樹的插入規則插入新結點(newNode)。
  (2)新插入的結點預設為紅色。
  (3)若 newNode 為根結點,則變為黑色,插入完畢。
  (4)若 newNode 不為根結點,若其父結點為黑色,插入完畢。
  (5)若 newNode 不為根結點,若其父結點為紅色,則按下面的情況討論(下面也主要討論這種情況)。
  以 {7, 3, 10, 1(5)} 為例,新增 {7, 3, 10} 的結果很容易理解。

  插入1(5)時,
  情況一:newNode(1或5) 的叔叔結點(10)存在且為紅色。
  調整方案:父結點(3)與叔叔結點(10)變為黑色;祖父結點變為紅色;新增結點 newNode 指向祖父結點(7),做遞迴的調整(這裡newNode == root,則變為黑色即可)。

  情況二:newNode(1或5) 的叔叔結點(10)不存在,或者為黑色。
  調整方案:分為左左、左右、右右、右左討論。(下面的討論中,不妨將叔叔結點畫為黑色)

1、左左

  左左:newNode == 祖父結點的左孩子的左孩子。
  調整方案:先對祖父結點(7)右旋;父結點變為黑色,祖父結點變為紅色。

2、左右

  左右:newNode == 祖父結點的左孩子的右孩子。
  調整方案:先對父結點(3)左旋;後續調整同"左左"的方案。(需注意newNode的位置不同)

3、右右(與左左對稱)

  右右:newNode == 祖父結點的右孩子的右孩子。
  調整方案:先對祖父結點(7)左旋;父結點變為黑色,祖父結點變為紅色。

4、右左

  右左:newNode == 祖父結點的右孩子的左孩子。
  調整方案:先對父結點(10)右旋;後續調整同"右右"的方案。(需注意newNode的位置不同)

5、總結

  程式碼示例:完整的紅黑樹插入及調整

資料結構與演算法(十三)——紅黑樹1
  1 public class RBTree<T extends Comparable<T>> {
  2     // 根結點
  3     private RBNode<T> root;
  4 
  5     public RBTree() {
  6     }
  7 
  8     public RBTree(T[] arr) {
  9         if (arr == null || arr.length == 0) {
 10             return;
 11         }
 12 
 13         for (T i : arr) {
 14             this.add(i);
 15         }
 16     }
 17 
 18     // 新增結點
 19     public void add(T value) {
 20         RBNode<T> newNode = new RBNode<>(value);
 21         if (root == null) {
 22             root = newNode;
 23             newNode.red = false;
 24             return;
 25         }
 26 
 27         // 1.新增
 28         this.add(root, newNode);
 29 
 30         // 2.調整
 31         this.fixUp(newNode);
 32     }
 33 
 34     private void fixUp(RBNode<T> newNode) {
 35         if (newNode == root) {
 36             newNode.red = false;
 37             return;
 38         }
 39 
 40         // newNode 的父結點為黑色
 41         if (!newNode.parent.red) {
 42             return;
 43         }
 44 
 45         /* 1.newNode 的叔叔結點存在且為紅色。*/
 46         final RBNode<T> uncle = newNode.getUncle();
 47         if (uncle != null && uncle.red) {
 48             newNode.parent.red = false;
 49             uncle.red = false;
 50 
 51             newNode.parent.parent.red = true;
 52             this.fixUp(newNode.parent.parent);
 53         } else {
 54             /* 2.newNode 的叔叔結點不存在,或者為黑色。*/
 55             final RBNode<T> grandFather = newNode.getGrandFather();
 56             // 2.1左左
 57             if (newNode == grandFather.left.left) {
 58                 this.rightRotate(grandFather);
 59                 newNode.parent.red = false;
 60                 grandFather.red = true;
 61             }
 62             // 2.2左右
 63             else if (newNode == grandFather.left.right) {
 64                 this.leftRotate(newNode.parent);
 65                 this.rightRotate(grandFather);
 66                 newNode.red = false;
 67                 grandFather.red = true;
 68             }
 69             // 2.3右右
 70             else if (newNode == grandFather.right.right) {
 71                 this.leftRotate(grandFather);
 72                 newNode.parent.red = false;
 73                 grandFather.red = true;
 74             }
 75             // 2.4右左
 76             else if (newNode == grandFather.right.left) {
 77                 this.rightRotate(newNode.parent);
 78                 this.leftRotate(grandFather);
 79                 newNode.red = false;
 80                 grandFather.red = true;
 81             }
 82         }
 83     }
 84 
 85     // 按 排序二叉樹 的規則插入結點
 86     private void add(RBNode<T> root, RBNode<T> newNode) {
 87         if (newNode.value.compareTo(root.value) <= 0) {
 88             if (root.left == null) {
 89                 root.left = newNode;
 90                 newNode.parent = root;
 91             } else {
 92                 this.add(root.left, newNode);
 93             }
 94         } else {
 95             if (root.right == null) {
 96                 root.right = newNode;
 97                 newNode.parent = root;
 98             } else {
 99                 this.add(root.right, newNode);
100             }
101         }
102     }
103 
104     // 左旋
105     private void leftRotate(RBNode<T> node) {
106         if (node == null) {
107             return;
108         }
109         final RBNode<T> p = node.parent;
110 
111         // 左旋. 應該認為 temp 不為null
112         final RBNode<T> temp = node.right;
113         node.right = temp.left;
114         if (temp.left != null) {
115             // 該結點可能不存在
116             temp.left.parent = node;
117         }
118 
119         temp.left = node;
120         node.parent = temp;
121 
122         // 旋轉完成.修正根結點與父結點
123         // 1.node為根結點
124         if (node == root) {
125             root = temp;
126             temp.parent = null;
127             return;
128         }
129 
130         // 2.node不為根結點
131         // node 為父結點的右孩子
132         if (node == p.right) {
133             p.right = temp;
134         } else {
135             p.left = temp;
136         }
137         temp.parent = p;
138     }
139 
140     // 右旋
141     private void rightRotate(RBNode<T> node) {
142         if (node == null) {
143             return;
144         }
145 
146         final RBNode<T> p = node.parent;
147 
148         // 右旋. 應該認為 temp 不為null
149         final RBNode<T> temp = node.left;
150         node.left = temp.right;
151         if (temp.right != null) {
152             // 該結點可能不存在
153             temp.right.parent = node;
154         }
155 
156         temp.right = node;
157         node.parent = temp;
158 
159         // 旋轉完成.修正根結點與父結點
160         // 1.node為根結點
161         if (node == root) {
162             root = temp;
163             temp.parent = null;
164             return;
165         }
166 
167         // 2.node不為根結點
168         // node 為父結點的右孩子
169         if (node == p.right) {
170             p.right = temp;
171         } else {
172             p.left = temp;
173         }
174         temp.parent = p;
175     }
176 
177     // 中序遍歷
178     public void infixOrder() {
179         this.infixOrder(root);
180     }
181 
182     private void infixOrder(RBNode<T> root) {
183         if (root != null) {
184             this.infixOrder(root.left);
185             System.out.print("-->" + root.value + ":" + (root.red ? "紅" : "黑"));
186             this.infixOrder(root.right);
187         }
188     }
189 
190     /**
191      * 紅黑樹 樹結點結構
192      */
193     protected static class RBNode<T extends Comparable<T>> {
194         private T value;
195         // 預設為 紅色 結點
196         private boolean red = true;
197 
198         private RBNode<T> left;
199         private RBNode<T> right;
200         private RBNode<T> parent;
201 
202         public RBNode() {
203         }
204 
205         public RBNode(T value) {
206             this.value = value;
207         }
208 
209         // 返回結點的度
210         public int getDegree() {
211             if (this.left == null && this.right == null) {
212                 return 0;
213             }
214 
215             if ((this.left != null && this.right == null) || (this.left == null && this.right != null)) {
216                 return 1;
217             }
218 
219             return 2;
220         }
221 
222         public RBNode<T> getUncle() {
223             final RBNode<T> grandFather = this.parent.parent;
224             final RBNode<T> parent = this.parent;
225 
226             if (parent == grandFather.left) {
227                 return grandFather.right;
228             }
229 
230             if (parent == grandFather.right) {
231                 return grandFather.left;
232             }
233 
234             return null;
235         }
236 
237         public RBNode<T> getGrandFather() {
238             return this.parent.parent;
239         }
240 
241         @Override
242         public String toString() {
243             return "RBNode{" +
244                     "value=" + value +
245                     ", red=" + red +
246                     '}';
247         }
248     }
249 }
完整的紅黑樹插入程式碼

  程式碼示例:測試

 1 public class Main {
 2     public static void main(String[] args) {
 3         Integer[] integers = {15, 7, 45, 3, 10, 25, 55, 1, 5, 75};
 4         RBTree<Integer> tree = new RBTree<>(integers);
 5 
 6         tree.infixOrder();
 7     }
 8 }
 9 
10 // 結果
11 -->1:紅-->3:黑-->5:紅-->7:紅-->10:黑-->15:黑-->25:黑-->45:紅-->55:黑-->75:紅

  最後,推薦一個線上構建紅黑樹的地址:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html  用於讀者驗證上述程式碼的結果。上述測試案例構建的紅黑樹為:

三、刪除

  見下一篇。

相關文章