mirror of
https://github.com/TheAlgorithms/Ruby
synced 2024-12-25 21:58:57 +01:00
Merge pull request #200 from aparibocci/feature/avl_tree
Adding `AVL Tree` implementation
This commit is contained in:
commit
a2730e24c2
2 changed files with 413 additions and 0 deletions
281
data_structures/binary_trees/avl_tree.rb
Normal file
281
data_structures/binary_trees/avl_tree.rb
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
class AvlTreeNode
|
||||||
|
|
||||||
|
attr_reader :key
|
||||||
|
attr_accessor :parent
|
||||||
|
attr_accessor :left
|
||||||
|
attr_accessor :right
|
||||||
|
attr_accessor :height
|
||||||
|
|
||||||
|
def initialize(key, parent=nil)
|
||||||
|
@key = key
|
||||||
|
@parent = parent
|
||||||
|
@height = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# This class represents an AVL tree (a self-balancing binary search tree) 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.
|
||||||
|
#
|
||||||
|
# Due to self-balancing upon key insertion and deletion, the main operations of this data structure
|
||||||
|
# (insertion, deletion, membership) run - in worst case - in O(log(n)), where n is the number of nodes in the tree.
|
||||||
|
|
||||||
|
class AvlTree
|
||||||
|
|
||||||
|
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 = AvlTreeNode.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 AvlTree") unless parent.left.nil?
|
||||||
|
parent.left = AvlTreeNode.new(key, parent)
|
||||||
|
else
|
||||||
|
raise ArgumentError.new("Key #{key} is already present in the AvlTree") unless parent.right.nil?
|
||||||
|
parent.right = AvlTreeNode.new(key, parent)
|
||||||
|
end
|
||||||
|
balance(parent)
|
||||||
|
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)
|
||||||
|
root.parent = nil
|
||||||
|
balance(root.right.nil? ? root : root.right)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if key < parent.key
|
||||||
|
node = parent.left
|
||||||
|
parent.left = adjusted_subtree_after_deletion(node.left, node.right)
|
||||||
|
unless parent.left.nil?
|
||||||
|
parent.left.parent = parent
|
||||||
|
balance(parent.left.right.nil? ? parent.left : parent.left.right)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
node = parent.right
|
||||||
|
parent.right = adjusted_subtree_after_deletion(node.left, node.right)
|
||||||
|
unless parent.right.nil?
|
||||||
|
parent.right.parent = parent
|
||||||
|
balance(parent.right.right.nil? ? parent.right : parent.right.right)
|
||||||
|
end
|
||||||
|
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
|
||||||
|
left.parent = right
|
||||||
|
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.parent = successor_parent unless successor.right.nil?
|
||||||
|
successor.right = right
|
||||||
|
right.parent = successor
|
||||||
|
successor.left = left
|
||||||
|
left.parent = successor
|
||||||
|
successor
|
||||||
|
end
|
||||||
|
|
||||||
|
def balance(node)
|
||||||
|
return if node.nil?
|
||||||
|
left_height = node.left&.height || 0
|
||||||
|
right_height = node.right&.height || 0
|
||||||
|
# Assumption: the subtrees rooted at `node.left` and `node.right` are balanced
|
||||||
|
adjust_height(node)
|
||||||
|
if right_height - left_height > 1
|
||||||
|
# `node` is right-heavy
|
||||||
|
if !node.right.left.nil? && (node.right.left.height || 0) > (node.right.right&.height || 0)
|
||||||
|
rotate_right_left(node)
|
||||||
|
else
|
||||||
|
rotate_left(node)
|
||||||
|
end
|
||||||
|
elsif left_height - right_height > 1
|
||||||
|
# `node` is left-heavy
|
||||||
|
if !node.left.right.nil? && (node.left.right.height || 0) > (node.left.left&.height || 0)
|
||||||
|
rotate_left_right(node)
|
||||||
|
else
|
||||||
|
rotate_right(node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
balance(node.parent)
|
||||||
|
end
|
||||||
|
|
||||||
|
def rotate_left(node)
|
||||||
|
new_root = node.right
|
||||||
|
if node == root
|
||||||
|
@root = new_root
|
||||||
|
elsif node.parent.left == node
|
||||||
|
node.parent.left = new_root
|
||||||
|
else
|
||||||
|
node.parent.right = new_root
|
||||||
|
end
|
||||||
|
new_root.parent = node.parent
|
||||||
|
if new_root.left.nil?
|
||||||
|
node.right = nil
|
||||||
|
new_root.left = node
|
||||||
|
node.parent = new_root
|
||||||
|
else
|
||||||
|
node.right = new_root.left
|
||||||
|
new_root.left.parent = node
|
||||||
|
new_root.left = node
|
||||||
|
node.parent = new_root
|
||||||
|
end
|
||||||
|
adjust_height(node)
|
||||||
|
adjust_height(new_root)
|
||||||
|
end
|
||||||
|
|
||||||
|
def rotate_right(node)
|
||||||
|
new_root = node.left
|
||||||
|
if node == root
|
||||||
|
@root = new_root
|
||||||
|
elsif node.parent.left == node
|
||||||
|
node.parent.left = new_root
|
||||||
|
else
|
||||||
|
node.parent.right = new_root
|
||||||
|
end
|
||||||
|
new_root.parent = node.parent
|
||||||
|
if new_root.right.nil?
|
||||||
|
node.left = nil
|
||||||
|
new_root.right = node
|
||||||
|
node.parent = new_root
|
||||||
|
else
|
||||||
|
node.left = new_root.right
|
||||||
|
new_root.right.parent = node
|
||||||
|
new_root.right = node
|
||||||
|
node.parent = new_root
|
||||||
|
end
|
||||||
|
adjust_height(node)
|
||||||
|
adjust_height(new_root)
|
||||||
|
end
|
||||||
|
|
||||||
|
def rotate_right_left(node)
|
||||||
|
rotate_right(node.right)
|
||||||
|
rotate_left(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
def rotate_left_right(node)
|
||||||
|
rotate_left(node.left)
|
||||||
|
rotate_right(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
def adjust_height(node)
|
||||||
|
node.height = 1 + [node.left&.height || 0, node.right&.height || 0].max
|
||||||
|
end
|
||||||
|
end
|
132
data_structures/binary_trees/avl_tree_test.rb
Normal file
132
data_structures/binary_trees/avl_tree_test.rb
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
require 'minitest/autorun'
|
||||||
|
require_relative 'avl_tree'
|
||||||
|
|
||||||
|
class TestAvlTree < Minitest::Test
|
||||||
|
def test_default_constructor_creates_empty_tree
|
||||||
|
tree = AvlTree.new
|
||||||
|
assert tree.to_array.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_default_constructor_creates_tree_with_given_keys
|
||||||
|
tree = AvlTree.new([1, 2, 3, 4, 5, 6, 7])
|
||||||
|
assert tree.to_array == [4, 2, 1, 3, 6, 5, 7]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_exception_when_inserting_key_already_present
|
||||||
|
tree = AvlTree.new([4, 2, 6, 3, 1])
|
||||||
|
assert_raises ArgumentError do
|
||||||
|
tree.insert_key(6)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_size_returns_zero_given_empty_tree
|
||||||
|
tree = AvlTree.new
|
||||||
|
assert tree.size == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_empty_returns_number_of_nodes_in_tree
|
||||||
|
tree = AvlTree.new([4, 2, 6, 3, 1])
|
||||||
|
assert tree.size == 5
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_empty_returns_true_given_empty_tree
|
||||||
|
tree = AvlTree.new
|
||||||
|
assert tree.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_empty_returns_false_given_non_empty_tree
|
||||||
|
tree = AvlTree.new([1])
|
||||||
|
assert !tree.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_min_key_returns_minimum_key
|
||||||
|
tree = AvlTree.new([4, 2, 6, 3, 1])
|
||||||
|
assert tree.min_key == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_max_key_returns_maximum_key
|
||||||
|
tree = AvlTree.new([4, 2, 6, 3, 1])
|
||||||
|
assert tree.max_key == 6
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_contains_key_returns_true_if_key_in_tree
|
||||||
|
tree = AvlTree.new([4, 2, 6, 3, 1])
|
||||||
|
assert tree.contains_key?(3)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_contains_key_returns_false_if_key_not_in_tree
|
||||||
|
tree = AvlTree.new([4, 2, 6, 3, 1])
|
||||||
|
assert !tree.contains_key?(7)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_delete_key_does_nothing_if_key_not_in_tree
|
||||||
|
tree = AvlTree.new([4, 2, 6, 3, 1])
|
||||||
|
tree.delete_key(7)
|
||||||
|
assert tree.to_array == [4, 2, 1, 3, 6]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_delete_key_keeps_avl_property_if_leaf_node
|
||||||
|
tree = AvlTree.new([1, 2, 3, 4, 5, 6, 7])
|
||||||
|
tree.delete_key(3)
|
||||||
|
assert tree.to_array == [4, 2, 1, 6, 5, 7]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_delete_key_keeps_avl_property_if_node_with_left_child
|
||||||
|
tree = AvlTree.new([4, 2, 5, 1])
|
||||||
|
tree.delete_key(2)
|
||||||
|
assert tree.to_array == [4, 1, 5]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_delete_key_keeps_avl_property_if_node_with_right_child
|
||||||
|
tree = AvlTree.new([4, 2, 5, 6])
|
||||||
|
tree.delete_key(5)
|
||||||
|
assert tree.to_array == [4, 2, 6]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_delete_key_keeps_avl_property_if_node_with_both_children
|
||||||
|
tree = AvlTree.new([1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||||
|
tree.delete_key(4)
|
||||||
|
assert tree.to_array == [5, 2, 1, 3, 8, 6, 7, 9]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preorder_traversal_uses_expected_order
|
||||||
|
tree = AvlTree.new([1, 2, 3, 4, 5, 6, 7])
|
||||||
|
visited = []
|
||||||
|
tree.traverse_preorder(->(key) { visited.append(key) })
|
||||||
|
assert visited == [4, 2, 1, 3, 6, 5, 7]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_inorder_traversal_uses_expected_order
|
||||||
|
tree = AvlTree.new([1, 2, 3, 4, 5, 6, 7])
|
||||||
|
visited = []
|
||||||
|
tree.traverse_inorder(->(key) { visited.append(key) })
|
||||||
|
assert visited == [1, 2, 3, 4, 5, 6, 7]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_postorder_traversal_uses_expected_order
|
||||||
|
tree = AvlTree.new([1, 2, 3, 4, 5, 6, 7])
|
||||||
|
visited = []
|
||||||
|
tree.traverse_postorder(->(key) { visited.append(key) })
|
||||||
|
assert visited == [1, 3, 2, 5, 7, 6, 4]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_left_rotation
|
||||||
|
tree = AvlTree.new([1, 2, 3])
|
||||||
|
assert tree.to_array == [2, 1, 3]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_right_rotation
|
||||||
|
tree = AvlTree.new([3, 2, 1])
|
||||||
|
assert tree.to_array == [2, 1, 3]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_right_left_rotation
|
||||||
|
tree = AvlTree.new([1, 3, 2])
|
||||||
|
assert tree.to_array == [2, 1, 3]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_left_right_rotation
|
||||||
|
tree = AvlTree.new([3, 1, 2])
|
||||||
|
assert tree.to_array == [2, 1, 3]
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue