Add ru version (#1865)

* Add Russian docs site baseline

* Add Russian localized codebase

* Polish Russian code wording

* Update ru code translation.

* Update code translation and chapter covers.

* Fix pythontutor extraction.

* Add README and landing page.

* placeholder of profiles

* Use figures of English version

* Remove chapter paperbook
This commit is contained in:
Yudong Jin
2026-03-28 04:24:07 +08:00
committed by GitHub
parent 2ca570cc33
commit 772183705e
1958 changed files with 108186 additions and 0 deletions
@@ -0,0 +1,108 @@
=begin
File: array.rb
Created Time: 2024-03-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Случайный доступ к элементу ###
def random_access(nums)
# Случайным образом выбрать число из интервала [0, nums.length)
random_index = Random.rand(0...nums.length)
# Получить и вернуть случайный элемент
nums[random_index]
end
# ## Увеличить длину массива ###
# Обратите внимание: Array в Ruby является динамическим массивом и может быть расширен напрямую
# Для удобства обучения эта функция рассматривает Array как массив неизменяемой длины
def extend(nums, enlarge)
# Инициализировать массив увеличенной длины
res = Array.new(nums.length + enlarge, 0)
# Скопировать все элементы исходного массива в новый массив
for i in 0...nums.length
res[i] = nums[i]
end
# Вернуть новый массив после расширения
res
end
# ## Вставка элемента num по индексу index в массив ###
def insert(nums, num, index)
# Сдвинуть элемент с индексом index и все последующие элементы на одну позицию назад
for i in (nums.length - 1).downto(index + 1)
nums[i] = nums[i - 1]
end
# Присвоить num элементу по индексу index
nums[index] = num
end
# ## Удаление элемента по индексу index ###
def remove(nums, index)
# Сдвинуть все элементы после индекса index на одну позицию вперед
for i in index...(nums.length - 1)
nums[i] = nums[i + 1]
end
end
# ## Обход массива ###
def traverse(nums)
count = 0
# Обход массива по индексам
for i in 0...nums.length
count += nums[i]
end
# Непосредственно обходить элементы массива
for num in nums
count += num
end
end
# ## Поиск заданного элемента в массиве ###
def find(nums, target)
for i in 0...nums.length
return i if nums[i] == target
end
-1
end
### Driver Code ###
if __FILE__ == $0
# Инициализация массива
arr = Array.new(5, 0)
puts "Массив arr = #{arr}"
nums = [1, 3, 2, 5, 4]
puts "Массив nums = #{nums}"
# Случайный доступ
random_num = random_access(nums)
puts "Случайный элемент из nums = #{random_num}"
# Расширение длины
nums = extend(nums, 3)
puts "После увеличения длины массива до 8 nums = #{nums}"
# Вставка элемента
insert(nums, 6, 3)
puts "После вставки числа 6 по индексу 3 nums = #{nums}"
# Удаление элемента
remove(nums, 2)
puts "После удаления элемента по индексу 2 nums = #{nums}"
# Обход массива
traverse(nums)
# Поиск элемента
index = find(nums, 3)
puts "Поиск элемента 3 в nums: индекс = #{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'
# ## Вставка узла _p после узла n0 в связном списке ###
# В Ruby `p` — встроенная функция, а `P` — константа, поэтому вместо этого можно использовать `_p`
def insert(n0, _p)
n1 = n0.next
_p.next = n1
n0.next = _p
end
# ## Удаление первого узла после узла n0 в связном списке ###
def remove(n0)
return if n0.next.nil?
# n0 -> remove_node -> n1
remove_node = n0.next
n1 = remove_node.next
n0.next = n1
end
# ## Доступ к узлу связного списка по индексу index ###
def access(head, index)
for i in 0...index
return nil if head.nil?
head = head.next
end
head
end
# ## Поиск первого узла со значением target в связном списке ###
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
# Инициализация связного списка
# Инициализация всех узлов
n0 = ListNode.new(1)
n1 = ListNode.new(3)
n2 = ListNode.new(2)
n3 = ListNode.new(5)
n4 = ListNode.new(4)
# Построить ссылки между узлами
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
puts "Исходный связный список"
print_linked_list(n0)
# Вставка узла
insert(n0, ListNode.new(0))
print_linked_list n0
# Удаление узла
remove(n0)
puts "Связный список после удаления узла"
print_linked_list(n0)
# Доступ к узлу
node = access(n0, 3)
puts "Значение узла по индексу 3 в связном списке = #{node.val}"
# Поиск узла
index = find(n0, 2)
puts "Индекс узла со значением 2 в связном списке = #{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
# Инициализация списка
nums = [1, 3, 2, 5, 4]
puts "Список nums = #{nums}"
# Доступ к элементу
num = nums[1]
puts "Элемент по индексу 1: num = #{num}"
# Обновление элемента
nums[1] = 0
puts "После обновления элемента по индексу 1 до 0 nums = #{nums}"
# Очистить список
nums.clear
puts "После очистки списка nums = #{nums}"
# Добавление элемента в конец
nums << 1
nums << 3
nums << 2
nums << 5
nums << 4
puts "После добавления элементов nums = #{nums}"
# Вставка элемента в середину
nums.insert(3, 6)
puts "После вставки элемента 6 по индексу 3 nums = #{nums}"
# Удаление элемента
nums.delete_at(3)
puts "После удаления элемента по индексу 3 nums = #{nums}"
# Обходить список по индексам
count = 0
for i in 0...nums.length
count += nums[i]
end
# Непосредственно обходить элементы списка
count = 0
nums.each do |x|
count += x
end
# Объединить два списка
nums1 = [6, 8, 7, 10, 9]
nums += nums1
puts "После конкатенации списка nums1 к nums nums = #{nums}"
nums = nums.sort { |a, b| a <=> b }
puts "После сортировки списка 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
# ## Класс списка ###
class MyList
attr_reader :size # Получить длину списка (текущее число элементов)
attr_reader :capacity # Получить вместимость списка
# ## Конструктор ###
def initialize
@capacity = 10
@size = 0
@extend_ratio = 2
@arr = Array.new(capacity)
end
# ## Доступ к элементу ###
def get(index)
# Если индекс выходит за границы, выбрасывается исключение; далее аналогично
raise IndexError, "индекс выходит за границы" if index < 0 || index >= size
@arr[index]
end
# ## Доступ к элементу ###
def set(index, num)
raise IndexError, "индекс выходит за границы" if index < 0 || index >= size
@arr[index] = num
end
# ## Добавление элемента в конец ###
def add(num)
# При превышении вместимости по числу элементов запускается расширение
extend_capacity if size == capacity
@arr[size] = num
# Обновить число элементов
@size += 1
end
# ## Вставка элемента в середину ###
def insert(index, num)
raise IndexError, "индекс выходит за границы" if index < 0 || index >= size
# При превышении вместимости по числу элементов запускается расширение
extend_capacity if size == capacity
# Сдвинуть элемент с индексом index и все следующие элементы на одну позицию назад
for j in (size - 1).downto(index)
@arr[j + 1] = @arr[j]
end
@arr[index] = num
# Обновить число элементов
@size += 1
end
# ## Удаление элемента ###
def remove(index)
raise IndexError, "индекс выходит за границы" if index < 0 || index >= size
num = @arr[index]
# Сдвинуть все элементы после индекса index на одну позицию вперед
for j in index...size
@arr[j] = @arr[j + 1]
end
# Обновить число элементов
@size -= 1
# Вернуть удаленный элемент
num
end
# ## Расширение списка ###
def extend_capacity
# Создать новый массив длиной в extend_ratio раз больше исходного и скопировать в него исходный массив
arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1))
# Обновить вместимость списка
@capacity = arr.length
end
# ## Преобразование списка в массив ###
def to_array
sz = size
# Преобразовывать только элементы списка в пределах фактической длины
arr = Array.new(sz)
for i in 0...sz
arr[i] = get(i)
end
arr
end
end
### Driver Code ###
if __FILE__ == $0
# Инициализация списка
nums = MyList.new
# Добавление элемента в конец
nums.add(1)
nums.add(3)
nums.add(2)
nums.add(5)
nums.add(4)
puts "Список nums = #{nums.to_array}, емкость = #{nums.capacity}, длина = #{nums.size}"
# Вставка элемента в середину
nums.insert(3, 6)
puts "После вставки числа 6 по индексу 3 nums = #{nums.to_array}"
# Удаление элемента
nums.remove(3)
puts "После удаления элемента по индексу 3 nums = #{nums.to_array}"
# Доступ к элементу
num = nums.get(1)
puts "Элемент по индексу 1: num = #{num}"
# Обновление элемента
nums.set(1, 0)
puts "После обновления элемента по индексу 1 до 0 nums = #{nums.to_array}"
# Проверка механизма расширения
for i in 0...10
# При i = 5 длина списка превысит его вместимость, и в этот момент сработает механизм расширения
nums.add(i)
end
puts "После расширения список nums = #{nums.to_array}, емкость = #{nums.capacity}, длина = #{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
# ## Алгоритм бэктрекинга: n ферзей ###
def backtrack(row, n, state, res, cols, diags1, diags2)
# Когда все строки уже обработаны, записать решение
if row == n
res << state.map { |row| row.dup }
return
end
# Обойти все столбцы
for col in 0...n
# Вычислить главную и побочную диагонали, соответствующие этой клетке
diag1 = row - col + n - 1
diag2 = row + col
# Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей
if !cols[col] && !diags1[diag1] && !diags2[diag2]
# Попытка: поставить ферзя в эту клетку
state[row][col] = "Q"
cols[col] = diags1[diag1] = diags2[diag2] = true
# Перейти к размещению следующей строки
backtrack(row + 1, n, state, res, cols, diags1, diags2)
# Откат: восстановить эту клетку как пустую
state[row][col] = "#"
cols[col] = diags1[diag1] = diags2[diag2] = false
end
end
end
# ## Решить задачу о n ферзях ###
def n_queens(n)
# Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку
state = Array.new(n) { Array.new(n, "#") }
cols = Array.new(n, false) # Отмечать, есть ли ферзь в столбце
diags1 = Array.new(2 * n - 1, false) # Отмечать наличие ферзя на главной диагонали
diags2 = Array.new(2 * n - 1, false) # Отмечать наличие ферзя на побочной диагонали
res = []
backtrack(0, n, state, res, cols, diags1, diags2)
res
end
### Driver Code ###
if __FILE__ == $0
n = 4
res = n_queens(n)
puts "Размер входной доски = #{n}"
puts "Количество способов расстановки ферзей: #{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
# ## Алгоритм бэктрекинга: все перестановки I ###
def backtrack(state, choices, selected, res)
# Когда длина состояния равна числу элементов, записать решение
if state.length == choices.length
res << state.dup
return
end
# Перебор всех вариантов выбора
choices.each_with_index do |choice, i|
# Отсечение: нельзя выбирать один и тот же элемент повторно
unless selected[i]
# Попытка: сделать выбор и обновить состояние
selected[i] = true
state << choice
# Перейти к следующему выбору
backtrack(state, choices, selected, res)
# Откат: отменить выбор и восстановить предыдущее состояние
selected[i] = false
state.pop
end
end
end
# ## Все перестановки 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 "Входной массив nums = #{nums}"
puts "Все перестановки 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
# ## Алгоритм бэктрекинга: все перестановки II ###
def backtrack(state, choices, selected, res)
# Когда длина состояния равна числу элементов, записать решение
if state.length == choices.length
res << state.dup
return
end
# Перебор всех вариантов выбора
duplicated = Set.new
choices.each_with_index do |choice, i|
# Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы
if !selected[i] && !duplicated.include?(choice)
# Попытка: сделать выбор и обновить состояние
duplicated.add(choice)
selected[i] = true
state << choice
# Перейти к следующему выбору
backtrack(state, choices, selected, res)
# Откат: отменить выбор и восстановить предыдущее состояние
selected[i] = false
state.pop
end
end
end
# ## Все перестановки 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 "Входной массив nums = #{nums}"
puts "Все перестановки 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'
# ## Предварительный обход: пример 1 ###
def pre_order(root)
return unless root
# Записать решение
$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 "\nИнициализация двоичного дерева"
print_tree(root)
# Предварительный обход
$res = []
pre_order(root)
puts "\nВсе узлы со значением 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'
# ## Предварительный обход: пример 2 ###
def pre_order(root)
return unless root
# Попытка
$path << root
# Записать решение
$res << $path.dup if root.val == 7
pre_order(root.left)
pre_order(root.right)
# Откат
$path.pop
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\nИнициализация двоичного дерева"
print_tree(root)
# Предварительный обход
$path, $res = [], []
pre_order(root)
puts "\nВсе пути от корня к узлу 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'
# ## Предварительный обход: пример 3 ###
def pre_order(root)
# Отсечение
return if !root || root.val == 3
# Попытка
$path.append(root)
# Записать решение
$res << $path.dup if root.val == 7
pre_order(root.left)
pre_order(root.right)
# Откат
$path.pop
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\nИнициализация двоичного дерева"
print_tree(root)
# Предварительный обход
$path, $res = [], []
pre_order(root)
puts "\nВсе пути от корня к узлу 7, не содержащие узлов со значением 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'
# ## Проверка, является ли текущее состояние решением ###
def is_solution?(state)
!state.empty? && state.last.val == 7
end
# ## Записать решение ###
def record_solution(state, res)
res << state.dup
end
# ## Проверка допустимости этого выбора в текущем состоянии ###
def is_valid?(state, choice)
choice && choice.val != 3
end
# ## Обновить состояние ###
def make_choice(state, choice)
state << choice
end
# ## Восстановить состояние ###
def undo_choice(state, choice)
state.pop
end
# ## Алгоритм бэктрекинга: пример 3 ###
def backtrack(state, choices, res)
# Проверить, является ли текущее состояние решением
record_solution(state, res) if is_solution?(state)
# Перебор всех вариантов выбора
for choice in choices
# Отсечение: проверить допустимость выбора
if is_valid?(state, choice)
# Попытка: сделать выбор и обновить состояние
make_choice(state, choice)
# Перейти к следующему выбору
backtrack(state, [choice.left, choice.right], res)
# Откат: отменить выбор и восстановить предыдущее состояние
undo_choice(state, choice)
end
end
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\nИнициализация двоичного дерева"
print_tree(root)
# Алгоритм бэктрекинга
res = []
backtrack([], [root], res)
puts "\nВсе пути от корня к узлу 7, в которых путь не содержит узлов со значением 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
# ## Алгоритм бэктрекинга: сумма подмножеств I ###
def backtrack(state, target, choices, start, res)
# Если сумма подмножества равна target, записать решение
if target.zero?
res << state.dup
return
end
# Обойти все варианты выбора
# Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств
for i in start...choices.length
# Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл
# Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target
break if target - choices[i] < 0
# Попытка: сделать выбор и обновить target и start
state << choices[i]
# Перейти к следующему выбору
backtrack(state, target - choices[i], choices, i, res)
# Откат: отменить выбор и восстановить предыдущее состояние
state.pop
end
end
# ## Решить задачу суммы подмножеств I ###
def subset_sum_i(nums, target)
state = [] # Состояние (подмножество)
nums.sort! # Отсортировать nums
start = 0 # Стартовая вершина обхода
res = [] # Список результатов (список подмножеств)
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 "Входной массив nums = #{nums}, target = #{target}"
puts "Все подмножества с суммой #{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
# ## Алгоритм бэктрекинга: сумма подмножеств I ###
def backtrack(state, target, total, choices, res)
# Если сумма подмножества равна target, записать решение
if total == target
res << state.dup
return
end
# Перебор всех вариантов выбора
for i in 0...choices.length
# Отсечение: если сумма подмножества превышает target, пропустить этот выбор
next if total + choices[i] > target
# Попытка: сделать выбор и обновить элемент и total
state << choices[i]
# Перейти к следующему выбору
backtrack(state, target, total + choices[i], choices, res)
# Откат: отменить выбор и восстановить предыдущее состояние
state.pop
end
end
# ## Решить задачу суммы подмножеств I (с повторяющимися подмножествами) ###
def subset_sum_i_naive(nums, target)
state = [] # Состояние (подмножество)
total = 0 # Сумма подмножеств
res = [] # Список результатов (список подмножеств)
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 "Входной массив nums = #{nums}, target = #{target}"
puts "Все подмножества с суммой #{target}: res = #{res}"
puts "Обратите внимание: результат этого метода содержит повторяющиеся множества"
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
# ## Алгоритм бэктрекинга: сумма подмножеств II ###
def backtrack(state, target, choices, start, res)
# Если сумма подмножества равна target, записать решение
if target.zero?
res << state.dup
return
end
# Обойти все варианты выбора
# Отсечение 2: начинать обход с start, чтобы избежать генерации повторяющихся подмножеств
# Отсечение 3: начинать обход с start, чтобы избежать повторного выбора одного и того же элемента
for i in start...choices.length
# Отсечение 1: если сумма подмножества превышает target, немедленно завершить цикл
# Это связано с тем, что массив уже отсортирован, следующие элементы больше, и сумма подмножества точно превысит target
break if target - choices[i] < 0
# Отсечение 4: если этот элемент равен элементу слева, значит ветвь поиска повторяется, ее нужно сразу пропустить
next if i > start && choices[i] == choices[i - 1]
# Попытка: сделать выбор и обновить target и start
state << choices[i]
# Перейти к следующему выбору
backtrack(state, target - choices[i], choices, i + 1, res)
# Откат: отменить выбор и восстановить предыдущее состояние
state.pop
end
end
# ## Решить задачу суммы подмножеств II ###
def subset_sum_ii(nums, target)
state = [] # Состояние (подмножество)
nums.sort! # Отсортировать nums
start = 0 # Стартовая вершина обхода
res = [] # Список результатов (список подмножеств)
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 "Входной массив nums = #{nums}, target = #{target}"
puts "Все подмножества с суммой #{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 ###
def for_loop(n)
res = 0
# Циклическое суммирование 1, 2, ..., n-1, n
for i in 1..n
res += i
end
res
end
# ## Цикл while ###
def while_loop(n)
res = 0
i = 1 # Инициализация условной переменной
# Циклическое суммирование 1, 2, ..., n-1, n
while i <= n
res += i
i += 1 # Обновить условную переменную
end
res
end
# ## Цикл while (двойное обновление) ###
def while_loop_ii(n)
res = 0
i = 1 # Инициализация условной переменной
# Циклическое суммирование 1, 4, 10, ...
while i <= n
res += i
# Обновить условную переменную
i += 1
i *= 2
end
res
end
# ## Двойной цикл for ###
def nested_for_loop(n)
res = ""
# Цикл по i = 1, 2, ..., n-1, n
for i in 1..n
# Цикл по 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 "\nРезультат суммирования в цикле for res = #{res}"
res = while_loop(n)
puts "\nРезультат суммирования в цикле while res = #{res}"
res = while_loop_ii(n)
puts "\nРезультат суммирования в цикле while (двойное обновление) res = #{res}"
res = nested_for_loop(n)
puts "\nРезультат обхода в двойном цикле for #{res}"
end
@@ -0,0 +1,70 @@
=begin
File: recursion.rb
Created Time: 2024-03-30
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Рекурсия ###
def recur(n)
# Условие завершения
return 1 if n == 1
# Рекурсия: рекурсивный вызов
res = recur(n - 1)
# Возврат: вернуть результат
n + res
end
# ## Имитация рекурсии итерацией ###
def for_loop_recur(n)
# Использовать явный стек для имитации системного стека вызовов
stack = []
res = 0
# Рекурсия: рекурсивный вызов
for i in n.downto(0)
# Имитировать «рекурсию» с помощью операции помещения в стек
stack << i
end
# Возврат: вернуть результат
while !stack.empty?
res += stack.pop
end
# res = 1+2+3+...+n
res
end
# ## Хвостовая рекурсия ###
def tail_recur(n, res)
# Условие завершения
return res if n == 0
# Хвостовой рекурсивный вызов
tail_recur(n - 1, res + n)
end
# ## Последовательность Фибоначчи: рекурсия ###
def fib(n)
# Условие завершения: f(1) = 0, f(2) = 1
return n - 1 if n == 1 || n == 2
# Рекурсивный вызов f(n) = f(n-1) + f(n-2)
res = fib(n - 1) + fib(n - 2)
# Вернуть результат f(n)
res
end
### Driver Code ###
if __FILE__ == $0
n = 5
res = recur(n)
puts "\nРезультат суммирования в рекурсивной функции res = #{res}"
res = for_loop_recur(n)
puts "\nРезультат суммирования при имитации рекурсии res = #{res}"
res = tail_recur(n, 0)
puts "\nРезультат суммирования в хвостовой рекурсии res = #{res}"
res = fib(n)
puts "\n#{n}-й элемент последовательности Фибоначчи: #{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'
# ## Функция ###
def function
# Выполнить некоторые операции
0
end
# ## Постоянная сложность ###
def constant(n)
# Константы, переменные и объекты занимают O(1) памяти
a = 0
nums = [0] * 10000
node = ListNode.new
# Переменные в цикле занимают O(1) памяти
(0...n).each { c = 0 }
# Функции в цикле занимают O(1) памяти
(0...n).each { function }
end
# ## Линейная сложность ###
def linear(n)
# Список длины n занимает O(n) памяти
nums = Array.new(n, 0)
# Хеш-таблица длины n занимает O(n) памяти
hmap = {}
for i in 0...n
hmap[i] = i.to_s
end
end
# ## Линейная сложность (рекурсивная реализация) ###
def linear_recur(n)
puts "Рекурсия n = #{n}"
return if n == 1
linear_recur(n - 1)
end
# ## Квадратичная сложность ###
def quadratic(n)
# Двумерный список занимает O(n^2) памяти
Array.new(n) { Array.new(n, 0) }
end
# ## Квадратичная сложность (рекурсивная реализация) ###
def quadratic_recur(n)
return 0 unless n > 0
# Длина массива nums равна n, n-1, ..., 2, 1
nums = Array.new(n, 0)
quadratic_recur(n - 1)
end
# ## Экспоненциальная сложность (построение полного двоичного дерева) ###
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(n)
# Линейная сложность
linear(n)
linear_recur(n)
# Квадратичная сложность
quadratic(n)
quadratic_recur(n)
# Экспоненциальная сложность
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
# ## Постоянная сложность ###
def constant(n)
count = 0
size = 100000
(0...size).each { count += 1 }
count
end
# ## Линейная сложность ###
def linear(n)
count = 0
(0...n).each { count += 1 }
count
end
# ## Линейная сложность (обход массива) ###
def array_traversal(nums)
count = 0
# Число итераций пропорционально длине массива
for num in nums
count += 1
end
count
end
# ## Квадратичная сложность ###
def quadratic(n)
count = 0
# Число итераций квадратично зависит от размера данных n
for i in 0...n
for j in 0...n
count += 1
end
end
count
end
# ## Квадратичная сложность (пузырьковая сортировка) ###
def bubble_sort(nums)
count = 0 # Счетчик
# Внешний цикл: неотсортированный диапазон [0, i]
for i in (nums.length - 1).downto(0)
# Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец
for j in 0...i
if nums[j] > nums[j + 1]
# Поменять местами nums[j] и nums[j + 1]
tmp = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = tmp
count += 3 # Обмен элементов включает 3 элементарные операции
end
end
end
count
end
# ## Экспоненциальная сложность (итеративная реализация) ###
def exponential(n)
count, base = 0, 1
# На каждом шаге клетка делится надвое, образуя последовательность 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
# ## Экспоненциальная сложность (рекурсивная реализация) ###
def exp_recur(n)
return 1 if n == 1
exp_recur(n - 1) + exp_recur(n - 1) + 1
end
# ## Логарифмическая сложность (итеративная реализация) ###
def logarithmic(n)
count = 0
while n > 1
n /= 2
count += 1
end
count
end
# ## Логарифмическая сложность (рекурсивная реализация) ###
def log_recur(n)
return 0 unless n > 1
log_recur(n / 2) + 1
end
# ## Линейно-логарифмическая сложность ###
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
# ## Факториальная сложность (рекурсивная реализация) ###
def factorial_recur(n)
return 1 if n == 0
count = 0
# Из одного получается n
(0...n).each { count += factorial_recur(n - 1) }
count
end
### Driver Code ###
if __FILE__ == $0
# Можно изменить n и запустить программу, чтобы увидеть, как меняется число операций при разных сложностях
n = 8
puts "Размер входных данных n = #{n}"
count = constant(n)
puts "Число операций константной сложности = #{count}"
count = linear(n)
puts "Число операций линейной сложности = #{count}"
count = array_traversal(Array.new(n, 0))
puts "Число операций линейной сложности (обход массива) = #{count}"
count = quadratic(n)
puts "Число операций квадратичной сложности = #{count}"
nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1]
count = bubble_sort(nums)
puts "Число операций квадратичной сложности (пузырьковая сортировка) = #{count}"
count = exponential(n)
puts "Число операций экспоненциальной сложности (итеративная реализация) = #{count}"
count = exp_recur(n)
puts "Число операций экспоненциальной сложности (рекурсивная реализация) = #{count}"
count = logarithmic(n)
puts "Число операций логарифмической сложности (итеративная реализация) = #{count}"
count = log_recur(n)
puts "Число операций логарифмической сложности (рекурсивная реализация) = #{count}"
count = linear_log_recur(n)
puts "Число операций линейно-логарифмической сложности (рекурсивная реализация) = #{count}"
count = factorial_recur(n)
puts "Число операций факториальной сложности (рекурсивная реализация) = #{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
# ## Создать массив с элементами: 1, 2, ..., n в случайном порядке ###
def random_numbers(n)
# Создать массив nums =: 1, 2, 3, ..., n
nums = Array.new(n) { |i| i + 1 }
# Случайно перемешать элементы массива
nums.shuffle!
end
# ## Найти индекс числа 1 в массиве nums ###
def find_one(nums)
for i in 0...nums.length
# Когда элемент 1 находится в начале массива, достигается лучшая временная сложность O(1)
# Когда элемент 1 находится в конце массива, достигается худшая временная сложность O(n)
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 "\nМассив [1, 2, ..., n] после перемешивания = #{nums}"
puts "Индекс числа 1 = #{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
# ## Бинарный поиск: задача f(i, j) ###
def dfs(nums, target, i, j)
# Если интервал пуст, целевой элемент отсутствует, вернуть -1
return -1 if i > j
# Вычислить индекс середины m
m = (i + j) / 2
if nums[m] < target
# Рекурсивная подзадача f(m+1, j)
return dfs(nums, target, m + 1, j)
elsif nums[m] > target
# Рекурсивная подзадача f(i, m-1)
return dfs(nums, target, i, m - 1)
else
# Целевой элемент найден, вернуть его индекс
return m
end
end
# ## Бинарный поиск ###
def binary_search(nums, target)
n = nums.length
# Решить задачу 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]
# Бинарный поиск (двусторонне замкнутый интервал)
index = binary_search(nums, target)
puts "Индекс целевого элемента 6 = #{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'
# ## Построить двоичное дерево: разделяй и властвуй ###
def dfs(preorder, inorder_map, i, l, r)
# Завершить при пустом диапазоне поддерева
return if r - l < 0
# Инициализировать корневой узел
root = TreeNode.new(preorder[i])
# Найти m, чтобы разделить левое и правое поддеревья
m = inorder_map[preorder[i]]
# Подзадача: построить левое поддерево
root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)
# Подзадача: построить правое поддерево
root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)
# Вернуть корневой узел
root
end
# ## Построить двоичное дерево ###
def build_tree(preorder, inorder)
# Инициализировать хеш-таблицу для хранения соответствия элементов inorder их индексам
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 "Предварительный обход = #{preorder}"
puts "Симметричный обход = #{inorder}"
root = build_tree(preorder, inorder)
puts "Построенное двоичное дерево:"
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
# ## Переместить один диск ###
def move(src, tar)
# Снять диск с вершины src
pan = src.pop
# Положить диск на вершину tar
tar << pan
end
# ## Решить задачу Ханойской башни f(i) ###
def dfs(i, src, buf, tar)
# Если в src остался только один диск, сразу переместить его в tar
if i == 1
move(src, tar)
return
end
# Подзадача f(i-1): переместить верхние i-1 дисков из src в buf с помощью tar
dfs(i - 1, src, tar, buf)
# Подзадача f(1): переместить оставшийся один диск из src в tar
move(src, tar)
# Подзадача f(i-1): переместить верхние i-1 дисков из buf в tar с помощью src
dfs(i - 1, buf, src, tar)
end
# ## Решить задачу Ханойской башни ###
def solve_hanota(_A, _B, _C)
n = _A.length
# Переместить верхние n дисков из A в C с помощью B
dfs(n, _A, _B, _C)
end
### Driver Code ###
if __FILE__ == $0
# Хвост списка соответствует вершине столбца
A = [5, 4, 3, 2, 1]
B = []
C = []
puts "Исходное состояние:"
puts "A = #{A}"
puts "B = #{B}"
puts "C = #{C}"
solve_hanota(A, B, C)
puts "После завершения перемещения дисков:"
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
# ## Бэктрекинг ###
def backtrack(choices, state, n, res)
# Когда подъем достигает n-й ступени, число вариантов увеличивается на 1
res[0] += 1 if state == n
# Перебор всех вариантов выбора
for choice in choices
# Отсечение: нельзя выходить за n-ю ступень
next if state + choice > n
# Попытка: сделать выбор и обновить состояние
backtrack(choices, state + choice, n, res)
end
# Откат
end
# ## Подъем по лестнице: бэктрекинг ###
def climbing_stairs_backtrack(n)
choices = [1, 2] # Можно подняться на 1 или 2 ступени
state = 0 # Начать подъем с 0-й ступени
res = [0] # Использовать res[0] для хранения числа решений
backtrack(choices, state, n, res)
res.first
end
### Driver Code ###
if __FILE__ == $0
n = 9
res = climbing_stairs_backtrack(n)
puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}"
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
# ## Подъем по лестнице с ограничениями: динамическое программирование ###
def climbing_stairs_constraint_dp(n)
return 1 if n == 1 || n == 2
# Инициализация таблицы dp для хранения решений подзадач
dp = Array.new(n + 1) { Array.new(3, 0) }
# Начальное состояние: заранее задать решения наименьших подзадач
dp[1][1], dp[1][2] = 1, 0
dp[2][1], dp[2][2] = 0, 1
# Переход состояний: постепенное решение больших подзадач через меньшие
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 "Количество способов подняться по лестнице из #{n} ступеней: #{res}"
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
# ## Поиск ###
def dfs(i)
# dp[1] и dp[2] уже известны, вернуть их
return i if i == 1 || i == 2
# dp[i] = dp[i-1] + dp[i-2]
dfs(i - 1) + dfs(i - 2)
end
# ## Подъем по лестнице: поиск ###
def climbing_stairs_dfs(n)
dfs(n)
end
### Driver Code ###
if __FILE__ == $0
n = 9
res = climbing_stairs_dfs(n)
puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}"
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
# ## Поиск с мемоизацией ###
def dfs(i, mem)
# dp[1] и dp[2] уже известны, вернуть их
return i if i == 1 || i == 2
# Если запись dp[i] существует, сразу вернуть ее
return mem[i] if mem[i] != -1
# dp[i] = dp[i-1] + dp[i-2]
count = dfs(i - 1, mem) + dfs(i - 2, mem)
# Сохранить dp[i]
mem[i] = count
end
# ## Подъем по лестнице: поиск с мемоизацией ###
def climbing_stairs_dfs_mem(n)
# mem[i] хранит число способов подняться на i-ю ступень, -1 означает отсутствие записи
mem = Array.new(n + 1, -1)
dfs(n, mem)
end
### Driver Code ###
if __FILE__ == $0
n = 9
res = climbing_stairs_dfs_mem(n)
puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}"
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
# ## Подъем по лестнице: динамическое программирование ###
def climbing_stairs_dp(n)
return n if n == 1 || n == 2
# Инициализация таблицы dp для хранения решений подзадач
dp = Array.new(n + 1, 0)
# Начальное состояние: заранее задать решения наименьших подзадач
dp[1], dp[2] = 1, 2
# Переход состояний: постепенное решение больших подзадач через меньшие
(3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] }
dp[n]
end
# ## Подъем по лестнице: динамическое программирование с оптимизацией памяти ###
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 "Количество способов подняться по лестнице из #{n} ступеней: #{res}"
res = climbing_stairs_dp_comp(n)
puts "Количество способов подняться по лестнице из #{n} ступеней: #{res}"
end
@@ -0,0 +1,65 @@
=begin
File: coin_change.rb
Created Time: 2024-05-29
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Размен монет: динамическое программирование ###
def coin_change_dp(coins, amt)
n = coins.length
_MAX = amt + 1
# Инициализация таблицы dp
dp = Array.new(n + 1) { Array.new(amt + 1, 0) }
# Переход состояний: первая строка и первый столбец
(1...(amt + 1)).each { |a| dp[0][a] = _MAX }
# Переход состояний: остальные строки и столбцы
for i in 1...(n + 1)
for a in 1...(amt + 1)
if coins[i - 1] > a
# Если целевая сумма превышена, монету i не выбирать
dp[i][a] = dp[i - 1][a]
else
# Меньшее из двух решений: не брать или взять монету 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
# ## Размен монет: динамическое программирование с оптимизацией памяти ###
def coin_change_dp_comp(coins, amt)
n = coins.length
_MAX = amt + 1
# Инициализация таблицы dp
dp = Array.new(amt + 1, _MAX)
dp[0] = 0
# Переход состояний
for i in 1...(n + 1)
# Прямой обход
for a in 1...(amt + 1)
if coins[i - 1] > a
# Если целевая сумма превышена, монету i не выбирать
dp[a] = dp[a]
else
# Меньшее из двух решений: не брать или взять монету 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
# Динамическое программирование
res = coin_change_dp(coins, amt)
puts "Минимальное число монет для набора целевой суммы = #{res}"
# Динамическое программирование с оптимизацией памяти
res = coin_change_dp_comp(coins, amt)
puts "Минимальное число монет для набора целевой суммы = #{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
# ## Размен монет II: динамическое программирование ###
def coin_change_ii_dp(coins, amt)
n = coins.length
# Инициализация таблицы dp
dp = Array.new(n + 1) { Array.new(amt + 1, 0) }
# Инициализация первого столбца
(0...(n + 1)).each { |i| dp[i][0] = 1 }
# Переход состояний
for i in 1...(n + 1)
for a in 1...(amt + 1)
if coins[i - 1] > a
# Если целевая сумма превышена, монету i не выбирать
dp[i][a] = dp[i - 1][a]
else
# Сумма двух решений: не брать или взять монету i
dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]
end
end
end
dp[n][amt]
end
# ## Размен монет II: динамическое программирование с оптимизацией памяти ###
def coin_change_ii_dp_comp(coins, amt)
n = coins.length
# Инициализация таблицы dp
dp = Array.new(amt + 1, 0)
dp[0] = 1
# Переход состояний
for i in 1...(n + 1)
# Прямой обход
for a in 1...(amt + 1)
if coins[i - 1] > a
# Если целевая сумма превышена, монету i не выбирать
dp[a] = dp[a]
else
# Сумма двух решений: не брать или взять монету 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
# Динамическое программирование
res = coin_change_ii_dp(coins, amt)
puts "Количество комбинаций монет для набора целевой суммы = #{res}"
# Динамическое программирование с оптимизацией памяти
res = coin_change_ii_dp_comp(coins, amt)
puts "Количество комбинаций монет для набора целевой суммы = #{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
# ## Редакционное расстояние: полный перебор ###
def edit_distance_dfs(s, t, i, j)
# Если s и t пусты, вернуть 0
return 0 if i == 0 && j == 0
# Если s пусто, вернуть длину t
return j if i == 0
# Если t пусто, вернуть длину s
return i if j == 0
# Если два символа равны, сразу пропустить их
return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1]
# Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 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)
# Вернуть минимальное число шагов редактирования
[insert, delete, replace].min + 1
end
def edit_distance_dfs_mem(s, t, mem, i, j)
# Если s и t пусты, вернуть 0
return 0 if i == 0 && j == 0
# Если s пусто, вернуть длину t
return j if i == 0
# Если t пусто, вернуть длину s
return i if j == 0
# Если запись уже есть, сразу вернуть ее
return mem[i][j] if mem[i][j] != -1
# Если два символа равны, сразу пропустить их
return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1]
# Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 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)
# Сохранить и вернуть минимальное число шагов редактирования
mem[i][j] = [insert, delete, replace].min + 1
end
# ## Редакционное расстояние: динамическое программирование ###
def edit_distance_dp(s, t)
n, m = s.length, t.length
dp = Array.new(n + 1) { Array.new(m + 1, 0) }
# Переход состояний: первая строка и первый столбец
(1...(n + 1)).each { |i| dp[i][0] = i }
(1...(m + 1)).each { |j| dp[0][j] = j }
# Переход состояний: остальные строки и столбцы
for i in 1...(n + 1)
for j in 1...(m +1)
if s[i - 1] == t[j - 1]
# Если два символа равны, сразу пропустить их
dp[i][j] = dp[i - 1][j - 1]
else
# Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 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
# ## Редакционное расстояние: динамическое программирование с оптимизацией памяти ###
def edit_distance_dp_comp(s, t)
n, m = s.length, t.length
dp = Array.new(m + 1, 0)
# Переход состояний: первая строка
(1...(m + 1)).each { |j| dp[j] = j }
# Переход состояний: остальные строки
for i in 1...(n + 1)
# Переход состояний: первый столбец
leftup = dp.first # Временно сохранить dp[i-1, j-1]
dp[0] += 1
# Переход состояний: остальные столбцы
for j in 1...(m + 1)
temp = dp[j]
if s[i - 1] == t[j - 1]
# Если два символа равны, сразу пропустить их
dp[j] = leftup
else
# Минимальное число шагов редактирования = минимальное число шагов для вставки, удаления и замены + 1
dp[j] = [dp[j - 1], dp[j], leftup].min + 1
end
leftup = temp # Обновить до значения 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
# Полный перебор
res = edit_distance_dfs(s, t, n, m)
puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок"
# Поиск с мемоизацией
mem = Array.new(n + 1) { Array.new(m + 1, -1) }
res = edit_distance_dfs_mem(s, t, mem, n, m)
puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок"
# Динамическое программирование
res = edit_distance_dp(s, t)
puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок"
# Динамическое программирование с оптимизацией памяти
res = edit_distance_dp_comp(s, t)
puts "Чтобы преобразовать #{s} в #{t}, требуется минимум #{res} правок"
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: полный перебор ###
def knapsack_dfs(wgt, val, i, c)
# Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0
return 0 if i == 0 || c == 0
# Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак
return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c
# Вычислить максимальную стоимость для случаев, когда предмет i не кладут и кладут
no = knapsack_dfs(wgt, val, i - 1, c)
yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]
# Вернуть вариант с большей стоимостью из двух возможных
[no, yes].max
end
# ## Рюкзак 0-1: поиск с мемоизацией ###
def knapsack_dfs_mem(wgt, val, mem, i, c)
# Если все предметы уже рассмотрены или в рюкзаке не осталось места, вернуть стоимость 0
return 0 if i == 0 || c == 0
# Если запись уже есть, вернуть сразу
return mem[i][c] if mem[i][c] != -1
# Если вместимость рюкзака превышена, можно только не класть предмет в рюкзак
return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c
# Вычислить максимальную стоимость для случаев, когда предмет 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]
# Сохранить и вернуть вариант с большей стоимостью из двух решений
mem[i][c] = [no, yes].max
end
# ## Рюкзак 0-1: динамическое программирование ###
def knapsack_dp(wgt, val, cap)
n = wgt.length
# Инициализация таблицы dp
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
# Переход состояний
for i in 1...(n + 1)
for c in 1...(cap + 1)
if wgt[i - 1] > c
# Если вместимость рюкзака превышена, предмет i не выбирать
dp[i][c] = dp[i - 1][c]
else
# Большее из двух решений: не брать или взять предмет 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: динамическое программирование с оптимизацией памяти ###
def knapsack_dp_comp(wgt, val, cap)
n = wgt.length
# Инициализация таблицы dp
dp = Array.new(cap + 1, 0)
# Переход состояний
for i in 1...(n + 1)
# Обход в обратном порядке
for c in cap.downto(1)
if wgt[i - 1] > c
# Если вместимость рюкзака превышена, предмет i не выбирать
dp[c] = dp[c]
else
# Большее из двух решений: не брать или взять предмет 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
# Полный перебор
res = knapsack_dfs(wgt, val, n, cap)
puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}"
# Поиск с мемоизацией
mem = Array.new(n + 1) { Array.new(cap + 1, -1) }
res = knapsack_dfs_mem(wgt, val, mem, n, cap)
puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}"
# Динамическое программирование
res = knapsack_dp(wgt, val, cap)
puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}"
# Динамическое программирование с оптимизацией памяти
res = knapsack_dp_comp(wgt, val, cap)
puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{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
# ## Минимальная стоимость подъема по лестнице: динамическое программирование ###
def min_cost_climbing_stairs_dp(cost)
n = cost.length - 1
return cost[n] if n == 1 || n == 2
# Инициализация таблицы dp для хранения решений подзадач
dp = Array.new(n + 1, 0)
# Начальное состояние: заранее задать решения наименьших подзадач
dp[1], dp[2] = cost[1], cost[2]
# Переход состояний: постепенное решение больших подзадач через меньшие
(3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }
dp[n]
end
# Минимальная стоимость подъема по лестнице: динамическое программирование с оптимизацией памяти
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 "Список стоимостей ступеней = #{cost}"
res = min_cost_climbing_stairs_dp(cost)
puts "Минимальная стоимость подъема по лестнице = #{res}"
res = min_cost_climbing_stairs_dp_comp(cost)
puts "Минимальная стоимость подъема по лестнице = #{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
# ## Минимальная сумма пути: полный перебор ###
def min_path_sum_dfs(grid, i, j)
# Если это верхняя левая ячейка, завершить поиск
return grid[i][j] if i == 0 && j == 0
# Если индексы строки или столбца выходят за границы, вернуть стоимость +∞
return Float::INFINITY if i < 0 || j < 0
# Вычислить минимальную стоимость пути из левого верхнего угла до (i-1, j) и (i, j-1)
up = min_path_sum_dfs(grid, i - 1, j)
left = min_path_sum_dfs(grid, i, j - 1)
# Вернуть минимальную стоимость пути из левого верхнего угла до (i, j)
[left, up].min + grid[i][j]
end
# ## Минимальная сумма пути: поиск с мемоизацией ###
def min_path_sum_dfs_mem(grid, mem, i, j)
# Если это верхняя левая ячейка, завершить поиск
return grid[0][0] if i == 0 && j == 0
# Если индексы строки или столбца выходят за границы, вернуть стоимость +∞
return Float::INFINITY if i < 0 || j < 0
# Если запись уже есть, вернуть сразу
return mem[i][j] if mem[i][j] != -1
# Минимальная стоимость пути для левой и верхней ячеек
up = min_path_sum_dfs_mem(grid, mem, i - 1, j)
left = min_path_sum_dfs_mem(grid, mem, i, j - 1)
# Сохранить и вернуть минимальную стоимость пути из левого верхнего угла до (i, j)
mem[i][j] = [left, up].min + grid[i][j]
end
# ## Минимальная сумма пути: динамическое программирование ###
def min_path_sum_dp(grid)
n, m = grid.length, grid.first.length
# Инициализация таблицы dp
dp = Array.new(n) { Array.new(m, 0) }
dp[0][0] = grid[0][0]
# Переход состояний: первая строка
(1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] }
# Переход состояний: первый столбец
(1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] }
# Переход состояний: остальные строки и столбцы
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
# ## Минимальная сумма пути: динамическое программирование с оптимизацией памяти ###
def min_path_sum_dp_comp(grid)
n, m = grid.length, grid.first.length
# Инициализация таблицы dp
dp = Array.new(m, 0)
# Переход состояний: первая строка
dp[0] = grid[0][0]
(1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] }
# Переход состояний: остальные строки
for i in 1...n
# Переход состояний: первый столбец
dp[0] = dp[0] + grid[i][0]
# Переход состояний: остальные столбцы
(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
# Полный перебор
res = min_path_sum_dfs(grid, n - 1, m - 1)
puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}"
# Поиск с мемоизацией
mem = Array.new(n) { Array.new(m, - 1) }
res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1)
puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}"
# Динамическое программирование
res = min_path_sum_dp(grid)
puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{res}"
# Динамическое программирование с оптимизацией памяти
res = min_path_sum_dp_comp(grid)
puts "Минимальная сумма пути из левого верхнего угла в правый нижний = #{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
# ## Полный рюкзак: динамическое программирование ###
def unbounded_knapsack_dp(wgt, val, cap)
n = wgt.length
# Инициализация таблицы dp
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
# Переход состояний
for i in 1...(n + 1)
for c in 1...(cap + 1)
if wgt[i - 1] > c
# Если вместимость рюкзака превышена, предмет i не выбирать
dp[i][c] = dp[i - 1][c]
else
# Большее из двух решений: не брать или взять предмет 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
# ## Полный рюкзак: динамическое программирование с оптимизацией памяти ##3
def unbounded_knapsack_dp_comp(wgt, val, cap)
n = wgt.length
# Инициализация таблицы dp
dp = Array.new(cap + 1, 0)
# Переход состояний
for i in 1...(n + 1)
# Прямой обход
for c in 1...(cap + 1)
if wgt[i -1] > c
# Если вместимость рюкзака превышена, предмет i не выбирать
dp[c] = dp[c]
else
# Большее из двух решений: не брать или взять предмет 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
# Динамическое программирование
res = unbounded_knapsack_dp(wgt, val, cap)
puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{res}"
# Динамическое программирование с оптимизацией памяти
res = unbounded_knapsack_dp_comp(wgt, val, cap)
puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{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'
# ## Класс неориентированного графа на основе списка смежности ###
class GraphAdjList
attr_reader :adj_list
# ## Конструктор ###
def initialize(edges)
# Список смежности, где key — вершина, а value — все смежные ей вершины
@adj_list = {}
# Добавить все вершины и ребра
for edge in edges
add_vertex(edge[0])
add_vertex(edge[1])
add_edge(edge[0], edge[1])
end
end
# ## Получение числа вершин ###
def size
@adj_list.length
end
# ## Добавление ребра ###
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
# ## Удаление ребра ###
def remove_edge(vet1, vet2)
raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)
# Удалить ребро vet1 - vet2
@adj_list[vet1].delete(vet2)
@adj_list[vet2].delete(vet1)
end
# ## Добавление вершины ###
def add_vertex(vet)
return if @adj_list.include?(vet)
# Добавить новый список в список смежности
@adj_list[vet] = []
end
# ## Удаление вершины ###
def remove_vertex(vet)
raise ArgumentError unless @adj_list.include?(vet)
# Удалить из списка смежности список, соответствующий вершине vet
@adj_list.delete(vet)
# Обойти списки других вершин и удалить все ребра, содержащие vet
for vertex in @adj_list
@adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet)
end
end
# ## Вывести список смежности ###
def __print__
puts 'Список смежности ='
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
# Инициализация неориентированного графа
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 "\nГраф после инициализации"
graph.__print__
# Добавить ребро
# Вершины 1 и 2, то есть v[0] и v[2]
graph.add_edge(v[0], v[2])
puts "\nГраф после добавления ребра 1-2"
graph.__print__
# Удалить ребро
# Вершины 1 и 3 соответствуют v[0], v[1]
graph.remove_edge(v[0], v[1])
puts "\nГраф после удаления ребра 1-3"
graph.__print__
# Добавление вершины
v5 = Vertex.new(6)
graph.add_vertex(v5)
puts "\nГраф после добавления вершины 6"
graph.__print__
# Удаление вершины
# Вершина 3 соответствует v[1]
graph.remove_vertex(v[1])
puts "\nГраф после удаления вершины 3"
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'
# ## Класс неориентированного графа на основе матрицы смежности ###
class GraphAdjMat
def initialize(vertices, edges)
# ## Конструктор ###
# Список вершин: элементы представляют «значения вершин», а индексы — «индексы вершин»
@vertices = []
# Матрица смежности, где индексы строк и столбцов соответствуют «индексам вершин»
@adj_mat = []
# Добавление вершины
vertices.each { |val| add_vertex(val) }
# Добавить ребра
# Обратите внимание: элементы edges представляют собой индексы вершин, то есть соответствуют индексам элементов vertices
edges.each { |e| add_edge(e[0], e[1]) }
end
# ## Получение числа вершин ###
def size
@vertices.length
end
# ## Добавление вершины ###
def add_vertex(val)
n = size
# Добавить значение новой вершины в список вершин
@vertices << val
# Добавить строку в матрицу смежности
new_row = Array.new(n, 0)
@adj_mat << new_row
# Добавить столбец в матрицу смежности
@adj_mat.each { |row| row << 0 }
end
# ## Удаление вершины ###
def remove_vertex(index)
raise IndexError if index >= size
# Удалить вершину с индексом index из списка вершин
@vertices.delete_at(index)
# Удалить строку с индексом index из матрицы смежности
@adj_mat.delete_at(index)
# Удалить столбец с индексом index из матрицы смежности
@adj_mat.each { |row| row.delete_at(index) }
end
# ## Добавление ребра ###
def add_edge(i, j)
# Параметры i и j соответствуют индексам элементов vertices
# Обработка выхода индекса за границы и случая равенства
if i < 0 || j < 0 || i >= size || j >= size || i == j
raise IndexError
end
# В неориентированном графе матрица смежности симметрична относительно главной диагонали, то есть выполняется (i, j) == (j, i)
@adj_mat[i][j] = 1
@adj_mat[j][i] = 1
end
# ## Удаление ребра ###
def remove_edge(i, j)
# Параметры i и j соответствуют индексам элементов vertices
# Обработка выхода индекса за границы и случая равенства
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
# ## Вывести матрицу смежности ###
def __print__
puts "Список вершин = #{@vertices}"
puts 'Матрица смежности ='
print_matrix(@adj_mat)
end
end
### Driver Code ###
if __FILE__ == $0
# Инициализация неориентированного графа
# Обратите внимание: элементы edges представляют индексы вершин, то есть соответствуют индексам элементов vertices
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 "\nГраф после инициализации"
graph.__print__
# Добавление ребра
# Индексы вершин 1 и 2 равны 0 и 2 соответственно
graph.add_edge(0, 2)
puts "\nГраф после добавления ребра 1-2"
graph.__print__
# Удалить ребро
# Индексы вершин 1 и 3 равны 0 и 1
graph.remove_edge(0, 1)
puts "\nГраф после удаления ребра 1-3"
graph.__print__
# Добавление вершины
graph.add_vertex(6)
puts "\nГраф после добавления вершины 6"
graph.__print__
# Удаление вершины
# Индекс вершины 3 равен 1
graph.remove_vertex(1)
puts "\nГраф после удаления вершины 3"
graph.__print__
end
+61
View File
@@ -0,0 +1,61 @@
=begin
File: graph_bfs.rb
Created Time: 2024-04-25
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require 'set'
require_relative './graph_adjacency_list'
require_relative '../utils/vertex'
# ## Обход в ширину ###
def graph_bfs(graph, start_vet)
# Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины
# Последовательность обхода вершин
res = []
# Хеш-множество для хранения уже посещенных вершин
visited = Set.new([start_vet])
# Очередь используется для реализации BFS
que = [start_vet]
# Начиная с вершины vet, продолжать цикл, пока не будут посещены все вершины
while que.length > 0
vet = que.shift # Извлечь головную вершину из очереди
res << vet # Отметить посещенную вершину
# Обойти все смежные вершины данной вершины
for adj_vet in graph.adj_list[vet]
next if visited.include?(adj_vet) # Пропустить уже посещенную вершину
que << adj_vet # Помещать в очередь только непосещенные вершины
visited.add(adj_vet) # Отметить эту вершину как посещенную
end
end
# Вернуть последовательность обхода вершин
res
end
### Driver Code ###
if __FILE__ == $0
# Инициализация неориентированного графа
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 "\nГраф после инициализации"
graph.__print__
# Обход в ширину
res = graph_bfs(graph, v.first)
puts "\nПоследовательность вершин при обходе в ширину (BFS)"
p vets_to_vals(res)
end
+54
View File
@@ -0,0 +1,54 @@
=begin
File: graph_dfs.rb
Created Time: 2024-04-25
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require 'set'
require_relative './graph_adjacency_list'
require_relative '../utils/vertex'
# ## Вспомогательная функция обхода в глубину ###
def dfs(graph, visited, res, vet)
res << vet # Отметить посещенную вершину
visited.add(vet) # Отметить эту вершину как посещенную
# Обойти все смежные вершины данной вершины
for adj_vet in graph.adj_list[vet]
next if visited.include?(adj_vet) # Пропустить уже посещенную вершину
# Рекурсивно обходить смежные вершины
dfs(graph, visited, res, adj_vet)
end
end
# ## Обход в глубину ###
def graph_dfs(graph, start_vet)
# Использовать список смежности для представления графа, чтобы получать все смежные вершины заданной вершины
# Последовательность обхода вершин
res = []
# Хеш-множество для хранения уже посещенных вершин
visited = Set.new
dfs(graph, visited, res, start_vet)
res
end
### Driver Code ###
if __FILE__ == $0
# Инициализация неориентированного графа
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 "\nГраф после инициализации"
graph.__print__
# Обход в глубину
res = graph_dfs(graph, v[0])
puts "\nПоследовательность вершин при обходе в глубину (DFS)"
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
# ## Размен монет: жадный алгоритм ###
def coin_change_greedy(coins, amt)
# Предположить, что список coins упорядочен
i = coins.length - 1
count = 0
# Циклически выполнять жадный выбор, пока не останется суммы
while amt > 0
# Найти монету, которая меньше остатка суммы и наиболее к нему близка
while i > 0 && coins[i] > amt
i -= 1
end
# Выбрать coins[i]
amt -= coins[i]
count += 1
end
# Если допустимое решение не найдено, вернуть -1
amt == 0 ? count : -1
end
### Driver Code ###
if __FILE__ == $0
# Жадный подход: гарантирует нахождение глобально оптимального решения
coins = [1, 5, 10, 20, 50, 100]
amt = 186
res = coin_change_greedy(coins, amt)
puts "\ncoins = #{coins}, amt = #{amt}"
puts "Минимальное количество монет для суммы #{amt}: #{res}"
# Жадный подход: не гарантирует нахождение глобально оптимального решения
coins = [1, 20, 50]
amt = 60
res = coin_change_greedy(coins, amt)
puts "\ncoins = #{coins}, amt = #{amt}"
puts "Минимальное количество монет для суммы #{amt}: #{res}"
puts "На самом деле минимум равен 3: 20 + 20 + 20"
# Жадный подход: не гарантирует нахождение глобально оптимального решения
coins = [1, 49, 50]
amt = 98
res = coin_change_greedy(coins, amt)
puts "\ncoins = #{coins}, amt = #{amt}"
puts "Минимальное количество монет для суммы #{amt}: #{res}"
puts "На самом деле минимум равен 2: 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
# ## Предмет ###
class Item
attr_accessor :w # Вес предмета
attr_accessor :v # Стоимость предмета
def initialize(w, v)
@w = w
@v = v
end
end
# ## Дробный рюкзак: жадный алгоритм ###
def fractional_knapsack(wgt, val, cap)
# Создать список предметов с двумя свойствами: вес и стоимость
items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) }
# Отсортировать по удельной стоимости item.v / item.w в порядке убывания
items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) }
# Циклический жадный выбор
res = 0
for item in items
if item.w <= cap
# Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v
cap -= item.w
else
# Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += (item.v.to_f / item.w) * cap
# Свободной вместимости больше не осталось, поэтому выйти из цикла
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
# Жадный алгоритм
res = fractional_knapsack(wgt, val, cap)
puts "Максимальная стоимость предметов без превышения вместимости рюкзака = #{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
# ## Максимальная вместимость: жадный алгоритм ###
def max_capacity(ht)
# Инициализировать i и j так, чтобы они располагались по двум концам массива
i, j = 0, ht.length - 1
# Начальная максимальная вместимость равна 0
res = 0
# Выполнять жадный выбор в цикле, пока две доски не встретятся
while i < j
# Обновить максимальную вместимость
cap = [ht[i], ht[j]].min * (j - i)
res = [res, cap].max
# Сдвигать внутрь более короткую сторону
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]
# Жадный алгоритм
res = max_capacity(ht)
puts "Максимальная вместимость = #{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
# ## Максимальное произведение разрезания: жадный алгоритм ###
def max_product_cutting(n)
# Когда n <= 3, обязательно нужно выделить одну 1
return 1 * (n - 1) if n <= 3
# Жадно выделить множители 3, где a — число троек, а b — остаток
a, b = n / 3, n % 3
# Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return (3.pow(a - 1) * 2 * 2).to_i if b == 1
# Если остаток равен 2, ничего не делать
return (3.pow(a) * 2).to_i if b == 2
# Если остаток равен 0, ничего не делать
3.pow(a).to_i
end
### Driver Code ###
if __FILE__ == $0
n = 58
# Жадный алгоритм
res = max_product_cutting(n)
puts "Максимальное произведение после разрезания = #{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
# ## Пара ключ-значение ###
class Pair
attr_accessor :key, :val
def initialize(key, val)
@key = key
@val = val
end
end
# ## Хеш-таблица на основе массива ###
class ArrayHashMap
# ## Конструктор ###
def initialize
# Инициализировать массив, содержащий 100 корзин
@buckets = Array.new(100)
end
# ## Хеш-функция ###
def hash_func(key)
index = key % 100
end
# ## Операция поиска ###
def get(key)
index = hash_func(key)
pair = @buckets[index]
return if pair.nil?
pair.val
end
# ## Операция добавления ###
def put(key, val)
pair = Pair.new(key, val)
index = hash_func(key)
@buckets[index] = pair
end
# ## Операция удаления ###
def remove(key)
index = hash_func(key)
# Присвоить nil, что означает удаление
@buckets[index] = nil
end
# ## Получить все пары ключ-значение ###
def entry_set
result = []
@buckets.each { |pair| result << pair unless pair.nil? }
result
end
# ## Получить все ключи ###
def key_set
result = []
@buckets.each { |pair| result << pair.key unless pair.nil? }
result
end
# ## Получить все значения ###
def value_set
result = []
@buckets.each { |pair| result << pair.val unless pair.nil? }
result
end
# ## Вывести хеш-таблицу ###
def print
@buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? }
end
end
### Driver Code ###
if __FILE__ == $0
# Инициализация хеш-таблицы
hmap = ArrayHashMap.new
# Операция добавления
# Добавить пару (key, value) в хеш-таблицу
hmap.put(12836, "Сяо Ха")
hmap.put(15937, "Сяо Ло")
hmap.put(16750, "Сяо Суань")
hmap.put(13276, "Сяо Фа")
hmap.put(10583, "Сяо Я")
puts "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"
hmap.print
# Операция поиска
# По ключу key получить из хеш-таблицы значение value
name = hmap.get(15937)
puts "\nДля номера 15937 найдено имя #{name}"
# Операция удаления
# Удалить пару значений (key, value) из хеш-таблицы
hmap.remove(10583)
puts "\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"
hmap.print
# Обход хеш-таблицы
puts "\nОтдельный обход пар ключ-значение"
for pair in hmap.entry_set
puts "#{pair.key} -> #{pair.val}"
end
puts "\nОтдельный обход ключей"
for key in hmap.key_set
puts key
end
puts "\nОтдельный обход значений"
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 "Хеш-значение целого числа #{num}: #{hash_num}"
bol = true
hash_bol = bol.hash
puts "Хеш-значение булева значения #{bol}: #{hash_bol}"
dec = 3.14159
hash_dec = dec.hash
puts "Хеш-значение десятичного числа #{dec}: #{hash_dec}"
str = "Hello Algo"
hash_str = str.hash
puts "Хеш-значение строки #{str}: #{hash_str}"
tup = [12836, 'Сяо Ха']
hash_tup = tup.hash
puts "Хеш-значение кортежа #{tup}: #{hash_tup}"
obj = ListNode.new(0)
hash_obj = obj.hash
puts "Хеш-значение объекта узла #{obj}: #{hash_obj}"
end
+44
View File
@@ -0,0 +1,44 @@
=begin
File: hash_map.rb
Created Time: 2024-04-14
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/print_util'
### Driver Code ###
if __FILE__ == $0
# Инициализация хеш-таблицы
hmap = {}
# Операция добавления
# Добавить пару (key, value) в хеш-таблицу
hmap[12836] = "Сяо Ха"
hmap[15937] = "Сяо Ло"
hmap[16750] = "Сяо Суань"
hmap[13276] = "Сяо Фа"
hmap[10583] = "Сяо Я"
puts "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"
print_hash_map(hmap)
# Операция поиска
# Передать ключ key в хеш-таблицу и получить значение value
name = hmap[15937]
puts "\nДля номера 15937 найдено имя #{name}"
# Операция удаления
# Удалить пару (key, value) из хеш-таблицы
hmap.delete(10583)
puts "\nПосле удаления 10583 хеш-таблица имеет вид\nКлюч -> Значение"
print_hash_map(hmap)
# Обход хеш-таблицы
puts "\nОтдельный обход пар ключ-значение"
hmap.entries.each { |key, value| puts "#{key} -> #{value}" }
puts "\nОтдельный обход ключей"
hmap.keys.each { |key| puts key }
puts "\nОтдельный обход значений"
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'
# ## Хеш-таблица с цепочками ###
class HashMapChaining
# ## Конструктор ###
def initialize
@size = 0 # Число пар ключ-значение
@capacity = 4 # Вместимость хеш-таблицы
@load_thres = 2.0 / 3.0 # Порог коэффициента загрузки для запуска расширения
@extend_ratio = 2 # Коэффициент расширения
@buckets = Array.new(@capacity) { [] } # Массив корзин
end
# ## Хеш-функция ###
def hash_func(key)
key % @capacity
end
# ## Коэффициент загрузки ###
def load_factor
@size / @capacity
end
# ## Операция поиска ###
def get(key)
index = hash_func(key)
bucket = @buckets[index]
# Обойти корзину; если найден key, вернуть соответствующее val
for pair in bucket
return pair.val if pair.key == key
end
# Если key не найден, вернуть nil
nil
end
# ## Операция добавления ###
def put(key, val)
# Когда коэффициент загрузки превышает порог, выполнить расширение
extend if load_factor > @load_thres
index = hash_func(key)
bucket = @buckets[index]
# Обойти корзину; если встретился указанный key, обновить соответствующее val и вернуть
for pair in bucket
if pair.key == key
pair.val = val
return
end
end
# Если такого key нет, добавить пару ключ-значение в конец
pair = Pair.new(key, val)
bucket << pair
@size += 1
end
# ## Операция удаления ###
def remove(key)
index = hash_func(key)
bucket = @buckets[index]
# Обойти корзину и удалить из нее пару ключ-значение
for pair in bucket
if pair.key == key
bucket.delete(pair)
@size -= 1
break
end
end
end
# ## Расширение хеш-таблицы ###
def extend
# Временно сохранить исходную хеш-таблицу
buckets = @buckets
# Инициализация новой хеш-таблицы после расширения
@capacity *= @extend_ratio
@buckets = Array.new(@capacity) { [] }
@size = 0
# Перенести пары ключ-значение из исходной хеш-таблицы в новую
for bucket in buckets
for pair in bucket
put(pair.key, pair.val)
end
end
end
# ## Вывести хеш-таблицу ###
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
# ## Инициализация хеш-таблицы
hashmap = HashMapChaining.new
# Операция добавления
# Добавить пару (key, value) в хеш-таблицу
hashmap.put(12836, "Сяо Ха")
hashmap.put(15937, "Сяо Ло")
hashmap.put(16750, "Сяо Суань")
hashmap.put(13276, "Сяо Фа")
hashmap.put(10583, "Сяо Я")
puts "\nПосле завершения добавления хеш-таблица имеет вид\n[Key1 -> Value1, Key2 -> Value2, ...]"
hashmap.print
# Операция поиска
# Передать ключ key в хеш-таблицу и получить значение value
name = hashmap.get(13276)
puts "\nДля номера 13276 найдено имя #{name}"
# Операция удаления
# Удалить пару (key, value) из хеш-таблицы
hashmap.remove(12836)
puts "\nПосле удаления 12836 хеш-таблица имеет вид\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'
# ## Хеш-таблица с открытой адресацией ###
class HashMapOpenAddressing
TOMBSTONE = Pair.new(-1, '-1') # Удалить метку
# ## Конструктор ###
def initialize
@size = 0 # Число пар ключ-значение
@capacity = 4 # Вместимость хеш-таблицы
@load_thres = 2.0 / 3.0 # Порог коэффициента загрузки для запуска расширения
@extend_ratio = 2 # Коэффициент расширения
@buckets = Array.new(@capacity) # Массив корзин
end
# ## Хеш-функция ###
def hash_func(key)
key % @capacity
end
# ## Коэффициент загрузки ###
def load_factor
@size / @capacity
end
# ## Найти индекс корзины, соответствующий key ###
def find_bucket(key)
index = hash_func(key)
first_tombstone = -1
# Выполнять линейное пробирование и завершить при встрече с пустой корзиной
while !@buckets[index].nil?
# Если встретился key, вернуть соответствующий индекс корзины
if @buckets[index].key == key
# Если ранее встретилась метка удаления, переместить пару ключ-значение на этот индекс
if first_tombstone != -1
@buckets[first_tombstone] = @buckets[index]
@buckets[index] = TOMBSTONE
return first_tombstone # Вернуть индекс корзины после перемещения
end
return index # Вернуть индекс корзины
end
# Записать первую встретившуюся метку удаления
first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE
# Вычислить индекс корзины; при выходе за конец вернуться к началу
index = (index + 1) % @capacity
end
# Если key не существует, вернуть индекс точки добавления
first_tombstone == -1 ? index : first_tombstone
end
# ## Операция поиска ###
def get(key)
# Найти индекс корзины, соответствующий key
index = find_bucket(key)
# Если пара ключ-значение найдена, вернуть соответствующее val
return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index])
# Если пара ключ-значение не существует, вернуть nil
nil
end
# ## Операция добавления ###
def put(key, val)
# Когда коэффициент загрузки превышает порог, выполнить расширение
extend if load_factor > @load_thres
# Найти индекс корзины, соответствующий key
index = find_bucket(key)
# Если пара ключ-значение найдена, перезаписать val и вернуть
unless [nil, TOMBSTONE].include?(@buckets[index])
@buckets[index].val = val
return
end
# Если пары ключ-значение нет, добавить ее
@buckets[index] = Pair.new(key, val)
@size += 1
end
# ## Операция удаления ###
def remove(key)
# Найти индекс корзины, соответствующий key
index = find_bucket(key)
# Если пара ключ-значение найдена, заменить ее меткой удаления
unless [nil, TOMBSTONE].include?(@buckets[index])
@buckets[index] = TOMBSTONE
@size -= 1
end
end
# ## Расширение хеш-таблицы ###
def extend
# Временно сохранить исходную хеш-таблицу
buckets_tmp = @buckets
# Инициализация новой хеш-таблицы после расширения
@capacity *= @extend_ratio
@buckets = Array.new(@capacity)
@size = 0
# Перенести пары ключ-значение из исходной хеш-таблицы в новую
for pair in buckets_tmp
put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair)
end
end
# ## Вывести хеш-таблицу ###
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
# Инициализация хеш-таблицы
hashmap = HashMapOpenAddressing.new
# Операция добавления
# Добавить пару (key, val) в хеш-таблицу
hashmap.put(12836, "Сяо Ха")
hashmap.put(15937, "Сяо Ло")
hashmap.put(16750, "Сяо Суань")
hashmap.put(13276, "Сяо Фа")
hashmap.put(10583, "Сяо Я")
puts "\nПосле добавления хеш-таблица имеет вид\nКлюч -> Значение"
hashmap.print
# Операция поиска
# Передать ключ key в хеш-таблицу и получить значение val
name = hashmap.get(13276)
puts "\nДля номера 13276 найдено имя #{name}"
# Операция удаления
# Удалить пару (key, val) из хеш-таблицы
hashmap.remove(16750)
puts "\nПосле удаления 16750 хеш-таблица имеет вид\nКлюч -> Значение"
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
# ## Аддитивное хеширование ###
def add_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash += c.ord }
hash % modulus
end
# ## Мультипликативное хеширование ###
def mul_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash = 31 * hash + c.ord }
hash % modulus
end
# ## XOR-хеширование ###
def xor_hash(key)
hash = 0
modulus = 1_000_000_007
key.each_char { |c| hash ^= c.ord }
hash % modulus
end
# ## Хеширование с циклическим сдвигом ###
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 "Хеш-сумма сложением = #{hash}"
hash = mul_hash(key)
puts "Хеш-сумма умножением = #{hash}"
hash = xor_hash(key)
puts "Хеш-сумма XOR = #{hash}"
hash = rot_hash(key)
puts "Хеш-сумма с циклическим сдвигом = #{hash}"
end
+147
View File
@@ -0,0 +1,147 @@
=begin
File: my_heap.rb
Created Time: 2024-04-19
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
require_relative '../utils/print_util'
# ## Максимальная куча ###
class MaxHeap
attr_reader :max_heap
# ## Конструктор, строящий кучу по входному списку ###
def initialize(nums)
# Добавить элементы списка в кучу без изменений
@max_heap = nums
# Выполнить heapify для всех узлов, кроме листовых
parent(size - 1).downto(0) do |i|
sift_down(i)
end
end
# ## Получить индекс левого дочернего узла ###
def left(i)
2 * i + 1
end
# ## Получить индекс правого дочернего узла ###
def right(i)
2 * i + 2
end
# ## Получить индекс родительского узла ###
def parent(i)
(i - 1) / 2 # Округление вниз при делении
end
# ## Обмен элементов ###
def swap(i, j)
@max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i]
end
# ## Получить размер кучи ###
def size
@max_heap.length
end
# ## Проверка, пуста ли куча ###
def is_empty?
size == 0
end
# ## Доступ к элементу на вершине кучи ###
def peek
@max_heap[0]
end
# ## Добавление элемента в кучу ###
def push(val)
# Добавление узла
@max_heap << val
# Просеивание снизу вверх
sift_up(size - 1)
end
# ## Начиная с узла i, выполнить просеивание снизу вверх ###
def sift_up(i)
loop do
# Получение родительского узла для узла i
p = parent(i)
# Завершить heapify, когда «корневой узел уже пройден» или «узел не требует исправления»
break if p < 0 || @max_heap[i] <= @max_heap[p]
# Поменять два узла местами
swap(i, p)
# Циклическое просеивание вверх
i = p
end
end
# ## Извлечение элемента из кучи ###
def pop
# Обработка пустого случая
raise IndexError, "куча пуста" if is_empty?
# Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы)
swap(0, size - 1)
# Удаление узла
val = @max_heap.pop
# Просеивание сверху вниз
sift_down(0)
# Вернуть элемент с вершины кучи
val
end
# ## Начиная с узла i, выполнить просеивание сверху вниз ###
def sift_down(i)
loop do
# Определить узел с максимальным значением среди i, l и r и обозначить его как ma
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]
# Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти
break if ma == i
# Поменять два узла местами
swap(i, ma)
# Циклическое просеивание вниз
i = ma
end
end
# ## Вывести кучу (двоичное дерево) ###
def __print__
print_heap(@max_heap)
end
end
### Driver Code ###
if __FILE__ == $0
# Инициализация максимальной кучи
max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2])
puts "\nПосле построения кучи из входного списка"
max_heap.__print__
# Получение элемента с вершины кучи
peek = max_heap.peek
puts "\nЭлемент на вершине кучи = #{peek}"
# Добавление элемента в кучу
val = 7
max_heap.push(val)
puts "\nПосле добавления элемента #{val} в кучу"
max_heap.__print__
# Извлечение элемента с вершины кучи
peek = max_heap.pop
puts "\nПосле извлечения вершины кучи #{peek}"
max_heap.__print__
# Получение размера кучи
size = max_heap.size
puts "\nКоличество элементов в куче = #{size}"
# Проверка, пуста ли куча
is_empty = max_heap.is_empty?
puts "\nПуста ли куча: #{is_empty}"
end
+64
View File
@@ -0,0 +1,64 @@
=begin
File: top_k.rb
Created Time: 2024-04-19
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
require_relative "./my_heap"
# ## Добавление элемента в кучу ###
def push_min_heap(heap, val)
# Инвертировать знак элемента
heap.push(-val)
end
# ## Извлечение элемента из кучи ###
def pop_min_heap(heap)
# Инвертировать знак элемента
-heap.pop
end
# ## Доступ к элементу на вершине кучи ###
def peek_min_heap(heap)
# Инвертировать знак элемента
-heap.peek
end
# ## Извлечение элементов из кучи ###
def get_min_heap(heap)
# Инвертировать все элементы кучи
heap.max_heap.map { |x| -x }
end
# ## Поиск k наибольших элементов массива с помощью кучи ###
def top_k_heap(nums, k)
# Инициализация минимальной кучи
# Обратите внимание: мы инвертируем все элементы кучи, чтобы с помощью максимальной кучи имитировать минимальную
max_heap = MaxHeap.new([])
# Поместить первые k элементов массива в кучу
for i in 0...k
push_min_heap(max_heap, nums[i])
end
# Начиная с элемента k+1, поддерживать длину кучи равной k
for i in k...nums.length
# Если текущий элемент больше элемента на вершине кучи, извлечь вершину кучи и добавить текущий элемент в кучу
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 "#{k} наибольших элементов:"
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
# ## Бинарный поиск (двусторонне замкнутый интервал) ###
def binary_search(nums, target)
# Инициализировать двусторонне замкнутый интервал [0, n-1], то есть i и j указывают на первый и последний элементы массива соответственно
i, j = 0, nums.length - 1
# Цикл завершается, когда диапазон поиска пуст (при i > j диапазон пуст)
while i <= j
# Теоретически числа в Ruby могут быть сколь угодно большими (ограничены только объемом памяти), поэтому не нужно учитывать переполнение больших чисел
m = (i + j) / 2 # Вычислить индекс середины m
if nums[m] < target
i = m + 1 # Это означает, что target находится в интервале [m+1, j]
elsif nums[m] > target
j = m - 1 # Это означает, что target находится в интервале [i, m-1]
else
return m # Целевой элемент найден, вернуть его индекс
end
end
-1 # Целевой элемент не найден, вернуть -1
end
# ## Бинарный поиск (лево замкнутый, право открытый интервал) ###
def binary_search_lcro(nums, target)
# Инициализировать лево замкнутый, право открытый интервал [0, n), то есть i и j указывают на первый элемент массива и позицию сразу за последним элементом соответственно
i, j = 0, nums.length
# Цикл завершается, когда диапазон поиска пуст (при i = j диапазон пуст)
while i < j
# Вычислить индекс середины m
m = (i + j) / 2
if nums[m] < target
i = m + 1 # Это означает, что target находится в интервале [m+1, j)
elsif nums[m] > target
j = m - 1 # Это означает, что target находится в интервале [i, m)
else
return m # Целевой элемент найден, вернуть его индекс
end
end
-1 # Целевой элемент не найден, вернуть -1
end
### Driver Code ###
if __FILE__ == $0
target = 6
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
# Бинарный поиск (двусторонне замкнутый интервал)
index = binary_search(nums, target)
puts "Индекс целевого элемента 6 = #{index}"
# Бинарный поиск (лево замкнутый, право открытый интервал)
index = binary_search_lcro(nums, target)
puts "Индекс целевого элемента 6 = #{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'
# ## Бинарный поиск самого левого target ###
def binary_search_left_edge(nums, target)
# Эквивалентно поиску точки вставки target
i = binary_search_insertion(nums, target)
# target не найден, вернуть -1
return -1 if i == nums.length || nums[i] != target
i # Найти target и вернуть индекс i
end
# ## Бинарный поиск самого правого target ###
def binary_search_right_edge(nums, target)
# Преобразовать задачу в поиск самого левого target + 1
i = binary_search_insertion(nums, target + 1)
# j указывает на самый правый target, а i — на первый элемент больше target
j = i - 1
# target не найден, вернуть -1
return -1 if j == -1 || nums[j] != target
j # Найти target и вернуть индекс j
end
### Driver Code ###
if __FILE__ == $0
# Массив с повторяющимися элементами
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]
puts "\nМассив nums = #{nums}"
# Бинарный поиск левой и правой границы
for target in [6, 7]
index = binary_search_left_edge(nums, target)
puts "Индекс крайнего левого элемента #{target}: #{index}"
index = binary_search_right_edge(nums, target)
puts "Индекс крайнего правого элемента #{target}: #{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
# ## Бинарный поиск точки вставки (без повторяющихся элементов) ###
def binary_search_insertion_simple(nums, target)
# Инициализировать двусторонне замкнутый интервал [0, n-1]
i, j = 0, nums.length - 1
while i <= j
# Вычислить индекс середины m
m = (i + j) / 2
if nums[m] < target
i = m + 1 # target находится в интервале [m+1, j]
elsif nums[m] > target
j = m - 1 # target находится в интервале [i, m-1]
else
return m # Найти target и вернуть точку вставки m
end
end
i # target не найден, вернуть точку вставки i
end
# ## Бинарный поиск точки вставки (с повторяющимися элементами) ###
def binary_search_insertion(nums, target)
# Инициализировать двусторонне замкнутый интервал [0, n-1]
i, j = 0, nums.length - 1
while i <= j
# Вычислить индекс середины m
m = (i + j) / 2
if nums[m] < target
i = m + 1 # target находится в интервале [m+1, j]
elsif nums[m] > target
j = m - 1 # target находится в интервале [i, m-1]
else
j = m - 1 # Первый элемент меньше target находится в интервале [i, m-1]
end
end
i # Вернуть точку вставки i
end
### Driver Code ###
if __FILE__ == $0
# Массив без повторяющихся элементов
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
puts "\nМассив nums = #{nums}"
# Бинарный поиск точки вставки
for target in [6, 9]
index = binary_search_insertion_simple(nums, target)
puts "Индекс позиции вставки элемента #{target}: #{index}"
end
# Массив с повторяющимися элементами
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]
puts "\nМассив nums = #{nums}"
# Бинарный поиск точки вставки
for target in [2, 6, 20]
index = binary_search_insertion(nums, target)
puts "Индекс позиции вставки элемента #{target}: #{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'
# ## Хеш-поиск (массив) ###
def hashing_search_array(hmap, target)
# key хеш-таблицы: целевой элемент, value: индекс
# Если такого key нет в хеш-таблице, вернуть -1
hmap[target] || -1
end
# ## Хеш-поиск (связный список) ###
def hashing_search_linkedlist(hmap, target)
# key хеш-таблицы: целевой элемент, value: объект узла
# Если такого key нет в хеш-таблице, вернуть None
hmap[target] || nil
end
### Driver Code ###
if __FILE__ == $0
target = 3
# Хеш-поиск (массив)
nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]
# Инициализация хеш-таблицы
map0 = {}
for i in 0...nums.length
map0[nums[i]] = i # key: элемент, value: индекс
end
index = hashing_search_array(map0, target)
puts "Индекс целевого элемента 3 = #{index}"
# Хеш-поиск (связный список)
head = arr_to_linked_list(nums)
# Инициализация хеш-таблицы
map1 = {}
while head
map1[head.val] = head
head = head.next
end
node = hashing_search_linkedlist(map1, target)
puts "Объект узла со значением 3 = #{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'
# ## Линейный поиск (массив) ###
def linear_search_array(nums, target)
# Обход массива
for i in 0...nums.length
return i if nums[i] == target # Целевой элемент найден, вернуть его индекс
end
-1 # Целевой элемент не найден, вернуть -1
end
# ## Линейный поиск (связный список) ###
def linear_search_linkedlist(head, target)
# Обойти связный список
while head
return head if head.val == target # Найти целевой узел и вернуть его
head = head.next
end
nil # Целевой узел не найден, вернуть None
end
### Driver Code ###
if __FILE__ == $0
target = 3
# Выполнить линейный поиск в массиве
nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]
index = linear_search_array(nums, target)
puts "Индекс целевого элемента 3 = #{index}"
# Выполнить линейный поиск в связном списке
head = arr_to_linked_list(nums)
node = linear_search_linkedlist(head, target)
puts "Объект узла со значением 3 = #{node}"
end
@@ -0,0 +1,46 @@
=begin
File: two_sum.rb
Created Time: 2024-04-09
Author: Blue Bean (lonnnnnnner@gmail.com)
=end
# ## Метод 1: полный перебор ###
def two_sum_brute_force(nums, target)
# Два вложенных цикла, временная сложность 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
# ## Метод 2: вспомогательная хеш-таблица ###
def two_sum_hash_table(nums, target)
# Вспомогательная хеш-таблица, пространственная сложность O(n)
dic = {}
# Один цикл, временная сложность 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
# ====== Основной код ======
# Метод 1
res = two_sum_brute_force(nums, target)
puts "Результат метода 1 res = #{res}"
# Метод 2
res = two_sum_hash_table(nums, target)
puts "Результат метода 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
# ## Пузырьковая сортировка ###
def bubble_sort(nums)
n = nums.length
# Внешний цикл: неотсортированный диапазон [0, i]
for i in (n - 1).downto(1)
# Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец
for j in 0...i
if nums[j] > nums[j + 1]
# Поменять местами nums[j] и nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
end
end
end
end
# ## Пузырьковая сортировка (оптимизация флагом) ###
def bubble_sort_with_flag(nums)
n = nums.length
# Внешний цикл: неотсортированный диапазон [0, i]
for i in (n - 1).downto(1)
flag = false # Инициализировать флаг
# Внутренний цикл: переместить максимальный элемент неотсортированного диапазона [0, i] в его правый конец
for j in 0...i
if nums[j] > nums[j + 1]
# Поменять местами nums[j] и nums[j + 1]
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = true # Записать обмен элементов
end
end
break unless flag # На этой итерации «всплытия» не было ни одного обмена, сразу выйти
end
end
### Driver Code ###
if __FILE__ == $0
nums = [4, 1, 3, 1, 5, 2]
bubble_sort(nums)
puts "После пузырьковой сортировки nums = #{nums}"
nums1 = [4, 1, 3, 1, 5, 2]
bubble_sort_with_flag(nums1)
puts "После пузырьковой сортировки 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
# ## Сортировка корзинами ###
def bucket_sort(nums)
# Инициализировать k = n/2 корзин, предполагая распределение 2 элементов в каждую корзину
k = nums.length / 2
buckets = Array.new(k) { [] }
# 1. Распределить элементы массива по корзинам
nums.each do |num|
# Входные данные лежат в диапазоне [0, 1); использовать num * k для отображения в диапазон индексов [0, k-1]
i = (num * k).to_i
# Добавить num в корзину i
buckets[i] << num
end
# 2. Выполнить сортировку внутри каждой корзины
buckets.each do |bucket|
# Использовать встроенную функцию сортировки; ее также можно заменить другим алгоритмом сортировки
bucket.sort!
end
# 3. Обойти корзины и объединить результаты
i = 0
buckets.each do |bucket|
bucket.each do |num|
nums[i] = num
i += 1
end
end
end
### Driver Code ###
if __FILE__ == $0
# Пусть входные данные — числа с плавающей точкой из диапазона [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 "После сортировки корзинами 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
# ## Сортировка подсчетом ###
def counting_sort_naive(nums)
# Простая реализация, не подходит для сортировки объектов
# 1. Найти максимальный элемент массива m
m = 0
nums.each { |num| m = [m, num].max }
# 2. Подсчитать число появлений каждой цифры
# counter[num] обозначает число появлений num
counter = Array.new(m + 1, 0)
nums.each { |num| counter[num] += 1 }
# 3. Обойти counter и заполнить исходный массив nums элементами
i = 0
for num in 0...(m + 1)
(0...counter[num]).each do
nums[i] = num
i += 1
end
end
end
# ## Сортировка подсчетом ###
def counting_sort(nums)
# Полная реализация, позволяет сортировать объекты и является стабильной сортировкой
# 1. Найти максимальный элемент массива m
m = nums.max
# 2. Подсчитать число появлений каждой цифры
# counter[num] обозначает число появлений num
counter = Array.new(m + 1, 0)
nums.each { |num| counter[num] += 1 }
# 3. Вычислить префиксные суммы counter и преобразовать «число появлений» в «конечный индекс»
# То есть counter[num]-1 — это индекс последнего появления num в res
(0...m).each { |i| counter[i + 1] += counter[i] }
# 4. Обойти nums в обратном порядке и поместить элементы в результирующий массив res
# Инициализировать массив res для хранения результата
n = nums.length
res = Array.new(n, 0)
(n - 1).downto(0).each do |i|
num = nums[i]
res[counter[num] - 1] = num # Поместить num по соответствующему индексу
counter[num] -= 1 # Уменьшить префиксную сумму на 1, чтобы получить индекс следующего размещения num
end
# Перезаписать исходный массив nums массивом результата res
(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 "После сортировки подсчетом (объекты не поддерживаются) nums = #{nums}"
nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]
counting_sort(nums1)
puts "После сортировки подсчетом nums1 = #{nums1}"
end
@@ -0,0 +1,45 @@
=begin
File: heap_sort.rb
Created Time: 2024-04-10
Author: junminhong (junminhong1110@gmail.com)
=end
# ## Длина кучи равна n; начиная с узла i, выполнить просеивание сверху вниз ###
def sift_down(nums, n, i)
while true
# Определить узел с максимальным значением среди i, l и r и обозначить его как ma
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]
# Если узел i уже максимален или индексы l и r вне границ, дальнейшее просеивание не требуется, выйти
break if ma == i
# Поменять два узла местами
nums[i], nums[ma] = nums[ma], nums[i]
# Циклическое просеивание вниз
i = ma
end
end
# ## Сортировка кучей ###
def heap_sort(nums)
# Построение кучи: выполнить heapify для всех узлов, кроме листовых
(nums.length / 2 - 1).downto(0) do |i|
sift_down(nums, nums.length, i)
end
# Извлекать максимальный элемент из кучи в течение n-1 итераций
(nums.length - 1).downto(1) do |i|
# Поменять корневой узел с самым правым листом местами (поменять первый и последний элементы)
nums[0], nums[i] = nums[i], nums[0]
# Начиная с корневого узла, выполнить просеивание сверху вниз
sift_down(nums, i, 0)
end
end
### Driver Code ###
if __FILE__ == $0
nums = [4, 1, 3, 1, 5, 2]
heap_sort(nums)
puts "После сортировки кучей 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
# ## Сортировка вставками ###
def insertion_sort(nums)
n = nums.length
# Внешний цикл: отсортированный диапазон [0, i-1]
for i in 1...n
base = nums[i]
j = i - 1
# Внутренний цикл: вставить base в правильную позицию отсортированного диапазона [0, i-1]
while j >= 0 && nums[j] > base
nums[j + 1] = nums[j] # Сдвинуть nums[j] на одну позицию вправо
j -= 1
end
nums[j + 1] = base # Поместить base в правильную позицию
end
end
### Driver Code ###
nums = [4, 1, 3, 1, 5, 2]
insertion_sort(nums)
puts "После сортировки вставками nums = #{nums}"
@@ -0,0 +1,60 @@
=begin
File: merge_sort.rb
Created Time: 2024-04-10
Author: junminhong (junminhong1110@gmail.com)
=end
# ## Слияние левого и правого подмассивов ###
def merge(nums, left, mid, right)
# Интервал левого подмассива: [left, mid], правого подмассива: [mid+1, right]
# Создать временный массив tmp для хранения результата слияния
tmp = Array.new(right - left + 1, 0)
# Инициализировать начальные индексы левого и правого подмассивов
i, j, k = left, mid + 1, 0
# Пока в левом и правом подмассивах еще есть элементы, сравнивать их и копировать меньший во временный массив
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
# Скопировать оставшиеся элементы левого и правого подмассивов во временный массив
while i <= mid
tmp[k] = nums[i]
i += 1
k += 1
end
while j <= right
tmp[k] = nums[j]
j += 1
k += 1
end
# Скопировать элементы временного массива tmp обратно в соответствующий диапазон исходного массива nums
(0...tmp.length).each do |k|
nums[left + k] = tmp[k]
end
end
# ## Сортировка слиянием ###
def merge_sort(nums, left, right)
# Условие завершения
# Когда длина подмассива равна 1, рекурсия завершается
return if left >= right
# Этап разбиения
mid = left + (right - left) / 2 # Вычислить середину
merge_sort(nums, left, mid) # Рекурсивно обработать левый подмассив
merge_sort(nums, mid + 1, right) # Рекурсивно обработать правый подмассив
# Этап слияния
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 "После сортировки слиянием nums = #{nums.inspect}"
end
+153
View File
@@ -0,0 +1,153 @@
=begin
File: quick_sort.rb
Created Time: 2024-04-01
Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Класс быстрой сортировки ###
class QuickSort
class << self
# ## Разбиение с опорными указателями ###
def partition(nums, left, right)
# Взять nums[left] в качестве опорного элемента
i, j = left, right
while i < j
while i < j && nums[j] >= nums[left]
j -= 1 # Идти справа налево в поисках первого элемента меньше опорного
end
while i < j && nums[i] <= nums[left]
i += 1 # Идти слева направо в поисках первого элемента больше опорного
end
# Обмен элементов
nums[i], nums[j] = nums[j], nums[i]
end
# Переместить опорный элемент на границу двух подмассивов
nums[i], nums[left] = nums[left], nums[i]
i # Вернуть индекс опорного элемента
end
# ## Класс быстрой сортировки ###
def quick_sort(nums, left, right)
# Рекурсивно обрабатывать, пока длина подмассива не станет равной 1
if left < right
# Разбиение с опорными указателями
pivot = partition(nums, left, right)
# Рекурсивно обработать левый и правый подмассивы
quick_sort(nums, left, pivot - 1)
quick_sort(nums, pivot + 1, right)
end
nums
end
end
end
# ## Класс быстрой сортировки (оптимизация медианой) ###
class QuickSortMedian
class << self
# ## Выбрать медиану из трех кандидатов ###
def median_three(nums, left, mid, right)
# Выбрать медиану из трех кандидатов
_l, _m, _r = nums[left], nums[mid], nums[right]
# m находится между l и r
return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l)
# l находится между m и r
return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m)
return right
end
# ## Разбиение с опорными указателями (медиана трех) ###
def partition(nums, left, right)
# ## Использовать nums[left] как опорный элемент
med = median_three(nums, left, (left + right) / 2, right)
# Переместить медиану в крайний левый элемент массива
nums[left], nums[med] = nums[med], nums[left]
i, j = left, right
while i < j
while i < j && nums[j] >= nums[left]
j -= 1 # Идти справа налево в поисках первого элемента меньше опорного
end
while i < j && nums[i] <= nums[left]
i += 1 # Идти слева направо в поисках первого элемента больше опорного
end
# Обмен элементов
nums[i], nums[j] = nums[j], nums[i]
end
# Переместить опорный элемент на границу двух подмассивов
nums[i], nums[left] = nums[left], nums[i]
i # Вернуть индекс опорного элемента
end
# ## Быстрая сортировка ###
def quick_sort(nums, left, right)
# Рекурсивно обрабатывать, пока длина подмассива не станет равной 1
if left < right
# Разбиение с опорными указателями
pivot = partition(nums, left, right)
# Рекурсивно обработать левый и правый подмассивы
quick_sort(nums, left, pivot - 1)
quick_sort(nums, pivot + 1, right)
end
nums
end
end
end
# ## Класс быстрой сортировки (оптимизация глубины рекурсии) ###
class QuickSortTailCall
class << self
# ## Разбиение с опорными указателями ###
def partition(nums, left, right)
# Использовать nums[left] как опорный элемент
i = left
j = right
while i < j
while i < j && nums[j] >= nums[left]
j -= 1 # Идти справа налево в поисках первого элемента меньше опорного
end
while i < j && nums[i] <= nums[left]
i += 1 # Идти слева направо в поисках первого элемента больше опорного
end
# Обмен элементов
nums[i], nums[j] = nums[j], nums[i]
end
# Переместить опорный элемент на границу двух подмассивов
nums[i], nums[left] = nums[left], nums[i]
i # Вернуть индекс опорного элемента
end
# ## Быстрая сортировка (оптимизация глубины рекурсии) ###
def quick_sort(nums, left, right)
# Рекурсивно обрабатывать, пока длина подмассива не станет равной 1
while left < right
# Разбиение с опорными указателями
pivot = partition(nums, left, right)
# Выполнить быструю сортировку для более короткого из двух подмассивов
if pivot - left < right - pivot
quick_sort(nums, left, pivot - 1)
left = pivot + 1 # Оставшийся неотсортированный диапазон: [pivot + 1, right]
else
quick_sort(nums, pivot + 1, right)
right = pivot - 1 # Оставшийся неотсортированный диапазон: [left, pivot - 1]
end
end
end
end
end
### Driver Code ###
if __FILE__ == $0
# Быстрая сортировка
nums = [2, 4, 1, 0, 3, 5]
QuickSort.quick_sort(nums, 0, nums.length - 1)
puts "После быстрой сортировки nums = #{nums}"
# Быстрая сортировка (оптимизация медианным опорным элементом)
nums1 = [2, 4, 1, 0, 3, 5]
QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1)
puts "После быстрой сортировки (оптимизация медианным опорным элементом) nums1 = #{nums1}"
# Быстрая сортировка (оптимизация глубины рекурсии)
nums2 = [2, 4, 1, 0, 3, 5]
QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1)
puts "После быстрой сортировки (оптимизация глубины рекурсии) 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
# ## Получить k-й разряд элемента num, где exp = 10^(k-1) ###
def digit(num, exp)
# Передача exp вместо k позволяет избежать повторного выполнения дорогостоящих вычислений степени
(num / exp) % 10
end
# ## Сортировка подсчетом (сортировка по k-му разряду nums) ###
def counting_sort_digit(nums, exp)
# Разряды десятичной системы лежат в диапазоне 0~9, поэтому нужен массив корзин длины 10
counter = Array.new(10, 0)
n = nums.length
# Подсчитать число появлений каждой цифры от 0 до 9
for i in 0...n
d = digit(nums[i], exp) # Получить k-й разряд nums[i], обозначив его как d
counter[d] += 1 # Подсчитать число появлений цифры d
end
# Вычислить префиксные суммы и преобразовать «число появлений» в «индекс массива»
(1...10).each { |i| counter[i] += counter[i - 1] }
# Выполняя обратный проход, заполнить res элементами по статистике в корзинах
res = Array.new(n, 0)
for i in (n - 1).downto(0)
d = digit(nums[i], exp)
j = counter[d] - 1 # Получить индекс j цифры d в массиве
res[j] = nums[i] # Поместить текущий элемент по индексу j
counter[d] -= 1 # Уменьшить количество d на 1
end
# Перезаписать исходный массив nums результатом
(0...n).each { |i| nums[i] = res[i] }
end
# ## Поразрядная сортировка ###
def radix_sort(nums)
# Получить максимальный элемент массива, чтобы определить максимальное число разрядов
m = nums.max
# Проходить разряды от младшего к старшему
exp = 1
while exp <= m
# Выполнить сортировку подсчетом по k-му разряду элементов массива
# k = 1 -> exp = 1
# k = 2 -> exp = 10
# то есть exp = 10^(k-1)
counting_sort_digit(nums, exp)
exp *= 10
end
end
### Driver Code ###
if __FILE__ == $0
# Поразрядная сортировка
nums = [
10546151,
35663510,
42865989,
34862445,
81883077,
88906420,
72429244,
30524779,
82060337,
63832996,
]
radix_sort(nums)
puts "После поразрядной сортировки 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
# ## Сортировка выбором ###
def selection_sort(nums)
n = nums.length
# Внешний цикл: неотсортированный диапазон [i, n-1]
for i in 0...(n - 1)
# Внутренний цикл: найти минимальный элемент в неотсортированном диапазоне
k = i
for j in (i + 1)...n
if nums[j] < nums[k]
k = j # Записать индекс минимального элемента
end
end
# Поменять этот минимальный элемент местами с первым элементом неотсортированного диапазона
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 "После сортировки выбором 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
# ## Двусторонняя очередь на основе кольцевого массива ###
class ArrayDeque
# ## Получение длины двусторонней очереди ###
attr_reader :size
# ## Конструктор ###
def initialize(capacity)
@nums = Array.new(capacity, 0)
@front = 0
@size = 0
end
# ## Получить вместимость двусторонней очереди ###
def capacity
@nums.length
end
# ## Проверка, пуста ли двусторонняя очередь ###
def is_empty?
size.zero?
end
# ## Добавление в голову очереди ###
def push_first(num)
if size == capacity
puts 'Двусторонняя очередь заполнена'
return
end
# Указатель головы сдвигается на одну позицию влево
# С помощью операции взятия по модулю front после выхода за начало массива возвращается в хвост
@front = index(@front - 1)
# Добавить num в голову очереди
@nums[@front] = num
@size += 1
end
# ## Добавление в хвост очереди ###
def push_last(num)
if size == capacity
puts 'Двусторонняя очередь заполнена'
return
end
# Вычислить указатель хвоста, указывающий на индекс хвоста + 1
rear = index(@front + size)
# Добавить num в хвост очереди
@nums[rear] = num
@size += 1
end
# ## Извлечение из головы очереди ###
def pop_first
num = peek_first
# Указатель головы сдвигается на одну позицию назад
@front = index(@front + 1)
@size -= 1
num
end
# ## Извлечение из хвоста очереди ###
def pop_last
num = peek_last
@size -= 1
num
end
# ## Доступ к элементу в начале очереди ###
def peek_first
raise IndexError, 'двусторонняя очередь пуста' if is_empty?
@nums[@front]
end
# ## Доступ к элементу в хвосте очереди ###
def peek_last
raise IndexError, 'двусторонняя очередь пуста' if is_empty?
# Вычислить индекс хвостового элемента
last = index(@front + size - 1)
@nums[last]
end
# ## Вернуть массив для вывода ###
def to_array
# Преобразовывать только элементы списка в пределах фактической длины
res = []
for i in 0...size
res << @nums[index(@front + i)]
end
res
end
private
# ## Вычислить индекс в кольцевом массиве ###
def index(i)
# С помощью операции взятия по модулю соединить начало и конец массива
# Когда i выходит за конец массива, он возвращается в начало
# Когда i выходит за начало массива, он возвращается в конец
(i + capacity) % capacity
end
end
### Driver Code ###
if __FILE__ == $0
# Инициализация двусторонней очереди
deque = ArrayDeque.new(10)
deque.push_last(3)
deque.push_last(2)
deque.push_last(5)
puts "Двусторонняя очередь deque = #{deque.to_array}"
# Доступ к элементу
peek_first = deque.peek_first
puts "Первый элемент peek_first = #{peek_first}"
peek_last = deque.peek_last
puts "Последний элемент peek_last = #{peek_last}"
# Добавление элемента в очередь
deque.push_last(4)
puts "После добавления элемента 4 в хвост deque = #{deque.to_array}"
deque.push_first(1)
puts "После добавления элемента 1 в хвост deque = #{deque.to_array}"
# Извлечение элемента из очереди
pop_last = deque.pop_last
puts "Извлечен элемент из хвоста = #{pop_last}, deque после извлечения из хвоста = #{deque.to_array}"
pop_first = deque.pop_first
puts "Извлечен элемент из головы = #{pop_first}, deque после извлечения из головы = #{deque.to_array}"
# Получение длины двусторонней очереди
size = deque.size
puts "Длина двусторонней очереди size = #{size}"
# Проверка, пуста ли двусторонняя очередь
is_empty = deque.is_empty?
puts "Пуста ли двусторонняя очередь = #{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
# ## Очередь на основе кольцевого массива ###
class ArrayQueue
# ## Получение длины очереди ###
attr_reader :size
# ## Конструктор ###
def initialize(size)
@nums = Array.new(size, 0) # Массив для хранения элементов очереди
@front = 0 # Указатель head, указывающий на первый элемент очереди
@size = 0 # Длина очереди
end
# ## Получить вместимость очереди ###
def capacity
@nums.length
end
# ## Проверка, пуста ли очередь ###
def is_empty?
size.zero?
end
# ## Добавление в очередь ###
def push(num)
raise IndexError, 'очередь заполнена' if size == capacity
# Вычислить указатель хвоста, указывающий на индекс хвоста + 1
# С помощью операции взятия по модулю вернуть rear к началу после выхода за конец массива
rear = (@front + size) % capacity
# Добавить num в хвост очереди
@nums[rear] = num
@size += 1
end
# ## Извлечение из очереди ###
def pop
num = peek
# Указатель head сдвигается на одну позицию назад; если он выходит за конец, то возвращается в начало массива
@front = (@front + 1) % capacity
@size -= 1
num
end
# ## Доступ к элементу в начале очереди ###
def peek
raise IndexError, 'очередь пуста' if is_empty?
@nums[@front]
end
# ## Вернуть список для вывода ###
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
# Инициализация очереди
queue = ArrayQueue.new(10)
# Добавление элемента в очередь
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
puts "Очередь queue = #{queue.to_array}"
# Доступ к элементу в начале очереди
peek = queue.peek
puts "Первый элемент peek = #{peek}"
# Извлечение элемента из очереди
pop = queue.pop
puts "Извлеченный элемент pop = #{pop}"
puts "queue после извлечения = #{queue.to_array}"
# Получение длины очереди
size = queue.size
puts "Длина очереди size = #{size}"
# Проверка, пуста ли очередь
is_empty = queue.is_empty?
puts "Пуста ли очередь = #{is_empty}"
# Проверка кольцевого массива
for i in 0...10
queue.push(i)
queue.pop
puts "После #{i}-го цикла 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
# ## Стек на основе массива ###
class ArrayStack
# ## Конструктор ###
def initialize
@stack = []
end
# ## Получить длину стека ###
def size
@stack.length
end
# ## Проверка, пуст ли стек ###
def is_empty?
@stack.empty?
end
# ## Помещение в стек ###
def push(item)
@stack << item
end
# ## Извлечение из стека ###
def pop
raise IndexError, 'стек пуст' if is_empty?
@stack.pop
end
# ## Доступ к верхнему элементу стека ###
def peek
raise IndexError, 'стек пуст' if is_empty?
@stack.last
end
# ## Вернуть список для вывода ###
def to_array
@stack
end
end
### Driver Code ###
if __FILE__ == $0
# Инициализация стека
stack = ArrayStack.new
# Помещение элемента в стек
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
puts "Стек stack = #{stack.to_array}"
# Доступ к верхнему элементу стека
peek = stack.peek
puts "Верхний элемент peek = #{peek}"
# Извлечение элемента из стека
pop = stack.pop
puts "Извлеченный элемент pop = #{pop}"
puts "stack после извлечения = #{stack.to_array}"
# Получение длины стека
size = stack.size
puts "Длина стека size = #{size}"
# Проверка на пустоту
is_empty = stack.is_empty?
puts "Пуст ли стек = #{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
# Инициализировать двустороннюю очередь
# В Ruby нет встроенной двусторонней очереди, поэтому Array можно использовать как двустороннюю очередь
deque = []
# Элемент помещается в очередь
deque << 2
deque << 5
deque << 4
# Обратите внимание: поскольку используется массив, временная сложность метода Array#unshift равна O(n)
deque.unshift(3)
deque.unshift(1)
puts "Двусторонняя очередь deque = #{deque}"
# Доступ к элементу
peek_first = deque.first
puts "Первый элемент peek_first = #{peek_first}"
peek_last = deque.last
puts "Последний элемент peek_last = #{peek_last}"
# Элемент извлекается из очереди
# Обратите внимание: поскольку используется массив, временная сложность метода Array#shift равна O(n)
pop_front = deque.shift
puts "Извлечен элемент из головы pop_front = #{pop_front}, deque после извлечения из головы = #{deque}"
pop_back = deque.pop
puts "Извлечен элемент из хвоста pop_back = #{pop_back}, deque после извлечения из хвоста = #{deque}"
# Получение длины двусторонней очереди
size = deque.length
puts "Длина двусторонней очереди size = #{size}"
# Проверка, пуста ли двусторонняя очередь
is_empty = size.zero?
puts "Пуста ли двусторонняя очередь = #{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
# ## Узел двусвязного списка
class ListNode
attr_accessor :val
attr_accessor :next # Ссылка на узел-преемник
attr_accessor :prev # Ссылка на узел-предшественник
# ## Конструктор ###
def initialize(val)
@val = val
end
end
# ## Двусторонняя очередь на основе двусвязного списка ###
class LinkedListDeque
# ## Получение длины двусторонней очереди ###
attr_reader :size
# ## Конструктор ###
def initialize
@front = nil # Головной узел front
@rear = nil # Хвостовой узел rear
@size = 0 # Длина двусторонней очереди
end
# ## Проверка, пуста ли двусторонняя очередь ###
def is_empty?
size.zero?
end
# ## Операция добавления в очередь ###
def push(num, is_front)
node = ListNode.new(num)
# Если связный список пуст, пусть front и rear оба указывают на node
if is_empty?
@front = @rear = node
# Операция добавления в голову очереди
elsif is_front
# Добавить node в голову списка
@front.prev = node
node.next = @front
@front = node # Обновить головной узел
# Операция добавления в хвост очереди
else
# Добавить node в хвост списка
@rear.next = node
node.prev = @rear
@rear = node # Обновить хвостовой узел
end
@size += 1 # Обновить длину очереди
end
# ## Добавление в голову очереди ###
def push_first(num)
push(num, true)
end
# ## Добавление в хвост очереди ###
def push_last(num)
push(num, false)
end
# ## Операция извлечения из очереди ###
def pop(is_front)
raise IndexError, 'двусторонняя очередь пуста' if is_empty?
# Операция извлечения из головы очереди
if is_front
val = @front.val # Временно сохранить значение головного узла
# Удалить головной узел
fnext = @front.next
unless fnext.nil?
fnext.prev = nil
@front.next = nil
end
@front = fnext # Обновить головной узел
# Операция извлечения из хвоста очереди
else
val = @rear.val # Временно сохранить значение хвостового узла
# Удалить хвостовой узел
rprev = @rear.prev
unless rprev.nil?
rprev.next = nil
@rear.prev = nil
end
@rear = rprev # Обновить хвостовой узел
end
@size -= 1 # Обновить длину очереди
val
end
# ## Извлечение из головы очереди ###
def pop_first
pop(true)
end
# ## Извлечение из головы очереди ###
def pop_last
pop(false)
end
# ## Доступ к элементу в начале очереди ###
def peek_first
raise IndexError, 'двусторонняя очередь пуста' if is_empty?
@front.val
end
# ## Доступ к элементу в хвосте очереди ###
def peek_last
raise IndexError, 'двусторонняя очередь пуста' if is_empty?
@rear.val
end
# ## Вернуть массив для вывода ###
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
# Инициализация двусторонней очереди
deque = LinkedListDeque.new
deque.push_last(3)
deque.push_last(2)
deque.push_last(5)
puts "Двусторонняя очередь deque = #{deque.to_array}"
# Доступ к элементу
peek_first = deque.peek_first
puts "Первый элемент peek_first = #{peek_first}"
peek_last = deque.peek_last
puts "Последний элемент peek_last = #{peek_last}"
# Добавление элемента в очередь
deque.push_last(4)
puts "После добавления элемента 4 в хвост deque = #{deque.to_array}"
deque.push_first(1)
puts "После добавления элемента 1 в голову deque = #{deque.to_array}"
# Извлечение элемента из очереди
pop_last = deque.pop_last
puts "Извлечен элемент из хвоста = #{pop_last}, deque после извлечения из хвоста = #{deque.to_array}"
pop_first = deque.pop_first
puts "Извлечен элемент из головы = #{pop_first}, deque после извлечения из головы = #{deque.to_array}"
# Получение длины двусторонней очереди
size = deque.size
puts "Длина двусторонней очереди size = #{size}"
# Проверка, пуста ли двусторонняя очередь
is_empty = deque.is_empty?
puts "Пуста ли двусторонняя очередь = #{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'
# ## Очередь на основе связного списка ###
class LinkedListQueue
# ## Получение длины очереди ###
attr_reader :size
# ## Конструктор ###
def initialize
@front = nil # Головной узел front
@rear = nil # Хвостовой узел rear
@size = 0
end
# ## Проверка, пуста ли очередь ###
def is_empty?
@front.nil?
end
# ## Добавление в очередь ###
def push(num)
# Добавить num после хвостового узла
node = ListNode.new(num)
# Если очередь пуста, сделать так, чтобы и head, и tail указывали на этот узел
if @front.nil?
@front = node
@rear = node
# Если очередь не пуста, добавить этот узел после хвостового узла
else
@rear.next = node
@rear = node
end
@size += 1
end
# ## Извлечение из очереди ###
def pop
num = peek
# Удалить головной узел
@front = @front.next
@size -= 1
num
end
# ## Доступ к элементу в начале очереди ###
def peek
raise IndexError, 'очередь пуста' if is_empty?
@front.val
end
# ## Преобразовать связный список в Array и вернуть ###
def to_array
queue = []
temp = @front
while temp
queue << temp.val
temp = temp.next
end
queue
end
end
### Driver Code ###
if __FILE__ == $0
# Инициализация очереди
queue = LinkedListQueue.new
# Элемент помещается в очередь
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
puts "Очередь queue = #{queue.to_array}"
# Доступ к элементу в начале очереди
peek = queue.peek
puts "Первый элемент front = #{peek}"
# Извлечение элемента из очереди
pop_front = queue.pop
puts "Извлеченный элемент pop = #{pop_front}"
puts "queue после извлечения = #{queue.to_array}"
# Получение длины очереди
size = queue.size
puts "Длина очереди size = #{size}"
# Проверка, пуста ли очередь
is_empty = queue.is_empty?
puts "Пуста ли очередь = #{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'
# ## Стек на основе связного списка ###
class LinkedListStack
attr_reader :size
# ## Конструктор ###
def initialize
@size = 0
end
# ## Проверка, пуст ли стек ###
def is_empty?
@peek.nil?
end
# ## Помещение в стек ###
def push(val)
node = ListNode.new(val)
node.next = @peek
@peek = node
@size += 1
end
# ## Извлечение из стека ###
def pop
num = peek
@peek = @peek.next
@size -= 1
num
end
# ## Доступ к верхнему элементу стека ###
def peek
raise IndexError, 'стек пуст' if is_empty?
@peek.val
end
# ## Преобразовать связный список в Array и вернуть ###
def to_array
arr = []
node = @peek
while node
arr << node.val
node = node.next
end
arr.reverse
end
end
### Driver Code ###
if __FILE__ == $0
# Инициализация стека
stack = LinkedListStack.new
# Помещение элемента в стек
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
puts "Стек stack = #{stack.to_array}"
# Доступ к верхнему элементу стека
peek = stack.peek
puts "Верхний элемент peek = #{peek}"
# Извлечение элемента из стека
pop = stack.pop
puts "Извлеченный элемент pop = #{pop}"
puts "stack после извлечения = #{stack.to_array}"
# Получение длины стека
size = stack.size
puts "Длина стека size = #{size}"
# Проверка на пустоту
is_empty = stack.is_empty?
puts "Пуст ли стек = #{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
# Инициализировать очередь
# Во встроенной очереди Ruby (Thread::Queue) нет методов peek и обхода, поэтому Array можно использовать как очередь
queue = []
# Добавление элемента в очередь
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
puts "Очередь queue = #{queue}"
# Обратиться к элементу очереди
peek = queue.first
puts "Первый элемент peek = #{peek}"
# Элемент извлекается из очереди
# Обратите внимание: поскольку используется массив, временная сложность метода Array#shift равна O(n)
pop = queue.shift
puts "Извлеченный элемент pop = #{pop}"
puts "queue после извлечения = #{queue}"
# Получение длины очереди
size = queue.length
puts "Длина очереди size = #{size}"
# Проверка, пуста ли очередь
is_empty = queue.empty?
puts "Пуста ли очередь = #{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
# Инициализировать стек
# В Ruby нет встроенного класса стека, поэтому Array можно использовать как стек
stack = []
# Помещение элемента в стек
stack << 1
stack << 3
stack << 2
stack << 5
stack << 4
puts "Стек stack = #{stack}"
# Доступ к верхнему элементу стека
peek = stack.last
puts "Верхний элемент peek = #{peek}"
# Извлечение элемента из стека
pop = stack.pop
puts "Извлеченный элемент pop = #{pop}"
puts "stack после извлечения = #{stack}"
# Получение длины стека
size = stack.length
puts "Длина стека size = #{size}"
# Проверка на пустоту
is_empty = stack.empty?
puts "Пуст ли стек = #{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'
# ## Класс двоичного дерева в массивном представлении ###
class ArrayBinaryTree
# ## Конструктор ###
def initialize(arr)
@tree = arr.to_a
end
# ## Вместимость списка ###
def size
@tree.length
end
# ## Получить значение узла с индексом i ###
def val(i)
# Если индекс выходит за границы, вернуть nil, обозначающий пустую ячейку
return if i < 0 || i >= size
@tree[i]
end
# ## Получить индекс левого дочернего узла узла с индексом i ###
def left(i)
2 * i + 1
end
# ## Получить индекс правого дочернего узла узла с индексом i ###
def right(i)
2 * i + 2
end
# ## Получить индекс родительского узла узла с индексом i ###
def parent(i)
(i - 1) / 2
end
# ## Обход в ширину ###
def level_order
@res = []
# Непосредственно обходить массив
for i in 0...size
@res << val(i) unless val(i).nil?
end
@res
end
# ## Обход в глубину ###
def dfs(i, order)
return if val(i).nil?
# Предварительный обход
@res << val(i) if order == :pre
dfs(left(i), order)
# Симметричный обход
@res << val(i) if order == :in
dfs(right(i), order)
# Обратный обход
@res << val(i) if order == :post
end
# ## Предварительный обход ###
def pre_order
@res = []
dfs(0, :pre)
@res
end
# ## Симметричный обход ###
def in_order
@res = []
dfs(0, :in)
@res
end
# ## Обратный обход ###
def post_order
@res = []
dfs(0, :post)
@res
end
end
### Driver Code ###
if __FILE__ == $0
# Инициализировать двоичное дерево
# Здесь используется функция, напрямую строящая двоичное дерево из массива
arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]
root = arr_to_tree(arr)
puts "\nИнициализация двоичного дерева\n\n"
puts 'Массивное представление двоичного дерева:'
pp arr
puts 'Связное представление двоичного дерева:'
print_tree(root)
# Класс двоичного дерева в массивном представлении
abt = ArrayBinaryTree.new(arr)
# Доступ к узлу
i = 1
l, r, _p = abt.left(i), abt.right(i), abt.parent(i)
puts "\nИндекс текущего узла = #{i}, значение = #{abt.val(i).inspect}"
puts "Индекс его левого дочернего узла = #{l}, значение = #{abt.val(l).inspect}"
puts "Индекс его правого дочернего узла = #{r}, значение = #{abt.val(r).inspect}"
puts "Индекс его родительского узла = #{_p}, значение = #{abt.val(_p).inspect}"
# Обходить дерево
res = abt.level_order
puts "\nОбход в ширину: #{res}"
res = abt.pre_order
puts "Предварительный обход: #{res}"
res = abt.in_order
puts "Симметричный обход: #{res}"
res = abt.post_order
puts "Обратный обход: #{res}"
end
+216
View File
@@ -0,0 +1,216 @@
=begin
File: avl_tree.rb
Created Time: 2024-04-17
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
# ## AVL-дерево ###
class AVLTree
# ## Конструктор ###
def initialize
@root = nil
end
# ## Получение корневого узла двоичного дерева ###
def get_root
@root
end
# ## Получить высоту узла ###
def height(node)
# Высота пустого узла равна -1, высота листового узла равна 0
return node.height unless node.nil?
-1
end
# ## Обновить высоту узла ###
def update_height(node)
# Высота узла равна высоте более высокого поддерева + 1
node.height = [height(node.left), height(node.right)].max + 1
end
# ## Получить коэффициент баланса ###
def balance_factor(node)
# Коэффициент баланса пустого узла равен 0
return 0 if node.nil?
# Коэффициент баланса узла = высота левого поддерева - высота правого поддерева
height(node.left) - height(node.right)
end
# ## Операция правого вращения ###
def right_rotate(node)
child = node.left
grand_child = child.right
# Выполнить правое вращение узла node вокруг child
child.right = node
node.left = grand_child
# Обновить высоту узла
update_height(node)
update_height(child)
# Вернуть корневой узел поддерева после вращения
child
end
# ## Операция левого вращения ###
def left_rotate(node)
child = node.right
grand_child = child.left
# Выполнить левое вращение узла node вокруг child
child.left = node
node.right = grand_child
# Обновить высоту узла
update_height(node)
update_height(child)
# Вернуть корневой узел поддерева после вращения
child
end
# ## Выполнить вращение, чтобы снова сбалансировать поддерево ###
def rotate(node)
# Получить коэффициент баланса узла node
balance_factor = balance_factor(node)
# Обойти левое поддерево
if balance_factor > 1
if balance_factor(node.left) >= 0
# Правое вращение
return right_rotate(node)
else
# Сначала левое вращение, затем правое
node.left = left_rotate(node.left)
return right_rotate(node)
end
# Правостороннее дерево обхода
elsif balance_factor < -1
if balance_factor(node.right) <= 0
# Левое вращение
return left_rotate(node)
else
# Сначала правое вращение, затем левое
node.right = right_rotate(node.right)
return left_rotate(node)
end
end
# Дерево сбалансировано, вращение не требуется, вернуть сразу
node
end
# ## Вставка узла ###
def insert(val)
@root = insert_helper(@root, val)
end
# ## Рекурсивная вставка узла (вспомогательный метод) ###
def insert_helper(node, val)
return TreeNode.new(val) if node.nil?
# 1. Найти позицию вставки и вставить узел
if val < node.val
node.left = insert_helper(node.left, val)
elsif val > node.val
node.right = insert_helper(node.right, val)
else
# Повторяющийся узел не вставлять, сразу вернуть
return node
end
# Обновить высоту узла
update_height(node)
# 2. Выполнить вращение, чтобы снова сбалансировать поддерево
rotate(node)
end
# ## Удаление узла ###
def remove(val)
@root = remove_helper(@root, val)
end
# ## Рекурсивное удаление узла (вспомогательный метод) ###
def remove_helper(node, val)
return if node.nil?
# 1. Найти узел и удалить его
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
# Число дочерних узлов = 0, удалить node и сразу вернуть
return if child.nil?
# Число дочерних узлов = 1, удалить node напрямую
node = child
else
# Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел
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_height(node)
# 2. Выполнить вращение, чтобы снова сбалансировать поддерево
rotate(node)
end
# ## Поиск узла ###
def search(val)
cur = @root
# Искать в цикле и выйти после прохода за листовой узел
while !cur.nil?
# Целевой узел находится в правом поддереве cur
if cur.val < val
cur = cur.right
# Целевой узел находится в левом поддереве cur
elsif cur.val > val
cur = cur.left
# Найти целевой узел и выйти из цикла
else
break
end
end
# Вернуть целевой узел
cur
end
end
### Driver Code ###
if __FILE__ == $0
def test_insert(tree, val)
tree.insert(val)
puts "\nAVL-дерево после вставки узла #{val}:"
print_tree(tree.get_root)
end
def test_remove(tree, val)
tree.remove(val)
puts "\nAVL-дерево после удаления узла #{val}:"
print_tree(tree.get_root)
end
# Инициализация пустого AVL-дерева
avl_tree = AVLTree.new
# Вставка узла
# Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла
for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]
test_insert(avl_tree, val)
end
# Вставка повторяющегося узла
test_insert(avl_tree, 7)
# Удаление узла
# Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла
test_remove(avl_tree, 8) # Удаление узла степени 0
test_remove(avl_tree, 5) # Удаление узла степени 1
test_remove(avl_tree, 4) # Удаление узла степени 2
result_node = avl_tree.search(7)
puts "\nНайденный объект узла = #{result_node}, значение узла = #{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'
# ## Двоичное дерево поиска ###
class BinarySearchTree
# ## Конструктор ###
def initialize
# Инициализировать пустое дерево
@root = nil
end
# ## Получение корневого узла двоичного дерева ###
def get_root
@root
end
# ## Поиск узла ###
def search(num)
cur = @root
# Искать в цикле и выйти после прохода за листовой узел
while !cur.nil?
# Целевой узел находится в правом поддереве cur
if cur.val < num
cur = cur.right
# Целевой узел находится в левом поддереве cur
elsif cur.val > num
cur = cur.left
# Найти целевой узел и выйти из цикла
else
break
end
end
cur
end
# ## Вставка узла ###
def insert(num)
# Если дерево пусто, инициализировать корневой узел
if @root.nil?
@root = TreeNode.new(num)
return
end
# Искать в цикле и выйти после прохода за листовой узел
cur, pre = @root, nil
while !cur.nil?
# Найти повторяющийся узел и сразу вернуть
return if cur.val == num
pre = cur
# Позиция вставки находится в правом поддереве cur
if cur.val < num
cur = cur.right
# Позиция вставки находится в левом поддереве cur
else
cur = cur.left
end
end
# Вставка узла
node = TreeNode.new(num)
if pre.val < num
pre.right = node
else
pre.left = node
end
end
# ## Удаление узла ###
def remove(num)
# Если дерево пусто, сразу вернуть
return if @root.nil?
# Искать в цикле и выйти после прохода за листовой узел
cur, pre = @root, nil
while !cur.nil?
# Найти узел для удаления и выйти из цикла
break if cur.val == num
pre = cur
# Узел для удаления находится в правом поддереве cur
if cur.val < num
cur = cur.right
# Узел для удаления находится в левом поддереве cur
else
cur = cur.left
end
end
# Если узел для удаления отсутствует, сразу вернуть
return if cur.nil?
# Число дочерних узлов = 0 или 1
if cur.left.nil? || cur.right.nil?
# Когда число дочерних узлов = 0 / 1, child = null / этот дочерний узел
child = cur.left || cur.right
# Удалить узел cur
if cur != @root
if pre.left == cur
pre.left = child
else
pre.right = child
end
else
# Если удаляемый узел является корнем, заново назначить корневой узел
@root = child
end
# Число дочерних узлов = 2
else
# Получить следующий узел после cur в симметричном обходе
tmp = cur.right
while !tmp.left.nil?
tmp = tmp.left
end
# Рекурсивно удалить узел tmp
remove(tmp.val)
# Перезаписать cur значением tmp
cur.val = tmp.val
end
end
end
### Driver Code ###
if __FILE__ == $0
# Инициализация двоичного дерева поиска
bst = BinarySearchTree.new
nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]
# Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево
nums.each { |num| bst.insert(num) }
puts "\nИсходное двоичное дерево\n"
print_tree(bst.get_root)
# Поиск узла
node = bst.search(7)
puts "\nНайденный объект узла: #{node}, значение узла = #{node.val}"
# Вставка узла
bst.insert(16)
puts "\nПосле вставки узла 16 двоичное дерево имеет вид\n"
print_tree(bst.get_root)
# Удаление узла
bst.remove(1)
puts "\nПосле удаления узла 1 двоичное дерево имеет вид\n"
print_tree(bst.get_root)
bst.remove(2)
puts "\nПосле удаления узла 2 двоичное дерево имеет вид\n"
print_tree(bst.get_root)
bst.remove(4)
puts "\nПосле удаления узла 4 двоичное дерево имеет вид\n"
print_tree(bst.get_root)
end
+38
View File
@@ -0,0 +1,38 @@
=begin
File: binary_tree.rb
Created Time: 2024-04-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### Driver Code ###
if __FILE__ == $0
# Инициализация двоичного дерева
# Инициализация узлов
n1 = TreeNode.new(1)
n2 = TreeNode.new(2)
n3 = TreeNode.new(3)
n4 = TreeNode.new(4)
n5 = TreeNode.new(5)
# Построить связи между узлами (указатели)
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
puts "\nИнициализация двоичного дерева\n\n"
print_tree(n1)
# Вставка и удаление узлов
_p = TreeNode.new(0)
# Вставить узел _p между n1 -> n2
n1.left = _p
_p.left = n2
puts "\nПосле вставки узла _p\n\n"
print_tree(n1)
# Удаление узла
n1.left = n2
puts "\nПосле удаления узла _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'
# ## Обход в ширину ###
def level_order(root)
# Инициализировать очередь и добавить корневой узел
queue = [root]
# Инициализировать список для хранения последовательности обхода
res = []
while !queue.empty?
node = queue.shift # Извлечение из очереди
res << node.val # Сохранить значение узла
queue << node.left unless node.left.nil? # Поместить левый дочерний узел в очередь
queue << node.right unless node.right.nil? # Поместить правый дочерний узел в очередь
end
res
end
### Driver Code ###
if __FILE__ == $0
# Инициализировать двоичное дерево
# Здесь используется функция, напрямую строящая двоичное дерево из массива
root = arr_to_tree([1, 2, 3, 4, 5, 6, 7])
puts "\nИнициализация двоичного дерева\n\n"
print_tree(root)
# Обход в ширину
res = level_order(root)
puts "\nПоследовательность печати узлов при обходе в ширину = #{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'
# ## Предварительный обход ###
def pre_order(root)
return if root.nil?
# Порядок обхода: корень -> левое поддерево -> правое поддерево
$res << root.val
pre_order(root.left)
pre_order(root.right)
end
# ## Симметричный обход ###
def in_order(root)
return if root.nil?
# Порядок обхода: левое поддерево -> корень -> правое поддерево
in_order(root.left)
$res << root.val
in_order(root.right)
end
# ## Обратный обход ###
def post_order(root)
return if root.nil?
# Порядок обхода: левое поддерево -> правое поддерево -> корень
post_order(root.left)
post_order(root.right)
$res << root.val
end
### Driver Code ###
if __FILE__ == $0
# Инициализировать двоичное дерево
# Здесь используется функция, напрямую строящая двоичное дерево из массива
root = arr_to_tree([1, 2, 3, 4, 5, 6, 7])
puts "\nИнициализация двоичного дерева\n\n"
print_tree(root)
# Предварительный обход
$res = []
pre_order(root)
puts "\nПоследовательность печати узлов при предварительном обходе = #{$res}"
# Симметричный обход
$res.clear
in_order(root)
puts "\nПоследовательность печати узлов при симметричном обходе = #{$res}"
# Обратный обход
$res.clear
post_order(root)
puts "\nПоследовательность печати узлов при обратном обходе = #{$res}"
end
+23
View File
@@ -0,0 +1,23 @@
require 'open3'
start_time = Time.now
ruby_code_dir = File.dirname(__FILE__)
files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb")
errors = []
files.each do |file|
stdout, stderr, status = Open3.capture3("ruby #{file}")
errors << stderr unless status.success?
end
puts "\x1b[34mTested #{files.count} files\x1b[m"
unless errors.empty?
puts "\x1b[33mFound exception in #{errors.length} files\x1b[m"
raise errors.join("\n\n")
else
puts "\x1b[32mPASS\x1b[m"
end
puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms"
+38
View File
@@ -0,0 +1,38 @@
=begin
File: list_node.rb
Created Time: 2024-03-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Класс узла связного списка ###
class ListNode
attr_accessor :val # Значение узла
attr_accessor :next # Ссылка на следующий узел
def initialize(val=0, next_node=nil)
@val = val
@next = next_node
end
end
# ## Десериализация списка в связный список ###
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
# ## Сериализация связного списка в список ###
def linked_list_to_arr(head)
arr = []
while head
arr << head.val
head = head.next
end
end
+80
View File
@@ -0,0 +1,80 @@
=begin
File: print_util.rb
Created Time: 2024-03-18
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative "./tree_node"
# ## Вывести матрицу ###
def print_matrix(mat)
s = []
mat.each { |arr| s << " #{arr.to_s}" }
puts "[\n#{s.join(",\n")}\n]"
end
# ## Вывести связный список ###
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
# ## Вывести двоичное дерево ###
# Этот вывод дерева заимствован из 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
# ## Вывести хеш-таблицу ###
def print_hash_map(hmap)
hmap.entries.each { |key, value| puts "#{key} -> #{value}" }
end
# ## Вывести кучу ###
def print_heap(heap)
puts "Массивное представление кучи:#{heap}"
puts "Древовидное представление кучи:"
root = arr_to_tree(heap)
print_tree(root)
end
+53
View File
@@ -0,0 +1,53 @@
=begin
File: tree_node.rb
Created Time: 2024-03-30
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Класс узла двоичного дерева ###
class TreeNode
attr_accessor :val # Значение узла
attr_accessor :height # Высота узла
attr_accessor :left # Ссылка на левый дочерний узел
attr_accessor :right # Ссылка на правый дочерний узел
def initialize(val=0)
@val = val
@height = 0
end
end
# ## Десериализация списка в двоичное дерево: рекурсия ###
def arr_to_tree_dfs(arr, i)
# Если индекс выходит за длину массива или соответствующий элемент равен nil, вернуть nil
return if i < 0 || i >= arr.length || arr[i].nil?
# Построить текущий узел
root = TreeNode.new(arr[i])
# Рекурсивно построить левое и правое поддеревья
root.left = arr_to_tree_dfs(arr, 2 * i + 1)
root.right = arr_to_tree_dfs(arr, 2 * i + 2)
root
end
# ## Десериализация списка в двоичное дерево ###
def arr_to_tree(arr)
arr_to_tree_dfs(arr, 0)
end
# ## Сериализация двоичного дерева в список: рекурсия ###
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
# ## Сериализация двоичного дерева в список ###
def tree_to_arr(root)
res = []
tree_to_arr_dfs(root, 0, res)
res
end
+24
View File
@@ -0,0 +1,24 @@
=begin
File: vertex.rb
Created Time: 2024-04-25
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Класс вершины ###
class Vertex
attr_accessor :val
def initialize(val)
@val = val
end
end
# ## На вход подается список значений vals, на выходе возвращается список вершин vets ###
def vals_to_vets(vals)
Array.new(vals.length) { |i| Vertex.new(vals[i]) }
end
# ## На вход подается список вершин vets, на выходе возвращается список значений vals ###
def vets_to_vals(vets)
Array.new(vets.length) { |i| vets[i].val }
end