平衡查找树(Java)

2-3查找树
定义:
一颗2-3查找树要么是一颗空树,要么满足以下性质
1、2-结点,含有一个键和两条链接,左链接指向的键都小于该结点,右链接指向的键都大于该结点;
2、3-结点,含有两个键和三条链接,左链接指向的键都小于该结点,中链接指向的键都位于该结点的两个键之间,右链接指向的键都大于该结点。
7sE4A.png

插入:
1、 向2-结点中插入新键
7sCT9.png

7scSu.png

2、 向一颗只含有一个3-结点的树中插入新键
7sL6O.png

3、 向一个父结点为2-结点的3-结点中插入新键
7sU3q.png
7sKMe.png

4、 向一个父结点为3-结点的3-结点中插入新键
7sARR.png
7s1ed.png
7s60r.png

5、 分解根结点
7sIaY.png
7sNSi.png

将一个4-结点分解为一棵2-3树可能有6种情况
1、根结点
7slTy.png

2、父结点是2-结点
7s8JX.png

3、父结点是3-结点
7sS6l.png

通过以上示例的分析,我们对2-3树的变化方式有了一个认识,接下来,我们使用红黑二叉查找树这种数据结构来表示2-3树并实现相关操作。


红黑二叉查找树
在红黑树中,我们用红链接将两个2-结点连接起来构成一个3-结点,黑链接是2-3树中的普通链接。
7si5J.png

有关红黑树的性质:
1、红链接均为左链接
2、没有任何一个结点同时和两条红链接相连
3、该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同。

以上为性质,所以当我们在对红黑树进行操作的过程中可能出现红色右链接或者两条连续的红链接,在这种情况下,我们就需要对该树进行旋转以满足红黑树的性质。
1、左旋转h的右链接
7sxOB.png

2、右旋转h的左链接(同理)
3、向单个2-结点中插入新键
4、向树底部的2-结点插入新键
5、向一棵双键树(即一个3-结点)中插入新键
7s7gp.png
7s2e6.png
7sgQ3.png

在了解各种旋转的情况之后,再来了解一个旋转的应用,那就是向树底部的3-结点插入一个新键。相信通过这个例子能够非常清晰地了解红黑树的旋转。
7snaK.png

为保证插入操作后红黑树和2-3树的一一对应关系,我们需要保持以下规则:
1、如果右子结点是红色的而左子结点是黑色的,进行左旋转。
2、如果左子结点是红色的且它的左子结点也是红色的,进行右旋转。
3、如果左右子结点均为红色,进行颜色转换。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
public class RedBlackBST<Key extends Comparable<Key>, Value> {

private static final boolean RED = true;
private static final boolean BLACK = false;

private Node root;

private class Node {
private Key key;
private Value val;
private Node left, right;
private boolean color;
private int size;

public Node(Key key, Value val, boolean color, int size) {
this.key = key;
this.val = val;
this.color = color;
this.size = size;
}
}

public RedBlackBST() {
}

private boolean isRed(Node x) {
if (x == null) return false;
return x.color == RED;
}

private int size(Node x) {
if (x == null) return 0;
return x.size;
}

public int size() {
return size(root);
}

public boolean isEmpty() {
return root == null;
}

public Value get(Key key) {
if (key == null) throw new IllegalArgumentException("argument to get() is null");
return get(root, key);
}

private Value get(Node x, Key key) {
while (x != null) {
int cmp = key.compareTo(x.key);
if (cmp < 0) x = x.left;
else if (cmp > 0) x = x.right;
else return x.val;
}
return null;
}

public boolean contains(Key key) {
return get(key) != null;
}

public void put(Key key, Value val) {
if (key == null) throw new IllegalArgumentException("first argument to put() is null");
if (val == null) {
delete(key);
return;
}

root = put(root, key, val);
root.color = BLACK;
}

private Node put(Node h, Key key, Value val) {
if (h == null) return new Node(key, val, RED, 1);

int cmp = key.compareTo(h.key);
if (cmp < 0) h.left = put(h.left, key, val);
else if (cmp > 0) h.right = put(h.right, key, val);
else h.val = val;

if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);
if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
if (isRed(h.left) && isRed(h.right)) flipColors(h);
h.size = size(h.left) + size(h.right) + 1;

return h;
}

public void deleteMin() {
if (isEmpty()) throw new NoSuchElementException("BST underflow");
if (!isRed(root.left) && !isRed(root.right))
root.color = RED;
root = deleteMin(root);
if (!isEmpty()) root.color = BLACK;
}

