Skip to content

Commit 74f8c49

Browse files
committed
Adding BinarySearchTree implementation
1 parent e2f6bd9 commit 74f8c49

File tree

2 files changed

+288
-0
lines changed

2 files changed

+288
-0
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
class BinarySearchTreeNode
2+
3+
attr_reader :key
4+
attr_accessor :left
5+
attr_accessor :right
6+
7+
def initialize(key)
8+
@key = key
9+
end
10+
end
11+
12+
##
13+
# This class represents a binary search tree (not implementing self-balancing) with distinct node keys.
14+
# Starting from the root, every node has up to two children (one left and one right child node).
15+
#
16+
# For the BST property:
17+
# - the keys of nodes in the left subtree of a node are strictly less than the key of the node;
18+
# - the keys of nodes in the right subtree of a node are strictly greater than the key of the node.
19+
#
20+
# The main operations of this data structure (insertion, deletion, membership) run - in worst case - in O(n),
21+
# where n is the number of nodes in the tree.
22+
# The average case for those operations is O(log(n)) due to the structure of the tree.
23+
24+
class BinarySearchTree
25+
26+
attr_reader :size
27+
attr_accessor :root
28+
29+
def initialize(keys=[])
30+
@size = 0
31+
keys.each {|key| insert_key(key) }
32+
end
33+
34+
def empty?
35+
size == 0
36+
end
37+
38+
def insert_key(key)
39+
@size += 1
40+
if root.nil?
41+
@root = BinarySearchTreeNode.new(key)
42+
return
43+
end
44+
parent = root
45+
while (key < parent.key && !parent.left.nil? && parent.left.key != key) ||
46+
(key > parent.key && !parent.right.nil? && parent.right.key != key)
47+
parent = key < parent.key ? parent.left : parent.right
48+
end
49+
if key < parent.key
50+
raise ArgumentError.new("Key #{key} is already present in the BinarySearchTree") unless parent.left.nil?
51+
parent.left = BinarySearchTreeNode.new(key)
52+
else
53+
raise ArgumentError.new("Key #{key} is already present in the BinarySearchTree") unless parent.right.nil?
54+
parent.right = BinarySearchTreeNode.new(key)
55+
end
56+
end
57+
58+
def min_key(node=root)
59+
return nil if node.nil?
60+
min_key_node(node).key
61+
end
62+
63+
def max_key(node=root)
64+
return nil if node.nil?
65+
max_key_node(node).key
66+
end
67+
68+
def contains_key?(key)
69+
!find_node_with_key(key).nil?
70+
end
71+
72+
def delete_key(key)
73+
parent = find_parent_of_node_with_key(key)
74+
if parent.nil?
75+
return if root.nil? || root.key != key
76+
@size -= 1
77+
@root = adjusted_subtree_after_deletion(root.left, root.right)
78+
return
79+
end
80+
if key < parent.key
81+
node = parent.left
82+
parent.left = adjusted_subtree_after_deletion(node.left, node.right)
83+
else
84+
node = parent.right
85+
parent.right = adjusted_subtree_after_deletion(node.left, node.right)
86+
end
87+
@size -= 1
88+
end
89+
90+
def traverse_preorder(key_consumer, node=root)
91+
return if node.nil?
92+
key_consumer.call(node.key)
93+
traverse_preorder(key_consumer, node.left) unless node.left.nil?
94+
traverse_preorder(key_consumer, node.right) unless node.right.nil?
95+
end
96+
97+
def traverse_inorder(key_consumer, node=root)
98+
return if node.nil?
99+
traverse_inorder(key_consumer, node.left) unless node.left.nil?
100+
key_consumer.call(node.key)
101+
traverse_inorder(key_consumer, node.right) unless node.right.nil?
102+
end
103+
104+
def traverse_postorder(key_consumer, node=root)
105+
return if node.nil?
106+
traverse_postorder(key_consumer, node.left) unless node.left.nil?
107+
traverse_postorder(key_consumer, node.right) unless node.right.nil?
108+
key_consumer.call(node.key)
109+
end
110+
111+
def to_array(visit_traversal=:traverse_preorder)
112+
visited = []
113+
method(visit_traversal).call(->(key) { visited.append(key) })
114+
visited
115+
end
116+
117+
private
118+
def min_key_node(node=root)
119+
return nil if node.nil?
120+
until node.left.nil?
121+
node = node.left
122+
end
123+
node
124+
end
125+
126+
def max_key_node(node=root)
127+
return nil if node.nil?
128+
until node.right.nil?
129+
node = node.right
130+
end
131+
node
132+
end
133+
134+
def find_node_with_key(key)
135+
node = root
136+
until node.nil? || node.key == key
137+
node = key < node.key ? node.left : node.right
138+
end
139+
node
140+
end
141+
142+
def find_parent_of_node_with_key(key)
143+
return nil if root.nil? || root.key == key
144+
parent = root
145+
until parent.nil?
146+
if key < parent.key
147+
return nil if parent.left.nil?
148+
return parent if parent.left.key == key
149+
parent = parent.left
150+
else
151+
return nil if parent.right.nil?
152+
return parent if parent.right.key == key
153+
parent = parent.right
154+
end
155+
end
156+
nil
157+
end
158+
159+
def adjusted_subtree_after_deletion(left, right)
160+
return right if left.nil?
161+
return left if right.nil?
162+
if right.left.nil?
163+
right.left = left
164+
return right
165+
end
166+
successor_parent = right
167+
until successor_parent.left.left.nil?
168+
successor_parent = successor_parent.left
169+
end
170+
successor = successor_parent.left
171+
successor_parent.left = successor.right
172+
successor.right = right
173+
successor.left = left
174+
successor
175+
end
176+
end
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
require 'minitest/autorun'
2+
require_relative 'bst'
3+
4+
class TestBinarySearchTree < Minitest::Test
5+
def test_default_constructor_creates_empty_tree
6+
bst = BinarySearchTree.new
7+
assert bst.to_array.empty?
8+
end
9+
10+
def test_default_constructor_creates_tree_with_given_keys
11+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
12+
assert bst.to_array == [4, 2, 1, 3, 6]
13+
end
14+
15+
def test_exception_when_inserting_key_already_present
16+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
17+
assert_raises ArgumentError do
18+
bst.insert_key(6)
19+
end
20+
end
21+
22+
def test_size_returns_zero_given_empty_tree
23+
bst = BinarySearchTree.new
24+
assert bst.size == 0
25+
end
26+
27+
def test_empty_returns_number_of_nodes_in_tree
28+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
29+
assert bst.size == 5
30+
end
31+
32+
def test_empty_returns_true_given_empty_tree
33+
bst = BinarySearchTree.new
34+
assert bst.empty?
35+
end
36+
37+
def test_empty_returns_false_given_non_empty_tree
38+
bst = BinarySearchTree.new([1])
39+
assert !bst.empty?
40+
end
41+
42+
def test_min_key_returns_minimum_key
43+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
44+
assert bst.min_key == 1
45+
end
46+
47+
def test_max_key_returns_maximum_key
48+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
49+
assert bst.max_key == 6
50+
end
51+
52+
def test_contains_key_returns_true_if_key_in_tree
53+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
54+
assert bst.contains_key?(3)
55+
end
56+
57+
def test_contains_key_returns_false_if_key_not_in_tree
58+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
59+
assert !bst.contains_key?(7)
60+
end
61+
62+
def test_delete_key_does_nothing_if_key_not_in_tree
63+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
64+
bst.delete_key(7)
65+
assert bst.to_array == [4, 2, 1, 3, 6]
66+
end
67+
68+
def test_delete_key_keeps_bst_property_if_leaf_node
69+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
70+
bst.delete_key(1)
71+
assert bst.to_array == [4, 2, 3, 6]
72+
end
73+
74+
def test_delete_key_keeps_bst_property_if_node_with_left_child
75+
bst = BinarySearchTree.new([4, 2, 3, 1])
76+
bst.delete_key(4)
77+
assert bst.to_array == [2, 1, 3]
78+
end
79+
80+
def test_delete_key_keeps_bst_property_if_node_with_right_child
81+
bst = BinarySearchTree.new([4, 2, 6, 3])
82+
bst.delete_key(2)
83+
assert bst.to_array == [4, 3, 6]
84+
end
85+
86+
def test_delete_key_keeps_bst_property_if_node_with_both_children
87+
bst = BinarySearchTree.new([4, 2, 7, 3, 1, 5, 10, 6])
88+
bst.delete_key(4)
89+
assert bst.to_array == [5, 2, 1, 3, 7, 6, 10]
90+
end
91+
92+
def test_preorder_traversal_uses_expected_order
93+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
94+
visited = []
95+
bst.traverse_preorder(->(key) { visited.append(key) })
96+
assert visited == [4, 2, 1, 3, 6]
97+
end
98+
99+
def test_inorder_traversal_uses_expected_order
100+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
101+
visited = []
102+
bst.traverse_inorder(->(key) { visited.append(key) })
103+
assert visited == [1, 2, 3, 4, 6]
104+
end
105+
106+
def test_postorder_traversal_uses_expected_order
107+
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
108+
visited = []
109+
bst.traverse_postorder(->(key) { visited.append(key) })
110+
assert visited == [1, 3, 2, 6, 4]
111+
end
112+
end

0 commit comments

Comments
 (0)