Translate all code to English (#1836)

* Review the EN heading format.

* Fix pythontutor headings.

* Fix pythontutor headings.

* bug fixes

* Fix headings in **/summary.md

* Revisit the CN-to-EN translation for Python code using Claude-4.5

* Revisit the CN-to-EN translation for Java code using Claude-4.5

* Revisit the CN-to-EN translation for Cpp code using Claude-4.5.

* Fix the dictionary.

* Fix cpp code translation for the multipart strings.

* Translate Go code to English.

* Update workflows to test EN code.

* Add EN translation for C.

* Add EN translation for CSharp.

* Add EN translation for Swift.

* Trigger the CI check.

* Revert.

* Update en/hash_map.md

* Add the EN version of Dart code.

* Add the EN version of Kotlin code.

* Add missing code files.

* Add the EN version of JavaScript code.

* Add the EN version of TypeScript code.

* Fix the workflows.

* Add the EN version of Ruby code.

* Add the EN version of Rust code.

* Update the CI check for the English version  code.

* Update Python CI check.

* Fix cmakelists for en/C code.

* Fix Ruby comments
This commit is contained in:
Yudong Jin
2025-12-31 07:44:52 +08:00
committed by GitHub
parent 45e1295241
commit 2778a6f9c7
1284 changed files with 71557 additions and 3275 deletions
@@ -0,0 +1,108 @@
=begin
File: array.rb
Created Time: 2024-03-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Random access element ###
def random_access(nums)
# Randomly select a number in the interval [0, nums.length)
random_index = Random.rand(0...nums.length)
# Retrieve and return the random element
nums[random_index]
end
### Extend array length ###
# Note: Ruby's Array is dynamic array, can be directly expanded
# For learning purposes, this function treats Array as fixed-length array
def extend(nums, enlarge)
# Initialize an array with extended length
res = Array.new(nums.length + enlarge, 0)
# Copy all elements from the original array to the new array
for i in 0...nums.length
res[i] = nums[i]
end
# Return the extended new array
res
end
### Insert element num at index in array ###
def insert(nums, num, index)
# Move all elements at and after index index backward by one position
for i in (nums.length - 1).downto(index + 1)
nums[i] = nums[i - 1]
end
# Assign num to the element at index index
nums[index] = num
end
### Delete element at index ###
def remove(nums, index)
# Move all elements after index index forward by one position
for i in index...(nums.length - 1)
nums[i] = nums[i + 1]
end
end
### Traverse array ###
def traverse(nums)
count = 0
# Traverse array by index
for i in 0...nums.length
count += nums[i]
end
# Direct traversal of array elements
for num in nums
count += num
end
end
### Find specified element in array ###
def find(nums, target)
for i in 0...nums.length
return i if nums[i] == target
end
-1
end
### Driver Code ###
if __FILE__ == $0
# Initialize array
arr = Array.new(5, 0)
puts "Array arr = #{arr}"
nums = [1, 3, 2, 5, 4]
puts "Array nums = #{nums}"
# Insert element
random_num = random_access(nums)
puts "Get random element #{random_num} from nums"
# Traverse array
nums = extend(nums, 3)
puts "Extend array length to 8, get nums = #{nums}"
# Insert element
insert(nums, 6, 3)
puts "Insert number 6 at index 3, get nums = #{nums}"
# Remove element
remove(nums, 2)
puts "Delete element at index 2, get nums = #{nums}"
# Traverse array
traverse(nums)
# Find element
index = find(nums, 3)
puts "Find element 3 in nums, index = #{index}"
end
@@ -0,0 +1,83 @@
=begin
File: linked_list.rb
Created Time: 2024-03-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/list_node'
require_relative '../utils/print_util'
### Insert node _p after node n0 in linked list ###
# Ruby's `p` is a built-in function, `P` is a constant, so use `_p` instead
def insert(n0, _p)
n1 = n0.next
_p.next = n1
n0.next = _p
end
### Delete first node after node n0 in linked list ###
def remove(n0)
return if n0.next.nil?
# n0 -> remove_node -> n1
remove_node = n0.next
n1 = remove_node.next
n0.next = n1
end
### Access node at index in linked list ###
def access(head, index)
for i in 0...index
return nil if head.nil?
head = head.next
end
head
end
### Find first node with value target in linked list ###
def find(head, target)
index = 0
while head
return index if head.val == target
head = head.next
index += 1
end
-1
end
### Driver Code ###
if __FILE__ == $0
# Initialize linked list
# Initialize each node
n0 = ListNode.new(1)
n1 = ListNode.new(3)
n2 = ListNode.new(2)
n3 = ListNode.new(5)
n4 = ListNode.new(4)
# Build references between nodes
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
puts "Initialized linked list is"
print_linked_list(n0)
# Insert node
insert(n0, ListNode.new(0))
print_linked_list n0
# Remove node
remove(n0)
puts "Linked list after removing node is"
print_linked_list(n0)
# Access node
node = access(n0, 3)
puts "Value of node at index 3 in linked list = #{node.val}"
# Search node
index = find(n0, 2)
puts "Index of node with value 2 in linked list = #{index}"
end
@@ -0,0 +1,60 @@
=begin
File: list.rb
Created Time: 2024-03-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Driver Code ###
if __FILE__ == $0
# Initialize list
nums = [1, 3, 2, 5, 4]
puts "List nums = #{nums}"
# Update element
num = nums[1]
puts "Access element at index 1, get num = #{num}"
# Add elements at the end
nums[1] = 0
puts "Update element at index 1 to 0, get nums = #{nums}"
# Remove element
nums.clear
puts "After clearing list, nums = #{nums}"
# Direct traversal of list elements
nums << 1
nums << 3
nums << 2
nums << 5
nums << 4
puts "After adding elements, nums = #{nums}"
# Sort list
nums.insert(3, 6)
puts "Insert element 6 at index 3, get nums = #{nums}"
# Remove element
nums.delete_at(3)
puts "Delete element at index 3, get nums = #{nums}"
# Traverse list by index
count = 0
for i in 0...nums.length
count += nums[i]
end
# Directly traverse list elements
count = 0
nums.each do |x|
count += x
end
# Concatenate two lists
nums1 = [6, 8, 7, 10, 9]
nums += nums1
puts "After concatenating list nums1 to nums, get nums = #{nums}"
nums = nums.sort { |a, b| a <=> b }
puts "After sorting list, nums = #{nums}"
end
@@ -0,0 +1,132 @@
=begin
File: my_list.rb
Created Time: 2024-03-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### List class ###
class MyList
attr_reader :size # Get list length (current number of elements)
attr_reader :capacity # Get list capacity
### Constructor ###
def initialize
@capacity = 10
@size = 0
@extend_ratio = 2
@arr = Array.new(capacity)
end
### Access element ###
def get(index)
# If the index is out of bounds, throw an exception, as below
raise IndexError, "Index out of bounds" if index < 0 || index >= size
@arr[index]
end
### Access element ###
def set(index, num)
raise IndexError, "Index out of bounds" if index < 0 || index >= size
@arr[index] = num
end
### Add element at end ###
def add(num)
# When the number of elements exceeds capacity, trigger the extension mechanism
extend_capacity if size == capacity
@arr[size] = num
# Update the number of elements
@size += 1
end
### Insert element in middle ###
def insert(index, num)
raise IndexError, "Index out of bounds" if index < 0 || index >= size
# When the number of elements exceeds capacity, trigger the extension mechanism
extend_capacity if size == capacity
# Move all elements after index index forward by one position
for j in (size - 1).downto(index)
@arr[j + 1] = @arr[j]
end
@arr[index] = num
# Update the number of elements
@size += 1
end
### Delete element ###
def remove(index)
raise IndexError, "Index out of bounds" if index < 0 || index >= size
num = @arr[index]
# Move all elements after index forward by one position
for j in index...size
@arr[j] = @arr[j + 1]
end
# Update the number of elements
@size -= 1
# Return the removed element
num
end
### Expand list capacity ###
def extend_capacity
# Create new array with length extend_ratio times original, copy original array to new array
arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1))
# Add elements at the end
@capacity = arr.length
end
### Convert list to array ###
def to_array
sz = size
# Elements enqueue
arr = Array.new(sz)
for i in 0...sz
arr[i] = get(i)
end
arr
end
end
### Driver Code ###
if __FILE__ == $0
# Initialize list
nums = MyList.new
# Direct traversal of list elements
nums.add(1)
nums.add(3)
nums.add(2)
nums.add(5)
nums.add(4)
puts "List nums = #{nums.to_array}, capacity = #{nums.capacity}, length = #{nums.size}"
# Sort list
nums.insert(3, 6)
puts "Insert number 6 at index 3, get nums = #{nums.to_array}"
# Remove element
nums.remove(3)
puts "Delete element at index 3, get nums = #{nums.to_array}"
# Update element
num = nums.get(1)
puts "Access element at index 1, get num = #{num}"
# Add elements at the end
nums.set(1, 0)
puts "Update element at index 1 to 0, get nums = #{nums.to_array}"
# Test capacity expansion mechanism
for i in 0...10
# At i = 5, the list length will exceed the list capacity, triggering the expansion mechanism
nums.add(i)
end
puts "After expansion, list nums = #{nums.to_array}, capacity = #{nums.capacity}, length = #{nums.size}"
end
@@ -0,0 +1,61 @@
=begin
File: n_queens.rb
Created Time: 2024-05-21
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Backtracking: n queens ###
def backtrack(row, n, state, res, cols, diags1, diags2)
# When all rows are placed, record the solution
if row == n
res << state.map { |row| row.dup }
return
end
# Traverse all columns
for col in 0...n
# Calculate the main diagonal and anti-diagonal corresponding to this cell
diag1 = row - col + n - 1
diag2 = row + col
# Pruning: do not allow queens to exist in the column, main diagonal, and anti-diagonal of this cell
if !cols[col] && !diags1[diag1] && !diags2[diag2]
# Attempt: place the queen in this cell
state[row][col] = "Q"
cols[col] = diags1[diag1] = diags2[diag2] = true
# Place the next row
backtrack(row + 1, n, state, res, cols, diags1, diags2)
# Backtrack: restore this cell to an empty cell
state[row][col] = "#"
cols[col] = diags1[diag1] = diags2[diag2] = false
end
end
end
### Solve n queens ###
def n_queens(n)
# Initialize an n*n chessboard, where 'Q' represents a queen and '#' represents an empty cell
state = Array.new(n) { Array.new(n, "#") }
cols = Array.new(n, false) # Record whether there is a queen in the column
diags1 = Array.new(2 * n - 1, false) # Record whether there is a queen on the main diagonal
diags2 = Array.new(2 * n - 1, false) # Record whether there is a queen on the anti-diagonal
res = []
backtrack(0, n, state, res, cols, diags1, diags2)
res
end
### Driver Code ###
if __FILE__ == $0
n = 4
res = n_queens(n)
puts "Input board size is #{n}"
puts "Total queen placement solutions: #{res.length}"
for state in res
puts "--------------------"
for row in state
p row
end
end
end
@@ -0,0 +1,46 @@
=begin
File: permutations_i.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Backtracking: permutations I ###
def backtrack(state, choices, selected, res)
# When the state length equals the number of elements, record the solution
if state.length == choices.length
res << state.dup
return
end
# Traverse all choices
choices.each_with_index do |choice, i|
# Pruning: do not allow repeated selection of elements
unless selected[i]
# Attempt: make choice, update state
selected[i] = true
state << choice
# Proceed to the next round of selection
backtrack(state, choices, selected, res)
# Backtrack: undo choice, restore to previous state
selected[i] = false
state.pop
end
end
end
### Permutations I ###
def permutations_i(nums)
res = []
backtrack([], nums, Array.new(nums.length, false), res)
res
end
### Driver Code ###
if __FILE__ == $0
nums = [1, 2, 3]
res = permutations_i(nums)
puts "Input array nums = #{nums}"
puts "All permutations res = #{res}"
end
@@ -0,0 +1,48 @@
=begin
File: permutations_ii.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Backtracking: permutations II ###
def backtrack(state, choices, selected, res)
# When the state length equals the number of elements, record the solution
if state.length == choices.length
res << state.dup
return
end
# Traverse all choices
duplicated = Set.new
choices.each_with_index do |choice, i|
# Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements
if !selected[i] && !duplicated.include?(choice)
# Attempt: make choice, update state
duplicated.add(choice)
selected[i] = true
state << choice
# Proceed to the next round of selection
backtrack(state, choices, selected, res)
# Backtrack: undo choice, restore to previous state
selected[i] = false
state.pop
end
end
end
### Permutations II ###
def permutations_ii(nums)
res = []
backtrack([], nums, Array.new(nums.length, false), res)
res
end
### Driver Code ###
if __FILE__ == $0
nums = [1, 2, 2]
res = permutations_ii(nums)
puts "Input array nums = #{nums}"
puts "All permutations res = #{res}"
end
@@ -0,0 +1,33 @@
=begin
File: preorder_traversal_i_compact.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Pre-order traversal: example 1 ###
def pre_order(root)
return unless root
# Record solution
$res << root if root.val == 7
pre_order(root.left)
pre_order(root.right)
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\nInitialize binary tree"
print_tree(root)
# Preorder traversal
$res = []
pre_order(root)
puts "\nOutput all nodes with value 7"
p $res.map { |node| node.val }
end
@@ -0,0 +1,41 @@
=begin
File: preorder_traversal_ii_compact.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Pre-order traversal: example 2 ###
def pre_order(root)
return unless root
# Attempt
$path << root
# Record solution
$res << $path.dup if root.val == 7
pre_order(root.left)
pre_order(root.right)
# Backtrack
$path.pop
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\nInitialize binary tree"
print_tree(root)
# Preorder traversal
$path, $res = [], []
pre_order(root)
puts "\nOutput all paths from root node to node 7"
for path in $res
p path.map { |node| node.val }
end
end
@@ -0,0 +1,42 @@
=begin
File: preorder_traversal_iii_compact.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Pre-order traversal: example 3 ###
def pre_order(root)
# Pruning
return if !root || root.val == 3
# Attempt
$path.append(root)
# Record solution
$res << $path.dup if root.val == 7
pre_order(root.left)
pre_order(root.right)
# Backtrack
$path.pop
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\nInitialize binary tree"
print_tree(root)
# Preorder traversal
$path, $res = [], []
pre_order(root)
puts "\nOutput all paths from root node to node 7, paths do not include nodes with value 3"
for path in $res
p path.map { |node| node.val }
end
end
@@ -0,0 +1,68 @@
=begin
File: preorder_traversal_iii_template.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Check if current state is solution ###
def is_solution?(state)
!state.empty? && state.last.val == 7
end
### Record solution ###
def record_solution(state, res)
res << state.dup
end
### Check if choice is valid in current state ###
def is_valid?(state, choice)
choice && choice.val != 3
end
### Update state ###
def make_choice(state, choice)
state << choice
end
### Restore state ###
def undo_choice(state, choice)
state.pop
end
### Backtracking: example 3 ###
def backtrack(state, choices, res)
# Check if it is a solution
record_solution(state, res) if is_solution?(state)
# Traverse all choices
for choice in choices
# Pruning: check if the choice is valid
if is_valid?(state, choice)
# Attempt: make choice, update state
make_choice(state, choice)
# Proceed to the next round of selection
backtrack(state, [choice.left, choice.right], res)
# Backtrack: undo choice, restore to previous state
undo_choice(state, choice)
end
end
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\nInitialize binary tree"
print_tree(root)
# Backtracking algorithm
res = []
backtrack([], [root], res)
puts "\nOutput all paths from root node to node 7, requiring paths do not include nodes with value 3"
for path in res
p path.map { |node| node.val }
end
end
@@ -0,0 +1,47 @@
=begin
File: subset_sum_i.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Backtracking: subset sum I ###
def backtrack(state, target, choices, start, res)
# When the subset sum equals target, record the solution
if target.zero?
res << state.dup
return
end
# Traverse all choices
# Pruning 2: start traversing from start to avoid generating duplicate subsets
for i in start...choices.length
# Pruning 1: if the subset sum exceeds target, end the loop directly
# This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target
break if target - choices[i] < 0
# Attempt: make choice, update target, start
state << choices[i]
# Proceed to the next round of selection
backtrack(state, target - choices[i], choices, i, res)
# Backtrack: undo choice, restore to previous state
state.pop
end
end
### Solve subset sum I ###
def subset_sum_i(nums, target)
state = [] # State (subset)
nums.sort! # Sort nums
start = 0 # Start point for traversal
res = [] # Result list (subset list)
backtrack(state, target, nums, start, res)
res
end
### Driver Code ###
if __FILE__ == $0
nums = [3, 4, 5]
target = 9
res = subset_sum_i(nums, target)
puts "Input array = #{nums}, target = #{target}"
puts "All subsets with sum equal to #{target} res = #{res}"
end
@@ -0,0 +1,46 @@
=begin
File: subset_sum_i_naive.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Backtracking: subset sum I ###
def backtrack(state, target, total, choices, res)
# When the subset sum equals target, record the solution
if total == target
res << state.dup
return
end
# Traverse all choices
for i in 0...choices.length
# Pruning: if the subset sum exceeds target, skip this choice
next if total + choices[i] > target
# Attempt: make choice, update element sum total
state << choices[i]
# Proceed to the next round of selection
backtrack(state, target, total + choices[i], choices, res)
# Backtrack: undo choice, restore to previous state
state.pop
end
end
### Solve subset sum I (with duplicate subsets) ###
def subset_sum_i_naive(nums, target)
state = [] # State (subset)
total = 0 # Subset sum
res = [] # Result list (subset list)
backtrack(state, target, total, nums, res)
res
end
### Driver Code ###
if __FILE__ == $0
nums = [3, 4, 5]
target = 9
res = subset_sum_i_naive(nums, target)
puts "Input array nums = #{nums}, target = #{target}"
puts "All subsets with sum equal to #{target} res = #{res}"
puts "Please note that this method outputs results containing duplicate sets"
end
@@ -0,0 +1,51 @@
=begin
File: subset_sum_ii.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Backtracking: subset sum II ###
def backtrack(state, target, choices, start, res)
# When the subset sum equals target, record the solution
if target.zero?
res << state.dup
return
end
# Traverse all choices
# Pruning 2: start traversing from start to avoid generating duplicate subsets
# Pruning 3: start traversing from start to avoid repeatedly selecting the same element
for i in start...choices.length
# Pruning 1: if the subset sum exceeds target, end the loop directly
# This is because the array is sorted, and later elements are larger, so the subset sum will definitely exceed target
break if target - choices[i] < 0
# Pruning 4: if this element equals the left element, it means this search branch is duplicate, skip it directly
next if i > start && choices[i] == choices[i - 1]
# Attempt: make choice, update target, start
state << choices[i]
# Proceed to the next round of selection
backtrack(state, target - choices[i], choices, i + 1, res)
# Backtrack: undo choice, restore to previous state
state.pop
end
end
### Solve subset sum II ###
def subset_sum_ii(nums, target)
state = [] # State (subset)
nums.sort! # Sort nums
start = 0 # Start point for traversal
res = [] # Result list (subset list)
backtrack(state, target, nums, start, res)
res
end
### Driver Code ###
if __FILE__ == $0
nums = [4, 4, 5]
target = 9
res = subset_sum_ii(nums, target)
puts "Input array nums = #{nums}, target = #{target}"
puts "All subsets with sum equal to #{target} res = #{res}"
end
@@ -0,0 +1,79 @@
=begin
File: iteration.rb
Created Time: 2024-03-30
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com)
=end
### for loop ###
def for_loop(n)
res = 0
# Sum 1, 2, ..., n-1, n
for i in 1..n
res += i
end
res
end
### while loop ###
def while_loop(n)
res = 0
i = 1 # Initialize condition variable
# Sum 1, 2, ..., n-1, n
while i <= n
res += i
i += 1 # Update condition variable
end
res
end
### while loop (two updates) ###
def while_loop_ii(n)
res = 0
i = 1 # Initialize condition variable
# Sum 1, 4, 10, ...
while i <= n
res += i
# Update condition variable
i += 1
i *= 2
end
res
end
### Nested for loop ###
def nested_for_loop(n)
res = ""
# Loop i = 1, 2, ..., n-1, n
for i in 1..n
# Loop j = 1, 2, ..., n-1, n
for j in 1..n
res += "(#{i}, #{j}), "
end
end
res
end
### Driver Code ###
if __FILE__ == $0
n = 5
res = for_loop(n)
puts "\nFor loop sum result res = #{res}"
res = while_loop(n)
puts "\nWhile loop sum result res = #{res}"
res = while_loop_ii(n)
puts "\nWhile loop (two updates) sum result res = #{res}"
res = nested_for_loop(n)
puts "\nNested for loop traversal result #{res}"
end
@@ -0,0 +1,70 @@
=begin
File: recursion.rb
Created Time: 2024-03-30
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Recursion ###
def recur(n)
# Termination condition
return 1 if n == 1
# Recurse: recursive call
res = recur(n - 1)
# Return: return result
n + res
end
### Use iteration to simulate recursion ###
def for_loop_recur(n)
# Use an explicit stack to simulate the system call stack
stack = []
res = 0
# Recurse: recursive call
for i in n.downto(0)
# Simulate "recurse" with "push"
stack << i
end
# Return: return result
while !stack.empty?
res += stack.pop
end
# res = 1+2+3+...+n
res
end
### Tail recursion ###
def tail_recur(n, res)
# Termination condition
return res if n == 0
# Tail recursive call
tail_recur(n - 1, res + n)
end
### Fibonacci sequence: recursion ###
def fib(n)
# Termination condition f(1) = 0, f(2) = 1
return n - 1 if n == 1 || n == 2
# Recursive call f(n) = f(n-1) + f(n-2)
res = fib(n - 1) + fib(n - 2)
# Return result f(n)
res
end
### Driver Code ###
if __FILE__ == $0
n = 5
res = recur(n)
puts "\nRecursion sum result res = #{res}"
res = for_loop_recur(n)
puts "\nUsing iteration to simulate recursion sum result res = #{res}"
res = tail_recur(n, 0)
puts "\nTail recursion sum result res = #{res}"
res = fib(n)
puts "\nThe #{n}th Fibonacci number is #{res}"
end
@@ -0,0 +1,92 @@
=begin
File: space_complexity.rb
Created Time: 2024-03-30
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/list_node'
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Function ###
def function
# Perform some operations
0
end
### Constant time ###
def constant(n)
# Constants, variables, objects occupy O(1) space
a = 0
nums = [0] * 10000
node = ListNode.new
# Variables in the loop occupy O(1) space
(0...n).each { c = 0 }
# Functions in the loop occupy O(1) space
(0...n).each { function }
end
### Linear time ###
def linear(n)
# A list of length n occupies O(n) space
nums = Array.new(n, 0)
# A hash table of length n occupies O(n) space
hmap = {}
for i in 0...n
hmap[i] = i.to_s
end
end
### Linear space (recursive) ###
def linear_recur(n)
puts "Recursion n = #{n}"
return if n == 1
linear_recur(n - 1)
end
### Quadratic time ###
def quadratic(n)
# 2D list uses O(n^2) space
Array.new(n) { Array.new(n, 0) }
end
### Quadratic space (recursive) ###
def quadratic_recur(n)
return 0 unless n > 0
# Array nums has length n, n-1, ..., 2, 1
nums = Array.new(n, 0)
quadratic_recur(n - 1)
end
### Exponential space (build full binary tree) ###
def build_tree(n)
return if n == 0
TreeNode.new.tap do |root|
root.left = build_tree(n - 1)
root.right = build_tree(n - 1)
end
end
### Driver Code ###
if __FILE__ == $0
n = 5
# Constant order
constant(n)
# Linear order
linear(n)
linear_recur(n)
# Exponential order
quadratic(n)
quadratic_recur(n)
# Exponential order
root = build_tree(n)
print_tree(root)
end
@@ -0,0 +1,165 @@
=begin
File: time_complexity.rb
Created Time: 2024-03-30
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Constant time ###
def constant(n)
count = 0
size = 100000
(0...size).each { count += 1 }
count
end
### Linear time ###
def linear(n)
count = 0
(0...n).each { count += 1 }
count
end
### Linear time (array traversal) ###
def array_traversal(nums)
count = 0
# Number of iterations is proportional to the array length
for num in nums
count += 1
end
count
end
### Quadratic time ###
def quadratic(n)
count = 0
# Number of iterations is quadratically related to the data size n
for i in 0...n
for j in 0...n
count += 1
end
end
count
end
### Quadratic time (bubble sort) ###
def bubble_sort(nums)
count = 0 # Counter
# Outer loop: unsorted range is [0, i]
for i in (nums.length - 1).downto(0)
# Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range
for j in 0...i
if nums[j] > nums[j + 1]
# Swap nums[j] and nums[j + 1]
tmp = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = tmp
count += 3 # Element swap includes 3 unit operations
end
end
end
count
end
### Exponential time (iterative) ###
def exponential(n)
count, base = 0, 1
# Cells divide into two every round, forming sequence 1, 2, 4, 8, ..., 2^(n-1)
(0...n).each do
(0...base).each { count += 1 }
base *= 2
end
# count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
count
end
### Exponential time (recursive) ###
def exp_recur(n)
return 1 if n == 1
exp_recur(n - 1) + exp_recur(n - 1) + 1
end
### Logarithmic time (iterative) ###
def logarithmic(n)
count = 0
while n > 1
n /= 2
count += 1
end
count
end
### Logarithmic time (recursive) ###
def log_recur(n)
return 0 unless n > 1
log_recur(n / 2) + 1
end
### Linearithmic time ###
def linear_log_recur(n)
return 1 unless n > 1
count = linear_log_recur(n / 2) + linear_log_recur(n / 2)
(0...n).each { count += 1 }
count
end
### Factorial time (recursive) ###
def factorial_recur(n)
return 1 if n == 0
count = 0
# Split from 1 into n
(0...n).each { count += factorial_recur(n - 1) }
count
end
### Driver Code ###
if __FILE__ == $0
# You can modify n to run and observe the trend of the number of operations for various complexities
n = 8
puts "Input data size n = #{n}"
count = constant(n)
puts "Constant-time operations count = #{count}"
count = linear(n)
puts "Linear-time operations count = #{count}"
count = array_traversal(Array.new(n, 0))
puts "Linear-time (array traversal) operations count = #{count}"
count = quadratic(n)
puts "Quadratic-time operations count = #{count}"
nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1]
count = bubble_sort(nums)
puts "Quadratic-time (bubble sort) operations count = #{count}"
count = exponential(n)
puts "Exponential-time (iterative) operations count = #{count}"
count = exp_recur(n)
puts "Exponential-time (recursive) operations count = #{count}"
count = logarithmic(n)
puts "Logarithmic-time (iterative) operations count = #{count}"
count = log_recur(n)
puts "Logarithmic-time (recursive) operations count = #{count}"
count = linear_log_recur(n)
puts "Linearithmic-time (recursive) operations count = #{count}"
count = factorial_recur(n)
puts "Factorial-time (recursive) operations count = #{count}"
end
@@ -0,0 +1,35 @@
=begin
File: worst_best_time_complexity.rb
Created Time: 2024-03-30
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Generate array with elements: 1, 2, ..., n, shuffled ###
def random_numbers(n)
# Generate array nums =: 1, 2, 3, ..., n
nums = Array.new(n) { |i| i + 1 }
# Randomly shuffle array elements
nums.shuffle!
end
### Find index of number 1 in array nums ###
def find_one(nums)
for i in 0...nums.length
# When element 1 is at the head of the array, best time complexity O(1) is achieved
# When element 1 is at the tail of the array, worst time complexity O(n) is achieved
return i if nums[i] == 1
end
-1
end
### Driver Code ###
if __FILE__ == $0
for i in 0...10
n = 100
nums = random_numbers(n)
index = find_one(nums)
puts "\nArray [ 1, 2, ..., n ] after shuffling = #{nums}"
puts "Index of number 1 is #{index}"
end
end
@@ -0,0 +1,42 @@
=begin
File: binary_search_recur.rb
Created Time: 2024-05-13
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Binary search: problem f(i, j) ###
def dfs(nums, target, i, j)
# If the interval is empty, it means there is no target element, return -1
return -1 if i > j
# Calculate the midpoint index m
m = (i + j) / 2
if nums[m] < target
# Recursion subproblem f(m+1, j)
return dfs(nums, target, m + 1, j)
elsif nums[m] > target
# Recursion subproblem f(i, m-1)
return dfs(nums, target, i, m - 1)
else
# Found the target element, return its index
return m
end
end
### Binary search ###
def binary_search(nums, target)
n = nums.length
# Solve the problem f(0, n-1)
dfs(nums, target, 0, n - 1)
end
### Driver Code ###
if __FILE__ == $0
target = 6
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
# Binary search (closed interval on both sides)
index = binary_search(nums, target)
puts "Index of target element 6 is #{index}"
end
@@ -0,0 +1,46 @@
=begin
File: build_tree.rb
Created Time: 2024-05-13
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Build binary tree: divide and conquer ###
def dfs(preorder, inorder_map, i, l, r)
# Terminate when the subtree interval is empty
return if r - l < 0
# Initialize the root node
root = TreeNode.new(preorder[i])
# Query m to divide the left and right subtrees
m = inorder_map[preorder[i]]
# Subproblem: build the left subtree
root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)
# Subproblem: build the right subtree
root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)
# Return the root node
root
end
### Build binary tree ###
def build_tree(preorder, inorder)
# Initialize hash map, storing the mapping from inorder elements to indices
inorder_map = {}
inorder.each_with_index { |val, i| inorder_map[val] = i }
dfs(preorder, inorder_map, 0, 0, inorder.length - 1)
end
### Driver Code ###
if __FILE__ == $0
preorder = [3, 9, 2, 1, 7]
inorder = [9, 3, 1, 2, 7]
puts "Pre-order traversal = #{preorder}"
puts "In-order traversal = #{inorder}"
root = build_tree(preorder, inorder)
puts "The constructed binary tree is:"
print_tree(root)
end
@@ -0,0 +1,55 @@
=begin
File: hanota.rb
Created Time: 2024-05-13
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Move one disk ###
def move(src, tar)
# Take out a disk from the top of src
pan = src.pop
# Place the disk on top of tar
tar << pan
end
### Solve Tower of Hanoi f(i) ###
def dfs(i, src, buf, tar)
# If there is only one disk left in src, move it directly to tar
if i == 1
move(src, tar)
return
end
# Subproblem f(i-1): move the top i-1 disks from src to buf using tar
dfs(i - 1, src, tar, buf)
# Subproblem f(1): move the remaining disk from src to tar
move(src, tar)
# Subproblem f(i-1): move the top i-1 disks from buf to tar using src
dfs(i - 1, buf, src, tar)
end
### Solve Tower of Hanoi ###
def solve_hanota(_A, _B, _C)
n = _A.length
# Move the top n disks from A to C using B
dfs(n, _A, _B, _C)
end
### Driver Code ###
if __FILE__ == $0
# The tail of the list is the top of the rod
A = [5, 4, 3, 2, 1]
B = []
C = []
puts "In initial state:"
puts "A = #{A}"
puts "B = #{B}"
puts "C = #{C}"
solve_hanota(A, B, C)
puts "After disk movement is complete:"
puts "A = #{A}"
puts "B = #{B}"
puts "C = #{C}"
end
@@ -0,0 +1,37 @@
=begin
File: climbing_stairs_backtrack.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Backtracking ###
def backtrack(choices, state, n, res)
# When climbing to the n-th stair, add 1 to the solution count
res[0] += 1 if state == n
# Traverse all choices
for choice in choices
# Pruning: not allowed to go beyond the n-th stair
next if state + choice > n
# Attempt: make choice, update state
backtrack(choices, state + choice, n, res)
end
# Backtrack
end
### Climbing stairs: backtracking ###
def climbing_stairs_backtrack(n)
choices = [1, 2] # Can choose to climb up 1 or 2 stairs
state = 0 # Start climbing from the 0-th stair
res = [0] # Use res[0] to record the solution count
backtrack(choices, state, n, res)
res.first
end
### Driver Code ###
if __FILE__ == $0
n = 9
res = climbing_stairs_backtrack(n)
puts "Climbing #{n} stairs has #{res} solutions"
end
@@ -0,0 +1,31 @@
=begin
File: climbing_stairs_constraint_dp.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Climbing stairs with constraint: DP ###
def climbing_stairs_constraint_dp(n)
return 1 if n == 1 || n == 2
# Initialize dp table, used to store solutions to subproblems
dp = Array.new(n + 1) { Array.new(3, 0) }
# Initial state: preset the solution to the smallest subproblem
dp[1][1], dp[1][2] = 1, 0
dp[2][1], dp[2][2] = 0, 1
# State transition: gradually solve larger subproblems from smaller ones
for i in 3...(n + 1)
dp[i][1] = dp[i - 1][2]
dp[i][2] = dp[i - 2][1] + dp[i - 2][2]
end
dp[n][1] + dp[n][2]
end
### Driver Code ###
if __FILE__ == $0
n = 9
res = climbing_stairs_constraint_dp(n)
puts "Climbing #{n} stairs has #{res} solutions"
end
@@ -0,0 +1,26 @@
=begin
File: climbing_stairs_dfs.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Search ###
def dfs(i)
# Known dp[1] and dp[2], return them
return i if i == 1 || i == 2
# dp[i] = dp[i-1] + dp[i-2]
dfs(i - 1) + dfs(i - 2)
end
### Climbing stairs: search ###
def climbing_stairs_dfs(n)
dfs(n)
end
### Driver Code ###
if __FILE__ == $0
n = 9
res = climbing_stairs_dfs(n)
puts "Climbing #{n} stairs has #{res} solutions"
end
@@ -0,0 +1,33 @@
=begin
File: climbing_stairs_dfs_mem.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Memoization search ###
def dfs(i, mem)
# Known dp[1] and dp[2], return them
return i if i == 1 || i == 2
# If record dp[i] exists, return it directly
return mem[i] if mem[i] != -1
# dp[i] = dp[i-1] + dp[i-2]
count = dfs(i - 1, mem) + dfs(i - 2, mem)
# Record dp[i]
mem[i] = count
end
### Climbing stairs: memoization search ###
def climbing_stairs_dfs_mem(n)
# mem[i] records the total number of solutions to climb to the i-th stair, -1 means no record
mem = Array.new(n + 1, -1)
dfs(n, mem)
end
### Driver Code ###
if __FILE__ == $0
n = 9
res = climbing_stairs_dfs_mem(n)
puts "Climbing #{n} stairs has #{res} solutions"
end
@@ -0,0 +1,40 @@
=begin
File: climbing_stairs_dp.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Climbing stairs: dynamic programming ###
def climbing_stairs_dp(n)
return n if n == 1 || n == 2
# Initialize dp table, used to store solutions to subproblems
dp = Array.new(n + 1, 0)
# Initial state: preset the solution to the smallest subproblem
dp[1], dp[2] = 1, 2
# State transition: gradually solve larger subproblems from smaller ones
(3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] }
dp[n]
end
### Climbing stairs: space-optimized DP ###
def climbing_stairs_dp_comp(n)
return n if n == 1 || n == 2
a, b = 1, 2
(3...(n + 1)).each { a, b = b, a + b }
b
end
### Driver Code ###
if __FILE__ == $0
n = 9
res = climbing_stairs_dp(n)
puts "Climbing #{n} stairs has #{res} solutions"
res = climbing_stairs_dp_comp(n)
puts "Climbing #{n} stairs has #{res} solutions"
end
@@ -0,0 +1,65 @@
=begin
File: coin_change.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Coin change: dynamic programming ###
def coin_change_dp(coins, amt)
n = coins.length
_MAX = amt + 1
# Initialize dp table
dp = Array.new(n + 1) { Array.new(amt + 1, 0) }
# State transition: first row and first column
(1...(amt + 1)).each { |a| dp[0][a] = _MAX }
# State transition: rest of the rows and columns
for i in 1...(n + 1)
for a in 1...(amt + 1)
if coins[i - 1] > a
# If exceeds target amount, don't select coin i
dp[i][a] = dp[i - 1][a]
else
# The smaller value between not selecting and selecting coin i
dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min
end
end
end
dp[n][amt] != _MAX ? dp[n][amt] : -1
end
### Coin change: space-optimized DP ###
def coin_change_dp_comp(coins, amt)
n = coins.length
_MAX = amt + 1
# Initialize dp table
dp = Array.new(amt + 1, _MAX)
dp[0] = 0
# State transition
for i in 1...(n + 1)
# Traverse in forward order
for a in 1...(amt + 1)
if coins[i - 1] > a
# If exceeds target amount, don't select coin i
dp[a] = dp[a]
else
# The smaller value between not selecting and selecting coin i
dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min
end
end
end
dp[amt] != _MAX ? dp[amt] : -1
end
### Driver Code ###
if __FILE__ == $0
coins = [1, 2, 5]
amt = 4
# Dynamic programming
res = coin_change_dp(coins, amt)
puts "Minimum coins needed to make target amount is #{res}"
# Space-optimized dynamic programming
res = coin_change_dp_comp(coins, amt)
puts "Minimum coins needed to make target amount is #{res}"
end
@@ -0,0 +1,63 @@
=begin
File: coin_change_ii.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Coin change II: dynamic programming ###
def coin_change_ii_dp(coins, amt)
n = coins.length
# Initialize dp table
dp = Array.new(n + 1) { Array.new(amt + 1, 0) }
# Initialize first column
(0...(n + 1)).each { |i| dp[i][0] = 1 }
# State transition
for i in 1...(n + 1)
for a in 1...(amt + 1)
if coins[i - 1] > a
# If exceeds target amount, don't select coin i
dp[i][a] = dp[i - 1][a]
else
# Sum of the two options: not selecting and selecting coin i
dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]
end
end
end
dp[n][amt]
end
### Coin change II: space-optimized DP ###
def coin_change_ii_dp_comp(coins, amt)
n = coins.length
# Initialize dp table
dp = Array.new(amt + 1, 0)
dp[0] = 1
# State transition
for i in 1...(n + 1)
# Traverse in forward order
for a in 1...(amt + 1)
if coins[i - 1] > a
# If exceeds target amount, don't select coin i
dp[a] = dp[a]
else
# Sum of the two options: not selecting and selecting coin i
dp[a] = dp[a] + dp[a - coins[i - 1]]
end
end
end
dp[amt]
end
### Driver Code ###
if __FILE__ == $0
coins = [1, 2, 5]
amt = 5
# Dynamic programming
res = coin_change_ii_dp(coins, amt)
puts "Number of coin combinations to make target amount is #{res}"
# Space-optimized dynamic programming
res = coin_change_ii_dp_comp(coins, amt)
puts "Number of coin combinations to make target amount is #{res}"
end
@@ -0,0 +1,115 @@
=begin
File: edit_distance.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Edit distance: brute force search ###
def edit_distance_dfs(s, t, i, j)
# If both s and t are empty, return 0
return 0 if i == 0 && j == 0
# If s is empty, return length of t
return j if i == 0
# If t is empty, return length of s
return i if j == 0
# If two characters are equal, skip both characters
return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1]
# Minimum edit steps = minimum edit steps of insert, delete, replace + 1
insert = edit_distance_dfs(s, t, i, j - 1)
delete = edit_distance_dfs(s, t, i - 1, j)
replace = edit_distance_dfs(s, t, i - 1, j - 1)
# Return minimum edit steps
[insert, delete, replace].min + 1
end
def edit_distance_dfs_mem(s, t, mem, i, j)
# If both s and t are empty, return 0
return 0 if i == 0 && j == 0
# If s is empty, return length of t
return j if i == 0
# If t is empty, return length of s
return i if j == 0
# If there's a record, return it directly
return mem[i][j] if mem[i][j] != -1
# If two characters are equal, skip both characters
return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1]
# Minimum edit steps = minimum edit steps of insert, delete, replace + 1
insert = edit_distance_dfs_mem(s, t, mem, i, j - 1)
delete = edit_distance_dfs_mem(s, t, mem, i - 1, j)
replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1)
# Record and return minimum edit steps
mem[i][j] = [insert, delete, replace].min + 1
end
### Edit distance: dynamic programming ###
def edit_distance_dp(s, t)
n, m = s.length, t.length
dp = Array.new(n + 1) { Array.new(m + 1, 0) }
# State transition: first row and first column
(1...(n + 1)).each { |i| dp[i][0] = i }
(1...(m + 1)).each { |j| dp[0][j] = j }
# State transition: rest of the rows and columns
for i in 1...(n + 1)
for j in 1...(m +1)
if s[i - 1] == t[j - 1]
# If two characters are equal, skip both characters
dp[i][j] = dp[i - 1][j - 1]
else
# Minimum edit steps = minimum edit steps of insert, delete, replace + 1
dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1
end
end
end
dp[n][m]
end
### Edit distance: space-optimized DP ###
def edit_distance_dp_comp(s, t)
n, m = s.length, t.length
dp = Array.new(m + 1, 0)
# State transition: first row
(1...(m + 1)).each { |j| dp[j] = j }
# State transition: rest of the rows
for i in 1...(n + 1)
# State transition: first column
leftup = dp.first # Temporarily store dp[i-1, j-1]
dp[0] += 1
# State transition: rest of the columns
for j in 1...(m + 1)
temp = dp[j]
if s[i - 1] == t[j - 1]
# If two characters are equal, skip both characters
dp[j] = leftup
else
# Minimum edit steps = minimum edit steps of insert, delete, replace + 1
dp[j] = [dp[j - 1], dp[j], leftup].min + 1
end
leftup = temp # Update for next round's dp[i-1, j-1]
end
end
dp[m]
end
### Driver Code ###
if __FILE__ == $0
s = 'bag'
t = 'pack'
n, m = s.length, t.length
# Brute-force search
res = edit_distance_dfs(s, t, n, m)
puts "Changing #{s} to #{t} requires minimum #{res} edits"
# Memoization search
mem = Array.new(n + 1) { Array.new(m + 1, -1) }
res = edit_distance_dfs_mem(s, t, mem, n, m)
puts "Changing #{s} to #{t} requires minimum #{res} edits"
# Dynamic programming
res = edit_distance_dp(s, t)
puts "Changing #{s} to #{t} requires minimum #{res} edits"
# Space-optimized dynamic programming
res = edit_distance_dp_comp(s, t)
puts "Changing #{s} to #{t} requires minimum #{res} edits"
end
@@ -0,0 +1,99 @@
=begin
File: knapsack.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### 0-1 knapsack: brute force search ###
def knapsack_dfs(wgt, val, i, c)
# If all items have been selected or knapsack has no remaining capacity, return value 0
return 0 if i == 0 || c == 0
# If exceeds knapsack capacity, can only choose not to put it in
return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c
# Calculate the maximum value of not putting in and putting in item i
no = knapsack_dfs(wgt, val, i - 1, c)
yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]
# Return the larger value of the two options
[no, yes].max
end
### 0-1 knapsack: memoization search ###
def knapsack_dfs_mem(wgt, val, mem, i, c)
# If all items have been selected or knapsack has no remaining capacity, return value 0
return 0 if i == 0 || c == 0
# If there's a record, return it directly
return mem[i][c] if mem[i][c] != -1
# If exceeds knapsack capacity, can only choose not to put it in
return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c
# Calculate the maximum value of not putting in and putting in item i
no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)
yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]
# Record and return the larger value of the two options
mem[i][c] = [no, yes].max
end
### 0-1 knapsack: dynamic programming ###
def knapsack_dp(wgt, val, cap)
n = wgt.length
# Initialize dp table
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
# State transition
for i in 1...(n + 1)
for c in 1...(cap + 1)
if wgt[i - 1] > c
# If exceeds knapsack capacity, don't select item i
dp[i][c] = dp[i - 1][c]
else
# The larger value between not selecting and selecting item i
dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[n][cap]
end
### 0-1 knapsack: space-optimized DP ###
def knapsack_dp_comp(wgt, val, cap)
n = wgt.length
# Initialize dp table
dp = Array.new(cap + 1, 0)
# State transition
for i in 1...(n + 1)
# Traverse in reverse order
for c in cap.downto(1)
if wgt[i - 1] > c
# If exceeds knapsack capacity, don't select item i
dp[c] = dp[c]
else
# The larger value between not selecting and selecting item i
dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[cap]
end
### Driver Code ###
if __FILE__ == $0
wgt = [10, 20, 30, 40, 50]
val = [50, 120, 150, 210, 240]
cap = 50
n = wgt.length
# Brute-force search
res = knapsack_dfs(wgt, val, n, cap)
puts "Maximum item value not exceeding knapsack capacity is #{res}"
# Memoization search
mem = Array.new(n + 1) { Array.new(cap + 1, -1) }
res = knapsack_dfs_mem(wgt, val, mem, n, cap)
puts "Maximum item value not exceeding knapsack capacity is #{res}"
# Dynamic programming
res = knapsack_dp(wgt, val, cap)
puts "Maximum item value not exceeding knapsack capacity is #{res}"
# Space-optimized dynamic programming
res = knapsack_dp_comp(wgt, val, cap)
puts "Maximum item value not exceeding knapsack capacity is #{res}"
end
@@ -0,0 +1,39 @@
=begin
File: min_cost_climbing_stairs_dp.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Minimum cost climbing stairs: DP ###
def min_cost_climbing_stairs_dp(cost)
n = cost.length - 1
return cost[n] if n == 1 || n == 2
# Initialize dp table, used to store solutions to subproblems
dp = Array.new(n + 1, 0)
# Initial state: preset the solution to the smallest subproblem
dp[1], dp[2] = cost[1], cost[2]
# State transition: gradually solve larger subproblems from smaller ones
(3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }
dp[n]
end
# Minimum cost climbing stairs: Space-optimized dynamic programming
def min_cost_climbing_stairs_dp_comp(cost)
n = cost.length - 1
return cost[n] if n == 1 || n == 2
a, b = cost[1], cost[2]
(3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] }
b
end
### Driver Code ###
if __FILE__ == $0
cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]
puts "Input stair cost list is #{cost}"
res = min_cost_climbing_stairs_dp(cost)
puts "Minimum cost to climb stairs is #{res}"
res = min_cost_climbing_stairs_dp_comp(cost)
puts "Minimum cost to climb stairs is #{res}"
end
@@ -0,0 +1,93 @@
=begin
File: min_path_sum.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Minimum path sum: brute force search ###
def min_path_sum_dfs(grid, i, j)
# If it's the top-left cell, terminate the search
return grid[i][j] if i == 0 && j == 0
# If row or column index is out of bounds, return +∞ cost
return Float::INFINITY if i < 0 || j < 0
# Calculate the minimum path cost from top-left to (i-1, j) and (i, j-1)
up = min_path_sum_dfs(grid, i - 1, j)
left = min_path_sum_dfs(grid, i, j - 1)
# Return the minimum path cost from top-left to (i, j)
[left, up].min + grid[i][j]
end
### Minimum path sum: memoization search ###
def min_path_sum_dfs_mem(grid, mem, i, j)
# If it's the top-left cell, terminate the search
return grid[0][0] if i == 0 && j == 0
# If row or column index is out of bounds, return +∞ cost
return Float::INFINITY if i < 0 || j < 0
# If there's a record, return it directly
return mem[i][j] if mem[i][j] != -1
# Minimum path cost for left and upper cells
up = min_path_sum_dfs_mem(grid, mem, i - 1, j)
left = min_path_sum_dfs_mem(grid, mem, i, j - 1)
# Record and return the minimum path cost from top-left to (i, j)
mem[i][j] = [left, up].min + grid[i][j]
end
### Minimum path sum: dynamic programming ###
def min_path_sum_dp(grid)
n, m = grid.length, grid.first.length
# Initialize dp table
dp = Array.new(n) { Array.new(m, 0) }
dp[0][0] = grid[0][0]
# State transition: first row
(1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] }
# State transition: first column
(1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] }
# State transition: rest of the rows and columns
for i in 1...n
for j in 1...m
dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j]
end
end
dp[n -1][m -1]
end
### Minimum path sum: space-optimized DP ###
def min_path_sum_dp_comp(grid)
n, m = grid.length, grid.first.length
# Initialize dp table
dp = Array.new(m, 0)
# State transition: first row
dp[0] = grid[0][0]
(1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] }
# State transition: rest of the rows
for i in 1...n
# State transition: first column
dp[0] = dp[0] + grid[i][0]
# State transition: rest of the columns
(1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] }
end
dp[m - 1]
end
### Driver Code ###
if __FILE__ == $0
grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]]
n, m = grid.length, grid.first.length
# Brute-force search
res = min_path_sum_dfs(grid, n - 1, m - 1)
puts "Minimum path sum from top-left to bottom-right is #{res}"
# Memoization search
mem = Array.new(n) { Array.new(m, - 1) }
res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1)
puts "Minimum path sum from top-left to bottom-right is #{res}"
# Dynamic programming
res = min_path_sum_dp(grid)
puts "Minimum path sum from top-left to bottom-right is #{res}"
# Space-optimized dynamic programming
res = min_path_sum_dp_comp(grid)
puts "Minimum path sum from top-left to bottom-right is #{res}"
end
@@ -0,0 +1,61 @@
=begin
File: unbounded_knapsack.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Unbounded knapsack: dynamic programming ###
def unbounded_knapsack_dp(wgt, val, cap)
n = wgt.length
# Initialize dp table
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
# State transition
for i in 1...(n + 1)
for c in 1...(cap + 1)
if wgt[i - 1] > c
# If exceeds knapsack capacity, don't select item i
dp[i][c] = dp[i - 1][c]
else
# The larger value between not selecting and selecting item i
dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[n][cap]
end
### Unbounded knapsack: space-optimized DP ###
def unbounded_knapsack_dp_comp(wgt, val, cap)
n = wgt.length
# Initialize dp table
dp = Array.new(cap + 1, 0)
# State transition
for i in 1...(n + 1)
# Traverse in forward order
for c in 1...(cap + 1)
if wgt[i -1] > c
# If exceeds knapsack capacity, don't select item i
dp[c] = dp[c]
else
# The larger value between not selecting and selecting item i
dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max
end
end
end
dp[cap]
end
### Driver Code ###
if __FILE__ == $0
wgt = [1, 2, 3]
val = [5, 11, 15]
cap = 4
# Dynamic programming
res = unbounded_knapsack_dp(wgt, val, cap)
puts "Maximum item value not exceeding knapsack capacity is #{res}"
# Space-optimized dynamic programming
res = unbounded_knapsack_dp_comp(wgt, val, cap)
puts "Maximum item value not exceeding knapsack capacity is #{res}"
end
@@ -0,0 +1,116 @@
=begin
File: graph_adjacency_list.rb
Created Time: 2024-04-25
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/vertex'
### Undirected graph class based on adjacency list ###
class GraphAdjList
attr_reader :adj_list
### Constructor ###
def initialize(edges)
# Adjacency list, key: vertex, value: all adjacent vertices of that vertex
@adj_list = {}
# Add all vertices and edges
for edge in edges
add_vertex(edge[0])
add_vertex(edge[1])
add_edge(edge[0], edge[1])
end
end
### Get number of vertices ###
def size
@adj_list.length
end
### Add edge ###
def add_edge(vet1, vet2)
raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)
@adj_list[vet1] << vet2
@adj_list[vet2] << vet1
end
### Delete edge ###
def remove_edge(vet1, vet2)
raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)
# Remove edge vet1 - vet2
@adj_list[vet1].delete(vet2)
@adj_list[vet2].delete(vet1)
end
### Add vertex ###
def add_vertex(vet)
return if @adj_list.include?(vet)
# Add a new linked list in the adjacency list
@adj_list[vet] = []
end
### Delete vertex ###
def remove_vertex(vet)
raise ArgumentError unless @adj_list.include?(vet)
# Remove the linked list corresponding to vertex vet in the adjacency list
@adj_list.delete(vet)
# Traverse the linked lists of other vertices and remove all edges containing vet
for vertex in @adj_list
@adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet)
end
end
### Print adjacency list ###
def __print__
puts 'Adjacency list ='
for vertex in @adj_list
tmp = @adj_list[vertex.first].map { |v| v.val }
puts "#{vertex.first.val}: #{tmp},"
end
end
end
### Driver Code ###
if __FILE__ == $0
# Add edge
v = vals_to_vets([1, 3, 2, 5, 4])
edges = [
[v[0], v[1]],
[v[0], v[3]],
[v[1], v[2]],
[v[2], v[3]],
[v[2], v[4]],
[v[3], v[4]],
]
graph = GraphAdjList.new(edges)
puts "\nAfter initialization, graph is"
graph.__print__
# Add edge
# Vertices 1, 2 are v[0], v[2]
graph.add_edge(v[0], v[2])
puts "\nAfter adding edge 1-2, graph is"
graph.__print__
# Remove edge
# Vertices 1, 3 are v[0], v[1]
graph.remove_edge(v[0], v[1])
puts "\nAfter removing edge 1-3, graph is"
graph.__print__
# Add vertex
v5 = Vertex.new(6)
graph.add_vertex(v5)
puts "\nAfter adding vertex 6, graph is"
graph.__print__
# Remove vertex
# Vertex 3 is v[1]
graph.remove_vertex(v[1])
puts "\nAfter removing vertex 3, graph is"
graph.__print__
end
@@ -0,0 +1,116 @@
=begin
File: graph_adjacency_matrix.rb
Created Time: 2024-04-25
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/print_util'
### Undirected graph class based on adjacency matrix ###
class GraphAdjMat
def initialize(vertices, edges)
### Constructor ###
# Vertex list, where the element represents the "vertex value" and the index represents the "vertex index"
@vertices = []
# Adjacency matrix, where the row and column indices correspond to the "vertex index"
@adj_mat = []
# Add vertex
vertices.each { |val| add_vertex(val) }
# Add edge
# Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices
edges.each { |e| add_edge(e[0], e[1]) }
end
### Get number of vertices ###
def size
@vertices.length
end
### Add vertex ###
def add_vertex(val)
n = size
# Add the value of the new vertex to the vertex list
@vertices << val
# Add a row to the adjacency matrix
new_row = Array.new(n, 0)
@adj_mat << new_row
# Add a column to the adjacency matrix
@adj_mat.each { |row| row << 0 }
end
### Delete vertex ###
def remove_vertex(index)
raise IndexError if index >= size
# Remove the vertex at index from the vertex list
@vertices.delete_at(index)
# Remove the row at index from the adjacency matrix
@adj_mat.delete_at(index)
# Remove the column at index from the adjacency matrix
@adj_mat.each { |row| row.delete_at(index) }
end
### Add edge ###
def add_edge(i, j)
# Parameters i, j correspond to the vertices element indices
# Handle index out of bounds and equality
if i < 0 || j < 0 || i >= size || j >= size || i == j
raise IndexError
end
# In an undirected graph, the adjacency matrix is symmetric about the main diagonal, i.e., (i, j) == (j, i)
@adj_mat[i][j] = 1
@adj_mat[j][i] = 1
end
### Delete edge ###
def remove_edge(i, j)
# Parameters i, j correspond to the vertices element indices
# Handle index out of bounds and equality
if i < 0 || j < 0 || i >= size || j >= size || i == j
raise IndexError
end
@adj_mat[i][j] = 0
@adj_mat[j][i] = 0
end
### Print adjacency matrix ###
def __print__
puts "Vertex list = #{@vertices}"
puts 'Adjacency matrix ='
print_matrix(@adj_mat)
end
end
### Driver Code ###
if __FILE__ == $0
# Add edge
# Note that the edges elements represent vertex indices, i.e., corresponding to the vertices element indices
vertices = [1, 3, 2, 5, 4]
edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]
graph = GraphAdjMat.new(vertices, edges)
puts "\nAfter initialization, graph is"
graph.__print__
# Add edge
# Add vertex
graph.add_edge(0, 2)
puts "\nAfter adding edge 1-2, graph is"
graph.__print__
# Remove edge
# Vertices 1, 3 have indices 0, 1 respectively
graph.remove_edge(0, 1)
puts "\nAfter removing edge 1-3, graph is"
graph.__print__
# Add vertex
graph.add_vertex(6)
puts "\nAfter adding vertex 6, graph is"
graph.__print__
# Remove vertex
# Vertex 3 has index 1
graph.remove_vertex(1)
puts "\nAfter removing vertex 3, graph is"
graph.__print__
end
+61
View File
@@ -0,0 +1,61 @@
=begin
File: graph_bfs.rb
Created Time: 2024-04-25
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require 'set'
require_relative './graph_adjacency_list'
require_relative '../utils/vertex'
### Breadth-first traversal ###
def graph_bfs(graph, start_vet)
# Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex
# Vertex traversal sequence
res = []
# Hash set for recording vertices that have been visited
visited = Set.new([start_vet])
# Queue used to implement BFS
que = [start_vet]
# Starting from vertex vet, loop until all vertices are visited
while que.length > 0
vet = que.shift # Dequeue the front vertex
res << vet # Record visited vertex
# Traverse all adjacent vertices of this vertex
for adj_vet in graph.adj_list[vet]
next if visited.include?(adj_vet) # Skip vertices that have been visited
que << adj_vet # Only enqueue unvisited vertices
visited.add(adj_vet) # Mark this vertex as visited
end
end
# Return vertex traversal sequence
res
end
### Driver Code ###
if __FILE__ == $0
# Add edge
v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
edges = [
[v[0], v[1]],
[v[0], v[3]],
[v[1], v[2]],
[v[1], v[4]],
[v[2], v[5]],
[v[3], v[4]],
[v[3], v[6]],
[v[4], v[5]],
[v[4], v[7]],
[v[5], v[8]],
[v[6], v[7]],
[v[7], v[8]],
]
graph = GraphAdjList.new(edges)
puts "\nAfter initialization, graph is"
graph.__print__
# Breadth-first traversal
res = graph_bfs(graph, v.first)
puts "\nBreadth-first traversal (BFS) vertex sequence is"
p vets_to_vals(res)
end
+54
View File
@@ -0,0 +1,54 @@
=begin
File: graph_dfs.rb
Created Time: 2024-04-25
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require 'set'
require_relative './graph_adjacency_list'
require_relative '../utils/vertex'
### Depth-first traversal helper function ###
def dfs(graph, visited, res, vet)
res << vet # Record visited vertex
visited.add(vet) # Mark this vertex as visited
# Traverse all adjacent vertices of this vertex
for adj_vet in graph.adj_list[vet]
next if visited.include?(adj_vet) # Skip vertices that have been visited
# Recursively visit adjacent vertices
dfs(graph, visited, res, adj_vet)
end
end
### Depth-first traversal ###
def graph_dfs(graph, start_vet)
# Use adjacency list to represent the graph, in order to obtain all adjacent vertices of a specified vertex
# Vertex traversal sequence
res = []
# Hash set for recording vertices that have been visited
visited = Set.new
dfs(graph, visited, res, start_vet)
res
end
### Driver Code ###
if __FILE__ == $0
# Add edge
v = vals_to_vets([0, 1, 2, 3, 4, 5, 6])
edges = [
[v[0], v[1]],
[v[0], v[3]],
[v[1], v[2]],
[v[2], v[5]],
[v[4], v[5]],
[v[5], v[6]],
]
graph = GraphAdjList.new(edges)
puts "\nAfter initialization, graph is"
graph.__print__
# Depth-first traversal
res = graph_dfs(graph, v[0])
puts "\nDepth-first traversal (DFS) vertex sequence is"
p vets_to_vals(res)
end
@@ -0,0 +1,50 @@
=begin
File: coin_change_greedy.rb
Created Time: 2024-05-07
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Coin change: greedy ###
def coin_change_greedy(coins, amt)
# Assume coins list is sorted
i = coins.length - 1
count = 0
# Loop to make greedy choices until no remaining amount
while amt > 0
# Find the coin that is less than and closest to the remaining amount
while i > 0 && coins[i] > amt
i -= 1
end
# Choose coins[i]
amt -= coins[i]
count += 1
end
# Return -1 if no solution found
amt == 0 ? count : -1
end
### Driver Code ###
if __FILE__ == $0
# Greedy algorithm: Can guarantee finding the global optimal solution
coins = [1, 5, 10, 20, 50, 100]
amt = 186
res = coin_change_greedy(coins, amt)
puts "\ncoins = #{coins}, amt = #{amt}"
puts "Minimum coins needed to make #{amt} is #{res}"
# Greedy algorithm: Cannot guarantee finding the global optimal solution
coins = [1, 20, 50]
amt = 60
res = coin_change_greedy(coins, amt)
puts "\ncoins = #{coins}, amt = #{amt}"
puts "Minimum coins needed to make #{amt} is #{res}"
puts "Actually minimum needed is 3, i.e., 20 + 20 + 20"
# Greedy algorithm: Cannot guarantee finding the global optimal solution
coins = [1, 49, 50]
amt = 98
res = coin_change_greedy(coins, amt)
puts "\ncoins = #{coins}, amt = #{amt}"
puts "Minimum coins needed to make #{amt} is #{res}"
puts "Actually minimum needed is 2, i.e., 49 + 49"
end
@@ -0,0 +1,51 @@
=begin
File: fractional_knapsack.rb
Created Time: 2024-05-07
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Item ###
class Item
attr_accessor :w # Item weight
attr_accessor :v # Item value
def initialize(w, v)
@w = w
@v = v
end
end
### Fractional knapsack: greedy ###
def fractional_knapsack(wgt, val, cap)
# Create item list with two attributes: weight, value
items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) }
# Sort by unit value item.v / item.w from high to low
items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) }
# Loop for greedy selection
res = 0
for item in items
if item.w <= cap
# If remaining capacity is sufficient, put the entire current item into the knapsack
res += item.v
cap -= item.w
else
# If remaining capacity is insufficient, put part of the current item into the knapsack
res += (item.v.to_f / item.w) * cap
# No remaining capacity, so break out of the loop
break
end
end
res
end
### Driver Code ###
if __FILE__ == $0
wgt = [10, 20, 30, 40, 50]
val = [50, 120, 150, 210, 240]
cap = 50
n = wgt.length
# Greedy algorithm
res = fractional_knapsack(wgt, val, cap)
puts "Maximum item value not exceeding knapsack capacity is #{res}"
end
@@ -0,0 +1,37 @@
=begin
File: max_capacity.rb
Created Time: 2024-05-07
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Maximum capacity: greedy ###
def max_capacity(ht)
# Initialize i, j to be at both ends of the array
i, j = 0, ht.length - 1
# Initial max capacity is 0
res = 0
# Loop for greedy selection until the two boards meet
while i < j
# Update max capacity
cap = [ht[i], ht[j]].min * (j - i)
res = [res, cap].max
# Move the shorter board inward
if ht[i] < ht[j]
i += 1
else
j -= 1
end
end
res
end
### Driver Code ###
if __FILE__ == $0
ht = [3, 8, 5, 2, 7, 7, 3, 4]
# Greedy algorithm
res = max_capacity(ht)
puts "Maximum capacity is #{res}"
end
@@ -0,0 +1,28 @@
=begin
File: max_product_cutting.rb
Created Time: 2024-05-07
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Maximum cutting product: greedy ###
def max_product_cutting(n)
# When n <= 3, must cut out a 1
return 1 * (n - 1) if n <= 3
# Greedily cut out 3, a is the number of 3s, b is the remainder
a, b = n / 3, n % 3
# When the remainder is 1, convert a pair of 1 * 3 to 2 * 2
return (3.pow(a - 1) * 2 * 2).to_i if b == 1
# When the remainder is 2, do nothing
return (3.pow(a) * 2).to_i if b == 2
# When the remainder is 0, do nothing
3.pow(a).to_i
end
### Driver Code ###
if __FILE__ == $0
n = 58
# Greedy algorithm
res = max_product_cutting(n)
puts "Maximum cutting product is #{res}"
end
@@ -0,0 +1,121 @@
=begin
File: array_hash_map.rb
Created Time: 2024-04-13
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Key-value pair ###
class Pair
attr_accessor :key, :val
def initialize(key, val)
@key = key
@val = val
end
end
### Hash map based on array ###
class ArrayHashMap
### Constructor ###
def initialize
# Initialize array with 100 buckets
@buckets = Array.new(100)
end
### Hash function ###
def hash_func(key)
index = key % 100
end
### Query operation ###
def get(key)
index = hash_func(key)
pair = @buckets[index]
return if pair.nil?
pair.val
end
### Add operation ###
def put(key, val)
pair = Pair.new(key, val)
index = hash_func(key)
@buckets[index] = pair
end
### Delete operation ###
def remove(key)
index = hash_func(key)
# Set to nil to delete
@buckets[index] = nil
end
### Get all key-value pairs ###
def entry_set
result = []
@buckets.each { |pair| result << pair unless pair.nil? }
result
end
### Get all keys ###
def key_set
result = []
@buckets.each { |pair| result << pair.key unless pair.nil? }
result
end
### Get all values ###
def value_set
result = []
@buckets.each { |pair| result << pair.val unless pair.nil? }
result
end
### Print hash table ###
def print
@buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? }
end
end
### Driver Code ###
if __FILE__ == $0
# Initialize hash table
hmap = ArrayHashMap.new
# Add operation
# Add key-value pair (key, value) to the hash table
hmap.put(12836, "Xiao Ha")
hmap.put(15937, "Xiao Luo")
hmap.put(16750, "Xiao Suan")
hmap.put(13276, "Xiao Fa")
hmap.put(10583, "Xiao Ya")
puts "\nAfter adding is complete, hash table is\nKey -> Value"
hmap.print
# Query operation
# Input key to hash table, get value
name = hmap.get(15937)
puts "\nInput student ID 15937, found name #{name}"
# Remove operation
# Delete key-value pair (key, value) from hash table
hmap.remove(10583)
puts "\nAfter removing 10583, hash table is\nKey -> Value"
hmap.print
# Traverse hash table
puts "\nTraverse key-value pairs Key->Value"
for pair in hmap.entry_set
puts "#{pair.key} -> #{pair.val}"
end
puts "\nTraverse keys separately"
for key in hmap.key_set
puts key
end
puts "\nTraverse values only Value"
for val in hmap.value_set
puts val
end
end
@@ -0,0 +1,34 @@
=begin
File: built_in_hash.rb
Created Time: 2024-04-13
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/list_node'
### Driver Code ###
if __FILE__ == $0
num = 3
hash_num = num.hash
puts "Hash value of integer #{num} is #{hash_num}"
bol = true
hash_bol = bol.hash
puts "Hash value of boolean #{bol} is #{hash_bol}"
dec = 3.14159
hash_dec = dec.hash
puts "Hash value of decimal #{dec} is #{hash_dec}"
str = "Hello Algo"
hash_str = str.hash
puts "Hash value of string #{str} is #{hash_str}"
tup = [12836, 'Xiao Ha']
hash_tup = tup.hash
puts "Hash value of tuple #{tup} is #{hash_tup}"
obj = ListNode.new(0)
hash_obj = obj.hash
puts "Hash value of object #{obj} is #{hash_obj}"
end
+44
View File
@@ -0,0 +1,44 @@
=begin
File: hash_map.rb
Created Time: 2024-04-14
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/print_util'
### Driver Code ###
if __FILE__ == $0
# Initialize hash table
hmap = {}
# Add operation
# Add key-value pair (key, value) to the hash table
hmap[12836] = "Xiao Ha"
hmap[15937] = "Xiao Luo"
hmap[16750] = "Xiao Suan"
hmap[13276] = "Xiao Fa"
hmap[10583] = "Xiao Ya"
puts "\nAfter adding is complete, hash table is\nKey -> Value"
print_hash_map(hmap)
# Query operation
# Input key into hash table to get value
name = hmap[15937]
puts "\nInput student ID 15937, found name #{name}"
# Remove operation
# Remove key-value pair (key, value) from hash table
hmap.delete(10583)
puts "\nAfter removing 10583, hash table is\nKey -> Value"
print_hash_map(hmap)
# Traverse hash table
puts "\nTraverse key-value pairs Key->Value"
hmap.entries.each { |key, value| puts "#{key} -> #{value}" }
puts "\nTraverse keys only Key"
hmap.keys.each { |key| puts key }
puts "\nTraverse values only Value"
hmap.values.each { |val| puts val }
end
@@ -0,0 +1,128 @@
=begin
File: hash_map_chaining.rb
Created Time: 2024-04-13
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative './array_hash_map'
### Hash map with chaining ###
class HashMapChaining
### Constructor ###
def initialize
@size = 0 # Number of key-value pairs
@capacity = 4 # Hash table capacity
@load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion
@extend_ratio = 2 # Expansion multiplier
@buckets = Array.new(@capacity) { [] } # Bucket array
end
### Hash function ###
def hash_func(key)
key % @capacity
end
### Load factor ###
def load_factor
@size / @capacity
end
### Query operation ###
def get(key)
index = hash_func(key)
bucket = @buckets[index]
# Traverse bucket, if key is found, return corresponding val
for pair in bucket
return pair.val if pair.key == key
end
# Return nil if key not found
nil
end
### Add operation ###
def put(key, val)
# When load factor exceeds threshold, perform expansion
extend if load_factor > @load_thres
index = hash_func(key)
bucket = @buckets[index]
# Traverse bucket, if specified key is encountered, update corresponding val and return
for pair in bucket
if pair.key == key
pair.val = val
return
end
end
# If key does not exist, append key-value pair to the end
pair = Pair.new(key, val)
bucket << pair
@size += 1
end
### Delete operation ###
def remove(key)
index = hash_func(key)
bucket = @buckets[index]
# Traverse bucket and remove key-value pair from it
for pair in bucket
if pair.key == key
bucket.delete(pair)
@size -= 1
break
end
end
end
### Expand hash table ###
def extend
# Temporarily store original hash table
buckets = @buckets
# Initialize expanded new hash table
@capacity *= @extend_ratio
@buckets = Array.new(@capacity) { [] }
@size = 0
# Move key-value pairs from original hash table to new hash table
for bucket in buckets
for pair in bucket
put(pair.key, pair.val)
end
end
end
### Print hash table ###
def print
for bucket in @buckets
res = []
for pair in bucket
res << "#{pair.key} -> #{pair.val}"
end
pp res
end
end
end
### Driver Code ###
if __FILE__ == $0
### Initialize hash table
hashmap = HashMapChaining.new
# Add operation
# Add key-value pair (key, value) to the hash table
hashmap.put(12836, "Xiao Ha")
hashmap.put(15937, "Xiao Luo")
hashmap.put(16750, "Xiao Suan")
hashmap.put(13276, "Xiao Fa")
hashmap.put(10583, "Xiao Ya")
puts "\nAfter adding, hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]"
hashmap.print
# Query operation
# Input key into hash table to get value
name = hashmap.get(13276)
puts "\nInput student ID 13276, found name #{name}"
# Remove operation
# Remove key-value pair (key, value) from hash table
hashmap.remove(12836)
puts "\nAfter deleting 12836, hash table is\n[Key1 -> Value1, Key2 -> Value2, ...]"
hashmap.print
end
@@ -0,0 +1,147 @@
=begin
File: hash_map_open_addressing.rb
Created Time: 2024-04-13
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative './array_hash_map'
### Hash map with open addressing ###
class HashMapOpenAddressing
TOMBSTONE = Pair.new(-1, '-1') # Removal marker
### Constructor ###
def initialize
@size = 0 # Number of key-value pairs
@capacity = 4 # Hash table capacity
@load_thres = 2.0 / 3.0 # Load factor threshold for triggering expansion
@extend_ratio = 2 # Expansion multiplier
@buckets = Array.new(@capacity) # Bucket array
end
### Hash function ###
def hash_func(key)
key % @capacity
end
### Load factor ###
def load_factor
@size / @capacity
end
### Search bucket index for key ###
def find_bucket(key)
index = hash_func(key)
first_tombstone = -1
# Linear probing, break when encountering an empty bucket
while !@buckets[index].nil?
# If key is encountered, return the corresponding bucket index
if @buckets[index].key == key
# If a removal marker was encountered before, move the key-value pair to that index
if first_tombstone != -1
@buckets[first_tombstone] = @buckets[index]
@buckets[index] = TOMBSTONE
return first_tombstone # Return the moved bucket index
end
return index # Return bucket index
end
# Record the first removal marker encountered
first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE
# Calculate bucket index, wrap around to the head if past the tail
index = (index + 1) % @capacity
end
# If key does not exist, return the index for insertion
first_tombstone == -1 ? index : first_tombstone
end
### Query operation ###
def get(key)
# Search for bucket index corresponding to key
index = find_bucket(key)
# If key-value pair is found, return corresponding val
return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index])
# Return nil if key-value pair does not exist
nil
end
### Add operation ###
def put(key, val)
# When load factor exceeds threshold, perform expansion
extend if load_factor > @load_thres
# Search for bucket index corresponding to key
index = find_bucket(key)
# If key-value pair found, overwrite val and return
unless [nil, TOMBSTONE].include?(@buckets[index])
@buckets[index].val = val
return
end
# If key-value pair does not exist, add the key-value pair
@buckets[index] = Pair.new(key, val)
@size += 1
end
### Delete operation ###
def remove(key)
# Search for bucket index corresponding to key
index = find_bucket(key)
# If key-value pair is found, overwrite it with removal marker
unless [nil, TOMBSTONE].include?(@buckets[index])
@buckets[index] = TOMBSTONE
@size -= 1
end
end
### Expand hash table ###
def extend
# Temporarily store the original hash table
buckets_tmp = @buckets
# Initialize expanded new hash table
@capacity *= @extend_ratio
@buckets = Array.new(@capacity)
@size = 0
# Move key-value pairs from original hash table to new hash table
for pair in buckets_tmp
put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair)
end
end
### Print hash table ###
def print
for pair in @buckets
if pair.nil?
puts "Nil"
elsif pair == TOMBSTONE
puts "TOMBSTONE"
else
puts "#{pair.key} -> #{pair.val}"
end
end
end
end
### Driver Code ###
if __FILE__ == $0
# Initialize hash table
hashmap = HashMapOpenAddressing.new
# Add operation
# Add key-value pair (key, val) to the hash table
hashmap.put(12836, "Xiao Ha")
hashmap.put(15937, "Xiao Luo")
hashmap.put(16750, "Xiao Suan")
hashmap.put(13276, "Xiao Fa")
hashmap.put(10583, "Xiao Ya")
puts "\nAfter adding is complete, hash table is\nKey -> Value"
hashmap.print
# Query operation
# Input key into hash table to get value val
name = hashmap.get(13276)
puts "\nInput student ID 13276, found name #{name}"
# Remove operation
# Remove key-value pair (key, val) from hash table
hashmap.remove(16750)
puts "\nAfter removing 16750, hash table is\nKey -> Value"
hashmap.print
end
@@ -0,0 +1,62 @@
=begin
File: simple_hash.rb
Created Time: 2024-04-14
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Additive hash ###
def add_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash += c.ord }
hash % modulus
end
### Multiplicative hash ###
def mul_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash = 31 * hash + c.ord }
hash % modulus
end
### XOR hash ###
def xor_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash ^= c.ord }
hash % modulus
end
### Rotational hash ###
def rot_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord }
hash % modulus
end
### Driver Code ###
if __FILE__ == $0
key = "Hello Algo"
hash = add_hash(key)
puts "Additive hash value is #{hash}"
hash = mul_hash(key)
puts "Multiplicative hash value is #{hash}"
hash = xor_hash(key)
puts "XOR hash value is #{hash}"
hash = rot_hash(key)
puts "Rotational hash value is #{hash}"
end
+147
View File
@@ -0,0 +1,147 @@
=begin
File: my_heap.rb
Created Time: 2024-04-19
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
require_relative '../utils/print_util'
### Max heap ###
class MaxHeap
attr_reader :max_heap
### Constructor, build heap from input list ###
def initialize(nums)
# Add list elements to heap as is
@max_heap = nums
# Heapify all nodes except leaf nodes
parent(size - 1).downto(0) do |i|
sift_down(i)
end
end
### Get left child index ###
def left(i)
2 * i + 1
end
### Get right child index ###
def right(i)
2 * i + 2
end
### Get parent node index ###
def parent(i)
(i - 1) / 2 # Floor division
end
### Swap elements ###
def swap(i, j)
@max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i]
end
### Get heap size ###
def size
@max_heap.length
end
### Check if heap is empty ###
def is_empty?
size == 0
end
### Access heap top element ###
def peek
@max_heap[0]
end
### Push element to heap ###
def push(val)
# Add node
@max_heap << val
# Heapify from bottom to top
sift_up(size - 1)
end
### Heapify from node i, bottom to top ###
def sift_up(i)
loop do
# Get parent node of node i
p = parent(i)
# When "crossing root node" or "node needs no repair", end heapify
break if p < 0 || @max_heap[i] <= @max_heap[p]
# Swap two nodes
swap(i, p)
# Loop upward heapify
i = p
end
end
### Pop element from heap ###
def pop
# Handle empty case
raise IndexError, "Heap is empty" if is_empty?
# Delete node
swap(0, size - 1)
# Remove node
val = @max_heap.pop
# Return top element
sift_down(0)
# Return heap top element
val
end
### Heapify from node i, top to bottom ###
def sift_down(i)
loop do
# If node i is largest or indices l, r are out of bounds, no need to continue heapify, break
l, r, ma = left(i), right(i), i
ma = l if l < size && @max_heap[l] > @max_heap[ma]
ma = r if r < size && @max_heap[r] > @max_heap[ma]
# Swap two nodes
break if ma == i
# Swap two nodes
swap(i, ma)
# Loop downwards heapification
i = ma
end
end
### Print heap (binary tree) ###
def __print__
print_heap(@max_heap)
end
end
### Driver Code ###
if __FILE__ == $0
# Consider negating the elements before entering the heap, which can reverse the size relationship, thus implementing max heap
max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2])
puts "\nAfter inputting list and building heap"
max_heap.__print__
# Check if heap is empty
peek = max_heap.peek
puts "\nHeap top element is #{peek}"
# Element enters heap
val = 7
max_heap.push(val)
puts "\nAfter element #{val} pushes to heap"
max_heap.__print__
# Time complexity is O(n), not O(nlogn)
peek = max_heap.pop
puts "\nAfter heap top element #{peek} pops from heap"
max_heap.__print__
# Get heap size
size = max_heap.size
puts "\nHeap size is #{size}"
# Check if heap is empty
is_empty = max_heap.is_empty?
puts "\nIs heap empty #{is_empty}"
end
+64
View File
@@ -0,0 +1,64 @@
=begin
File: top_k.rb
Created Time: 2024-04-19
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
require_relative "./my_heap"
### Push element to heap ###
def push_min_heap(heap, val)
# Negate element
heap.push(-val)
end
### Pop element from heap ###
def pop_min_heap(heap)
# Negate element
-heap.pop
end
### Access heap top element ###
def peek_min_heap(heap)
# Negate element
-heap.peek
end
### Get elements from heap ###
def get_min_heap(heap)
# Negate all elements in heap
heap.max_heap.map { |x| -x }
end
### Find largest k elements in array using heap ###
def top_k_heap(nums, k)
# Python's heapq module implements min heap by default
# Note: We negate all heap elements to simulate min heap using max heap
max_heap = MaxHeap.new([])
# Enter the first k elements of array into heap
for i in 0...k
push_min_heap(max_heap, nums[i])
end
# Starting from the (k+1)th element, maintain heap length as k
for i in k...nums.length
# If current element is greater than top element, top element exits heap, current element enters heap
if nums[i] > peek_min_heap(max_heap)
pop_min_heap(max_heap)
push_min_heap(max_heap, nums[i])
end
end
get_min_heap(max_heap)
end
### Driver Code ###
if __FILE__ == $0
nums = [1, 7, 6, 3, 2]
k = 3
res = top_k_heap(nums, k)
puts "The largest #{k} elements are"
print_heap(res)
end
@@ -0,0 +1,63 @@
=begin
File: binary_search.rb
Created Time: 2024-04-09
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
### Binary search (closed interval) ###
def binary_search(nums, target)
# Initialize closed interval [0, n-1], i.e., i, j point to the first and last elements of the array
i, j = 0, nums.length - 1
# Loop, exit when the search interval is empty (empty when i > j)
while i <= j
# In theory, Ruby numbers can be infinitely large (limited by memory), no need to consider overflow
m = (i + j) / 2 # Calculate the midpoint index m
if nums[m] < target
i = m + 1 # This means target is in the interval [m+1, j]
elsif nums[m] > target
j = m - 1 # This means target is in the interval [i, m-1]
else
return m # Found the target element, return its index
end
end
-1 # Target element not found, return -1
end
### Binary search (left-closed right-open interval) ###
def binary_search_lcro(nums, target)
# Initialize left-closed right-open interval [0, n), i.e., i, j point to the first element and last element+1
i, j = 0, nums.length
# Loop, exit when the search interval is empty (empty when i = j)
while i < j
# Calculate the midpoint index m
m = (i + j) / 2
if nums[m] < target
i = m + 1 # This means target is in the interval [m+1, j)
elsif nums[m] > target
j = m - 1 # This means target is in the interval [i, m)
else
return m # Found the target element, return its index
end
end
-1 # Target element not found, return -1
end
### Driver Code ###
if __FILE__ == $0
target = 6
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
# Binary search (closed interval on both sides)
index = binary_search(nums, target)
puts "Index of target element 6 is #{index}"
# Binary search (left-closed right-open interval)
index = binary_search_lcro(nums, target)
puts "Index of target element 6 is #{index}"
end
@@ -0,0 +1,47 @@
=begin
File: binary_search_edge.rb
Created Time: 2024-04-09
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
require_relative './binary_search_insertion'
### Binary search leftmost target ###
def binary_search_left_edge(nums, target)
# Equivalent to finding the insertion point of target
i = binary_search_insertion(nums, target)
# Target not found, return -1
return -1 if i == nums.length || nums[i] != target
i # Found target, return index i
end
### Binary search rightmost target ###
def binary_search_right_edge(nums, target)
# Convert to finding the leftmost target + 1
i = binary_search_insertion(nums, target + 1)
# j points to the rightmost target, i points to the first element greater than target
j = i - 1
# Target not found, return -1
return -1 if j == -1 || nums[j] != target
j # Found target, return index j
end
### Driver Code ###
if __FILE__ == $0
# Array with duplicate elements
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]
puts "\nArray nums = #{nums}"
# Binary search left and right boundaries
for target in [6, 7]
index = binary_search_left_edge(nums, target)
puts "Leftmost element #{target} index is #{index}"
index = binary_search_right_edge(nums, target)
puts "Rightmost element #{target} index is #{index}"
end
end
@@ -0,0 +1,68 @@
=begin
File: binary_search_insertion.rb
Created Time: 2024-04-09
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
### Binary search insertion point (no duplicates) ###
def binary_search_insertion_simple(nums, target)
# Initialize closed interval [0, n-1]
i, j = 0, nums.length - 1
while i <= j
# Calculate the midpoint index m
m = (i + j) / 2
if nums[m] < target
i = m + 1 # target is in the interval [m+1, j]
elsif nums[m] > target
j = m - 1 # target is in the interval [i, m-1]
else
return m # Found target, return insertion point m
end
end
i # Target not found, return insertion point i
end
### Binary search insertion point (with duplicates) ###
def binary_search_insertion(nums, target)
# Initialize closed interval [0, n-1]
i, j = 0, nums.length - 1
while i <= j
# Calculate the midpoint index m
m = (i + j) / 2
if nums[m] < target
i = m + 1 # target is in the interval [m+1, j]
elsif nums[m] > target
j = m - 1 # target is in the interval [i, m-1]
else
j = m - 1 # The first element less than target is in the interval [i, m-1]
end
end
i # Return insertion point i
end
### Driver Code ###
if __FILE__ == $0
# Array without duplicate elements
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
puts "\nArray nums = #{nums}"
# Binary search for insertion point
for target in [6, 9]
index = binary_search_insertion_simple(nums, target)
puts "Insertion point index for element #{target} is #{index}"
end
# Array with duplicate elements
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]
puts "\nArray nums = #{nums}"
# Binary search for insertion point
for target in [2, 6, 20]
index = binary_search_insertion(nums, target)
puts "Insertion point index for element #{target} is #{index}"
end
end
@@ -0,0 +1,47 @@
=begin
File: hashing_search.rb
Created Time: 2024-04-09
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
require_relative '../utils/list_node'
### Hash search (array) ###
def hashing_search_array(hmap, target)
# Hash table's key: target element, value: index
# If this key does not exist in the hash table, return -1
hmap[target] || -1
end
### Hash search (linked list) ###
def hashing_search_linkedlist(hmap, target)
# Hash table's key: target element, value: node object
# If this key does not exist in the hash table, return None
hmap[target] || nil
end
### Driver Code ###
if __FILE__ == $0
target = 3
# Hash search (array)
nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]
# Initialize hash table
map0 = {}
for i in 0...nums.length
map0[nums[i]] = i # key: element, value: index
end
index = hashing_search_array(map0, target)
puts "Index of target element 3 = #{index}"
# Hash search (linked list)
head = arr_to_linked_list(nums)
# Initialize hash table
map1 = {}
while head
map1[head.val] = head
head = head.next
end
node = hashing_search_linkedlist(map1, target)
puts "Node object for target value 3 is #{node}"
end
@@ -0,0 +1,44 @@
=begin
File: linear_search.rb
Created Time: 2024-04-09
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
require_relative '../utils/list_node'
### Linear search (array) ###
def linear_search_array(nums, target)
# Traverse array
for i in 0...nums.length
return i if nums[i] == target # Found the target element, return its index
end
-1 # Target element not found, return -1
end
### Linear search (linked list) ###
def linear_search_linkedlist(head, target)
# Traverse the linked list
while head
return head if head.val == target # Found the target node, return it
head = head.next
end
nil # Target node not found, return None
end
### Driver Code ###
if __FILE__ == $0
target = 3
# Perform linear search in array
nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]
index = linear_search_array(nums, target)
puts "Index of target element 3 = #{index}"
# Perform linear search in linked list
head = arr_to_linked_list(nums)
node = linear_search_linkedlist(head, target)
puts "Node object for target value 3 is #{node}"
end
@@ -0,0 +1,46 @@
=begin
File: two_sum.rb
Created Time: 2024-04-09
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
### Method 1: Brute force enumeration ###
def two_sum_brute_force(nums, target)
# Two nested loops, time complexity is O(n^2)
for i in 0...(nums.length - 1)
for j in (i + 1)...nums.length
return [i, j] if nums[i] + nums[j] == target
end
end
[]
end
### Method 2: Auxiliary hash table ###
def two_sum_hash_table(nums, target)
# Auxiliary hash table, space complexity is O(n)
dic = {}
# Single loop, time complexity is O(n)
for i in 0...nums.length
return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i])
dic[nums[i]] = i
end
[]
end
### Driver Code ###
if __FILE__ == $0
# ======= Test Case =======
nums = [2, 7, 11, 15]
target = 13
# ====== Driver Code ======
# Method 1
res = two_sum_brute_force(nums, target)
puts "Method 1 res = #{res}"
# Method 2
res = two_sum_hash_table(nums, target)
puts "Method 2 res = #{res}"
end
@@ -0,0 +1,51 @@
=begin
File: bubble_sort.rb
Created Time: 2024-05-02
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Bubble sort ###
def bubble_sort(nums)
n = nums.length
# Outer loop: unsorted range is [0, i]
for i in (n - 1).downto(1)
# Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range
for j in 0...i
if nums[j] > nums[j + 1]
# Swap nums[j] and nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
end
end
end
end
### Bubble sort (flag optimization) ###
def bubble_sort_with_flag(nums)
n = nums.length
# Outer loop: unsorted range is [0, i]
for i in (n - 1).downto(1)
flag = false # Initialize flag
# Inner loop: swap the largest element in the unsorted range [0, i] to the rightmost end of that range
for j in 0...i
if nums[j] > nums[j + 1]
# Swap nums[j] and nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = true # Record element swap
end
end
break unless flag # No elements were swapped in this round of "bubbling", exit directly
end
end
### Driver Code ###
if __FILE__ == $0
nums = [4, 1, 3, 1, 5, 2]
bubble_sort(nums)
puts "After bubble sort, nums = #{nums}"
nums1 = [4, 1, 3, 1, 5, 2]
bubble_sort_with_flag(nums1)
puts "After bubble sort, nums = #{nums1}"
end
@@ -0,0 +1,43 @@
=begin
File: bucket_sort.rb
Created Time: 2024-04-17
Author: Martin Xu (martin.xus@gmail.com)
=end
### Bucket sort ###
def bucket_sort(nums)
# Initialize k = n/2 buckets, expected to allocate 2 elements per bucket
k = nums.length / 2
buckets = Array.new(k) { [] }
# 1. Distribute array elements into various buckets
nums.each do |num|
# Input data range is [0, 1), use num * k to map to index range [0, k-1]
i = (num * k).to_i
# Add num to bucket i
buckets[i] << num
end
# 2. Sort each bucket
buckets.each do |bucket|
# Use built-in sorting function, can also replace with other sorting algorithms
bucket.sort!
end
# 3. Traverse buckets to merge results
i = 0
buckets.each do |bucket|
bucket.each do |num|
nums[i] = num
i += 1
end
end
end
### Driver Code ###
if __FILE__ == $0
# Assume input data is floating point, interval [0, 1)
nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]
bucket_sort(nums)
puts "After bucket sort, nums = #{nums}"
end
@@ -0,0 +1,62 @@
=begin
File: counting_sort.rb
Created Time: 2024-05-02
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Counting sort ###
def counting_sort_naive(nums)
# Simple implementation, cannot be used for sorting objects
# 1. Count the maximum element m in the array
m = 0
nums.each { |num| m = [m, num].max }
# 2. Count the occurrence of each number
# counter[num] represents the occurrence of num
counter = Array.new(m + 1, 0)
nums.each { |num| counter[num] += 1 }
# 3. Traverse counter, filling each element back into the original array nums
i = 0
for num in 0...(m + 1)
(0...counter[num]).each do
nums[i] = num
i += 1
end
end
end
### Counting sort ###
def counting_sort(nums)
# Complete implementation, can sort objects and is a stable sort
# 1. Count the maximum element m in the array
m = nums.max
# 2. Count the occurrence of each number
# counter[num] represents the occurrence of num
counter = Array.new(m + 1, 0)
nums.each { |num| counter[num] += 1 }
# 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index"
# counter[num]-1 is the last index where num appears in res
(0...m).each { |i| counter[i + 1] += counter[i] }
# 4. Traverse nums in reverse, fill elements into result array res
# Initialize the array res to record results
n = nums.length
res = Array.new(n, 0)
(n - 1).downto(0).each do |i|
num = nums[i]
res[counter[num] - 1] = num # Place num at the corresponding index
counter[num] -= 1 # Decrement the prefix sum by 1, getting the next index to place num
end
# Use result array res to overwrite the original array nums
(0...n).each { |i| nums[i] = res[i] }
end
### Driver Code ###
if __FILE__ == $0
nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]
counting_sort_naive(nums)
puts "After counting sort (cannot sort objects), nums = #{nums}"
nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]
counting_sort(nums1)
puts "After counting sort, nums1 = #{nums1}"
end
@@ -0,0 +1,45 @@
=begin
File: heap_sort.rb
Created Time: 2024-04-10
Author: junminhong (junminhong1110@gmail.com)
=end
### Heap length is n, heapify from node i, top to bottom ###
def sift_down(nums, n, i)
while true
# If node i is largest or indices l, r are out of bounds, no need to continue heapify, break
l = 2 * i + 1
r = 2 * i + 2
ma = i
ma = l if l < n && nums[l] > nums[ma]
ma = r if r < n && nums[r] > nums[ma]
# Swap two nodes
break if ma == i
# Swap two nodes
nums[i], nums[ma] = nums[ma], nums[i]
# Loop downwards heapification
i = ma
end
end
### Heap sort ###
def heap_sort(nums)
# Build heap operation: heapify all nodes except leaves
(nums.length / 2 - 1).downto(0) do |i|
sift_down(nums, nums.length, i)
end
# Extract the largest element from the heap and repeat for n-1 rounds
(nums.length - 1).downto(1) do |i|
# Delete node
nums[0], nums[i] = nums[i], nums[0]
# Start heapifying the root node, from top to bottom
sift_down(nums, i, 0)
end
end
### Driver Code ###
if __FILE__ == $0
nums = [4, 1, 3, 1, 5, 2]
heap_sort(nums)
puts "After heap sort, nums = #{nums.inspect}"
end
@@ -0,0 +1,26 @@
=begin
File: insertion_sort.rb
Created Time: 2024-04-02
Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Insertion sort ###
def insertion_sort(nums)
n = nums.length
# Outer loop: sorted interval is [0, i-1]
for i in 1...n
base = nums[i]
j = i - 1
# Inner loop: insert base into the correct position within the sorted interval [0, i-1]
while j >= 0 && nums[j] > base
nums[j + 1] = nums[j] # Move nums[j] to the right by one position
j -= 1
end
nums[j + 1] = base # Assign base to the correct position
end
end
### Driver Code ###
nums = [4, 1, 3, 1, 5, 2]
insertion_sort(nums)
puts "After insertion sort, nums = #{nums}"
@@ -0,0 +1,60 @@
=begin
File: merge_sort.rb
Created Time: 2024-04-10
Author: junminhong (junminhong1110@gmail.com)
=end
### Merge left and right subarrays ###
def merge(nums, left, mid, right)
# Left subarray interval is [left, mid], right subarray interval is [mid+1, right]
# Create temporary array tmp to store merged result
tmp = Array.new(right - left + 1, 0)
# Initialize the start indices of the left and right subarrays
i, j, k = left, mid + 1, 0
# While both subarrays still have elements, compare and copy the smaller element into the temporary array
while i <= mid && j <= right
if nums[i] <= nums[j]
tmp[k] = nums[i]
i += 1
else
tmp[k] = nums[j]
j += 1
end
k += 1
end
# Copy the remaining elements of the left and right subarrays into the temporary array
while i <= mid
tmp[k] = nums[i]
i += 1
k += 1
end
while j <= right
tmp[k] = nums[j]
j += 1
k += 1
end
# Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval
(0...tmp.length).each do |k|
nums[left + k] = tmp[k]
end
end
### Merge sort ###
def merge_sort(nums, left, right)
# Termination condition
# Terminate recursion when subarray length is 1
return if left >= right
# Divide and conquer stage
mid = left + (right - left) / 2 # Calculate midpoint
merge_sort(nums, left, mid) # Recursively process the left subarray
merge_sort(nums, mid + 1, right) # Recursively process the right subarray
# Merge stage
merge(nums, left, mid, right)
end
### Driver Code ###
if __FILE__ == $0
nums = [7, 3, 2, 6, 0, 1, 5, 4]
merge_sort(nums, 0, nums.length - 1)
puts "After merge sort, nums = #{nums.inspect}"
end
+153
View File
@@ -0,0 +1,153 @@
=begin
File: quick_sort.rb
Created Time: 2024-04-01
Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Quick sort class ###
class QuickSort
class << self
### Sentinel partition ###
def partition(nums, left, right)
# Use nums[left] as the pivot
i, j = left, right
while i < j
while i < j && nums[j] >= nums[left]
j -= 1 # Search from right to left for the first element smaller than the pivot
end
while i < j && nums[i] <= nums[left]
i += 1 # Search from left to right for the first element greater than the pivot
end
# Swap elements
nums[i], nums[j] = nums[j], nums[i]
end
# Swap the pivot to the boundary between the two subarrays
nums[i], nums[left] = nums[left], nums[i]
i # Return the index of the pivot
end
### Quick sort class ###
def quick_sort(nums, left, right)
# Recurse when subarray length is not 1
if left < right
# Sentinel partition
pivot = partition(nums, left, right)
# Recursively process the left subarray and right subarray
quick_sort(nums, left, pivot - 1)
quick_sort(nums, pivot + 1, right)
end
nums
end
end
end
### Quick sort class (median optimization) ###
class QuickSortMedian
class << self
### Select median of three candidate elements ###
def median_three(nums, left, mid, right)
# Select the median of three candidate elements
_l, _m, _r = nums[left], nums[mid], nums[right]
# m is between l and r
return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l)
# l is between m and r
return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m)
return right
end
### Sentinel partition (median of three) ###
def partition(nums, left, right)
### Use nums[left] as pivot
med = median_three(nums, left, (left + right) / 2, right)
# Swap median to leftmost position of array
nums[left], nums[med] = nums[med], nums[left]
i, j = left, right
while i < j
while i < j && nums[j] >= nums[left]
j -= 1 # Search from right to left for the first element smaller than the pivot
end
while i < j && nums[i] <= nums[left]
i += 1 # Search from left to right for the first element greater than the pivot
end
# Swap elements
nums[i], nums[j] = nums[j], nums[i]
end
# Swap the pivot to the boundary between the two subarrays
nums[i], nums[left] = nums[left], nums[i]
i # Return the index of the pivot
end
### Quick sort ###
def quick_sort(nums, left, right)
# Recurse when subarray length is not 1
if left < right
# Sentinel partition
pivot = partition(nums, left, right)
# Recursively process the left subarray and right subarray
quick_sort(nums, left, pivot - 1)
quick_sort(nums, pivot + 1, right)
end
nums
end
end
end
### Quick sort class (recursion depth optimization) ###
class QuickSortTailCall
class << self
### Sentinel partition ###
def partition(nums, left, right)
# Use nums[left] as pivot
i = left
j = right
while i < j
while i < j && nums[j] >= nums[left]
j -= 1 # Search from right to left for the first element smaller than the pivot
end
while i < j && nums[i] <= nums[left]
i += 1 # Search from left to right for the first element greater than the pivot
end
# Swap elements
nums[i], nums[j] = nums[j], nums[i]
end
# Swap the pivot to the boundary between the two subarrays
nums[i], nums[left] = nums[left], nums[i]
i # Return the index of the pivot
end
### Quick sort (recursion depth optimization) ###
def quick_sort(nums, left, right)
# Recurse when subarray length is not 1
while left < right
# Sentinel partition
pivot = partition(nums, left, right)
# Perform quick sort on the shorter of the two subarrays
if pivot - left < right - pivot
quick_sort(nums, left, pivot - 1)
left = pivot + 1 # Remaining unsorted interval is [pivot + 1, right]
else
quick_sort(nums, pivot + 1, right)
right = pivot - 1 # Remaining unsorted interval is [left, pivot - 1]
end
end
end
end
end
### Driver Code ###
if __FILE__ == $0
# Quick sort
nums = [2, 4, 1, 0, 3, 5]
QuickSort.quick_sort(nums, 0, nums.length - 1)
puts "After quick sort, nums = #{nums}"
# Quick sort (recursion depth optimization)
nums1 = [2, 4, 1, 0, 3, 5]
QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1)
puts "After quick sort (median pivot optimization), nums1 = #{nums1}"
# Quick sort (recursion depth optimization)
nums2 = [2, 4, 1, 0, 3, 5]
QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1)
puts "After quick sort (recursion depth optimization), nums2 = #{nums2}"
end
@@ -0,0 +1,70 @@
=begin
File: radix_sort.rb
Created Time: 2024-05-03
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Get k-th digit of element num, where exp = 10^(k-1) ###
def digit(num, exp)
# Passing exp instead of k avoids expensive exponentiation calculations
(num / exp) % 10
end
### Counting sort (sort by k-th digit of nums) ###
def counting_sort_digit(nums, exp)
# Decimal digit range is 0~9, therefore need a bucket array of length 10
counter = Array.new(10, 0)
n = nums.length
# Count the occurrence of digits 0~9
for i in 0...n
d = digit(nums[i], exp) # Get the k-th digit of nums[i], noted as d
counter[d] += 1 # Count the occurrence of digit d
end
# Calculate prefix sum, converting "occurrence count" into "array index"
(1...10).each { |i| counter[i] += counter[i - 1] }
# Traverse in reverse, based on bucket statistics, place each element into res
res = Array.new(n, 0)
for i in (n - 1).downto(0)
d = digit(nums[i], exp)
j = counter[d] - 1 # Get the index j for d in the array
res[j] = nums[i] # Place the current element at index j
counter[d] -= 1 # Decrease the count of d by 1
end
# Use result to overwrite the original array nums
(0...n).each { |i| nums[i] = res[i] }
end
### Radix sort ###
def radix_sort(nums)
# Get the maximum element of the array, used to determine the maximum number of digits
m = nums.max
# Traverse from the lowest to the highest digit
exp = 1
while exp <= m
# Perform counting sort on the k-th digit of array elements
# k = 1 -> exp = 1
# k = 2 -> exp = 10
# i.e., exp = 10^(k-1)
counting_sort_digit(nums, exp)
exp *= 10
end
end
### Driver Code ###
if __FILE__ == $0
# Radix sort
nums = [
10546151,
35663510,
42865989,
34862445,
81883077,
88906420,
72429244,
30524779,
82060337,
63832996,
]
radix_sort(nums)
puts "After radix sort, nums = #{nums}"
end
@@ -0,0 +1,29 @@
=begin
File: selection_sort.rb
Created Time: 2024-05-03
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Selection sort ###
def selection_sort(nums)
n = nums.length
# Outer loop: unsorted interval is [i, n-1]
for i in 0...(n - 1)
# Inner loop: find the smallest element within the unsorted interval
k = i
for j in (i + 1)...n
if nums[j] < nums[k]
k = j # Record the index of the smallest element
end
end
# Swap the smallest element with the first element of the unsorted interval
nums[i], nums[k] = nums[k], nums[i]
end
end
### Driver Code ###
if __FILE__ == $0
nums = [4, 1, 3, 1, 5, 2]
selection_sort(nums)
puts "After selection sort, nums = #{nums}"
end
@@ -0,0 +1,145 @@
=begin
File: array_deque.rb
Created Time: 2024-04-05
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Deque based on circular array ###
class ArrayDeque
### Get deque length ###
attr_reader :size
### Constructor ###
def initialize(capacity)
@nums = Array.new(capacity, 0)
@front = 0
@size = 0
end
### Get deque capacity ###
def capacity
@nums.length
end
### Check if deque is empty ###
def is_empty?
size.zero?
end
### Enqueue at front ###
def push_first(num)
if size == capacity
puts 'Double-ended queue is full'
return
end
# Use modulo operation to wrap front around to the tail after passing the head of the array
# Add num to the front of the queue
@front = index(@front - 1)
# Add num to front of queue
@nums[@front] = num
@size += 1
end
### Enqueue at rear ###
def push_last(num)
if size == capacity
puts 'Double-ended queue is full'
return
end
# Use modulo operation to wrap rear around to the head after passing the tail of the array
rear = index(@front + size)
# Front pointer moves one position backward
@nums[rear] = num
@size += 1
end
### Dequeue from front ###
def pop_first
num = peek_first
# Move front pointer backward by one position
@front = index(@front + 1)
@size -= 1
num
end
### Dequeue from rear ###
def pop_last
num = peek_last
@size -= 1
num
end
### Access front element ###
def peek_first
raise IndexError, 'Deque is empty' if is_empty?
@nums[@front]
end
### Access rear element ###
def peek_last
raise IndexError, 'Deque is empty' if is_empty?
# Initialize double-ended queue
last = index(@front + size - 1)
@nums[last]
end
### Return array for printing ###
def to_array
# Elements enqueue
res = []
for i in 0...size
res << @nums[index(@front + i)]
end
res
end
private
### Calculate circular array index ###
def index(i)
# Use modulo operation to wrap the array head and tail together
# When i passes the tail of the array, return to the head
# When i passes the head of the array, return to the tail
(i + capacity) % capacity
end
end
### Driver Code ###
if __FILE__ == $0
# Get the length of the double-ended queue
deque = ArrayDeque.new(10)
deque.push_last(3)
deque.push_last(2)
deque.push_last(5)
puts "Deque deque = #{deque.to_array}"
# Update element
peek_first = deque.peek_first
puts "Front element peek_first = #{peek_first}"
peek_last = deque.peek_last
puts "Rear element peek_last = #{peek_last}"
# Elements enqueue
deque.push_last(4)
puts "After element 4 enqueues at rear, deque = #{deque.to_array}"
deque.push_first(1)
puts "After element 1 enqueues at rear, deque = #{deque.to_array}"
# Element dequeue
pop_last = deque.pop_last
puts "Dequeue rear element = #{pop_last}, after dequeue deque = #{deque.to_array}"
pop_first = deque.pop_first
puts "Dequeue front element = #{pop_first}, after dequeue deque = #{deque.to_array}"
# Get the length of the double-ended queue
size = deque.size
puts "Deque length size = #{size}"
# Check if the double-ended queue is empty
is_empty = deque.is_empty?
puts "Is deque empty = #{is_empty}"
end
@@ -0,0 +1,107 @@
=begin
File: array_queue.rb
Created Time: 2024-04-05
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Queue based on circular array ###
class ArrayQueue
### Get queue length ###
attr_reader :size
### Constructor ###
def initialize(size)
@nums = Array.new(size, 0) # Array for storing queue elements
@front = 0 # Front pointer, points to the front of the queue element
@size = 0 # Queue length
end
### Get queue capacity ###
def capacity
@nums.length
end
### Check if queue is empty ###
def is_empty?
size.zero?
end
### Enqueue ###
def push(num)
raise IndexError, 'Queue is full' if size == capacity
# Use modulo operation to wrap rear around to the head after passing the tail of the array
# Add num to the rear of the queue
rear = (@front + size) % capacity
# Front pointer moves one position backward
@nums[rear] = num
@size += 1
end
### Dequeue ###
def pop
num = peek
# Move front pointer backward by one position, if it passes the tail, return to array head
@front = (@front + 1) % capacity
@size -= 1
num
end
### Access front element ###
def peek
raise IndexError, 'Queue is empty' if is_empty?
@nums[@front]
end
### Return list for printing ###
def to_array
res = Array.new(size, 0)
j = @front
for i in 0...size
res[i] = @nums[j % capacity]
j += 1
end
res
end
end
### Driver Code ###
if __FILE__ == $0
# Access front of the queue element
queue = ArrayQueue.new(10)
# Elements enqueue
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
puts "Queue queue = #{queue.to_array}"
# Return list for printing
peek = queue.peek
puts "Front element peek = #{peek}"
# Element dequeue
pop = queue.pop
puts "Dequeue element pop = #{pop}"
puts "After dequeue, queue = #{queue.to_array}"
# Get the length of the queue
size = queue.size
puts "Queue length size = #{size}"
# Check if the queue is empty
is_empty = queue.is_empty?
puts "Is queue empty = #{is_empty}"
# Test circular array
for i in 0...10
queue.push(i)
queue.pop
puts "After round #{i} of enqueue + dequeue, queue = #{queue.to_array}"
end
end
@@ -0,0 +1,78 @@
=begin
File: array_stack.rb
Created Time: 2024-04-06
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Stack based on array ###
class ArrayStack
### Constructor ###
def initialize
@stack = []
end
### Get stack length ###
def size
@stack.length
end
### Check if stack is empty ###
def is_empty?
@stack.empty?
end
### Push ###
def push(item)
@stack << item
end
### Pop ###
def pop
raise IndexError, 'Stack is empty' if is_empty?
@stack.pop
end
### Access top element ###
def peek
raise IndexError, 'Stack is empty' if is_empty?
@stack.last
end
### Return list for printing ###
def to_array
@stack
end
end
### Driver Code ###
if __FILE__ == $0
# Access top of the stack element
stack = ArrayStack.new
# Elements push onto stack
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
puts "Stack stack = #{stack.to_array}"
# Return list for printing
peek = stack.peek
puts "Top element peek = #{peek}"
# Element pop from stack
pop = stack.pop
puts "Pop element pop = #{pop}"
puts "After pop, stack = #{stack.to_array}"
# Get the length of the stack
size = stack.size
puts "Stack length size = #{size}"
# Check if empty
is_empty = stack.is_empty?
puts "Is stack empty = #{is_empty}"
end
@@ -0,0 +1,42 @@
=begin
File: deque.rb
Created Time: 2024-04-06
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Driver Code ###
if __FILE__ == $0
# Get the length of the double-ended queue
# Ruby has no built-in deque, can only use Array as deque
deque = []
# Element enqueues
deque << 2
deque << 5
deque << 4
# Note: due to array, Array#unshift method has O(n) time complexity
deque.unshift(3)
deque.unshift(1)
puts "Deque deque = #{deque}"
# Update element
peek_first = deque.first
puts "Front element peek_first = #{peek_first}"
peek_last = deque.last
puts "Rear element peek_last = #{peek_last}"
# Element dequeue
# Note: due to array, Array#shift method has O(n) time complexity
pop_front = deque.shift
puts "Dequeue front element pop_front = #{pop_front}, after dequeue deque = #{deque}"
pop_back = deque.pop
puts "Dequeue rear element pop_back = #{pop_back}, after dequeue deque = #{deque}"
# Get the length of the double-ended queue
size = deque.length
puts "Deque length size = #{size}"
# Check if the double-ended queue is empty
is_empty = size.zero?
puts "Is deque empty = #{is_empty}"
end
@@ -0,0 +1,168 @@
=begin
File: linkedlist_deque.rb
Created Time: 2024-04-06
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Doubly linked list node
class ListNode
attr_accessor :val
attr_accessor :next # Successor node reference
attr_accessor :prev # Predecessor node reference
### Constructor ###
def initialize(val)
@val = val
end
end
### Deque based on doubly linked list ###
class LinkedListDeque
### Get deque length ###
attr_reader :size
### Constructor ###
def initialize
@front = nil # Head node front
@rear = nil # Tail node rear
@size = 0 # Length of the double-ended queue
end
### Check if deque is empty ###
def is_empty?
size.zero?
end
### Enqueue operation ###
def push(num, is_front)
node = ListNode.new(num)
# If list is empty, set both front and rear to node
if is_empty?
@front = @rear = node
# Front of the queue enqueue operation
elsif is_front
# Add node to the head of the linked list
@front.prev = node
node.next = @front
@front = node # Update head node
# Rear of the queue enqueue operation
else
# Add node to the tail of the linked list
@rear.next = node
node.prev = @rear
@rear = node # Update tail node
end
@size += 1 # Update queue length
end
### Enqueue at front ###
def push_first(num)
push(num, true)
end
### Enqueue at rear ###
def push_last(num)
push(num, false)
end
### Dequeue operation ###
def pop(is_front)
raise IndexError, 'Deque is empty' if is_empty?
# Temporarily store head node value
if is_front
val = @front.val # Delete head node
# Delete head node
fnext = @front.next
unless fnext.nil?
fnext.prev = nil
@front.next = nil
end
@front = fnext # Update head node
# Temporarily store tail node value
else
val = @rear.val # Delete tail node
# Update tail node
rprev = @rear.prev
unless rprev.nil?
rprev.next = nil
@rear.prev = nil
end
@rear = rprev # Update tail node
end
@size -= 1 # Update queue length
val
end
### Dequeue from front ###
def pop_first
pop(true)
end
### Dequeue from front ###
def pop_last
pop(false)
end
### Access front element ###
def peek_first
raise IndexError, 'Deque is empty' if is_empty?
@front.val
end
### Access rear element ###
def peek_last
raise IndexError, 'Deque is empty' if is_empty?
@rear.val
end
### Return array for printing ###
def to_array
node = @front
res = Array.new(size, 0)
for i in 0...size
res[i] = node.val
node = node.next
end
res
end
end
### Driver Code ###
if __FILE__ == $0
# Get the length of the double-ended queue
deque = LinkedListDeque.new
deque.push_last(3)
deque.push_last(2)
deque.push_last(5)
puts "Deque deque = #{deque.to_array}"
# Update element
peek_first = deque.peek_first
puts "Front element peek_first = #{peek_first}"
peek_last = deque.peek_last
puts "Rear element peek_last = #{peek_last}"
# Elements enqueue
deque.push_last(4)
puts "After element 4 enqueues at rear, deque = #{deque.to_array}"
deque.push_first(1)
puts "After element 1 enqueues at front, deque = #{deque.to_array}"
# Element dequeue
pop_last = deque.pop_last
puts "Dequeue rear element = #{pop_last}, after dequeue deque = #{deque.to_array}"
pop_first = deque.pop_first
puts "Dequeue front element = #{pop_first}, after dequeue deque = #{deque.to_array}"
# Get the length of the double-ended queue
size = deque.size
puts "Deque length size = #{size}"
# Check if the double-ended queue is empty
is_empty = deque.is_empty?
puts "Is deque empty = #{is_empty}"
end
@@ -0,0 +1,101 @@
=begin
File: linkedlist_queue.rb
Created Time: 2024-04-06
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/list_node'
### Queue based on linked list ###
class LinkedListQueue
### Get queue length ###
attr_reader :size
### Constructor ###
def initialize
@front = nil # Head node front
@rear = nil # Tail node rear
@size = 0
end
### Check if queue is empty ###
def is_empty?
@front.nil?
end
### Enqueue ###
def push(num)
# Add num after the tail node
node = ListNode.new(num)
# If queue is empty, set both front and rear to this node
if @front.nil?
@front = node
@rear = node
# If queue is not empty, add this node after rear
else
@rear.next = node
@rear = node
end
@size += 1
end
### Dequeue ###
def pop
num = peek
# Delete head node
@front = @front.next
@size -= 1
num
end
### Access front element ###
def peek
raise IndexError, 'Queue is empty' if is_empty?
@front.val
end
### Convert linked list to Array and return ###
def to_array
queue = []
temp = @front
while temp
queue << temp.val
temp = temp.next
end
queue
end
end
### Driver Code ###
if __FILE__ == $0
# Access front of the queue element
queue = LinkedListQueue.new
# Element enqueues
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
puts "Queue queue = #{queue.to_array}"
# Return list for printing
peek = queue.peek
puts "Front element = #{peek}"
# Element dequeue
pop_front = queue.pop
puts "Dequeue element pop = #{pop_front}"
puts "After dequeue, queue = #{queue.to_array}"
# Get the length of the queue
size = queue.size
puts "Queue length size = #{size}"
# Check if the queue is empty
is_empty = queue.is_empty?
puts "Is queue empty = #{is_empty}"
end
@@ -0,0 +1,87 @@
=begin
File: linkedlist_stack.rb
Created Time: 2024-04-06
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/list_node'
### Stack based on linked list ###
class LinkedListStack
attr_reader :size
### Constructor ###
def initialize
@size = 0
end
### Check if stack is empty ###
def is_empty?
@peek.nil?
end
### Push ###
def push(val)
node = ListNode.new(val)
node.next = @peek
@peek = node
@size += 1
end
### Pop ###
def pop
num = peek
@peek = @peek.next
@size -= 1
num
end
### Access top element ###
def peek
raise IndexError, 'Stack is empty' if is_empty?
@peek.val
end
### Convert linked list to Array and return ###
def to_array
arr = []
node = @peek
while node
arr << node.val
node = node.next
end
arr.reverse
end
end
### Driver Code ###
if __FILE__ == $0
# Access top of the stack element
stack = LinkedListStack.new
# Elements push onto stack
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
puts "Stack stack = #{stack.to_array}"
# Return list for printing
peek = stack.peek
puts "Top element peek = #{peek}"
# Element pop from stack
pop = stack.pop
puts "Pop element pop = #{pop}"
puts "After pop, stack = #{stack.to_array}"
# Get the length of the stack
size = stack.size
puts "Stack length size = #{size}"
# Check if empty
is_empty = stack.is_empty?
puts "Is stack empty = #{is_empty}"
end
@@ -0,0 +1,38 @@
=begin
File: queue.rb
Created Time: 2024-04-06
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Driver Code ###
if __FILE__ == $0
# Access front of the queue element
# Ruby's built-in queue (Thread::Queue) has no peek and traversal methods, can use Array as queue
queue = []
# Elements enqueue
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
puts "Queue queue = #{queue}"
# Access queue elements
peek = queue.first
puts "Front element peek = #{peek}"
# Element dequeue
# Note: due to array, Array#shift method has O(n) time complexity
pop = queue.shift
puts "Dequeue element pop = #{pop}"
puts "After dequeue, queue = #{queue}"
# Get the length of the queue
size = queue.length
puts "Queue length size = #{size}"
# Check if the queue is empty
is_empty = queue.empty?
puts "Is queue empty = #{is_empty}"
end
@@ -0,0 +1,37 @@
=begin
File: stack.rb
Created Time: 2024-04-06
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Driver Code ###
if __FILE__ == $0
# Access top of the stack element
# Ruby has no built-in stack class, can use Array as stack
stack = []
# Elements push onto stack
stack << 1
stack << 3
stack << 2
stack << 5
stack << 4
puts "Stack stack = #{stack}"
# Return list for printing
peek = stack.last
puts "Top element peek = #{peek}"
# Element pop from stack
pop = stack.pop
puts "Pop element pop = #{pop}"
puts "After pop, stack = #{stack}"
# Get the length of the stack
size = stack.length
puts "Stack length size = #{size}"
# Check if empty
is_empty = stack.empty?
puts "Is stack empty = #{is_empty}"
end
@@ -0,0 +1,124 @@
=begin
File: array_binary_tree.rb
Created Time: 2024-04-17
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Array representation of binary tree class ###
class ArrayBinaryTree
### Constructor ###
def initialize(arr)
@tree = arr.to_a
end
### List capacity ###
def size
@tree.length
end
### Get value of node at index i ###
def val(i)
# Return nil if index out of bounds, representing empty position
return if i < 0 || i >= size
@tree[i]
end
### Get left child index of node at index i ###
def left(i)
2 * i + 1
end
### Get right child index of node at index i ###
def right(i)
2 * i + 2
end
### Get parent node index of node at index i ###
def parent(i)
(i - 1) / 2
end
### Level-order traversal ###
def level_order
@res = []
# Traverse array directly
for i in 0...size
@res << val(i) unless val(i).nil?
end
@res
end
### Depth-first traversal ###
def dfs(i, order)
return if val(i).nil?
# Preorder traversal
@res << val(i) if order == :pre
dfs(left(i), order)
# Inorder traversal
@res << val(i) if order == :in
dfs(right(i), order)
# Postorder traversal
@res << val(i) if order == :post
end
### Pre-order traversal ###
def pre_order
@res = []
dfs(0, :pre)
@res
end
### In-order traversal ###
def in_order
@res = []
dfs(0, :in)
@res
end
### Post-order traversal ###
def post_order
@res = []
dfs(0, :post)
@res
end
end
### Driver Code ###
if __FILE__ == $0
# Initialize binary tree
# Here we use a function to generate a binary tree directly from an array
arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]
root = arr_to_tree(arr)
puts "\nInitialize binary tree\n\n"
puts 'Array representation of binary tree:'
pp arr
puts 'Linked list representation of binary tree:'
print_tree(root)
# Binary tree class represented by array
abt = ArrayBinaryTree.new(arr)
# Access node
i = 1
l, r, _p = abt.left(i), abt.right(i), abt.parent(i)
puts "\nCurrent node index is #{i}, value is #{abt.val(i).inspect}"
puts "Left child index is #{l}, value is #{abt.val(l).inspect}"
puts "Right child index is #{r}, value is #{abt.val(r).inspect}"
puts "Parent node index is #{_p}, value is #{abt.val(_p).inspect}"
# Traverse tree
res = abt.level_order
puts "\nLevel-order traversal is: #{res}"
res = abt.pre_order
puts "Pre-order traversal is: #{res}"
res = abt.in_order
puts "In-order traversal is: #{res}"
res = abt.post_order
puts "Post-order traversal is: #{res}"
end
+216
View File
@@ -0,0 +1,216 @@
=begin
File: avl_tree.rb
Created Time: 2024-04-17
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### AVL tree ###
class AVLTree
### Constructor ###
def initialize
@root = nil
end
### Get binary tree root node ###
def get_root
@root
end
### Get node height ###
def height(node)
# Empty node height is -1, leaf node height is 0
return node.height unless node.nil?
-1
end
### Update node height ###
def update_height(node)
# Node height equals the height of the tallest subtree + 1
node.height = [height(node.left), height(node.right)].max + 1
end
### Get balance factor ###
def balance_factor(node)
# Empty node balance factor is 0
return 0 if node.nil?
# Node balance factor = left subtree height - right subtree height
height(node.left) - height(node.right)
end
### Right rotation ###
def right_rotate(node)
child = node.left
grand_child = child.right
# Using child as pivot, rotate node to the right
child.right = node
node.left = grand_child
# Update node height
update_height(node)
update_height(child)
# Return root node of subtree after rotation
child
end
### Left rotation ###
def left_rotate(node)
child = node.right
grand_child = child.left
# Using child as pivot, rotate node to the left
child.left = node
node.right = grand_child
# Update node height
update_height(node)
update_height(child)
# Return root node of subtree after rotation
child
end
### Perform rotation to rebalance subtree ###
def rotate(node)
# Get balance factor of node
balance_factor = balance_factor(node)
# Left-heavy tree
if balance_factor > 1
if balance_factor(node.left) >= 0
# Right rotation
return right_rotate(node)
else
# First left rotation then right rotation
node.left = left_rotate(node.left)
return right_rotate(node)
end
# Right-heavy tree
elsif balance_factor < -1
if balance_factor(node.right) <= 0
# Left rotation
return left_rotate(node)
else
# First right rotation then left rotation
node.right = right_rotate(node.right)
return left_rotate(node)
end
end
# Balanced tree, no rotation needed, return directly
node
end
### Insert node ###
def insert(val)
@root = insert_helper(@root, val)
end
### Recursively insert node (helper method) ###
def insert_helper(node, val)
return TreeNode.new(val) if node.nil?
# 1. Find insertion position and insert node
if val < node.val
node.left = insert_helper(node.left, val)
elsif val > node.val
node.right = insert_helper(node.right, val)
else
# Duplicate node not inserted, return directly
return node
end
# Update node height
update_height(node)
# 2. Perform rotation operation to restore balance to this subtree
rotate(node)
end
### Delete node ###
def remove(val)
@root = remove_helper(@root, val)
end
### Recursively delete node (helper method) ###
def remove_helper(node, val)
return if node.nil?
# 1. Find node and delete
if val < node.val
node.left = remove_helper(node.left, val)
elsif val > node.val
node.right = remove_helper(node.right, val)
else
if node.left.nil? || node.right.nil?
child = node.left || node.right
# Number of child nodes = 0, delete node directly and return
return if child.nil?
# Number of child nodes = 1, delete node directly
node = child
else
# Number of child nodes = 2, delete the next node in inorder traversal and replace current node with it
temp = node.right
while !temp.left.nil?
temp = temp.left
end
node.right = remove_helper(node.right, temp.val)
node.val = temp.val
end
end
# Update node height
update_height(node)
# 2. Perform rotation operation to restore balance to this subtree
rotate(node)
end
### Search node ###
def search(val)
cur = @root
# Loop search, exit after passing leaf node
while !cur.nil?
# Target node is in cur's right subtree
if cur.val < val
cur = cur.right
# Target node is in cur's left subtree
elsif cur.val > val
cur = cur.left
# Found target node, exit loop
else
break
end
end
# Return target node
cur
end
end
### Driver Code ###
if __FILE__ == $0
def test_insert(tree, val)
tree.insert(val)
puts "\nAfter inserting node #{val}, AVL tree is"
print_tree(tree.get_root)
end
def test_remove(tree, val)
tree.remove(val)
puts "\nAfter deleting node #{val}, AVL tree is"
print_tree(tree.get_root)
end
# Please pay attention to how the AVL tree maintains balance after inserting nodes
avl_tree = AVLTree.new
# Insert node
# Delete nodes
for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]
test_insert(avl_tree, val)
end
# Please pay attention to how the AVL tree maintains balance after deleting nodes
test_insert(avl_tree, 7)
# Remove node
# Delete node with degree 1
test_remove(avl_tree, 8) # Delete node with degree 2
test_remove(avl_tree, 5) # Remove node with degree 1
test_remove(avl_tree, 4) # Remove node with degree 2
result_node = avl_tree.search(7)
puts "\nFound node object #{result_node}, node value = #{result_node.val}"
end
@@ -0,0 +1,161 @@
=begin
File: binary_search_tree.rb
Created Time: 2024-04-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Binary search tree ###
class BinarySearchTree
### Constructor ###
def initialize
# Initialize empty tree
@root = nil
end
### Get binary tree root node ###
def get_root
@root
end
### Search node ###
def search(num)
cur = @root
# Loop search, exit after passing leaf node
while !cur.nil?
# Target node is in cur's right subtree
if cur.val < num
cur = cur.right
# Target node is in cur's left subtree
elsif cur.val > num
cur = cur.left
# Found target node, exit loop
else
break
end
end
cur
end
### Insert node ###
def insert(num)
# If tree is empty, initialize root node
if @root.nil?
@root = TreeNode.new(num)
return
end
# Loop search, exit after passing leaf node
cur, pre = @root, nil
while !cur.nil?
# Found duplicate node, return directly
return if cur.val == num
pre = cur
# Insertion position is in cur's right subtree
if cur.val < num
cur = cur.right
# Insertion position is in cur's left subtree
else
cur = cur.left
end
end
# Insert node
node = TreeNode.new(num)
if pre.val < num
pre.right = node
else
pre.left = node
end
end
### Delete node ###
def remove(num)
# If tree is empty, return directly
return if @root.nil?
# Loop search, exit after passing leaf node
cur, pre = @root, nil
while !cur.nil?
# Found node to delete, exit loop
break if cur.val == num
pre = cur
# Node to delete is in cur's right subtree
if cur.val < num
cur = cur.right
# Node to delete is in cur's left subtree
else
cur = cur.left
end
end
# If no node to delete, return directly
return if cur.nil?
# Number of child nodes = 0 or 1
if cur.left.nil? || cur.right.nil?
# When number of child nodes = 0 / 1, child = null / that child node
child = cur.left || cur.right
# Delete node cur
if cur != @root
if pre.left == cur
pre.left = child
else
pre.right = child
end
else
# If deleted node is root node, reassign root node
@root = child
end
# Number of child nodes = 2
else
# Get next node of cur in inorder traversal
tmp = cur.right
while !tmp.left.nil?
tmp = tmp.left
end
# Recursively delete node tmp
remove(tmp.val)
# Replace cur with tmp
cur.val = tmp.val
end
end
end
### Driver Code ###
if __FILE__ == $0
# Initialize binary search tree
bst = BinarySearchTree.new
nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]
# Please note that different insertion orders will generate different binary trees, this sequence can generate a perfect binary tree
nums.each { |num| bst.insert(num) }
puts "\nInitialized binary tree is\n"
print_tree(bst.get_root)
# Search node
node = bst.search(7)
puts "\nFound node object: #{node}, node value = #{node.val}"
# Insert node
bst.insert(16)
puts "\nAfter inserting node 16, binary tree is\n"
print_tree(bst.get_root)
# Remove node
bst.remove(1)
puts "\nAfter removing node 1, binary tree is\n"
print_tree(bst.get_root)
bst.remove(2)
puts "\nAfter removing node 2, binary tree is\n"
print_tree(bst.get_root)
bst.remove(4)
puts "\nAfter removing node 4, binary tree is\n"
print_tree(bst.get_root)
end
+38
View File
@@ -0,0 +1,38 @@
=begin
File: binary_tree.rb
Created Time: 2024-04-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Driver Code ###
if __FILE__ == $0
# Initialize binary tree
# Initialize nodes
n1 = TreeNode.new(1)
n2 = TreeNode.new(2)
n3 = TreeNode.new(3)
n4 = TreeNode.new(4)
n5 = TreeNode.new(5)
# Build references (pointers) between nodes
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
puts "\nInitialize binary tree\n\n"
print_tree(n1)
# Insert node P between n1 -> n2
_p = TreeNode.new(0)
# Insert node _p between n1 -> n2
n1.left = _p
_p.left = n2
puts "\nAfter inserting node _p\n\n"
print_tree(n1)
# Remove node
n1.left = n2
puts "\nAfter deleting node _p\n\n"
print_tree(n1)
end
@@ -0,0 +1,36 @@
=begin
File: binary_tree_bfs.rb
Created Time: 2024-04-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Level-order traversal ###
def level_order(root)
# Initialize queue, add root node
queue = [root]
# Initialize a list to save the traversal sequence
res = []
while !queue.empty?
node = queue.shift # Dequeue
res << node.val # Save node value
queue << node.left unless node.left.nil? # Left child node enqueue
queue << node.right unless node.right.nil? # Right child node enqueue
end
res
end
### Driver Code ###
if __FILE__ == $0
# Initialize binary tree
# Here we use a function to generate a binary tree directly from an array
root = arr_to_tree([1, 2, 3, 4, 5, 6, 7])
puts "\nInitialize binary tree\n\n"
print_tree(root)
# Level-order traversal
res = level_order(root)
puts "\nLevel-order traversal node sequence = #{res}"
end
@@ -0,0 +1,62 @@
=begin
File: binary_tree_dfs.rb
Created Time: 2024-04-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Pre-order traversal ###
def pre_order(root)
return if root.nil?
# Visit priority: root node -> left subtree -> right subtree
$res << root.val
pre_order(root.left)
pre_order(root.right)
end
### In-order traversal ###
def in_order(root)
return if root.nil?
# Visit priority: left subtree -> root node -> right subtree
in_order(root.left)
$res << root.val
in_order(root.right)
end
### Post-order traversal ###
def post_order(root)
return if root.nil?
# Visit priority: left subtree -> right subtree -> root node
post_order(root.left)
post_order(root.right)
$res << root.val
end
### Driver Code ###
if __FILE__ == $0
# Initialize binary tree
# Here we use a function to generate a binary tree directly from an array
root = arr_to_tree([1, 2, 3, 4, 5, 6, 7])
puts "\nInitialize binary tree\n\n"
print_tree(root)
# Preorder traversal
$res = []
pre_order(root)
puts "\nPre-order traversal node sequence = #{$res}"
# Inorder traversal
$res.clear
in_order(root)
puts "\nIn-order traversal node sequence = #{$res}"
# Postorder traversal
$res.clear
post_order(root)
puts "\nPost-order traversal node sequence = #{$res}"
end
+23
View File
@@ -0,0 +1,23 @@
require 'open3'
start_time = Time.now
ruby_code_dir = File.dirname(__FILE__)
files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb")
errors = []
files.each do |file|
stdout, stderr, status = Open3.capture3("ruby #{file}")
errors << stderr unless status.success?
end
puts "\x1b[34mTested #{files.count} files\x1b[m"
unless errors.empty?
puts "\x1b[33mFound exception in #{errors.length} files\x1b[m"
raise errors.join("\n\n")
else
puts "\x1b[32mPASS\x1b[m"
end
puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms"
+38
View File
@@ -0,0 +1,38 @@
=begin
File: list_node.rb
Created Time: 2024-03-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Linked list node class ###
class ListNode
attr_accessor :val # Node value
attr_accessor :next # Reference to next node
def initialize(val=0, next_node=nil)
@val = val
@next = next_node
end
end
### Deserialize list to linked list ###
def arr_to_linked_list(arr)
head = current = ListNode.new(arr[0])
for i in 1...arr.length
current.next = ListNode.new(arr[i])
current = current.next
end
head
end
### Serialize linked list to list ###
def linked_list_to_arr(head)
arr = []
while head
arr << head.val
head = head.next
end
end
+80
View File
@@ -0,0 +1,80 @@
=begin
File: print_util.rb
Created Time: 2024-03-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative "./tree_node"
### Print matrix ###
def print_matrix(mat)
s = []
mat.each { |arr| s << " #{arr.to_s}" }
puts "[\n#{s.join(",\n")}\n]"
end
### Print linked list ###
def print_linked_list(head)
list = []
while head
list << head.val
head = head.next
end
puts "#{list.join(" -> ")}"
end
class Trunk
attr_accessor :prev, :str
def initialize(prev, str)
@prev = prev
@str = str
end
end
def show_trunk(p)
return if p.nil?
show_trunk(p.prev)
print p.str
end
### Print binary tree ###
# This tree printer is borrowed from TECHIE DELIGHT
# https://www.techiedelight.com/c-program-print-binary-tree/
def print_tree(root, prev=nil, is_right=false)
return if root.nil?
prev_str = " "
trunk = Trunk.new(prev, prev_str)
print_tree(root.right, trunk, true)
if prev.nil?
trunk.str = "———"
elsif is_right
trunk.str = "/———"
prev_str = " |"
else
trunk.str = "\\———"
prev.str = prev_str
end
show_trunk(trunk)
puts " #{root.val}"
prev.str = prev_str if prev
trunk.str = " |"
print_tree(root.left, trunk, false)
end
### Print hash table ###
def print_hash_map(hmap)
hmap.entries.each { |key, value| puts "#{key} -> #{value}" }
end
### Print heap ###
def print_heap(heap)
puts "Array representation of heap: #{heap}"
puts "Heap tree representation:"
root = arr_to_tree(heap)
print_tree(root)
end
+53
View File
@@ -0,0 +1,53 @@
=begin
File: tree_node.rb
Created Time: 2024-03-30
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Binary tree node class ###
class TreeNode
attr_accessor :val # Node value
attr_accessor :height # Node height
attr_accessor :left # Reference to left child node
attr_accessor :right # Reference to right child node
def initialize(val=0)
@val = val
@height = 0
end
end
### Deserialize list to binary tree: recursion ###
def arr_to_tree_dfs(arr, i)
# Return nil if index exceeds array length or element is nil
return if i < 0 || i >= arr.length || arr[i].nil?
# Build the current node
root = TreeNode.new(arr[i])
# Recursively build the left and right subtrees
root.left = arr_to_tree_dfs(arr, 2 * i + 1)
root.right = arr_to_tree_dfs(arr, 2 * i + 2)
root
end
### Deserialize list to binary tree ###
def arr_to_tree(arr)
arr_to_tree_dfs(arr, 0)
end
### Serialize binary tree to list: recursion ###
def tree_to_arr_dfs(root, i, res)
return if root.nil?
res += Array.new(i - res.length + 1) if i >= res.length
res[i] = root.val
tree_to_arr_dfs(root.left, 2 * i + 1, res)
tree_to_arr_dfs(root.right, 2 * i + 2, res)
end
### Serialize binary tree to list ###
def tree_to_arr(root)
res = []
tree_to_arr_dfs(root, 0, res)
res
end
+24
View File
@@ -0,0 +1,24 @@
=begin
File: vertex.rb
Created Time: 2024-04-25
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### Vertex class ###
class Vertex
attr_accessor :val
def initialize(val)
@val = val
end
end
### Input value list vals, return vertex list vets ###
def vals_to_vets(vals)
Array.new(vals.length) { |i| Vertex.new(vals[i]) }
end
### Input vertex list vets, return value list vals ###
def vets_to_vals(vets)
Array.new(vets.length) { |i| vets[i].val }
end