private Node deleteMin(Node h) {
if (h.left == null)
return null;
if (!isRed(h.left) && !isRed(h.left.left))
h = moveRedLeft(h);
h.left = deleteMin(h.left);
return balance(h);
}

public void deleteMax() {
if (isEmpty()) throw new NoSuchElementException("BST underflow");
if (!isRed(root.left) && !isRed(root.right))
root.color = RED;
root = deleteMax(root);
if (!isEmpty()) root.color = BLACK;
}

private Node deleteMax(Node h) {
if (isRed(h.left))
h = rotateRight(h);
if (h.right == null)
return null;
if (!isRed(h.right) && !isRed(h.right.left))
h = moveRedRight(h);
h.right = deleteMax(h.right);
return balance(h);
}

public void delete(Key key) {
if (key == null) throw new IllegalArgumentException("argument to delete() is null");
if (!contains(key)) return;
if (!isRed(root.left) && !isRed(root.right))
root.color = RED;

root = delete(root, key);
if (!isEmpty()) root.color = BLACK;
}

private Node delete(Node h, Key key) {
if (key.compareTo(h.key) < 0) {
if (!isRed(h.left) && !isRed(h.left.left))
h = moveRedLeft(h);
h.left = delete(h.left, key);
}
else {
if (isRed(h.left))
h = rotateRight(h);
if (key.compareTo(h.key) == 0 && (h.right == null))
return null;
if (!isRed(h.right) && !isRed(h.right.left))
h = moveRedRight(h);
if (key.compareTo(h.key) == 0) {
Node x = min(h.right);
h.key = x.key;
h.val = x.val;
h.right = deleteMin(h.right);
}
else h.right = delete(h.right, key);
}
return balance(h);
}

private Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = x.right.color;
x.right.color = RED;
x.size = h.size;
h.size = size(h.left) + size(h.right) + 1;
return x;
}

private Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = x.left.color;
x.left.color = RED;
x.size = h.size;
h.size = size(h.left) + size(h.right) + 1;
return x;
}

private void flipColors(Node h) {
h.color = !h.color;
h.left.color = !h.left.color;
h.right.color = !h.right.color;
}

private Node moveRedLeft(Node h) {
flipColors(h);
if (isRed(h.right.left)) {
h.right = rotateRight(h.right);
h = rotateLeft(h);
flipColors(h);
}
return h;
}

private Node moveRedRight(Node h) {
flipColors(h);
if (isRed(h.left.left)) {
h = rotateRight(h);
flipColors(h);
}
return h;
}

private Node balance(Node h) {
if (isRed(h.right)) h = rotateLeft(h);
if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
if (isRed(h.left) && isRed(h.right)) flipColors(h);

h.size = size(h.left) + size(h.right) + 1;
return h;
}

public int height() {
return height(root);
}
private int height(Node x) {
if (x == null) return -1;
return 1 + Math.max(height(x.left), height(x.right));
}

public Key min() {
if (isEmpty()) throw new NoSuchElementException("calls min() with empty symbol table");
return min(root).key;
}

private Node min(Node x) {
if (x.left == null) return x;
else return min(x.left);
}

public Key max() {
if (isEmpty()) throw new NoSuchElementException("calls max() with empty symbol table");
return max(root).key;
}

private Node max(Node x) {
if (x.right == null) return x;
else return max(x.right);
}

public Key floor(Key key) {
if (key == null) throw new IllegalArgumentException("argument to floor() is null");
if (isEmpty()) throw new NoSuchElementException("calls floor() with empty symbol table");
Node x = floor(root, key);
if (x == null) return null;
else return x.key;
}

private Node floor(Node x, Key key) {
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp == 0) return x;
if (cmp < 0) return floor(x.left, key);
Node t = floor(x.right, key);
if (t != null) return t;
else return x;
}

public Key ceiling(Key key) {
if (key == null) throw new IllegalArgumentException("argument to ceiling() is null");
if (isEmpty()) throw new NoSuchElementException("calls ceiling() with empty symbol table");
Node x = ceiling(root, key);
if (x == null) return null;
else return x.key;
}

private Node ceiling(Node x, Key key) {
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp == 0) return x;
if (cmp > 0) return ceiling(x.right, key);
Node t = ceiling(x.left, key);
if (t != null) return t;
else return x;
}

public Key select(int k) {
if (k < 0 || k >= size()) {
throw new IllegalArgumentException("argument to select() is invalid: " + k);
}
Node x = select(root, k);
return x.key;
}

private Node select(Node x, int k) {
int t = size(x.left);
if (t > k) return select(x.left, k);
else if (t < k) return select(x.right, k-t-1);
else return x;
}

