binary_search_tree 2.0 → 2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +32 -0
- data/README +76 -0
- data/Rakefile +1 -0
- data/binary_search_tree.gemspec +20 -0
- data/lib/binary_search_tree.rb +4 -522
- data/lib/binary_search_tree/binary_node.rb +42 -0
- data/lib/binary_search_tree/binary_search_tree.rb +493 -0
- data/lib/{binary_search_tree_hash.rb → binary_search_tree/binary_search_tree_hash.rb} +7 -7
- data/lib/binary_search_tree/version.rb +3 -0
- data/spec/binary_node_spec.rb +84 -0
- data/spec/binary_search_tree_spec.rb +107 -0
- metadata +31 -5
@@ -0,0 +1,42 @@
|
|
1
|
+
class BinaryNode
|
2
|
+
attr_accessor :height, :parent, :left, :right, :key, :value
|
3
|
+
|
4
|
+
def initialize key, value, parent
|
5
|
+
@key = key
|
6
|
+
@value = value
|
7
|
+
@parent = parent
|
8
|
+
@height = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def is_leaf?
|
12
|
+
height.zero?
|
13
|
+
end
|
14
|
+
|
15
|
+
def max_children_height
|
16
|
+
if left && right
|
17
|
+
[left.height, right.height].max
|
18
|
+
elsif left
|
19
|
+
left.height
|
20
|
+
elsif right
|
21
|
+
right.height
|
22
|
+
else
|
23
|
+
-1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def balance_factor
|
28
|
+
left_height = if left
|
29
|
+
left.height
|
30
|
+
else
|
31
|
+
-1
|
32
|
+
end
|
33
|
+
|
34
|
+
right_height = if right
|
35
|
+
right.height
|
36
|
+
else
|
37
|
+
-1
|
38
|
+
end
|
39
|
+
|
40
|
+
left_height - right_height
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,493 @@
|
|
1
|
+
class BinarySearchTree
|
2
|
+
attr_reader :size, :root
|
3
|
+
|
4
|
+
def balanced?
|
5
|
+
if -1 == compute_and_check_height(@root)
|
6
|
+
false
|
7
|
+
else
|
8
|
+
true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize logger=nil
|
13
|
+
@logger = logger
|
14
|
+
clear
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear
|
18
|
+
@root = nil
|
19
|
+
@size = 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def empty?
|
23
|
+
@root.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def find key
|
27
|
+
@num_comparisons = 0
|
28
|
+
node = locate key, @root
|
29
|
+
@logger.debug "find operation completed in #{@num_comparisons} lookups..." if @logger
|
30
|
+
node
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_value value
|
34
|
+
find_value_ex @root, value
|
35
|
+
end
|
36
|
+
|
37
|
+
def min
|
38
|
+
@min ||= locate_min @root
|
39
|
+
end
|
40
|
+
|
41
|
+
def max
|
42
|
+
@max ||= locate_max @root
|
43
|
+
end
|
44
|
+
|
45
|
+
def insert element, value
|
46
|
+
put element, value, @root, nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def remove node_or_key
|
50
|
+
delete node_or_key
|
51
|
+
end
|
52
|
+
|
53
|
+
def remove_min
|
54
|
+
delete min
|
55
|
+
end
|
56
|
+
|
57
|
+
def nodes
|
58
|
+
@nodes = []
|
59
|
+
serialize_nodes @root
|
60
|
+
@nodes
|
61
|
+
end
|
62
|
+
|
63
|
+
def == other_bst
|
64
|
+
compare @root, other_bst.root
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def invalidate_cached_values
|
69
|
+
@min = @max = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def locate target, leaf
|
73
|
+
@num_comparisons += 1
|
74
|
+
if leaf.nil?
|
75
|
+
return nil
|
76
|
+
elsif leaf.key < target
|
77
|
+
return locate target, leaf.right
|
78
|
+
elsif leaf.key > target
|
79
|
+
return locate target, leaf.left
|
80
|
+
elsif leaf.key == target
|
81
|
+
return leaf
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def locate_min leaf
|
86
|
+
return nil if leaf.nil?
|
87
|
+
return leaf if leaf.left.nil?
|
88
|
+
return locate_min leaf.left
|
89
|
+
end
|
90
|
+
|
91
|
+
def locate_max leaf
|
92
|
+
return nil if leaf.nil?
|
93
|
+
return leaf if leaf.right.nil?
|
94
|
+
return locate_max leaf.right
|
95
|
+
end
|
96
|
+
|
97
|
+
def recompute_heights start_from_node
|
98
|
+
changed = true
|
99
|
+
node = start_from_node
|
100
|
+
while node && changed
|
101
|
+
old_height = node.height
|
102
|
+
if node.right || node.left
|
103
|
+
node.height = node.max_children_height + 1
|
104
|
+
else
|
105
|
+
node.height = 0
|
106
|
+
end
|
107
|
+
changed = node.height != old_height
|
108
|
+
node = node.parent
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def put element, value, leaf, parent, link_type=nil
|
113
|
+
#once you reach a point where you can place a new node
|
114
|
+
if leaf.nil?
|
115
|
+
#create that new node
|
116
|
+
leaf = BinaryNode.new element, value, parent
|
117
|
+
@size += 1
|
118
|
+
invalidate_cached_values
|
119
|
+
if parent
|
120
|
+
if 'left' == link_type
|
121
|
+
parent.left = leaf
|
122
|
+
else
|
123
|
+
parent.right = leaf
|
124
|
+
end
|
125
|
+
else
|
126
|
+
@root = leaf
|
127
|
+
end
|
128
|
+
if parent && parent.height.zero?
|
129
|
+
#if it has a parent but it is balanced, move up
|
130
|
+
node = parent
|
131
|
+
node_to_rebalance = nil
|
132
|
+
|
133
|
+
#continue moving up until you git the root
|
134
|
+
while node
|
135
|
+
node.height = node.max_children_height + 1
|
136
|
+
if node.balance_factor.abs > 1
|
137
|
+
node_to_rebalance = node
|
138
|
+
break
|
139
|
+
end
|
140
|
+
node = node.parent
|
141
|
+
end
|
142
|
+
#if at any point you reach an unbalanced node, rebalance it
|
143
|
+
rebalance node_to_rebalance if node_to_rebalance
|
144
|
+
end
|
145
|
+
|
146
|
+
elsif leaf.key < element
|
147
|
+
put element, value, leaf.right, leaf, "right"
|
148
|
+
elsif leaf.key > element
|
149
|
+
put element, value, leaf.left, leaf, "left"
|
150
|
+
elsif leaf.key == element
|
151
|
+
leaf.value = value
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def find_value_ex leaf, value
|
156
|
+
if leaf
|
157
|
+
node_with_value = find_value_ex leaf.left, value
|
158
|
+
return node_with_value if node_with_value
|
159
|
+
return leaf if leaf.value == value
|
160
|
+
node_with_value = find_value_ex leaf.right, value
|
161
|
+
return node_with_value if node_with_value
|
162
|
+
end
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
|
166
|
+
def serialize_nodes leaf
|
167
|
+
unless leaf.nil?
|
168
|
+
serialize_nodes leaf.left
|
169
|
+
@nodes << leaf
|
170
|
+
serialize_nodes leaf.right
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def compare leaf, other_bst_leaf
|
175
|
+
if leaf && other_bst_leaf
|
176
|
+
leaf.value == other_bst_leaf.value &&
|
177
|
+
compare(leaf.left, other_bst_leaf.left) &&
|
178
|
+
compare(leaf.right, other_bst_leaf.right)
|
179
|
+
else
|
180
|
+
leaf.nil? && other_bst_leaf.nil?
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def assert condition
|
185
|
+
raise "assertion failed" unless condition
|
186
|
+
end
|
187
|
+
|
188
|
+
def rrc_rebalance a, f
|
189
|
+
b = a.right
|
190
|
+
c = b.right
|
191
|
+
assert a && b && c
|
192
|
+
a.right = b.left
|
193
|
+
if a.right
|
194
|
+
a.right.parent = a
|
195
|
+
end
|
196
|
+
b.left = a
|
197
|
+
a.parent = b
|
198
|
+
if f.nil?
|
199
|
+
@root = b
|
200
|
+
@root.parent = nil
|
201
|
+
else
|
202
|
+
if f.right == a
|
203
|
+
f.right = b
|
204
|
+
else
|
205
|
+
f.left = b
|
206
|
+
end
|
207
|
+
b.parent = f
|
208
|
+
end
|
209
|
+
recompute_heights a
|
210
|
+
recompute_heights b.parent
|
211
|
+
end
|
212
|
+
|
213
|
+
def rlc_rebalance a, f
|
214
|
+
b = a.right
|
215
|
+
c = b.left
|
216
|
+
assert a && b && c
|
217
|
+
b.left = c.right
|
218
|
+
if b.left
|
219
|
+
b.left.parent = b
|
220
|
+
end
|
221
|
+
a.right = c.left
|
222
|
+
if a.right
|
223
|
+
a.right.parent = a
|
224
|
+
end
|
225
|
+
c.right = b
|
226
|
+
b.parent = c
|
227
|
+
c.left = a
|
228
|
+
a.parent = c
|
229
|
+
if f.nil?
|
230
|
+
@root = c
|
231
|
+
@root.parent = nil
|
232
|
+
else
|
233
|
+
if f.right == a
|
234
|
+
f.right = c
|
235
|
+
else
|
236
|
+
f.left = c
|
237
|
+
end
|
238
|
+
c.parent = f
|
239
|
+
end
|
240
|
+
recompute_heights a
|
241
|
+
recompute_heights b
|
242
|
+
end
|
243
|
+
|
244
|
+
def llc_rebalance a, b, c, f
|
245
|
+
assert a && b && c
|
246
|
+
a.left = b.right
|
247
|
+
if a.left
|
248
|
+
a.left.parent = a
|
249
|
+
end
|
250
|
+
b.right = a
|
251
|
+
a.parent = b
|
252
|
+
if f.nil?
|
253
|
+
@root = b
|
254
|
+
@root.parent = nil
|
255
|
+
else
|
256
|
+
if f.right == a
|
257
|
+
f.right = b
|
258
|
+
else
|
259
|
+
f.left = b
|
260
|
+
end
|
261
|
+
b.parent = f
|
262
|
+
end
|
263
|
+
recompute_heights a
|
264
|
+
recompute_heights b.parent
|
265
|
+
end
|
266
|
+
|
267
|
+
def lrc_rebalance a, b, c, f
|
268
|
+
assert a && b && c
|
269
|
+
a.left = c.right
|
270
|
+
if a.left
|
271
|
+
a.left.parent = a
|
272
|
+
end
|
273
|
+
b.right = c.left
|
274
|
+
if b.right
|
275
|
+
b.right.parent = b
|
276
|
+
end
|
277
|
+
c.left = b
|
278
|
+
b.parent = c
|
279
|
+
c.right = a
|
280
|
+
a.parent = c
|
281
|
+
if f.nil?
|
282
|
+
@root = c
|
283
|
+
@root.parent = nil
|
284
|
+
else
|
285
|
+
if f.right == a
|
286
|
+
f.right = c
|
287
|
+
else
|
288
|
+
f.left = c
|
289
|
+
end
|
290
|
+
c.parent = f
|
291
|
+
end
|
292
|
+
recompute_heights a
|
293
|
+
recompute_heights b
|
294
|
+
end
|
295
|
+
|
296
|
+
def rebalance node_to_rebalance
|
297
|
+
a = node_to_rebalance
|
298
|
+
f = a.parent #allowed to be NULL
|
299
|
+
if node_to_rebalance.balance_factor == -2
|
300
|
+
if node_to_rebalance.right.balance_factor <= 0
|
301
|
+
# """Rebalance, case RRC """
|
302
|
+
rrc_rebalance a, f
|
303
|
+
else
|
304
|
+
rlc_rebalance a, f
|
305
|
+
# """Rebalance, case RLC """
|
306
|
+
end
|
307
|
+
else
|
308
|
+
assert node_to_rebalance.balance_factor == 2
|
309
|
+
if node_to_rebalance.left.balance_factor >= 0
|
310
|
+
b = a.left
|
311
|
+
c = b.left
|
312
|
+
# """Rebalance, case LLC """
|
313
|
+
llc_rebalance a, b, c, f
|
314
|
+
else
|
315
|
+
b = a.left
|
316
|
+
c = b.right
|
317
|
+
# """Rebalance, case LRC """
|
318
|
+
lrc_rebalance a, b, c, f
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def delete node_or_key
|
324
|
+
if BinaryNode == node_or_key.class
|
325
|
+
node = node_or_key
|
326
|
+
else
|
327
|
+
node = find node_or_key
|
328
|
+
end
|
329
|
+
|
330
|
+
if node
|
331
|
+
@size -= 1
|
332
|
+
invalidate_cached_values
|
333
|
+
|
334
|
+
# There are three cases:
|
335
|
+
#
|
336
|
+
# 1) The node is a leaf. Remove it and return.
|
337
|
+
#
|
338
|
+
# 2) The node is a branch (has only 1 child). Make the pointer to this node
|
339
|
+
# point to the child of this node.
|
340
|
+
#
|
341
|
+
# 3) The node has two children. Swap items with the successor
|
342
|
+
# of the node (the smallest item in its right subtree) and
|
343
|
+
# delete the successor from the right subtree of the node.
|
344
|
+
if node.is_leaf?
|
345
|
+
remove_leaf node
|
346
|
+
elsif (!!node.left) ^ (!!node.right)
|
347
|
+
remove_branch node
|
348
|
+
else
|
349
|
+
assert node.left && node.right
|
350
|
+
swap_with_successor_and_remove node
|
351
|
+
end
|
352
|
+
end
|
353
|
+
node
|
354
|
+
end
|
355
|
+
|
356
|
+
def remove_leaf node
|
357
|
+
parent = node.parent
|
358
|
+
if parent
|
359
|
+
if parent.left == node
|
360
|
+
parent.left = nil
|
361
|
+
else
|
362
|
+
assert parent.right == node
|
363
|
+
parent.right = nil
|
364
|
+
end
|
365
|
+
recompute_heights parent
|
366
|
+
else
|
367
|
+
@root = nil
|
368
|
+
end
|
369
|
+
#del node
|
370
|
+
# rebalance
|
371
|
+
node = parent
|
372
|
+
while node
|
373
|
+
rebalance node unless [-1, 0, 1].include? node.balance_factor
|
374
|
+
node = node.parent
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
|
379
|
+
|
380
|
+
|
381
|
+
def remove_branch node
|
382
|
+
parent = node.parent
|
383
|
+
if parent
|
384
|
+
if parent.left == node
|
385
|
+
parent.left = node.right || node.left
|
386
|
+
else
|
387
|
+
assert parent.right == node
|
388
|
+
parent.right = node.right || node.left
|
389
|
+
end
|
390
|
+
if node.left
|
391
|
+
node.left.parent = parent
|
392
|
+
else
|
393
|
+
assert node.right
|
394
|
+
node.right.parent = parent
|
395
|
+
end
|
396
|
+
recompute_heights parent
|
397
|
+
else
|
398
|
+
if node.left
|
399
|
+
@root = node.left
|
400
|
+
node.left.parent = nil
|
401
|
+
else
|
402
|
+
@root = node.right
|
403
|
+
node.right.parent = nil
|
404
|
+
end
|
405
|
+
recompute_heights @root
|
406
|
+
end
|
407
|
+
|
408
|
+
# rebalance
|
409
|
+
node = parent
|
410
|
+
while node
|
411
|
+
rebalance node unless [-1,0,1].include? node.balance_factor
|
412
|
+
node = node.parent
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def swap_with_successor_and_remove node
|
417
|
+
successor = locate_min node.right
|
418
|
+
swap_nodes node, successor
|
419
|
+
assert node.left.nil?
|
420
|
+
if node.height == 0
|
421
|
+
remove_leaf node
|
422
|
+
else
|
423
|
+
remove_branch node
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def swap_nodes node1, node2
|
428
|
+
assert node1.height > node2.height
|
429
|
+
parent1 = node1.parent
|
430
|
+
left_child1 = node1.left
|
431
|
+
right_child1 = node1.right
|
432
|
+
parent2 = node2.parent
|
433
|
+
assert parent2
|
434
|
+
assert parent2.left == node2 || parent2 == node1
|
435
|
+
left_child2 = node2.left
|
436
|
+
assert left_child2.nil?
|
437
|
+
right_child2 = node2.right
|
438
|
+
|
439
|
+
# swap heights
|
440
|
+
tmp = node1.height
|
441
|
+
node1.height = node2.height
|
442
|
+
node2.height = tmp
|
443
|
+
|
444
|
+
if parent1
|
445
|
+
if parent1.left == node1
|
446
|
+
parent1.left = node2
|
447
|
+
else
|
448
|
+
assert parent1.right == node1
|
449
|
+
parent1.right = node2
|
450
|
+
end
|
451
|
+
node2.parent = parent1
|
452
|
+
else
|
453
|
+
@root = node2
|
454
|
+
@root.parent = nil
|
455
|
+
end
|
456
|
+
|
457
|
+
node2.left = left_child1
|
458
|
+
left_child1.parent = node2
|
459
|
+
node1.left = left_child2 # None
|
460
|
+
node1.right = right_child2
|
461
|
+
if right_child2
|
462
|
+
right_child2.parent = node1
|
463
|
+
end
|
464
|
+
if parent2 != node1
|
465
|
+
node2.right = right_child1
|
466
|
+
right_child1.parent = node2
|
467
|
+
|
468
|
+
parent2.left = node1
|
469
|
+
node1.parent = parent2
|
470
|
+
else
|
471
|
+
node2.right = node1
|
472
|
+
node1.parent = node2
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
def compute_and_check_height root
|
477
|
+
return 0 if root.nil?
|
478
|
+
left_sub_tree_height = compute_and_check_height root.left
|
479
|
+
return -1 if -1 == left_sub_tree_height
|
480
|
+
|
481
|
+
right_sub_tree_height = compute_and_check_height root.right
|
482
|
+
return -1 if -1 == right_sub_tree_height
|
483
|
+
|
484
|
+
height_difference = (left_sub_tree_height - right_sub_tree_height).abs;
|
485
|
+
|
486
|
+
if height_difference > 1
|
487
|
+
-1
|
488
|
+
else
|
489
|
+
[left_sub_tree_height, right_sub_tree_height].max + 1
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|