red-black-tree 0.1.6 → 0.1.8
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/CHANGELOG.md +9 -0
- data/README.md +3 -1
- data/lib/red-black-tree.rb +150 -125
- data/lib/red_black_tree/node/left_right_element_referencers.rb +9 -2
- data/lib/red_black_tree/node.rb +7 -4
- data/lib/red_black_tree/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7cc7e3138c1b0ceea2d6b5a2f91620323f75ebe346288bc69761d77d95eb5c4
|
4
|
+
data.tar.gz: 057d9d283d5312f6c5f8e155e3c039c342d036b779d115738c7afbc57ca419f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61f4a9a44d3949b3c2072de4fbca1507cf78306adc32dae8bad26ec5a71171ae952cb81effddf41990a8e2b6dc0e06d39a121389e5bc4c3438eca981dc2a0b59
|
7
|
+
data.tar.gz: c9bced3565a3ec9439c1c08dca09676d1c3966cb18a97507c1f815e938b73419f9f3bce0e4d13d06e97dc59a92f845a3b9b4f094841aa900bde3b004959baeca
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.1.8] - 2025-07-26
|
4
|
+
|
5
|
+
- Refactor to slim down and de-duplicate some methods
|
6
|
+
|
7
|
+
## [0.1.7] - 2025-01-05
|
8
|
+
|
9
|
+
- Change `RedBlackTree#delete!` return value to the deleted node
|
10
|
+
- Add `RedBlackTree#clear!`
|
11
|
+
|
3
12
|
## [0.1.6] - 2025-01-01
|
4
13
|
|
5
14
|
- Prefer node deletion over data swapping
|
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# RedBlackTree
|
2
2
|
|
3
|
+

|
4
|
+

