Skip to content

Commit a2730e2

Browse files
authored
Merge pull request #200 from aparibocci/feature/avl_tree
Adding `AVL Tree` implementation
2 parents d0c1411 + 286c0e4 commit a2730e2

File tree

2 files changed

+413
-0
lines changed

2 files changed

+413
-0
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
class AvlTreeNode
2+
3+
attr_reader :key
4+
attr_accessor :parent
5+
attr_accessor :left
6+
attr_accessor :right
7+
attr_accessor :height
8+
9+
def initialize(key, parent=nil)
10+
@key = key
11+
@parent = parent
12+
@height = 1
13+
end
14+
end
15+
16+
##
17+
# This class represents an AVL tree (a self-balancing binary search tree) with distinct node keys.
18+
# Starting from the root, every node has up to two children (one left and one right child node).
19+
#
20+
# For the BST property:
21+
# - the keys of nodes in the left subtree of a node are strictly less than the key of the node;
22+
# - the keys of nodes in the right subtree of a node are strictly greater than the key of the node.
23+
#
24+
# Due to self-balancing upon key insertion and deletion, the main operations of this data structure
25+
# (insertion, deletion, membership) run - in worst case - in O(log(n)), where n is the number of nodes in the tree.
26+
27+
class AvlTree
28+
29+
attr_reader :size
30+
attr_accessor :root
31+
32+
def initialize(keys=[])
33+
@size = 0
34+
keys.each {|key| insert_key(key) }
35+
end
36+
37+
def empty?
38+
size == 0
39+
end
40+
41+
def insert_key(key)
42+
@size += 1
43+
if root.nil?
44+
@root = AvlTreeNode.new(key)
45+
return
46+
end
47+
parent = root
48+
while (key < parent.key && !parent.left.nil? && parent.left.key != key) ||
49+
(key > parent.key && !parent.right.nil? && parent.right.key != key)
50+
parent = key < parent.key ? parent.left : parent.right
51+
end
52+
if key < parent.key
53+
raise ArgumentError.new("Key #{key} is already present in the AvlTree") unless parent.left.nil?
54+
parent.left = AvlTreeNode.new(key, parent)
55+
else
56+
raise ArgumentError.new("Key #{key} is already present in the AvlTree") unless parent.right.nil?
57+
parent.right = AvlTreeNode.new(key, parent)
58+
end
59+
balance(parent)
60+
end
61+
62+
def min_key(node=root)
63+
return nil if node.nil?
64+
min_key_node(node).key
65+
end
66+
67+
def max_key(node=root)
68+
return nil if node.nil?
69+
max_key_node(node).key
70+
end
71+
72+
def contains_key?(key)
73+
!find_node_with_key(key).nil?
74+
end
75+
76+
def delete_key(key)
77+
parent = find_parent_of_node_with_key(key)
78+
if parent.nil?
79+
return if root.nil? || root.key != key
80+
@size -= 1
81+
@root = adjusted_subtree_after_deletion(root.left, root.right)
82+
root.parent = nil
83+
balance(root.right.nil? ? root : root.right)
84+
return
85+
end
86+
if key < parent.key
87+
node = parent.left
88+
parent.left = adjusted_subtree_after_deletion(node.left, node.right)
89+
unless parent.left.nil?
90+
parent.left.parent = parent
91+
balance(parent.left.right.nil? ? parent.left : parent.left.right)
92+
end
93+
else
94+
node = parent.right
95+
parent.right = adjusted_subtree_after_deletion(node.left, node.right)
96+
unless parent.right.nil?
97+
parent.right.parent = parent
98+
balance(parent.right.right.nil? ? parent.right : parent.right.right)
99+
end
100+
end
101+
@size -= 1
102+
end
103+
104+
def traverse_preorder(key_consumer, node=root)
105+
return if node.nil?
106+
key_consumer.call(node.key)
107+
traverse_preorder(key_consumer, node.left) unless node.left.nil?
108+
traverse_preorder(key_consumer, node.right) unless node.right.nil?
109+
end
110+
111+
def traverse_inorder(key_consumer, node=root)
112+
return if node.nil?
113+
traverse_inorder(key_consumer, node.left) unless node.left.nil?
114+
key_consumer.call(node.key)
115+
traverse_inorder(key_consumer, node.right) unless node.right.nil?
116+
end
117+
118+
def traverse_postorder(key_consumer, node=root)
119+
return if node.nil?
120+
traverse_postorder(key_consumer, node.left) unless node.left.nil?
121+
traverse_postorder(key_consumer, node.right) unless node.right.nil?
122+
key_consumer.call(node.key)
123+
end
124+
125+
def to_array(visit_traversal=:traverse_preorder)
126+
visited = []
127+
method(visit_traversal).call(->(key) { visited.append(key) })
128+
visited
129+
end
130+
131+
private
132+
def min_key_node(node=root)
133+
return nil if node.nil?
134+
until node.left.nil?
135+
node = node.left
136+
end
137+
node
138+
end
139+
140+
def max_key_node(node=root)
141+
return nil if node.nil?
142+
until node.right.nil?
143+
node = node.right
144+
end
145+
node
146+
end
147+
148+
def find_node_with_key(key)
149+
node = root
150+
until node.nil? || node.key == key
151+
node = key < node.key ? node.left : node.right
152+
end
153+
node
154+
end
155+
156+
def find_parent_of_node_with_key(key)
157+
return nil if root.nil? || root.key == key
158+
parent = root
159+
until parent.nil?
160+
if key < parent.key
161+
return nil if parent.left.nil?
162+
return parent if parent.left.key == key
163+
parent = parent.left
164+
else
165+
return nil if parent.right.nil?
166+
return parent if parent.right.key == key
167+
parent = parent.right
168+
end
169+
end
170+
nil
171+
end
172+
173+
def adjusted_subtree_after_deletion(left, right)
174+
return right if left.nil?
175+
return left if right.nil?
176+
if right.left.nil?
177+
right.left = left
178+
left.parent = right
179+
return right
180+
end
181+
successor_parent = right
182+
until successor_parent.left.left.nil?
183+
successor_parent = successor_parent.left
184+
end
185+
successor = successor_parent.left
186+
successor_parent.left = successor.right
187+
successor.right.parent = successor_parent unless successor.right.nil?
188+
successor.right = right
189+
right.parent = successor
190+
successor.left = left
191+
left.parent = successor
192+
successor
193+
end
194+
195+
def balance(node)
196+
return if node.nil?
197+
left_height = node.left&.height || 0
198+
right_height = node.right&.height || 0
199+
# Assumption: the subtrees rooted at `node.left` and `node.right` are balanced
200+
adjust_height(node)
201+
if right_height - left_height > 1
202+
# `node` is right-heavy
203+
if !node.right.left.nil? && (node.right.left.height || 0) > (node.right.right&.height || 0)
204+
rotate_right_left(node)
205+
else
206+
rotate_left(node)
207+
end
208+
elsif left_height - right_height > 1
209+
# `node` is left-heavy
210+
if !node.left.right.nil? && (node.left.right.height || 0) > (node.left.left&.height || 0)
211+
rotate_left_right(node)
212+
else
213+
rotate_right(node)
214+
end
215+
end
216+
217+
balance(node.parent)
218+
end
219+
220+
def rotate_left(node)
221+
new_root = node.right
222+
if node == root
223+
@root = new_root
224+
elsif node.parent.left == node
225+
node.parent.left = new_root
226+
else
227+
node.parent.right = new_root
228+
end
229+
new_root.parent = node.parent
230+
if new_root.left.nil?
231+
node.right = nil
232+
new_root.left = node
233+
node.parent = new_root
234+
else
235+
node.right = new_root.left
236+
new_root.left.parent = node
237+
new_root.left = node
238+
node.parent = new_root
239+
end
240+
adjust_height(node)
241+
adjust_height(new_root)
242+
end
243+
244+
def rotate_right(node)
245+
new_root = node.left
246+
if node == root
247+
@root = new_root
248+
elsif node.parent.left == node
249+
node.parent.left = new_root
250+
else
251+
node.parent.right = new_root
252+
end
253+
new_root.parent = node.parent
254+
if new_root.right.nil?
255+
node.left = nil
256+
new_root.right = node
257+
node.parent = new_root
258+
else
259+
node.left = new_root.right
260+
new_root.right.parent = node
261+
new_root.right = node
262+
node.parent = new_root
263+
end
264+
adjust_height(node)
265+
adjust_height(new_root)
266+
end
267+
268+
def rotate_right_left(node)
269+
rotate_right(node.right)
270+
rotate_left(node)
271+
end
272+
273+
def rotate_left_right(node)
274+
rotate_left(node.left)
275+
rotate_right(node)
276+
end
277+
278+
def adjust_height(node)
279+
node.height = 1 + [node.left&.height || 0, node.right&.height || 0].max
280+
end
281+
end

0 commit comments

Comments
 (0)