tree-red_black 0.4.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 +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +10 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +36 -0
- data/LICENSE.txt +21 -0
- data/README.md +572 -0
- data/Rakefile +12 -0
- data/contrib/iterate_dot_graphs.rb +208 -0
- data/lib/tree-red_black.rb +1 -0
- data/lib/tree/red_black.rb +243 -0
- data/lib/tree/red_black/red_black_node.rb +645 -0
- data/lib/tree/red_black/version.rb +6 -0
- data/tree-red_black.gemspec +31 -0
- metadata +122 -0
data/Rakefile
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tree/red_black'
|
4
|
+
require 'benchmark'
|
5
|
+
|
6
|
+
values = [*0..10]
|
7
|
+
# values = [2, 10, 4, 5, 6, 3, 1, 0, 9, 8, 7]
|
8
|
+
# values = [0, 2, 6, 4, 5, 3, 7, 1, 9, 8, 10]
|
9
|
+
puts "values.size: #{values.size}"
|
10
|
+
|
11
|
+
# rbt = Tree::RedBlack.new
|
12
|
+
# values.each do |v|
|
13
|
+
# # rbt = rbt.insert_red_black(v)
|
14
|
+
# rbt.insert(v)
|
15
|
+
# end
|
16
|
+
|
17
|
+
|
18
|
+
rbt = rbt_copy = nil
|
19
|
+
Benchmark.bm do |benchmark|
|
20
|
+
benchmark.report("insert:") do
|
21
|
+
# rbt = values.reduce(Tree::RedBlackNode.new) do |acc, v|
|
22
|
+
# acc.insert_red_black(v)
|
23
|
+
# end
|
24
|
+
rbt = values.reduce(Tree::RedBlack.new) do |acc, v|
|
25
|
+
acc.insert(v)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# benchmark.report("dup:") do
|
30
|
+
# rbt_copy = rbt.dup
|
31
|
+
# end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# rbt = rbt.insert_red_black(values.size)
|
36
|
+
# rbt_copy = rbt_copy.insert_red_black(values.size).delete_red_black(rbt_copy.key)
|
37
|
+
# rbt.insert(values.size)
|
38
|
+
# rbt_copy.insert(values.size)
|
39
|
+
|
40
|
+
|
41
|
+
File.open('dot.txt', 'w') do |file|
|
42
|
+
file.write "graph \"\"\n{\n label=\"Red-Black Tree\"\n"
|
43
|
+
|
44
|
+
i = 0
|
45
|
+
rbt.each do |node|
|
46
|
+
file.write(" #{node.key} [style=filled,color=#{node.color.to_s.downcase},fontcolor=white];\n")
|
47
|
+
file.write(" #{node.key} -- %s;\n" % [node.left ? "#{node.left.key}" : 'NULL' + (i += 1).to_s])
|
48
|
+
file.write(" #{node.key} -- %s;\n" % [node.right ? "#{node.right.key}" : 'NULL' + (i += 1).to_s])
|
49
|
+
end
|
50
|
+
i.times do |n|
|
51
|
+
file.write(" NULL#{n + 1} [fontsize=6,shape=box,width=0.2,height=0.2,style=filled,color=gray,label=\"NULL\"];\n")
|
52
|
+
end
|
53
|
+
file.write("}\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
system 'dot -Tpng -odot.png dot.txt'
|
57
|
+
system 'open dot.png'
|
58
|
+
|
59
|
+
# puts "root: #{rbt_copy.key}"
|
60
|
+
puts "root: #{rbt.root.key}"
|
61
|
+
puts "size: #{rbt.size}"
|
62
|
+
|
63
|
+
p rbt.each.map(&:key)
|
64
|
+
|
65
|
+
paths = {}
|
66
|
+
rbt.pre_order do |node; path, count, ancestor|
|
67
|
+
if node.left.nil? || node.right.nil?
|
68
|
+
path = []
|
69
|
+
count = 0
|
70
|
+
ancestor = node
|
71
|
+
loop do
|
72
|
+
path.unshift ancestor.key
|
73
|
+
count += 1 if ancestor.color == :BLACK
|
74
|
+
break if (ancestor = ancestor.parent).nil?
|
75
|
+
end
|
76
|
+
paths[path] = count
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# puts "paths from root (#{paths.size}):"
|
81
|
+
# paths.each do |path, count|
|
82
|
+
# p path
|
83
|
+
# end
|
84
|
+
|
85
|
+
puts "black-node counts: #{paths.values.uniq}"
|
86
|
+
|
87
|
+
|
88
|
+
def delete_keys_of_interest(rbt, deleted)
|
89
|
+
# puts "root: #{rbt.root.key}"
|
90
|
+
# puts "size: #{rbt.size}"
|
91
|
+
|
92
|
+
paths = {}
|
93
|
+
rbt.pre_order do |node; path, count, ancestor|
|
94
|
+
if node.left.nil? || node.right.nil?
|
95
|
+
path = []
|
96
|
+
count = 0
|
97
|
+
ancestor = node
|
98
|
+
loop do
|
99
|
+
path.unshift ancestor.key
|
100
|
+
count += 1 if ancestor.color == :BLACK
|
101
|
+
break if (ancestor = ancestor.parent).nil?
|
102
|
+
end
|
103
|
+
paths[path] = count
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# puts "paths from root (#{paths.size}):"
|
108
|
+
# paths.each do |path, count|
|
109
|
+
# p path
|
110
|
+
# end
|
111
|
+
|
112
|
+
# puts "black-node counts: #{paths.values.uniq}"
|
113
|
+
|
114
|
+
raise "Unbalanced tree after deleting: #{deleted}" if paths.values.uniq.size > 1
|
115
|
+
|
116
|
+
koi = rbt.each.select do |node|
|
117
|
+
node.color == :BLACK && node.left.nil? && node.right.nil?
|
118
|
+
end.map(&:key)
|
119
|
+
# puts "keys of interest: #{koi}"
|
120
|
+
|
121
|
+
v = koi.size > 0 ? koi.shuffle.first : rbt.root&.key
|
122
|
+
|
123
|
+
return if v.nil?
|
124
|
+
|
125
|
+
deleted << v
|
126
|
+
# rbt = rbt.delete_red_black(v)
|
127
|
+
rbt.delete(v)
|
128
|
+
|
129
|
+
delete_keys_of_interest(rbt, deleted)
|
130
|
+
end
|
131
|
+
|
132
|
+
# deleted = []
|
133
|
+
|
134
|
+
# Benchmark.bm do |benchmark|
|
135
|
+
# # benchmark.report("koi:") do
|
136
|
+
# # delete_keys_of_interest(rbt_copy, deleted)
|
137
|
+
# # end
|
138
|
+
# # rbt_copy = rbt.dup
|
139
|
+
# benchmark.report("delete:") do
|
140
|
+
# # values.each { |v| rbt_copy = rbt_copy.delete_red_black(v) }
|
141
|
+
# values.each { |v| rbt.delete(v) }
|
142
|
+
# end
|
143
|
+
# end
|
144
|
+
|
145
|
+
# # p "deleted: #{deleted}"
|
146
|
+
# p rbt.inspect
|
147
|
+
|
148
|
+
print "Value to delete? "
|
149
|
+
n = gets.to_i
|
150
|
+
|
151
|
+
toggle = 0
|
152
|
+
while n >= 0
|
153
|
+
# rbt_copy = rbt.delete_red_black(n)
|
154
|
+
rbt.delete(n)
|
155
|
+
|
156
|
+
name = toggle == 0 ? 'dot2' : 'dot'
|
157
|
+
File.open(name + '.txt', 'w') do |file|
|
158
|
+
file.write "graph \"\"\n{\n label=\"Red-Black Tree - #{n}\"\n"
|
159
|
+
# file.write " labelfontsize=8.0\n"
|
160
|
+
|
161
|
+
i = 0
|
162
|
+
rbt.each do |node|
|
163
|
+
file.write(" #{node.key} [style=filled,color=#{node.color.to_s.downcase},fontcolor=white];\n")
|
164
|
+
file.write(" #{node.key} -- %s;\n" % [node.left ? "#{node.left.key}" : 'NULL' + (i += 1).to_s])
|
165
|
+
file.write(" #{node.key} -- %s;\n" % [node.right ? "#{node.right.key}" : 'NULL' + (i += 1).to_s])
|
166
|
+
end
|
167
|
+
i.times do |n|
|
168
|
+
file.write(" NULL#{n + 1} [fontsize=6,shape=box,width=0.2,height=0.2,style=filled,color=gray,label=\"NULL\"];\n")
|
169
|
+
end
|
170
|
+
file.write("}\n")
|
171
|
+
end
|
172
|
+
|
173
|
+
system 'dot -Tpng -o' + name + '.png ' + name + '.txt'
|
174
|
+
system 'open ' + name + '.png'
|
175
|
+
|
176
|
+
print "Value to delete? "
|
177
|
+
n = gets.to_i
|
178
|
+
toggle ^= 1
|
179
|
+
end
|
180
|
+
|
181
|
+
# values.delete(n)
|
182
|
+
# values.each do |v|
|
183
|
+
# puts "deleting #{v}"
|
184
|
+
# rbt = rbt.delete_red_black(v)
|
185
|
+
# end
|
186
|
+
|
187
|
+
# puts "empty rbt: #{rbt.inspect}"
|
188
|
+
|
189
|
+
|
190
|
+
# name = 'dot2'
|
191
|
+
# File.open(name + '.txt', 'w') do |file|
|
192
|
+
# file.write "graph \"\"\n{\n label=\"Red-Black Tree Copy\"\n"
|
193
|
+
# # file.write " labelfontsize=8.0\n"
|
194
|
+
|
195
|
+
# i = 0
|
196
|
+
# rbt_copy.each do |node|
|
197
|
+
# file.write(" #{node.key} [style=filled,color=#{node.color.to_s.downcase},fontcolor=white];\n")
|
198
|
+
# file.write(" #{node.key} -- %s;\n" % [node.left ? "#{node.left.key}" : 'NULL' + (i += 1).to_s])
|
199
|
+
# file.write(" #{node.key} -- %s;\n" % [node.right ? "#{node.right.key}" : 'NULL' + (i += 1).to_s])
|
200
|
+
# end
|
201
|
+
# i.times do |n|
|
202
|
+
# file.write(" NULL#{n + 1} [fontsize=6,shape=box,width=0.2,height=0.2,style=filled,color=gray,label=\"NULL\"];\n")
|
203
|
+
# end
|
204
|
+
# file.write("}\n")
|
205
|
+
# end
|
206
|
+
|
207
|
+
# system 'dot -Tpng -o' + name + '.png ' + name + '.txt'
|
208
|
+
# system 'open ' + name + '.png'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'tree/red_black'
|
@@ -0,0 +1,243 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# coding: UTF-8
|
3
|
+
|
4
|
+
require 'tree/red_black/red_black_node'
|
5
|
+
|
6
|
+
module Tree
|
7
|
+
|
8
|
+
##
|
9
|
+
# Tree::RedBlack is a pure-Ruby implementation of a
|
10
|
+
# {Red-Black tree}[https://en.wikipedia.org/wiki/Red–black_tree] --
|
11
|
+
# i.e., a self-balancing binary search tree with
|
12
|
+
# {O(log n)}[https://en.wikipedia.org/wiki/Big-O_notation]
|
13
|
+
# search, insert and delete operations. It is appropriate for
|
14
|
+
# maintaining an ordered collection where insertion and deletion
|
15
|
+
# are desired at arbitrary positions.
|
16
|
+
#
|
17
|
+
# The implementation differs slightly from the Wikipedia description
|
18
|
+
# referenced above. In particular, leaf nodes are +nil+, which
|
19
|
+
# affects the details of node deletion.
|
20
|
+
|
21
|
+
class RedBlack
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
attr_accessor :root, :size, :allow_duplicates
|
25
|
+
|
26
|
+
##
|
27
|
+
# Returns a new, empty Red-Black tree. If option +allow_duplicates+ is
|
28
|
+
# +false+, then only unique values are inserted in a Red-Black tree.
|
29
|
+
#
|
30
|
+
# The +root+ attribute references the root node of the tree.
|
31
|
+
# The +size+ attribute indicates the number of nodes in the tree.
|
32
|
+
# When +size+ is +0+, +root+ is always +nil+.
|
33
|
+
#
|
34
|
+
# === Example
|
35
|
+
#
|
36
|
+
# require 'tree/red_black'
|
37
|
+
#
|
38
|
+
# rbt = Tree::RedBlack.new
|
39
|
+
# p rbt.root #=> nil
|
40
|
+
# p rbt.size #=> 0
|
41
|
+
# p rbt.allow_duplicates? #=> true
|
42
|
+
|
43
|
+
def initialize(allow_duplicates = true)
|
44
|
+
@root = nil
|
45
|
+
@size = 0
|
46
|
+
@allow_duplicates = allow_duplicates
|
47
|
+
end
|
48
|
+
|
49
|
+
def allow_duplicates?
|
50
|
+
@allow_duplicates
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Inserts a value or sequence of values in a Red-Black tree and
|
55
|
+
# increments the +size+ attribute by the number of values
|
56
|
+
# inserted.
|
57
|
+
#
|
58
|
+
# Since a Red-Black tree maintains an ordered, Enumerable
|
59
|
+
# collection, every value inserted must be comparable with every
|
60
|
+
# other value. Methods +each+, +map+, +select+, +find+, +sort+,
|
61
|
+
# etc., can be applied directly to the tree.
|
62
|
+
#
|
63
|
+
# The individual nodes yielded by enumeration respond to method
|
64
|
+
# +key+ to retrieve the value stored in that node. Method +each+,
|
65
|
+
# in particular, is aliased to +in_order+, so that nodes are
|
66
|
+
# sorted in ascending order by +key+ value. Nodes can also be
|
67
|
+
# traversed by method +pre_order+, e.g., to generate paths in the
|
68
|
+
# tree.
|
69
|
+
#
|
70
|
+
# === Example
|
71
|
+
#
|
72
|
+
# require 'tree/red_black'
|
73
|
+
#
|
74
|
+
# rbt = Tree::RedBlack.new
|
75
|
+
# rbt.insert(*1..10) #=> #<Tree::RedBlack:0x00...>
|
76
|
+
# p rbt.size #=> 10
|
77
|
+
# rbt.map(&:key) #=> [1, 2, ..., 10]
|
78
|
+
# rbt.select { |node| node.key % 2 == 0 }.map(&:key)
|
79
|
+
# #=> [2, 4, ..., 10]
|
80
|
+
|
81
|
+
def insert(*values)
|
82
|
+
values.each do |value; new_root|
|
83
|
+
new_root = (root.nil? ? RedBlackNode.new(value, :BLACK) :
|
84
|
+
root.insert_red_black(value, @allow_duplicates))
|
85
|
+
unless new_root.nil?
|
86
|
+
@root = new_root
|
87
|
+
@size += 1
|
88
|
+
end
|
89
|
+
end
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Deletes a value or sequence of values from a Red-Black tree.
|
95
|
+
#
|
96
|
+
# === Example
|
97
|
+
#
|
98
|
+
# require 'tree/red_black'
|
99
|
+
#
|
100
|
+
# rbt = Tree::RedBlack.new
|
101
|
+
# rbt.insert(*1..10) #=> #<Tree::RedBlack:0x00...>
|
102
|
+
# p rbt.size #=> 10
|
103
|
+
# rbt.delete(*4..8) #=> #<Tree::RedBlack:0x00...>
|
104
|
+
# p rbt.size #=> 5
|
105
|
+
# rbt.map(&:key) #=> [1, 2, 3, 9, 10]
|
106
|
+
|
107
|
+
def delete(*values)
|
108
|
+
values.each do |value; new_root|
|
109
|
+
new_root = root.nil? ? nil : root.delete_red_black(value)
|
110
|
+
unless new_root.nil?
|
111
|
+
@root = new_root
|
112
|
+
@size -= 1
|
113
|
+
end
|
114
|
+
@root = nil if size == 0
|
115
|
+
end
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Returns a Red-Black tree node whose +key+ matches +value+ by
|
121
|
+
# binary search. If no match is found, calls non-nil +ifnone+,
|
122
|
+
# otherwise returns +nil+.
|
123
|
+
#
|
124
|
+
# === Example
|
125
|
+
# require 'tree/red_black'
|
126
|
+
#
|
127
|
+
# shuffled_values = [*1..10].shuffle
|
128
|
+
# rbt = shuffled_values.reduce(Tree::RedBlack.new) do |acc, v|
|
129
|
+
# acc.insert(v)
|
130
|
+
# end
|
131
|
+
# rbt.search(7) #=> <Tree::RedBlackNode:0x00..., @key=7, ...>
|
132
|
+
|
133
|
+
def search(value, ifnone = nil)
|
134
|
+
root ? root.search(value, ifnone) : nil
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Returns a Red-Black tree node satisfying a criterion defined in
|
139
|
+
# +block+ by binary search.
|
140
|
+
#
|
141
|
+
# If +block+ evaluates to +true+ or +false+, returns the first node
|
142
|
+
# for which the +block+ evaluates to +true+. In this case, the
|
143
|
+
# criterion is expected to return +false+ for nodes preceding
|
144
|
+
# the matching node and +true+ for subsequent nodes.
|
145
|
+
#
|
146
|
+
# === Example
|
147
|
+
# require 'tree/red_black'
|
148
|
+
#
|
149
|
+
# shuffled_values = [*1..10].shuffle
|
150
|
+
# rbt = shuffled_values.reduce(Tree::RedBlack.new) do |acc, v|
|
151
|
+
# acc.insert(v)
|
152
|
+
# end
|
153
|
+
# rbt.bsearch { |node| node.key >= 7 }
|
154
|
+
# #=> <Tree::RedBlackNode:0x00... @key=7 ...>
|
155
|
+
#
|
156
|
+
# If +block+ evaluates to <tt><0</tt>, +0+ or <tt>>0</tt>, returns
|
157
|
+
# first node for which +block+ evaluates to +0+. Otherwise returns
|
158
|
+
# +nil+. In this case, the criterion is expected to return
|
159
|
+
# <tt><0</tt> for nodes preceding the matching node, +0+ for some
|
160
|
+
# subsequent nodes and <tt>>0</tt> for nodes beyond that.
|
161
|
+
#
|
162
|
+
# === Example
|
163
|
+
# require 'tree/red_black'
|
164
|
+
#
|
165
|
+
# shuffled_values = [*1..10].shuffle
|
166
|
+
# rbt = shuffled_values.reduce(Tree::RedBlack.new) do |acc, v|
|
167
|
+
# acc.insert(v)
|
168
|
+
# end
|
169
|
+
# rbt.bsearch { |node| 7 <=> node.key }
|
170
|
+
# #=> <Tree::RedBlackNode:0x00... @key=7 ...>
|
171
|
+
#
|
172
|
+
# If +block++ is not given, returns an enumerator.
|
173
|
+
|
174
|
+
def bsearch(&block)
|
175
|
+
return enum_for(:bsearch) unless block_given?
|
176
|
+
|
177
|
+
root ? root.bsearch(&block) : nil
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Returns an enumerator for nodes in a Red-Black tree by pre-order
|
182
|
+
# traversal.
|
183
|
+
#
|
184
|
+
# === Example
|
185
|
+
# require 'tree/red_black'
|
186
|
+
#
|
187
|
+
# rbt = Tree::RedBlack.new
|
188
|
+
# rbt.insert(*1..10) #=> #<Tree::RedBlack:0x00...>
|
189
|
+
# rbt.pre_order.map(&:key) #=> [4, 2, 1, 3, 6, 5, 8, 7, 9, 10]
|
190
|
+
|
191
|
+
def pre_order(&block)
|
192
|
+
return enum_for(:pre_order) unless block_given?
|
193
|
+
return if root.nil?
|
194
|
+
|
195
|
+
root.pre_order(&block)
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
# Returns an enumerator for nodes in a Red-Black tree by in-order
|
200
|
+
# traversal. The +each+ method is aliased to +in_order+
|
201
|
+
#
|
202
|
+
# === Example
|
203
|
+
# require 'tree/red_black'
|
204
|
+
#
|
205
|
+
# rbt = Tree::RedBlack.new
|
206
|
+
# shuffled_values = [*1..10].shuffle
|
207
|
+
# rbt.insert(*shuffled_values)
|
208
|
+
# rbt.in_order.map(&:key) #=> [1, 2, ..., 10]
|
209
|
+
|
210
|
+
def in_order(&block)
|
211
|
+
return enum_for(:in_order) unless block_given?
|
212
|
+
return if root.nil?
|
213
|
+
|
214
|
+
root.in_order(&block)
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Returns a deep copy of a Red-Black tree, provided that the
|
219
|
+
# +dup+ method for values in the tree is also a deep copy.
|
220
|
+
#
|
221
|
+
# === Example
|
222
|
+
#
|
223
|
+
# require 'tree/red_black'
|
224
|
+
#
|
225
|
+
# rbt = Tree::RedBlack.new
|
226
|
+
# rbt.insert({a: 1, b: 2})
|
227
|
+
# rbt_copy = rbt.dup
|
228
|
+
# p rbt.root.key #=> {:a=>1, :b=>2}
|
229
|
+
# p rbt.root.key.delete(:a) #=> 1
|
230
|
+
# p rbt.root.key #=> {:b=>2}
|
231
|
+
# p rbt_copy.root.key #=> {:a=>1, :b=>2}
|
232
|
+
|
233
|
+
def dup
|
234
|
+
copy = RedBlack.new
|
235
|
+
copy.size = size
|
236
|
+
copy.allow_duplicates = allow_duplicates
|
237
|
+
copy.root = root.dup
|
238
|
+
copy
|
239
|
+
end
|
240
|
+
|
241
|
+
alias each in_order
|
242
|
+
end
|
243
|
+
end
|