|
5
|
+
|
3
6
|
[Red-black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) data structure for Ruby.
|
4
7
|
|
5
8
|
## Installation
|
@@ -187,7 +190,6 @@ end
|
|
187
190
|
|
188
191
|
- `RedBlackTree#max`
|
189
192
|
- `RedBlackTree#height`
|
190
|
-
- `RedBlackTree#clear`
|
191
193
|
|
192
194
|
## Development
|
193
195
|
|
data/lib/red-black-tree.rb
CHANGED
@@ -20,6 +20,7 @@ class RedBlackTree
|
|
20
20
|
|
21
21
|
def initialize
|
22
22
|
@size = 0
|
23
|
+
@root = nil
|
23
24
|
@left_most_node = nil
|
24
25
|
end
|
25
26
|
|
@@ -90,6 +91,7 @@ class RedBlackTree
|
|
90
91
|
raise ArgumentError, "Target parent already has #{direction} child" if (child = target_parent[direction]) && child.valid?
|
91
92
|
end
|
92
93
|
|
94
|
+
node.tree = self
|
93
95
|
node.parent = nil
|
94
96
|
node.left = LeafNode.new
|
95
97
|
node.left.parent = node
|
@@ -104,27 +106,7 @@ class RedBlackTree
|
|
104
106
|
node.parent = target_parent
|
105
107
|
node.red!
|
106
108
|
|
107
|
-
|
108
|
-
if node.parent.sibling && node.parent.sibling.red?
|
109
|
-
node.parent.black!
|
110
|
-
node.parent.sibling.black!
|
111
|
-
node.parent.parent.red!
|
112
|
-
node = node.parent.parent
|
113
|
-
else
|
114
|
-
opp_direction = node.opposite_position
|
115
|
-
if node.parent.position == opp_direction
|
116
|
-
rotate_sub_tree! node.parent, opp_direction
|
117
|
-
node = node[opp_direction]
|
118
|
-
end
|
119
|
-
|
120
|
-
opp_direction = node.opposite_position
|
121
|
-
rotate_sub_tree! node.parent.parent, opp_direction
|
122
|
-
node.parent.black!
|
123
|
-
node.parent[opp_direction].red!
|
124
|
-
end
|
125
|
-
|
126
|
-
@root.black!
|
127
|
-
end
|
109
|
+
rebalance_after_insertion! node
|
128
110
|
end
|
129
111
|
|
130
112
|
node.validate! @root == node
|
@@ -138,99 +120,43 @@ class RedBlackTree
|
|
138
120
|
# Removes the given node from the tree.
|
139
121
|
#
|
140
122
|
# @param node [RedBlackTree::Node] the node to be removed
|
141
|
-
# @return [RedBlackTree]
|
123
|
+
# @return [RedBlackTree::Node, nil] the removed node
|
142
124
|
def delete! node
|
143
125
|
raise ArgumentError, "cannot delete leaf node" if node.instance_of? LeafNode
|
144
126
|
|
127
|
+
if node.tree.nil?
|
128
|
+
return
|
129
|
+
elsif node.tree != self
|
130
|
+
raise ArgumentError, "node does not belong to this tree"
|
131
|
+
end
|
132
|
+
|
145
133
|
original_node = node
|
146
134
|
|
147
135
|
if node.children_are_valid?
|
148
|
-
|
149
|
-
|
150
|
-
successor = node.left
|
151
|
-
successor = successor.left until successor.left.leaf?
|
152
|
-
node.swap_colour_with! successor
|
153
|
-
node.swap_position_with! successor
|
154
|
-
node.swap_position_with! LeafNode.new
|
155
|
-
|
156
|
-
@root = successor if is_root
|
157
|
-
|
158
|
-
original_node.validate_free!
|
159
|
-
|
160
|
-
decrement_size!
|
161
|
-
update_left_most_node!
|
162
|
-
|
163
|
-
return self
|
136
|
+
delete_node_with_two_children! node
|
164
137
|
elsif node.single_child_is_valid?
|
165
|
-
|
166
|
-
|
167
|
-
valid_child = node.children.find(&:valid?)
|
168
|
-
valid_child.black!
|
169
|
-
node.swap_position_with! valid_child
|
170
|
-
node.swap_position_with! LeafNode.new
|
171
|
-
|
172
|
-
@root = valid_child if is_root
|
173
|
-
|
174
|
-
original_node.validate_free!
|
175
|
-
|
176
|
-
decrement_size!
|
177
|
-
update_left_most_node!
|
178
|
-
|
179
|
-
return self
|
138
|
+
delete_node_with_one_child! node
|
180
139
|
elsif node.children_are_leaves?
|
181
|
-
|
182
|
-
@root = nil
|
183
|
-
elsif node.red?
|
184
|
-
node.swap_position_with! LeafNode.new
|
185
|
-
else
|
186
|
-
loop do
|
187
|
-
if node.sibling && node.sibling.valid? && node.sibling.red?
|
188
|
-
node.parent.red!
|
189
|
-
node.sibling.black!
|
190
|
-
rotate_sub_tree! node.parent, node.position
|
191
|
-
end
|
192
|
-
|
193
|
-
if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
|
194
|
-
node.sibling.red! unless node.sibling.leaf?
|
195
|
-
node.close_nephew.black!
|
196
|
-
rotate_sub_tree! node.sibling, node.opposite_position
|
197
|
-
end
|
198
|
-
|
199
|
-
if node.distant_nephew && node.distant_nephew.valid? && node.distant_nephew.red?
|
200
|
-
case node.parent.colour
|
201
|
-
when Node::RED then node.sibling.red!
|
202
|
-
when Node::BLACK then node.sibling.black!
|
203
|
-
end
|
204
|
-
node.parent.black!
|
205
|
-
node.distant_nephew.black!
|
206
|
-
rotate_sub_tree! node.parent, node.position
|
207
|
-
|
208
|
-
break
|
209
|
-
end
|
210
|
-
|
211
|
-
if node.parent && node.parent.red?
|
212
|
-
node.sibling.red! unless node.sibling.leaf?
|
213
|
-
node.parent.black!
|
214
|
-
|
215
|
-
break
|
216
|
-
end
|
217
|
-
|
218
|
-
if node.sibling && !node.sibling.leaf?
|
219
|
-
node.sibling.red!
|
220
|
-
end
|
221
|
-
|
222
|
-
break unless node = node.parent
|
223
|
-
end
|
224
|
-
|
225
|
-
original_node.swap_position_with! LeafNode.new
|
226
|
-
end
|
140
|
+
delete_leaf_node! node, original_node
|
227
141
|
end
|
228
142
|
|
143
|
+
original_node.tree = nil
|
229
144
|
original_node.validate_free!
|
230
145
|
|
231
146
|
decrement_size!
|
232
147
|
update_left_most_node!
|
233
148
|
|
149
|
+
original_node
|
150
|
+
end
|
151
|
+
|
152
|
+
# Removes all nodes from the tree.
|
153
|
+
#
|
154
|
+
# @return [RedBlackTree] self
|
155
|
+
def clear!
|
156
|
+
@root = nil
|
157
|
+
@size = 0
|
158
|
+
@left_most_node = nil
|
159
|
+
|
234
160
|
self
|
235
161
|
end
|
236
162
|
|
@@ -264,7 +190,7 @@ class RedBlackTree
|
|
264
190
|
#
|
265
191
|
# @return [true, false]
|
266
192
|
def include? data
|
267
|
-
|
193
|
+
!search(data).nil?
|
268
194
|
end
|
269
195
|
|
270
196
|
# Traverses the tree in pre-order and yields each node.
|
@@ -327,6 +253,29 @@ class RedBlackTree
|
|
327
253
|
|
328
254
|
private
|
329
255
|
|
256
|
+
def is_root? node
|
257
|
+
node && @root && node.object_id == @root.object_id
|
258
|
+
end
|
259
|
+
|
260
|
+
def increment_size!
|
261
|
+
@size += 1
|
262
|
+
end
|
263
|
+
|
264
|
+
def decrement_size!
|
265
|
+
@size -= 1
|
266
|
+
end
|
267
|
+
|
268
|
+
def update_left_most_node!
|
269
|
+
unless @root
|
270
|
+
@left_most_node = nil
|
271
|
+
return
|
272
|
+
end
|
273
|
+
|
274
|
+
current = @root
|
275
|
+
current = current.left until current.left.leaf?
|
276
|
+
@left_most_node = current
|
277
|
+
end
|
278
|
+
|
330
279
|
# Rotates a (sub-)tree starting from the given node in the given direction.
|
331
280
|
#
|
332
281
|
# @param node [RedBlackTree::Node] the root node of the sub-tree
|
@@ -353,12 +302,111 @@ class RedBlackTree
|
|
353
302
|
opp_direction_child
|
354
303
|
end
|
355
304
|
|
305
|
+
def rebalance_after_insertion! node
|
306
|
+
while node.parent && node.parent.red? do
|
307
|
+
if node.parent.sibling && node.parent.sibling.red?
|
308
|
+
node.parent.black!
|
309
|
+
node.parent.sibling.black!
|
310
|
+
node.parent.parent.red!
|
311
|
+
node = node.parent.parent
|
312
|
+
else
|
313
|
+
opp_direction = node.opposite_position
|
314
|
+
if node.parent.position == opp_direction
|
315
|
+
rotate_sub_tree! node.parent, opp_direction
|
316
|
+
node = node[opp_direction]
|
317
|
+
end
|
318
|
+
|
319
|
+
opp_direction = node.opposite_position
|
320
|
+
rotate_sub_tree! node.parent.parent, opp_direction
|
321
|
+
node.parent.black!
|
322
|
+
node.parent[opp_direction].red!
|
323
|
+
end
|
324
|
+
|
325
|
+
@root.black!
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def delete_node_with_two_children! node
|
330
|
+
is_root = is_root? node
|
331
|
+
|
332
|
+
successor = node.left
|
333
|
+
successor = successor.right until successor.right.leaf?
|
334
|
+
node.swap_colour_with! successor
|
335
|
+
node.swap_position_with! successor
|
336
|
+
node.swap_position_with! LeafNode.new
|
337
|
+
|
338
|
+
@root = successor if is_root
|
339
|
+
end
|
340
|
+
|
341
|
+
def delete_node_with_one_child! node
|
342
|
+
is_root = is_root? node
|
343
|
+
|
344
|
+
valid_child = node.children.find(&:valid?)
|
345
|
+
valid_child.black!
|
346
|
+
node.swap_position_with! valid_child
|
347
|
+
node.swap_position_with! LeafNode.new
|
348
|
+
|
349
|
+
@root = valid_child if is_root
|
350
|
+
end
|
351
|
+
|
352
|
+
def delete_leaf_node! node, original_node
|
353
|
+
if is_root? node
|
354
|
+
@root = nil
|
355
|
+
elsif node.red?
|
356
|
+
node.swap_position_with! LeafNode.new
|
357
|
+
else
|
358
|
+
rebalance_after_deletion! node
|
359
|
+
original_node.swap_position_with! LeafNode.new
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def rebalance_after_deletion! node
|
364
|
+
loop do
|
365
|
+
if node.sibling && node.sibling.valid? && node.sibling.red?
|
366
|
+
node.parent.red!
|
367
|
+
node.sibling.black!
|
368
|
+
rotate_sub_tree! node.parent, node.position
|
369
|
+
end
|
370
|
+
|
371
|
+
if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
|
372
|
+
node.sibling.red! unless node.sibling.leaf?
|
373
|
+
node.close_nephew.black!
|
374
|
+
rotate_sub_tree! node.sibling, node.opposite_position
|
375
|
+
end
|
376
|
+
|
377
|
+
if node.distant_nephew && node.distant_nephew.valid? && node.distant_nephew.red?
|
378
|
+
case node.parent.colour
|
379
|
+
when Node::RED then node.sibling.red!
|
380
|
+
when Node::BLACK then node.sibling.black!
|
381
|
+
end
|
382
|
+
node.parent.black!
|
383
|
+
node.distant_nephew.black!
|
384
|
+
rotate_sub_tree! node.parent, node.position
|
385
|
+
|
386
|
+
break
|
387
|
+
end
|
388
|
+
|
389
|
+
if node.parent && node.parent.red?
|
390
|
+
node.sibling.red! unless node.sibling.leaf?
|
391
|
+
node.parent.black!
|
392
|
+
|
393
|
+
break
|
394
|
+
end
|
395
|
+
|
396
|
+
if node.sibling && !node.sibling.leaf?
|
397
|
+
node.sibling.red!
|
398
|
+
end
|
399
|
+
|
400
|
+
break unless node = node.parent
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
356
404
|
def _search_by_data data, node
|
357
405
|
return if node.nil? || node.leaf?
|
358
406
|
return node if data == node.data
|
359
407
|
|
360
|
-
|
361
|
-
if
|
408
|
+
comparison = data <=> node.data
|
409
|
+
if comparison && comparison >= 0
|
362
410
|
_search_by_data data, node.right
|
363
411
|
else
|
364
412
|
_search_by_data data, node.left
|
@@ -382,27 +430,4 @@ class RedBlackTree
|
|
382
430
|
end
|
383
431
|
end
|
384
432
|
end
|
385
|
-
|
386
|
-
def is_root? node
|
387
|
-
node && @root && node.object_id == @root.object_id
|
388
|
-
end
|
389
|
-
|
390
|
-
def increment_size!
|
391
|
-
@size += 1
|
392
|
-
end
|
393
|
-
|
394
|
-
def decrement_size!
|
395
|
-
@size -= 1
|
396
|
-
end
|
397
|
-
|
398
|
-
def update_left_most_node!
|
399
|
-
unless @root
|
400
|
-
@left_most_node = nil
|
401
|
-
return
|
402
|
-
end
|
403
|
-
|
404
|
-
current = @root
|
405
|
-
current = current.left until current.left.leaf?
|
406
|
-
@left_most_node = current
|
407
|
-
end
|
408
433
|
end
|
@@ -4,20 +4,27 @@ class RedBlackTree
|
|
4
4
|
class Node
|
5
5
|
module LeftRightElementReferencers # @private
|
6
6
|
def [] direction
|
7
|
+
validate_direction! direction
|
7
8
|
case direction
|
8
9
|
when Node::LEFT then @left
|
9
10
|
when Node::RIGHT then @right
|
10
|
-
else raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}, got #{direction}"
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
14
|
def []= direction, node
|
15
|
+
validate_direction! direction
|
15
16
|
case direction
|
16
17
|
when Node::LEFT then @left = node
|
17
18
|
when Node::RIGHT then @right = node
|
18
|
-
else raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}, got #{direction}"
|
19
19
|
end
|
20
20
|
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def validate_direction! direction
|
25
|
+
return if [Node::LEFT, Node::RIGHT].include?(direction)
|
26
|
+
raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}, got #{direction}"
|
27
|
+
end
|
21
28
|
end
|
22
29
|
end
|
23
30
|
end
|
data/lib/red_black_tree/node.rb
CHANGED
@@ -19,6 +19,9 @@ class RedBlackTree
|
|
19
19
|
# @return [any] the data/value representing the node
|
20
20
|
attr_reader :data
|
21
21
|
|
22
|
+
# @return [RedBlackTree::Node, nil] the tree this node belongs to
|
23
|
+
attr_reader :tree
|
24
|
+
|
22
25
|
# @param data [any] a non-nil data/value representing the node
|
23
26
|
# @return [Node] a new instance of Node
|
24
27
|
def initialize data
|
@@ -51,10 +54,8 @@ class RedBlackTree
|
|
51
54
|
RIGHT = "right"
|
52
55
|
DIRECTIONS = [LEFT, RIGHT].freeze
|
53
56
|
|
54
|
-
attr_writer :data
|
55
|
-
attr_accessor :colour
|
56
|
-
attr_accessor :tree, :parent
|
57
|
-
attr_accessor :left, :right
|
57
|
+
attr_writer :data, :tree
|
58
|
+
attr_accessor :colour, :parent, :left, :right
|
58
59
|
include LeftRightElementReferencers
|
59
60
|
|
60
61
|
def red? = @colour == RED
|
@@ -116,6 +117,8 @@ class RedBlackTree
|
|
116
117
|
end
|
117
118
|
|
118
119
|
def validate_free!
|
120
|
+
raise StructuralError, "Node is still chained to a tree" if @tree
|
121
|
+
|
119
122
|
anchors = []
|
120
123
|
anchors << "parent" if @parent
|
121
124
|
anchors << "left child" if @left && @left.valid?
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: red-black-tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Young
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
email:
|
13
13
|
- djry1999@gmail.com
|
@@ -49,7 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
49
|
- !ruby/object:Gem::Version
|
50
50
|
version: '0'
|
51
51
|
requirements: []
|
52
|
-
rubygems_version: 3.6.
|
52
|
+
rubygems_version: 3.6.9
|
53
53
|
specification_version: 4
|
54
54
|
summary: Red-Black Tree Data Structure for Ruby
|
55
55
|
test_files: []
|