public int rank(Key key) {
if (key == null) throw new IllegalArgumentException("argument to rank() is null");
return rank(key, root);
}

private int rank(Key key, Node x) {
if (x == null) return 0;
int cmp = key.compareTo(x.key);
if (cmp < 0) return rank(key, x.left);
else if (cmp > 0) return 1 + size(x.left) + rank(key, x.right);
else return size(x.left);
}

public Iterable<Key> keys() {
if (isEmpty()) return new Queue<Key>();
return keys(min(), max());
}

public Iterable<Key> keys(Key lo, Key hi) {
if (lo == null) throw new IllegalArgumentException("first argument to keys() is null");
if (hi == null) throw new IllegalArgumentException("second argument to keys() is null");

Queue<Key> queue = new Queue<Key>();
keys(root, queue, lo, hi);
return queue;
}

private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
if (x == null) return;
int cmplo = lo.compareTo(x.key);
int cmphi = hi.compareTo(x.key);
if (cmplo < 0) keys(x.left, queue, lo, hi);
if (cmplo <= 0 && cmphi >= 0) queue.enqueue(x.key);
if (cmphi > 0) keys(x.right, queue, lo, hi);
}

public int size(Key lo, Key hi) {
if (lo == null) throw new IllegalArgumentException("first argument to size() is null");
if (hi == null) throw new IllegalArgumentException("second argument to size() is null");

if (lo.compareTo(hi) > 0) return 0;
if (contains(hi)) return rank(hi) - rank(lo) + 1;
else return rank(hi) - rank(lo);
}

private boolean check() {
if (!isBST()) StdOut.println("Not in symmetric order");
if (!isSizeConsistent()) StdOut.println("Subtree counts not consistent");
if (!isRankConsistent()) StdOut.println("Ranks not consistent");
if (!is23()) StdOut.println("Not a 2-3 tree");
if (!isBalanced()) StdOut.println("Not balanced");
return isBST() && isSizeConsistent() && isRankConsistent() && is23() && isBalanced();
}

private boolean isBST() {
return isBST(root, null, null);
}

private boolean isBST(Node x, Key min, Key max) {
if (x == null) return true;
if (min != null && x.key.compareTo(min) <= 0) return false;
if (max != null && x.key.compareTo(max) >= 0) return false;
return isBST(x.left, min, x.key) && isBST(x.right, x.key, max);
}

private boolean isSizeConsistent() { return isSizeConsistent(root); }
private boolean isSizeConsistent(Node x) {
if (x == null) return true;
if (x.size != size(x.left) + size(x.right) + 1) return false;
return isSizeConsistent(x.left) && isSizeConsistent(x.right);
}

private boolean isRankConsistent() {
for (int i = 0; i < size(); i++)
if (i != rank(select(i))) return false;
for (Key key : keys())
if (key.compareTo(select(rank(key))) != 0) return false;
return true;
}

private boolean is23() { return is23(root); }
private boolean is23(Node x) {
if (x == null) return true;
if (isRed(x.right)) return false;
if (x != root && isRed(x) && isRed(x.left))
return false;
return is23(x.left) && is23(x.right);
}

private boolean isBalanced() {
int black = 0;
Node x = root;
while (x != null) {
if (!isRed(x)) black++;
x = x.left;
}
return isBalanced(root, black);
}

private boolean isBalanced(Node x, int black) {
if (x == null) return black == 0;
if (!isRed(x)) black--;
return isBalanced(x.left, black) && isBalanced(x.right, black);
}

public static void main(String[] args) {
RedBlackBST<String, Integer> st = new RedBlackBST<String, Integer>();
for (int i = 0; !StdIn.isEmpty(); i++) {
String key = StdIn.readString();
st.put(key, i);
}
for (String s : st.keys())
StdOut.println(s + " " + st.get(s));
StdOut.println();
}
}

这其中的StdOut以及StdIn库使用自algs4这个jar包,下载地址:
https://algs4.cs.princeton.edu/code/algs4.jar


分析:
在对红黑树进行分析之前重新再次强调下红黑树的性质:
1、结点或者是黑色,或者是红色。
2、根结点是黑色。
3、每个叶子结点(NIL)是黑色。
4、如果一个结点是红色的,则它的子结点必须是黑色的。
5、从一个结点到该结点的子孙结点的所有路径上包含相同数目的黑结点。

主要需要分析的地方是红黑树的删除问题,关于这个问题,我们开始可以将红黑树直接当作2-3树完成删除操作,然后再去按照红黑树的要求将可能不符合要求的树“红黑化”。