Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions data_structures/binary_trees/bst.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
class BinarySearchTreeNode

attr_reader :key
attr_accessor :left
attr_accessor :right

def initialize(key)
@key = key
end
end

##
# This class represents a binary search tree (not implementing self-balancing) with distinct node keys.
# Starting from the root, every node has up to two children (one left and one right child node).
#
# For the BST property:
# - the keys of nodes in the left subtree of a node are strictly less than the key of the node;
# - the keys of nodes in the right subtree of a node are strictly greater than the key of the node.
#
# The main operations of this data structure (insertion, deletion, membership) run - in worst case - in O(n),
# where n is the number of nodes in the tree.
# The average case for those operations is O(log(n)) due to the structure of the tree.

class BinarySearchTree

attr_reader :size
attr_accessor :root

def initialize(keys=[])
@size = 0
keys.each {|key| insert_key(key) }
end

def empty?
size == 0
end

def insert_key(key)
@size += 1
if root.nil?
@root = BinarySearchTreeNode.new(key)
return
end
parent = root
while (key < parent.key && !parent.left.nil? && parent.left.key != key) ||
(key > parent.key && !parent.right.nil? && parent.right.key != key)
parent = key < parent.key ? parent.left : parent.right
end
if key < parent.key
raise ArgumentError.new("Key #{key} is already present in the BinarySearchTree") unless parent.left.nil?
parent.left = BinarySearchTreeNode.new(key)
else
raise ArgumentError.new("Key #{key} is already present in the BinarySearchTree") unless parent.right.nil?
parent.right = BinarySearchTreeNode.new(key)
end
end

def min_key(node=root)
return nil if node.nil?
min_key_node(node).key
end

def max_key(node=root)
return nil if node.nil?
max_key_node(node).key
end

def contains_key?(key)
!find_node_with_key(key).nil?
end

def delete_key(key)
parent = find_parent_of_node_with_key(key)
if parent.nil?
return if root.nil? || root.key != key
@size -= 1
@root = adjusted_subtree_after_deletion(root.left, root.right)
return
end
if key < parent.key
node = parent.left
parent.left = adjusted_subtree_after_deletion(node.left, node.right)
else
node = parent.right
parent.right = adjusted_subtree_after_deletion(node.left, node.right)
end
@size -= 1
end

def traverse_preorder(key_consumer, node=root)
return if node.nil?
key_consumer.call(node.key)
traverse_preorder(key_consumer, node.left) unless node.left.nil?
traverse_preorder(key_consumer, node.right) unless node.right.nil?
end

def traverse_inorder(key_consumer, node=root)
return if node.nil?
traverse_inorder(key_consumer, node.left) unless node.left.nil?
key_consumer.call(node.key)
traverse_inorder(key_consumer, node.right) unless node.right.nil?
end

def traverse_postorder(key_consumer, node=root)
return if node.nil?
traverse_postorder(key_consumer, node.left) unless node.left.nil?
traverse_postorder(key_consumer, node.right) unless node.right.nil?
key_consumer.call(node.key)
end

def to_array(visit_traversal=:traverse_preorder)
visited = []
method(visit_traversal).call(->(key) { visited.append(key) })
visited
end

private
def min_key_node(node=root)
return nil if node.nil?
until node.left.nil?
node = node.left
end
node
end

def max_key_node(node=root)
return nil if node.nil?
until node.right.nil?
node = node.right
end
node
end

def find_node_with_key(key)
node = root
until node.nil? || node.key == key
node = key < node.key ? node.left : node.right
end
node
end

def find_parent_of_node_with_key(key)
return nil if root.nil? || root.key == key
parent = root
until parent.nil?
if key < parent.key
return nil if parent.left.nil?
return parent if parent.left.key == key
parent = parent.left
else
return nil if parent.right.nil?
return parent if parent.right.key == key
parent = parent.right
end
end
nil
end

def adjusted_subtree_after_deletion(left, right)
return right if left.nil?
return left if right.nil?
if right.left.nil?
right.left = left
return right
end
successor_parent = right
until successor_parent.left.left.nil?
successor_parent = successor_parent.left
end
successor = successor_parent.left
successor_parent.left = successor.right
successor.right = right
successor.left = left
successor
end
end
112 changes: 112 additions & 0 deletions data_structures/binary_trees/bst_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
require 'minitest/autorun'
require_relative 'bst'

class TestBinarySearchTree < Minitest::Test
def test_default_constructor_creates_empty_tree
bst = BinarySearchTree.new
assert bst.to_array.empty?
end

def test_default_constructor_creates_tree_with_given_keys
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
assert bst.to_array == [4, 2, 1, 3, 6]
end

def test_exception_when_inserting_key_already_present
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
assert_raises ArgumentError do
bst.insert_key(6)
end
end

def test_size_returns_zero_given_empty_tree
bst = BinarySearchTree.new
assert bst.size == 0
end

def test_empty_returns_number_of_nodes_in_tree
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
assert bst.size == 5
end

def test_empty_returns_true_given_empty_tree
bst = BinarySearchTree.new
assert bst.empty?
end

def test_empty_returns_false_given_non_empty_tree
bst = BinarySearchTree.new([1])
assert !bst.empty?
end

def test_min_key_returns_minimum_key
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
assert bst.min_key == 1
end

def test_max_key_returns_maximum_key
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
assert bst.max_key == 6
end

def test_contains_key_returns_true_if_key_in_tree
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
assert bst.contains_key?(3)
end

def test_contains_key_returns_false_if_key_not_in_tree
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
assert !bst.contains_key?(7)
end

def test_delete_key_does_nothing_if_key_not_in_tree
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
bst.delete_key(7)
assert bst.to_array == [4, 2, 1, 3, 6]
end

def test_delete_key_keeps_bst_property_if_leaf_node
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
bst.delete_key(1)
assert bst.to_array == [4, 2, 3, 6]
end

def test_delete_key_keeps_bst_property_if_node_with_left_child
bst = BinarySearchTree.new([4, 2, 3, 1])
bst.delete_key(4)
assert bst.to_array == [2, 1, 3]
end

def test_delete_key_keeps_bst_property_if_node_with_right_child
bst = BinarySearchTree.new([4, 2, 6, 3])
bst.delete_key(2)
assert bst.to_array == [4, 3, 6]
end

def test_delete_key_keeps_bst_property_if_node_with_both_children
bst = BinarySearchTree.new([4, 2, 7, 3, 1, 5, 10, 6])
bst.delete_key(4)
assert bst.to_array == [5, 2, 1, 3, 7, 6, 10]
end

def test_preorder_traversal_uses_expected_order
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
visited = []
bst.traverse_preorder(->(key) { visited.append(key) })
assert visited == [4, 2, 1, 3, 6]
end

def test_inorder_traversal_uses_expected_order
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
visited = []
bst.traverse_inorder(->(key) { visited.append(key) })
assert visited == [1, 2, 3, 4, 6]
end

def test_postorder_traversal_uses_expected_order
bst = BinarySearchTree.new([4, 2, 6, 3, 1])
visited = []
bst.traverse_postorder(->(key) { visited.append(key) })
assert visited == [1, 3, 2, 6, 4]
end
end