Skip to content

Commit a189cf0

Browse files
committed
fix a bunch of issues in RedBlackTree#insert! and RedBlackTree#delete! algorithms
1 parent 243a6b9 commit a189cf0

File tree

4 files changed

+41
-48
lines changed

4 files changed

+41
-48
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## [Unreleased]
22

3+
- Fix a bunch of issues in `RedBlackTree#insert!` and `RedBlackTree#delete!` algorithms
34
- Fix `RedBlackTree::LeafNode`s being marked red
45
- Handle comparison with `RedBlackTree::LeafNode` in subclasses of `RedBlackTreeNode`
56
- Add `RedBlackTree#include?`

lib/red-black-tree.rb

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,6 @@ def insert! node, target_parent = nil, direction = nil
9191
raise ArgumentError, "Target parent already has #{direction} child" if (child = target_parent[direction]) && child.valid?
9292
end
9393

94-
opp_direction = opposite_direction direction if direction
95-
9694
node.parent = nil
9795
node.left = LeafNode.new
9896
node.left.parent = node
@@ -114,17 +112,16 @@ def insert! node, target_parent = nil, direction = nil
114112
node.parent.parent.red!
115113
node = node.parent.parent
116114
else
117-
if node.position == direction
118-
node = node.parent
119-
rotate_sub_tree! node, opp_direction
115+
opp_direction = node.opposite_position
116+
if node.parent.position == opp_direction
117+
rotate_sub_tree! node.parent, opp_direction
118+
node = node[opp_direction]
120119
end
121120

121+
opp_direction = node.opposite_position
122+
rotate_sub_tree! node.parent.parent, opp_direction
122123
node.parent.black!
123-
124-
if node.parent.parent
125-
node.parent.parent.red!
126-
rotate_sub_tree! node.parent.parent, direction
127-
end
124+
node.parent[opp_direction].red!
128125
end
129126

130127
@root.black!
@@ -146,6 +143,8 @@ def insert! node, target_parent = nil, direction = nil
146143
def delete! node
147144
raise ArgumentError, "cannot delete leaf node" if node.instance_of? LeafNode
148145

146+
original_node = node
147+
149148
if node.children_are_valid?
150149
successor = node.left
151150
successor = successor.left until successor.left.leaf?
@@ -166,59 +165,54 @@ def delete! node
166165
if is_root? node
167166
@root = nil
168167
elsif node.red?
169-
leaf = LeafNode.new
170-
node.swap_position_with! leaf
168+
node.swap_position_with! LeafNode.new
171169
else
172-
direction = node.position
173-
opp_direction = opposite_direction direction
174-
175170
loop do
176-
if node.sibling.valid? && node.sibling.red?
171+
if node.sibling && node.sibling.valid? && node.sibling.red?
177172
node.parent.red!
178173
node.sibling.black!
179-
rotate_sub_tree! node.parent, direction
180-
181-
next
174+
rotate_sub_tree! node.parent, node.position
182175
end
183176

184177
if node.distant_nephew && node.distant_nephew.valid? && node.distant_nephew.red?
185-
unless node.sibling.leaf?
186-
case node.parent.colour
187-
when Node::RED then node.sibling.red!
188-
when Node::BLACK then node.sibling.black!
189-
end
178+
case node.parent.colour
179+
when Node::RED then node.sibling.red!
180+
when Node::BLACK then node.sibling.black!
190181
end
191182
node.parent.black!
192183
node.distant_nephew.black!
193-
rotate_sub_tree! node.parent, direction
184+
rotate_sub_tree! node.parent, node.position
194185

195186
break
196187
end
197188

198189
if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
199190
node.sibling.red! unless node.sibling.leaf?
200191
node.close_nephew.black!
201-
rotate_sub_tree! node.sibling, opp_direction
192+
rotate_sub_tree! node.sibling, node.opposite_position
202193

203194
next
204195
end
205196

206-
if node.parent.red?
197+
if node.parent && node.parent.red?
207198
node.sibling.red! unless node.sibling.leaf?
208199
node.parent.black!
209200

210201
break
211202
end
212203

213-
break
204+
if node.sibling && !node.sibling.leaf?
205+
node.sibling.red!
206+
end
207+
208+
break unless node = node.parent
214209
end
215210

216-
leaf = LeafNode.new
217-
node.swap_position_with! leaf
211+
original_node.swap_position_with! LeafNode.new
218212
end
219213
end
220214

221-
node.validate_free!
215+
original_node.validate_free!
222216

223217
decrement_size!
224218
update_left_most_node!

lib/red_black_tree/node.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ def single_child_is_leaf? = children.any?(&:leaf?) && !children_are_leaves?
7272
def position
7373
return unless @parent
7474

75-
case self
76-
when @parent.left then LEFT
77-
when @parent.right then RIGHT
75+
case self.object_id
76+
when @parent.left.object_id then LEFT
77+
when @parent.right.object_id then RIGHT
7878
else raise StructuralError, "Disowned by parent"
7979
end
8080
end
@@ -147,13 +147,13 @@ def swap_position_with! other_node
147147
self_position = position
148148
other_position = other_node.position
149149

150-
if other_node.parent == self
150+
if other_node.parent.object_id == self.object_id
151151
self[other_position] = other_node[other_position]
152152
other_node[other_position] = self
153153

154154
other_node.parent = @parent
155155
@parent = other_node
156-
elsif other_node == @parent
156+
elsif other_node.object_id == @parent.object_id
157157
other_node[self_position] = self[self_position]
158158
self[self_position] = other_node
159159

test/test_red_black_tree.rb

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,7 @@ def test_single_node_tree
829829
end
830830
end
831831

832-
class TestInheritance < Minitest::Test
832+
class TestIntegration < Minitest::Test
833833
Work = Struct.new :min_latency, keyword_init: true
834834

835835
class WorkNode < RedBlackTree::Node
@@ -838,17 +838,15 @@ def <=> other
838838
end
839839
end
840840

841-
def test_multiple_nodes
842-
tree = RedBlackTree.new
843-
tree << WorkNode.new(Work.new min_latency: 0.8)
844-
tree << WorkNode.new(Work.new min_latency: 1.2)
845-
tree << WorkNode.new(Work.new min_latency: 0.8)
846-
tree << WorkNode.new(Work.new min_latency: 0.4)
847-
848-
expected = [0.4, 0.8, 0.8, 1.2]
849-
until tree.empty?
850-
work = tree.shift
851-
assert_equal work.data.min_latency, expected.shift
841+
def test_ordering
842+
10.times do
843+
tree = RedBlackTree.new
844+
expected = 250.times.map do
845+
rand(10).tap do |value|
846+
tree << WorkNode.new(Work.new min_latency: value)
847+
end
848+
end.sort
849+
assert_equal expected.shift, tree.shift.data.min_latency until tree.empty?
852850
end
853851
end
854852
end

0 commit comments

Comments
 (0)