mirror of
https://github.com/krahets/hello-algo.git
synced 2026-09-17 20:17:14 +00:00
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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user