mirror of
https://github.com/krahets/hello-algo.git
synced 2026-08-14 20:50:58 +00:00
Re-translate the Japanese version (#1871)
* Retranslate Japanese docs with GPT-5.4 * Retranslate Japanese code with GPT-5.4
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
=begin
|
||||
File: array.rb
|
||||
Created Time: 2024-03-18
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 要素にランダムアクセス ###
|
||||
def random_access(nums)
|
||||
# 区間 [0, nums.length) からランダムに 1 つの数を選ぶ
|
||||
random_index = Random.rand(0...nums.length)
|
||||
|
||||
# ランダムな要素を取得して返す
|
||||
nums[random_index]
|
||||
end
|
||||
|
||||
|
||||
### 配列長を拡張 ###
|
||||
# Ruby の Array は動的配列であり、直接拡張できます
|
||||
# 学習しやすいよう、本関数では 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
|
||||
|
||||
### 配列のインデックス index に要素 num を挿入 ###
|
||||
def insert(nums, num, index)
|
||||
# インデックス index 以降の全要素を 1 つ後ろへ移動する
|
||||
for i in (nums.length - 1).downto(index + 1)
|
||||
nums[i] = nums[i - 1]
|
||||
end
|
||||
|
||||
# index の要素に num を代入する
|
||||
nums[index] = num
|
||||
end
|
||||
|
||||
|
||||
### インデックス index の要素を削除 ###
|
||||
def remove(nums, index)
|
||||
# インデックス index より後ろの全要素を 1 つ前へ移動する
|
||||
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 "インデックス 3 に数値 6 を挿入し、nums = #{nums}"
|
||||
|
||||
# 要素を削除
|
||||
remove(nums, 2)
|
||||
puts "インデックス 2 の要素を削除し、nums = #{nums}"
|
||||
|
||||
# 配列を走査
|
||||
traverse(nums)
|
||||
|
||||
# 要素を探索する
|
||||
index = find(nums, 3)
|
||||
puts "nums 内で要素 3 を検索し、インデックス = #{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'
|
||||
|
||||
### 連結リストのノード n0 の後にノード _p を挿入 ###
|
||||
# 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 "インデックス 3 に要素 6 を挿入し、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
|
||||
|
||||
# 2 つのリストを連結する
|
||||
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 以降の要素をすべて 1 つ後ろへずらす
|
||||
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 より後の要素をすべて 1 つ前に移動する
|
||||
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 "インデックス 3 に数値 6 を挿入し、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}, 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 ループ(2 回更新)###
|
||||
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 "\nfor ループの合計結果 res = #{res}"
|
||||
|
||||
res = while_loop(n)
|
||||
puts "\nwhile ループの合計結果 res = #{res}"
|
||||
|
||||
res = while_loop_ii(n)
|
||||
puts "\nwhile ループ(2 回更新)の合計結果 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
|
||||
|
||||
# 細胞は各ラウンドで 2 つに分裂し、数列 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
|
||||
# 1個から 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
|
||||
|
||||
### 配列 nums 内の数値 1 のインデックスを探す ###
|
||||
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
|
||||
|
||||
### 円盤を1枚移動 ###
|
||||
def move(src, tar)
|
||||
# src の上から円盤を1枚取り出す
|
||||
pan = src.pop
|
||||
# 円盤を tar の上に置く
|
||||
tar << pan
|
||||
end
|
||||
|
||||
### ハノイの塔 f(i) を解く ###
|
||||
def dfs(i, src, buf, tar)
|
||||
# src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す
|
||||
if i == 1
|
||||
move(src, tar)
|
||||
return
|
||||
end
|
||||
|
||||
# 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す
|
||||
dfs(i - 1, src, tar, buf)
|
||||
# 部分問題 f(1):src に残る 1 枚の円盤を tar に移す
|
||||
move(src, tar)
|
||||
# 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す
|
||||
dfs(i - 1, buf, src, tar)
|
||||
end
|
||||
|
||||
### ハノイの塔を解く ###
|
||||
def solve_hanota(_A, _B, _C)
|
||||
n = _A.length
|
||||
# A の上から n 枚の円盤を B を介して C へ移す
|
||||
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
|
||||
# 2 つの文字が等しければ、その 2 文字をそのままスキップする
|
||||
return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1]
|
||||
# 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 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
|
||||
# 2 つの文字が等しければ、その 2 文字をそのままスキップする
|
||||
return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1]
|
||||
# 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 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]
|
||||
# 2 つの文字が等しければ、その 2 文字をそのままスキップする
|
||||
dp[i][j] = dp[i - 1][j - 1]
|
||||
else
|
||||
# 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 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]
|
||||
# 2 つの文字が等しければ、その 2 文字をそのままスキップする
|
||||
dp[j] = leftup
|
||||
else
|
||||
# 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 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]
|
||||
# 2つの案のうち価値が大きいほうを返す
|
||||
[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]
|
||||
# 2 つの案のうち価値が大きい方を記録して返す
|
||||
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
|
||||
# 隣接行列に 1 行追加
|
||||
new_row = Array.new(n, 0)
|
||||
@adj_mat << new_row
|
||||
# 隣接行列に 1 列追加
|
||||
@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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
# 重さと価値の 2 属性を持つ品物リストを作成する
|
||||
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
|
||||
|
||||
# 2 枚の板が出会うまで貪欲選択を繰り返す
|
||||
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 を 3 の個数、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追加完了後、ハッシュテーブルは\nKey -> Value"
|
||||
hmap.print
|
||||
|
||||
# 検索操作
|
||||
# ハッシュテーブルにキー `key` を入力し、値 `value` を取得する
|
||||
name = hmap.get(15937)
|
||||
puts "\n学籍番号 15937 を入力すると、名前 #{name} が見つかりました"
|
||||
|
||||
# 削除操作
|
||||
# ハッシュテーブルからキーと値の組 (key, value) を削除する
|
||||
hmap.remove(10583)
|
||||
puts "\n10583 を削除した後、ハッシュテーブルは\nKey -> Value"
|
||||
hmap.print
|
||||
|
||||
# ハッシュテーブルを走査
|
||||
puts "\nキーと値のペア Key->Value を走査"
|
||||
for pair in hmap.entry_set
|
||||
puts "#{pair.key} -> #{pair.val}"
|
||||
end
|
||||
|
||||
puts "\nキー Key のみを個別に走査"
|
||||
for key in hmap.key_set
|
||||
puts key
|
||||
end
|
||||
|
||||
puts "\n値 Value のみを個別に走査"
|
||||
for val in hmap.value_set
|
||||
puts val
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
=begin
|
||||
File: built_in_hash.rb
|
||||
Created Time: 2024-04-13
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
require_relative '../utils/list_node'
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
num = 3
|
||||
hash_num = num.hash
|
||||
puts "整数 #{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 アルゴリズム"
|
||||
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
|
||||
@@ -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追加完了後、ハッシュテーブルは\nKey -> Value"
|
||||
print_hash_map(hmap)
|
||||
|
||||
# 検索操作
|
||||
# ハッシュテーブルにキー key を入力し、値 value を取得する
|
||||
name = hmap[15937]
|
||||
puts "\n学籍番号 15937 を入力すると、名前 #{name} が見つかりました"
|
||||
|
||||
# 削除操作
|
||||
# ハッシュテーブルからキーと値の組 (key, value) を削除する
|
||||
hmap.delete(10583)
|
||||
puts "\n10583 を削除した後、ハッシュテーブルは\nKey -> Value"
|
||||
print_hash_map(hmap)
|
||||
|
||||
# ハッシュテーブルを走査
|
||||
puts "\nキーと値のペア Key->Value を走査"
|
||||
hmap.entries.each { |key, value| puts "#{key} -> #{value}" }
|
||||
|
||||
puts "\nキー Key のみを個別に走査"
|
||||
hmap.keys.each { |key| puts key }
|
||||
|
||||
puts "\n値 Value のみを個別に走査"
|
||||
hmap.values.each { |val| puts val }
|
||||
end
|
||||
@@ -0,0 +1,128 @@
|
||||
=begin
|
||||
File: hash_map_chaining.rb
|
||||
Created Time: 2024-04-13
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
require_relative './array_hash_map'
|
||||
|
||||
### キーアドレス法ハッシュテーブル ###
|
||||
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 "\n12836 を削除した後、ハッシュテーブルは\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追加完了後、ハッシュテーブルは\nKey -> Value"
|
||||
hashmap.print
|
||||
|
||||
# 検索操作
|
||||
# ハッシュテーブルにキー key を入力し、値 val を得る
|
||||
name = hashmap.get(13276)
|
||||
puts "\n学籍番号 13276 を入力すると、名前 #{name} が見つかりました"
|
||||
|
||||
# 削除操作
|
||||
# ハッシュテーブルからキーと値の組 (key, val) を削除する
|
||||
hashmap.remove(16750)
|
||||
puts "\n16750 を削除した後、ハッシュテーブルは\nKey -> Value"
|
||||
hashmap.print
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
=begin
|
||||
File: simple_hash.rb
|
||||
Created Time: 2024-04-14
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 加算ハッシュ ###
|
||||
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 アルゴリズム"
|
||||
|
||||
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
|
||||
@@ -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
|
||||
# 葉ノード以外のすべてのノードをヒープ化
|
||||
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)
|
||||
# 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了
|
||||
break if p < 0 || @max_heap[i] <= @max_heap[p]
|
||||
# 2 つのノードを交換
|
||||
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
|
||||
|
||||
# 2 つのノードを交換
|
||||
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
|
||||
@@ -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 はそれぞれ配列の先頭要素と末尾要素+1を指す
|
||||
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)
|
||||
# 2重ループのため、時間計算量は 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
|
||||
|
||||
# ====== Driver Code ======
|
||||
# 方法 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
|
||||
# 結果配列 res で元の配列 nums を上書きする
|
||||
(0...n).each { |i| nums[i] = res[i] }
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]
|
||||
|
||||
counting_sort_naive(nums)
|
||||
puts "カウントソート(オブジェクトをソートできない)完了後 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
|
||||
# 2 つのノードを交換
|
||||
nums[i], nums[ma] = nums[ma], nums[i]
|
||||
# ループで上から下へヒープ化
|
||||
i = ma
|
||||
end
|
||||
end
|
||||
|
||||
### ヒープソート ###
|
||||
def heap_sort(nums)
|
||||
# ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
|
||||
(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] を 1 つ右へ移動する
|
||||
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
|
||||
@@ -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
|
||||
# 基準値を 2 つの部分配列の境界へ交換する
|
||||
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
|
||||
### 3 つの候補要素の中央値を選ぶ ###
|
||||
def median_three(nums, left, mid, right)
|
||||
# 3つの候補要素の中央値を選ぶ
|
||||
_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
|
||||
# 基準値を 2 つの部分配列の境界へ交換する
|
||||
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
|
||||
# 基準値を 2 つの部分配列の境界へ交換する
|
||||
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)
|
||||
# 2 つの部分配列のうち短いほうにクイックソートを適用する
|
||||
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
|
||||
|
||||
### num の第 k 桁を取得する。ここで exp = 10^(k-1) ###
|
||||
def digit(num, exp)
|
||||
# k ではなく exp を渡すことで、ここで高コストな累乗計算を繰り返し実行するのを避けられる
|
||||
(num / exp) % 10
|
||||
end
|
||||
|
||||
# ## 計数ソート(nums の k 桁目でソート)###
|
||||
def counting_sort_digit(nums, exp)
|
||||
# 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
|
||||
counter = Array.new(10, 0)
|
||||
n = nums.length
|
||||
# 0~9 の各数字の出現回数を集計する
|
||||
for i in 0...n
|
||||
d = digit(nums[i], exp) # nums[i] の第 k 位を取得し、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 # d の配列内インデックス j を取得する
|
||||
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
|
||||
|
||||
# 先頭ポインタを左に 1 つ移動する
|
||||
# 剰余演算により、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
|
||||
# 先頭ポインタを 1 つ後ろへ進める
|
||||
@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 # 先頭ポインタ。先頭要素を指す
|
||||
@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
|
||||
# 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
|
||||
@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} 回の追加 + 取り出し後 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)
|
||||
|
||||
# キューが空なら、先頭ノードと末尾ノードの両方をそのノードに向ける
|
||||
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
|
||||
@@ -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
|
||||
# child を支点として node を右回転させる
|
||||
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
|
||||
# child を支点として node を左回転させる
|
||||
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 "\nノード #{val} を挿入した後、AVL 木は"
|
||||
print_tree(tree.get_root)
|
||||
end
|
||||
|
||||
def test_remove(tree, val)
|
||||
tree.remove(val)
|
||||
puts "\nノード #{val} を削除した後、AVL 木は"
|
||||
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 or 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)
|
||||
# tmp で cur を上書きする
|
||||
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
|
||||
@@ -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)
|
||||
# n1 -> n2 の間にノード _p を挿入する
|
||||
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 "\nn中順走査のノード出力順 = #{$res}"
|
||||
|
||||
# 後順走査
|
||||
$res.clear
|
||||
post_order(root)
|
||||
puts "\nn後順走査のノード出力順 = #{$res}"
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
require 'open3'
|
||||
|
||||
start_time = Time.now
|
||||
ruby_code_dir = File.dirname(__FILE__)
|
||||
files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb")
|
||||
|
||||
errors = []
|
||||
|
||||
files.each do |file|
|
||||
stdout, stderr, status = Open3.capture3("ruby #{file}")
|
||||
errors << stderr unless status.success?
|
||||
end
|
||||
|
||||
puts "\x1b[34mTested #{files.count} files\x1b[m"
|
||||
|
||||
unless errors.empty?
|
||||
puts "\x1b[33mFound exception in #{errors.length} files\x1b[m"
|
||||
raise errors.join("\n\n")
|
||||
else
|
||||
puts "\x1b[32mPASS\x1b[m"
|
||||
end
|
||||
|
||||
puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms"
|
||||
@@ -0,0 +1,38 @@
|
||||
=begin
|
||||
File: list_node.rb
|
||||
Created Time: 2024-03-18
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 連結リストノードクラス ###
|
||||
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
|
||||
@@ -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
|
||||
|
||||
### 二分木を出力 ###
|
||||
# This tree printer is borrowed from TECHIE DELIGHT
|
||||
# https://www.techiedelight.com/c-program-print-binary-tree/
|
||||
def print_tree(root, prev=nil, is_right=false)
|
||||
return if root.nil?
|
||||
|
||||
prev_str = " "
|
||||
trunk = Trunk.new(prev, prev_str)
|
||||
print_tree(root.right, trunk, true)
|
||||
|
||||
if prev.nil?
|
||||
trunk.str = "———"
|
||||
elsif is_right
|
||||
trunk.str = "/———"
|
||||
prev_str = " |"
|
||||
else
|
||||
trunk.str = "\\———"
|
||||
prev.str = prev_str
|
||||
end
|
||||
|
||||
show_trunk(trunk)
|
||||
puts " #{root.val}"
|
||||
prev.str = prev_str if prev
|
||||
trunk.str = " |"
|
||||
print_tree(root.left, trunk, false)
|
||||
end
|
||||
|
||||
### ハッシュテーブルを出力 ###
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user