docs: add Japanese translate documents (#1812)

* docs: add Japanese documents (`ja/docs`)

* docs: add Japanese documents (`ja/codes`)

* docs: add Japanese documents

* Remove pythontutor blocks in ja/

* Add an empty at the end of each markdown file.

* Add the missing figures (use the English version temporarily).

* Add index.md for Japanese version.

* Add index.html for Japanese version.

* Add missing index.assets

* Fix backtracking_algorithm.md for Japanese version.

* Add avatar_eltociear.jpg. Fix image links on the Japanese landing page.

* Add the Japanese banner.

---------

Co-authored-by: krahets <krahets@163.com>
This commit is contained in:
Ikko Eltociear Ashimine
2025-10-17 06:04:43 +09:00
committed by GitHub
parent 2487a27036
commit 954c45864b
886 changed files with 33569 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
"""
File: array.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
import random
def random_access(nums: list[int]) -> int:
"""要素へのランダムアクセス"""
# 区間 [0, len(nums)-1] から数値をランダムに選択
random_index = random.randint(0, len(nums) - 1)
# ランダムな要素を取得して返す
random_num = nums[random_index]
return random_num
# PythonのlistはextendできるDynamic Arrayであることに注意
# 学習を容易にするため、この関数ではlistをStatic Arrayとして扱う
def extend(nums: list[int], enlarge: int) -> list[int]:
"""配列の長さを拡張"""
# 拡張された長さの配列を初期化
res = [0] * (len(nums) + enlarge)
# 元の配列のすべての要素を新しい配列にコピー
for i in range(len(nums)):
res[i] = nums[i]
# 拡張後の新しい配列を返す
return res
def insert(nums: list[int], num: int, index: int):
"""インデックス index に要素 num を挿入"""
# インデックス index より後のすべての要素を1つ後ろに移動
for i in range(len(nums) - 1, index, -1):
nums[i] = nums[i - 1]
# num を index の位置の要素に代入
nums[index] = num
def remove(nums: list[int], index: int):
"""インデックス index の要素を削除"""
# インデックス index より後のすべての要素を1つ前に移動
for i in range(index, len(nums) - 1):
nums[i] = nums[i + 1]
def traverse(nums: list[int]):
"""配列の走査"""
count = 0
# インデックスによる配列の走査
for i in range(len(nums)):
count += nums[i]
# 配列要素の走査
for num in nums:
count += num
# データのインデックスと要素の両方を走査
for i, num in enumerate(nums):
count += nums[i]
count += num
def find(nums: list[int], target: int) -> int:
"""配列内の指定された要素を検索"""
for i in range(len(nums)):
if nums[i] == target:
return i
return -1
"""Driver Code"""
if __name__ == "__main__":
# 配列を初期化
arr = [0] * 5
print("配列 arr =", arr)
nums = [1, 3, 2, 5, 4]
print("配列 nums =", nums)
# ランダムアクセス
random_num: int = random_access(nums)
print("nums のランダムな要素を取得", random_num)
# 長さの拡張
nums: list[int] = extend(nums, 3)
print("配列の長さを 8 に拡張、結果は nums =", nums)
# 要素の挿入
insert(nums, 6, 3)
print("インデックス 3 に数値 6 を挿入、結果は nums =", nums)
# 要素の削除
remove(nums, 2)
print("インデックス 2 の要素を削除、結果は nums =", nums)
# 配列の走査
traverse(nums)
# 要素の検索
index: int = find(nums, 3)
print("nums で要素 3 を検索、結果は index =", index)

View File

@@ -0,0 +1,85 @@
"""
File: linked_list.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode, print_linked_list
def insert(n0: ListNode, P: ListNode):
"""連結リストのノード n0 の後にノード P を挿入"""
n1 = n0.next
P.next = n1
n0.next = P
def remove(n0: ListNode):
"""連結リストのノード n0 の後の最初のノードを削除"""
if not n0.next:
return
# n0 -> P -> n1
P = n0.next
n1 = P.next
n0.next = n1
def access(head: ListNode, index: int) -> ListNode | None:
"""連結リストのインデックス index のノードにアクセス"""
for _ in range(index):
if not head:
return None
head = head.next
return head
def find(head: ListNode, target: int) -> int:
"""連結リストで値 target を持つ最初のノードを検索"""
index = 0
while head:
if head.val == target:
return index
head = head.next
index += 1
return -1
"""Driver Code"""
if __name__ == "__main__":
# 連結リストを初期化
# 各ノードを初期化
n0 = ListNode(1)
n1 = ListNode(3)
n2 = ListNode(2)
n3 = ListNode(5)
n4 = ListNode(4)
# ノード間の参照を構築
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
print("初期化された連結リスト")
print_linked_list(n0)
# ノードを挿入
p = ListNode(0)
insert(n0, p)
print("ノード挿入後の連結リスト")
print_linked_list(n0)
# ノードを削除
remove(n0)
print("ノード削除後の連結リスト")
print_linked_list(n0)
# ノードにアクセス
node: ListNode = access(n0, 3)
print("連結リストのインデックス 3 のノードの値 = {}".format(node.val))
# ノードを検索
index: int = find(n0, 2)
print("連結リストで値 2 を持つノードのインデックス = {}".format(index))

View File

@@ -0,0 +1,56 @@
"""
File: list.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
"""Driver Code"""
if __name__ == "__main__":
# リストを初期化
nums: list[int] = [1, 3, 2, 5, 4]
print("\nリスト nums =", nums)
# 要素にアクセス
x: int = nums[1]
print("\nインデックス 1 の要素にアクセス、結果は x =", x)
# 要素を更新
nums[1] = 0
print("\nインデックス 1 の要素を 0 に更新、結果は nums =", nums)
# リストをクリア
nums.clear()
print("\nリストをクリア後、nums =", nums)
# 末尾に要素を追加
nums.append(1)
nums.append(3)
nums.append(2)
nums.append(5)
nums.append(4)
print("\n要素を追加後、nums =", nums)
# 中間に要素を挿入
nums.insert(3, 6)
print("\nインデックス 3 に数値 6 を挿入、結果は nums =", nums)
# 要素を削除
nums.pop(3)
print("\nインデックス 3 の要素を削除、結果は nums =", nums)
# インデックスによるリストの走査
count = 0
for i in range(len(nums)):
count += nums[i]
# リスト要素の走査
for num in nums:
count += num
# 2つのリストを連結
nums1 = [6, 8, 7, 10, 9]
nums += nums1
print("\nリスト nums1 を nums に連結、結果は nums =", nums)
# リストをソート
nums.sort()
print("\nリストをソート後、nums =", nums)

View File

@@ -0,0 +1,118 @@
"""
File: my_list.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
class MyList:
"""リストクラス"""
def __init__(self):
"""コンストラクタ"""
self._capacity: int = 10 # リストの容量
self._arr: list[int] = [0] * self._capacity # 配列(リスト要素を格納)
self._size: int = 0 # リストの長さ(現在の要素数)
self._extend_ratio: int = 2 # 各リスト拡張の倍数
def size(self) -> int:
"""リストの長さ(現在の要素数)を取得"""
return self._size
def capacity(self) -> int:
"""リストの容量を取得"""
return self._capacity
def get(self, index: int) -> int:
"""要素にアクセス"""
# インデックスが範囲外の場合、以下のように例外をスロー
if index < 0 or index >= self._size:
raise IndexError("Index out of bounds")
return self._arr[index]
def set(self, num: int, index: int):
"""要素を更新"""
if index < 0 or index >= self._size:
raise IndexError("Index out of bounds")
self._arr[index] = num
def add(self, num: int):
"""末尾に要素を追加"""
# 要素数が容量を超える場合、拡張メカニズムをトリガー
if self.size() == self.capacity():
self.extend_capacity()
self._arr[self._size] = num
self._size += 1
def insert(self, num: int, index: int):
"""中間に要素を挿入"""
if index < 0 or index >= self._size:
raise IndexError("Index out of bounds")
# 要素数が容量を超える場合、拡張メカニズムをトリガー
if self._size == self.capacity():
self.extend_capacity()
# インデックス index より後のすべての要素を1つ後ろに移動
for j in range(self._size - 1, index - 1, -1):
self._arr[j + 1] = self._arr[j]
self._arr[index] = num
# 要素数を更新
self._size += 1
def remove(self, index: int) -> int:
"""要素を削除"""
if index < 0 or index >= self._size:
raise IndexError("Index out of bounds")
num = self._arr[index]
# インデックス index より後のすべての要素を1つ前に移動
for j in range(index, self._size - 1):
self._arr[j] = self._arr[j + 1]
# 要素数を更新
self._size -= 1
# 削除された要素を返す
return num
def extend_capacity(self):
"""リストを拡張"""
# 元の配列の _extend_ratio 倍の長さの新しい配列を作成し、元の配列を新しい配列にコピー
self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1)
# リストの容量を更新
self._capacity = len(self._arr)
def to_array(self) -> list[int]:
"""有効な長さのリストを返す"""
return self._arr[: self._size]
"""Driver Code"""
if __name__ == "__main__":
# リストを初期化
nums = MyList()
# 末尾に要素を追加
nums.add(1)
nums.add(3)
nums.add(2)
nums.add(5)
nums.add(4)
print(f"リスト nums = {nums.to_array()} ,容量 = {nums.capacity()} ,長さ = {nums.size()}")
# 中間に要素を挿入
nums.insert(6, index=3)
print("インデックス 3 に数値 6 を挿入、結果は nums =", nums.to_array())
# 要素を削除
nums.remove(3)
print("インデックス 3 の要素を削除、結果は nums =", nums.to_array())
# 要素にアクセス
num = nums.get(1)
print("インデックス 1 の要素にアクセス、結果は num =", num)
# 要素を更新
nums.set(0, 1)
print("インデックス 1 の要素を 0 に更新、結果は nums =", nums.to_array())
# 拡張メカニズムのテスト
for i in range(10):
# i = 5 のとき、リストの長さがリストの容量を超え、この時点で拡張メカニズムがトリガーされる
nums.add(i)
print(f"拡張後、リスト {nums.to_array()} ,容量 = {nums.capacity()} ,長さ = {nums.size()}")

View File

@@ -0,0 +1,62 @@
"""
File: n_queens.py
Created Time: 2023-04-26
Author: krahets (krahets@163.com)
"""
def backtrack(
row: int,
n: int,
state: list[list[str]],
res: list[list[list[str]]],
cols: list[bool],
diags1: list[bool],
diags2: list[bool],
):
"""バックトラッキングアルゴリズムn クイーン"""
# すべての行が配置されたら、解を記録
if row == n:
res.append([list(row) for row in state])
return
# すべての列を走査
for col in range(n):
# セルに対応する主対角線と副対角線を計算
diag1 = row - col + n - 1
diag2 = row + col
# 枝刈り:セルの列、主対角線、副対角線にクイーンを配置しない
if not cols[col] and not diags1[diag1] and not 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
def n_queens(n: int) -> list[list[list[str]]]:
"""n クイーンを解く"""
# n*n サイズのチェスボードを初期化、'Q' はクイーンを表し、'#' は空のスポットを表す
state = [["#" for _ in range(n)] for _ in range(n)]
cols = [False] * n # クイーンがある列を記録
diags1 = [False] * (2 * n - 1) # クイーンがある主対角線を記録
diags2 = [False] * (2 * n - 1) # クイーンがある副対角線を記録
res = []
backtrack(0, n, state, res, cols, diags1, diags2)
return res
"""ドライバーコード"""
if __name__ == "__main__":
n = 4
res = n_queens(n)
print(f"チェスボードの寸法入力:{n}")
print(f"クイーン配置解の総数は {len(res)}")
for state in res:
print("--------------------")
for row in state:
print(row)

View File

@@ -0,0 +1,44 @@
"""
File: permutations_i.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
def backtrack(
state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]
):
"""バックトラッキングアルゴリズム:順列 I"""
# 状態の長さが要素数と等しいとき、解を記録
if len(state) == len(choices):
res.append(list(state))
return
# すべての選択肢を走査
for i, choice in enumerate(choices):
# 枝刈り:要素の重複選択を許可しない
if not selected[i]:
# 試行:選択を行い、状態を更新
selected[i] = True
state.append(choice)
# 次の選択ラウンドに進む
backtrack(state, choices, selected, res)
# 撤回:選択を取り消し、前の状態に復元
selected[i] = False
state.pop()
def permutations_i(nums: list[int]) -> list[list[int]]:
"""順列 I"""
res = []
backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)
return res
"""ドライバーコード"""
if __name__ == "__main__":
nums = [1, 2, 3]
res = permutations_i(nums)
print(f"入力配列 nums = {nums}")
print(f"すべての順列 res = {res}")

View File

@@ -0,0 +1,46 @@
"""
File: permutations_ii.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
def backtrack(
state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]
):
"""バックトラッキングアルゴリズム:順列 II"""
# 状態の長さが要素数と等しいとき、解を記録
if len(state) == len(choices):
res.append(list(state))
return
# すべての選択肢を走査
duplicated = set[int]()
for i, choice in enumerate(choices):
# 枝刈り:要素の重複選択を許可せず、等しい要素の重複選択も許可しない
if not selected[i] and choice not in duplicated:
# 試行:選択を行い、状態を更新
duplicated.add(choice) # 選択された要素値を記録
selected[i] = True
state.append(choice)
# 次の選択ラウンドに進む
backtrack(state, choices, selected, res)
# 撤回:選択を取り消し、前の状態に復元
selected[i] = False
state.pop()
def permutations_ii(nums: list[int]) -> list[list[int]]:
"""順列 II"""
res = []
backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)
return res
"""ドライバーコード"""
if __name__ == "__main__":
nums = [1, 2, 2]
res = permutations_ii(nums)
print(f"入力配列 nums = {nums}")
print(f"すべての順列 res = {res}")

View File

@@ -0,0 +1,36 @@
"""
File: preorder_traversal_i_compact.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree, list_to_tree
def pre_order(root: TreeNode):
"""前順走査:例一"""
if root is None:
return
if root.val == 7:
# 解を記録
res.append(root)
pre_order(root.left)
pre_order(root.right)
"""ドライバーコード"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\n二分木を初期化")
print_tree(root)
# 前順走査
res = list[TreeNode]()
pre_order(root)
print("\n値が 7 のすべてのノードを出力")
print([node.val for node in res])

View File

@@ -0,0 +1,42 @@
"""
File: preorder_traversal_ii_compact.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree, list_to_tree
def pre_order(root: TreeNode):
"""前順走査:例二"""
if root is None:
return
# 試行
path.append(root)
if root.val == 7:
# 解を記録
res.append(list(path))
pre_order(root.left)
pre_order(root.right)
# 撤回
path.pop()
"""ドライバーコード"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\n二分木を初期化")
print_tree(root)
# 前順走査
path = list[TreeNode]()
res = list[list[TreeNode]]()
pre_order(root)
print("\nルートからノード 7 へのすべてのパスを出力")
for path in res:
print([node.val for node in path])

View File

@@ -0,0 +1,43 @@
"""
File: preorder_traversal_iii_compact.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree, list_to_tree
def pre_order(root: TreeNode):
"""前順走査:例三"""
# 枝刈り
if root is None or root.val == 3:
return
# 試行
path.append(root)
if root.val == 7:
# 解を記録
res.append(list(path))
pre_order(root.left)
pre_order(root.right)
# 撤回
path.pop()
"""ドライバーコード"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\n二分木を初期化")
print_tree(root)
# 前順走査
path = list[TreeNode]()
res = list[list[TreeNode]]()
pre_order(root)
print("\nルートからノード 7 へのすべてのパスを出力、値が 3 のノードは含まない")
for path in res:
print([node.val for node in path])

View File

@@ -0,0 +1,71 @@
"""
File: preorder_traversal_iii_template.py
Created Time: 2023-04-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree, list_to_tree
def is_solution(state: list[TreeNode]) -> bool:
"""現在の状態が解かどうかを判定"""
return state and state[-1].val == 7
def record_solution(state: list[TreeNode], res: list[list[TreeNode]]):
"""解を記録"""
res.append(list(state))
def is_valid(state: list[TreeNode], choice: TreeNode) -> bool:
"""現在の状態下で選択が合法かどうかを判定"""
return choice is not None and choice.val != 3
def make_choice(state: list[TreeNode], choice: TreeNode):
"""状態を更新"""
state.append(choice)
def undo_choice(state: list[TreeNode], choice: TreeNode):
"""状態を復元"""
state.pop()
def backtrack(
state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]
):
"""バックトラッキングアルゴリズム:例三"""
# 解かどうかをチェック
if is_solution(state):
# 解を記録
record_solution(state, res)
# すべての選択肢を走査
for choice in choices:
# 枝刈り:選択が合法かどうかをチェック
if is_valid(state, choice):
# 試行:選択を行い、状態を更新
make_choice(state, choice)
# 次の選択ラウンドに進む
backtrack(state, [choice.left, choice.right], res)
# 撤回:選択を取り消し、前の状態に復元
undo_choice(state, choice)
"""ドライバーコード"""
if __name__ == "__main__":
root = list_to_tree([1, 7, 3, 4, 5, 6, 7])
print("\n二分木を初期化")
print_tree(root)
# バックトラッキングアルゴリズム
res = []
backtrack(state=[], choices=[root], res=res)
print("\nルートからノード 7 へのすべてのパスを出力、パスに値が 3 のノードを含まないことを要求")
for path in res:
print([node.val for node in path])

View File

@@ -0,0 +1,48 @@
"""
File: subset_sum_i.py
Created Time: 2023-06-17
Author: krahets (krahets@163.com)
"""
def backtrack(
state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]
):
"""バックトラッキングアルゴリズム:部分集合の和 I"""
# 部分集合の和が target と等しいとき、解を記録
if target == 0:
res.append(list(state))
return
# すべての選択肢を走査
# 枝刈り二start から走査を開始して重複する部分集合の生成を避ける
for i in range(start, len(choices)):
# 枝刈り一:部分集合の和が target を超える場合、直ちにループを終了
# これは配列がソートされており、後の要素がより大きいため、部分集合の和は必ず target を超えるため
if target - choices[i] < 0:
break
# 試行選択を行い、target、start を更新
state.append(choices[i])
# 次の選択ラウンドに進む
backtrack(state, target - choices[i], choices, i, res)
# 撤回:選択を取り消し、前の状態に復元
state.pop()
def subset_sum_i(nums: list[int], target: int) -> list[list[int]]:
"""部分集合の和 I を解く"""
state = [] # 状態(部分集合)
nums.sort() # nums をソート
start = 0 # 走査の開始点
res = [] # 結果リスト(部分集合リスト)
backtrack(state, target, nums, start, res)
return res
"""ドライバーコード"""
if __name__ == "__main__":
nums = [3, 4, 5]
target = 9
res = subset_sum_i(nums, target)
print(f"入力配列 nums = {nums}, target = {target}")
print(f"{target} と等しいすべての部分集合 res = {res}")

View File

@@ -0,0 +1,50 @@
"""
File: subset_sum_i_naive.py
Created Time: 2023-06-17
Author: krahets (krahets@163.com)
"""
def backtrack(
state: list[int],
target: int,
total: int,
choices: list[int],
res: list[list[int]],
):
"""バックトラッキングアルゴリズム:部分集合の和 I"""
# 部分集合の和が target と等しいとき、解を記録
if total == target:
res.append(list(state))
return
# すべての選択肢を走査
for i in range(len(choices)):
# 枝刈り:部分集合の和が target を超える場合、その選択をスキップ
if total + choices[i] > target:
continue
# 試行:選択を行い、要素と total を更新
state.append(choices[i])
# 次の選択ラウンドに進む
backtrack(state, target, total + choices[i], choices, res)
# 撤回:選択を取り消し、前の状態に復元
state.pop()
def subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]:
"""部分集合の和 I を解く(重複する部分集合を含む)"""
state = [] # 状態(部分集合)
total = 0 # 部分集合の和
res = [] # 結果リスト(部分集合リスト)
backtrack(state, target, total, nums, res)
return res
"""ドライバーコード"""
if __name__ == "__main__":
nums = [3, 4, 5]
target = 9
res = subset_sum_i_naive(nums, target)
print(f"入力配列 nums = {nums}, target = {target}")
print(f"{target} と等しいすべての部分集合 res = {res}")
print(f"この方法の結果には重複する集合が含まれる")

View File

@@ -0,0 +1,52 @@
"""
File: subset_sum_ii.py
Created Time: 2023-06-17
Author: krahets (krahets@163.com)
"""
def backtrack(
state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]
):
"""バックトラッキングアルゴリズム:部分集合の和 II"""
# 部分集合の和が target と等しいとき、解を記録
if target == 0:
res.append(list(state))
return
# すべての選択肢を走査
# 枝刈り二start から走査を開始して重複する部分集合の生成を避ける
# 枝刈り三start から走査を開始して同じ要素の重複選択を避ける
for i in range(start, len(choices)):
# 枝刈り一:部分集合の和が target を超える場合、直ちにループを終了
# これは配列がソートされており、後の要素がより大きいため、部分集合の和は必ず target を超えるため
if target - choices[i] < 0:
break
# 枝刈り四:要素が左の要素と等しい場合、検索分岐が重複していることを示すため、スキップ
if i > start and choices[i] == choices[i - 1]:
continue
# 試行選択を行い、target、start を更新
state.append(choices[i])
# 次の選択ラウンドに進む
backtrack(state, target - choices[i], choices, i + 1, res)
# 撤回:選択を取り消し、前の状態に復元
state.pop()
def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]:
"""部分集合の和 II を解く"""
state = [] # 状態(部分集合)
nums.sort() # nums をソート
start = 0 # 走査の開始点
res = [] # 結果リスト(部分集合リスト)
backtrack(state, target, nums, start, res)
return res
"""ドライバーコード"""
if __name__ == "__main__":
nums = [4, 4, 5]
target = 9
res = subset_sum_ii(nums, target)
print(f"入力配列 nums = {nums}, target = {target}")
print(f"{target} と等しいすべての部分集合 res = {res}")

View File

@@ -0,0 +1,65 @@
"""
File: iteration.py
Created Time: 2023-08-24
Author: krahets (krahets@163.com)
"""
def for_loop(n: int) -> int:
"""forループ"""
res = 0
# 1, 2, ..., n-1, n の合計をループ
for i in range(1, n + 1):
res += i
return res
def while_loop(n: int) -> int:
"""whileループ"""
res = 0
i = 1 # 条件変数を初期化
# 1, 2, ..., n-1, n の合計をループ
while i <= n:
res += i
i += 1 # 条件変数を更新
return res
def while_loop_ii(n: int) -> int:
"""whileループ2つの更新"""
res = 0
i = 1 # 条件変数を初期化
# 1, 4, 10, ... の合計をループ
while i <= n:
res += i
# 条件変数を更新
i += 1
i *= 2
return res
def nested_for_loop(n: int) -> str:
"""二重forループ"""
res = ""
# i = 1, 2, ..., n-1, n をループ
for i in range(1, n + 1):
# j = 1, 2, ..., n-1, n をループ
for j in range(1, n + 1):
res += f"({i}, {j}), "
return res
"""Driver Code"""
if __name__ == "__main__":
n = 5
res = for_loop(n)
print(f"\nforループの合計結果 res = {res}")
res = while_loop(n)
print(f"\nwhileループの合計結果 res = {res}")
res = while_loop_ii(n)
print(f"\nwhileループ2つの更新の合計結果 res = {res}")
res = nested_for_loop(n)
print(f"\n二重forループの走査結果 {res}")

View File

@@ -0,0 +1,69 @@
"""
File: recursion.py
Created Time: 2023-08-24
Author: krahets (krahets@163.com)
"""
def recur(n: int) -> int:
"""再帰"""
# 終了条件
if n == 1:
return 1
# 再帰:再帰呼び出し
res = recur(n - 1)
# 復帰:結果を返す
return n + res
def for_loop_recur(n: int) -> int:
"""反復で再帰をシミュレート"""
# 明示的なスタックを使用してシステムコールスタックをシミュレート
stack = []
res = 0
# 再帰:再帰呼び出し
for i in range(n, 0, -1):
# 「スタックへのプッシュ」で「再帰」をシミュレート
stack.append(i)
# 復帰:結果を返す
while stack:
# 「スタックからのポップ」で「復帰」をシミュレート
res += stack.pop()
# res = 1+2+3+...+n
return res
def tail_recur(n, res):
"""末尾再帰"""
# 終了条件
if n == 0:
return res
# 末尾再帰呼び出し
return tail_recur(n - 1, res + n)
def fib(n: int) -> int:
"""フィボナッチ数列:再帰"""
# 終了条件 f(1) = 0, f(2) = 1
if n == 1 or n == 2:
return n - 1
# 再帰呼び出し f(n) = f(n-1) + f(n-2)
res = fib(n - 1) + fib(n - 2)
# 結果 f(n) を返す
return res
"""Driver Code"""
if __name__ == "__main__":
n = 5
res = recur(n)
print(f"\n再帰関数の合計結果 res = {res}")
res = for_loop_recur(n)
print(f"\n反復で再帰をシミュレートする合計結果 res = {res}")
res = tail_recur(n, 0)
print(f"\n末尾再帰関数の合計結果 res = {res}")
res = fib(n)
print(f"\nフィボナッチ数列の第 {n} 項は {res} です")

View File

@@ -0,0 +1,90 @@
"""
File: space_complexity.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode, TreeNode, print_tree
def function() -> int:
"""関数"""
# 何らかの操作を実行
return 0
def constant(n: int):
"""定数複雑度"""
# 定数、変数、オブジェクトは O(1) のスペースを占有
a = 0
nums = [0] * 10000
node = ListNode(0)
# ループ内の変数は O(1) のスペースを占有
for _ in range(n):
c = 0
# ループ内の関数は O(1) のスペースを占有
for _ in range(n):
function()
def linear(n: int):
"""線形複雑度"""
# 長さ n のリストは O(n) のスペースを占有
nums = [0] * n
# 長さ n のハッシュマップは O(n) のスペースを占有
hmap = dict[int, str]()
for i in range(n):
hmap[i] = str(i)
def linear_recur(n: int):
"""線形複雑度(再帰実装)"""
print("再帰 n =", n)
if n == 1:
return
linear_recur(n - 1)
def quadratic(n: int):
"""平方複雑度"""
# 二次元リストは O(n^2) のスペースを占有
num_matrix = [[0] * n for _ in range(n)]
def quadratic_recur(n: int) -> int:
"""平方複雑度(再帰実装)"""
if n <= 0:
return 0
nums = [0] * n
print(f"再帰 n = {n} の中で配列の長さ = {len(nums)}")
return quadratic_recur(n - 1)
def build_tree(n: int) -> TreeNode | None:
"""指数複雑度(完全二分木の構築)"""
if n == 0:
return None
root = TreeNode(0)
root.left = build_tree(n - 1)
root.right = build_tree(n - 1)
return root
"""Driver Code"""
if __name__ == "__main__":
n = 5
# 定数複雑度
constant(n)
# 線形複雑度
linear(n)
linear_recur(n)
# 平方複雑度
quadratic(n)
quadratic_recur(n)
# 指数複雑度
root = build_tree(n)
print_tree(root)

View File

@@ -0,0 +1,151 @@
"""
File: time_complexity.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
def constant(n: int) -> int:
"""定数複雑度"""
count = 0
size = 100000
for _ in range(size):
count += 1
return count
def linear(n: int) -> int:
"""線形複雑度"""
count = 0
for _ in range(n):
count += 1
return count
def array_traversal(nums: list[int]) -> int:
"""線形複雑度(配列の走査)"""
count = 0
# ループ回数は配列の長さに比例する
for num in nums:
count += 1
return count
def quadratic(n: int) -> int:
"""二次複雑度"""
count = 0
# ループ回数はデータサイズnの二乗に比例する
for i in range(n):
for j in range(n):
count += 1
return count
def bubble_sort(nums: list[int]) -> int:
"""二次複雑度(バブルソート)"""
count = 0 # カウンタ
# 外側のループ: 未ソート範囲は [0, i]
for i in range(len(nums) - 1, 0, -1):
# 内側のループ: 未ソート範囲 [0, i] の最大要素を右端にスワップ
for j in range(i):
if nums[j] > nums[j + 1]:
# nums[j] と nums[j + 1] をスワップ
tmp: int = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = tmp
count += 3 # 要素のスワップは3つの個別操作を含む
return count
def exponential(n: int) -> int:
"""指数複雑度(ループ実装)"""
count = 0
base = 1
# セルは毎回2つに分裂し、1, 2, 4, 8, ..., 2^(n-1) の数列を形成する
for _ in range(n):
for _ in range(base):
count += 1
base *= 2
# count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
return count
def exp_recur(n: int) -> int:
"""指数複雑度(再帰実装)"""
if n == 1:
return 1
return exp_recur(n - 1) + exp_recur(n - 1) + 1
def logarithmic(n: int) -> int:
"""対数複雑度(ループ実装)"""
count = 0
while n > 1:
n = n / 2
count += 1
return count
def log_recur(n: int) -> int:
"""対数複雑度(再帰実装)"""
if n <= 1:
return 0
return log_recur(n / 2) + 1
def linear_log_recur(n: int) -> int:
"""線形対数複雑度"""
if n <= 1:
return 1
count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2)
for _ in range(n):
count += 1
return count
def factorial_recur(n: int) -> int:
"""階乗複雑度(再帰実装)"""
if n == 0:
return 1
count = 0
# 1つからnに分岐
for _ in range(n):
count += factorial_recur(n - 1)
return count
"""ドライバコード"""
if __name__ == "__main__":
# nを変更して、様々な複雑度での操作回数の変化傾向を体験できる
n = 8
print("入力データサイズ n =", n)
count: int = constant(n)
print("定数複雑度の操作回数 =", count)
count: int = linear(n)
print("線形複雑度の操作回数 =", count)
count: int = array_traversal([0] * n)
print("線形複雑度(配列の走査)の操作回数 =", count)
count: int = quadratic(n)
print("二次複雑度の操作回数 =", count)
nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1]
count: int = bubble_sort(nums)
print("二次複雑度(バブルソート)の操作回数 =", count)
count: int = exponential(n)
print("指数複雑度(ループ実装)の操作回数 =", count)
count: int = exp_recur(n)
print("指数複雑度(再帰実装)の操作回数 =", count)
count: int = logarithmic(n)
print("対数複雑度(ループ実装)の操作回数 =", count)
count: int = log_recur(n)
print("対数複雑度(再帰実装)の操作回数 =", count)
count: int = linear_log_recur(n)
print("線形対数複雑度(再帰実装)の操作回数 =", count)
count: int = factorial_recur(n)
print("階乗複雑度(再帰実装)の操作回数 =", count)

View File

@@ -0,0 +1,36 @@
"""
File: worst_best_time_complexity.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
import random
def random_numbers(n: int) -> list[int]:
"""要素 1, 2, ..., n を含む配列を生成、順序はシャッフル"""
# 配列 nums = 1, 2, 3, ..., n を生成
nums = [i for i in range(1, n + 1)]
# 配列要素をランダムにシャッフル
random.shuffle(nums)
return nums
def find_one(nums: list[int]) -> int:
"""配列 nums で数値 1 のインデックスを検索"""
for i in range(len(nums)):
# 要素 1 が配列の最初にある場合、最良時間計算量 O(1) を達成
# 要素 1 が配列の最後にある場合、最悪時間計算量 O(n) を達成
if nums[i] == 1:
return i
return -1
"""Driver Code"""
if __name__ == "__main__":
for i in range(10):
n = 100
nums: list[int] = random_numbers(n)
index: int = find_one(nums)
print("\nシャッフル後の配列 [ 1, 2, ..., n ] =", nums)
print("数値 1 のインデックス =", index)

View File

@@ -0,0 +1,40 @@
"""
File: binary_search_recur.py
Created Time: 2023-07-17
Author: krahets (krahets@163.com)
"""
def dfs(nums: list[int], target: int, i: int, j: int) -> int:
"""二分探索:問題 f(i, j)"""
# 区間が空の場合、対象要素がないことを示すため、-1 を返す
if i > j:
return -1
# 中点インデックス m を計算
m = (i + j) // 2
if nums[m] < target:
# 再帰部分問題 f(m+1, j)
return dfs(nums, target, m + 1, j)
elif nums[m] > target:
# 再帰部分問題 f(i, m-1)
return dfs(nums, target, i, m - 1)
else:
# 対象要素を発見したため、そのインデックスを返す
return m
def binary_search(nums: list[int], target: int) -> int:
"""二分探索"""
n = len(nums)
# 問題 f(0, n-1) を解く
return dfs(nums, target, 0, n - 1)
"""ドライバーコード"""
if __name__ == "__main__":
target = 6
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
# 二分探索(両端閉区間)
index = binary_search(nums, target)
print("対象要素 6 のインデックス =", index)

View File

@@ -0,0 +1,54 @@
"""
File: build_tree.py
Created Time: 2023-07-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
def dfs(
preorder: list[int],
inorder_map: dict[int, int],
i: int,
l: int,
r: int,
) -> TreeNode | None:
"""二分木の構築:分割統治"""
# 部分木の区間が空のとき終了
if r - l < 0:
return None
# ルートノードを初期化
root = TreeNode(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)
# ルートノードを返す
return root
def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None:
"""二分木を構築"""
# ハッシュテーブルを初期化、中順走査の要素からインデックスへのマッピングを保存
inorder_map = {val: i for i, val in enumerate(inorder)}
root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1)
return root
"""ドライバーコード"""
if __name__ == "__main__":
preorder = [3, 9, 2, 1, 7]
inorder = [9, 3, 1, 2, 7]
print(f"前順走査 = {preorder}")
print(f"中順走査 = {inorder}")
root = build_tree(preorder, inorder)
print("構築された二分木は:")
print_tree(root)

View File

@@ -0,0 +1,53 @@
"""
File: hanota.py
Created Time: 2023-07-16
Author: krahets (krahets@163.com)
"""
def move(src: list[int], tar: list[int]):
"""円盤を移動"""
# src の上から円盤を取り出す
pan = src.pop()
# 円盤を tar の上に置く
tar.append(pan)
def dfs(i: int, src: list[int], buf: list[int], tar: list[int]):
"""ハノイの塔問題 f(i) を解く"""
# src に円盤が 1 つだけ残っている場合、それを tar に移動
if i == 1:
move(src, tar)
return
# 部分問題 f(i-1)tar の助けを借りて src の上の i-1 個の円盤を buf に移動
dfs(i - 1, src, tar, buf)
# 部分問題 f(1):残りの 1 個の円盤を src から tar に移動
move(src, tar)
# 部分問題 f(i-1)src の助けを借りて buf の上の i-1 個の円盤を tar に移動
dfs(i - 1, buf, src, tar)
def solve_hanota(A: list[int], B: list[int], C: list[int]):
"""ハノイの塔問題を解く"""
n = len(A)
# B の助けを借りて A の上の n 個の円盤を C に移動
dfs(n, A, B, C)
"""ドライバーコード"""
if __name__ == "__main__":
# リストの末尾が柱の上部
A = [5, 4, 3, 2, 1]
B = []
C = []
print("初期状態:")
print(f"A = {A}")
print(f"B = {B}")
print(f"C = {C}")
solve_hanota(A, B, C)
print("円盤移動後:")
print(f"A = {A}")
print(f"B = {B}")
print(f"C = {C}")

View File

@@ -0,0 +1,37 @@
"""
File: climbing_stairs_backtrack.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int:
"""バックトラッキング"""
# n 段目に登ったとき、解の数に 1 を加える
if state == n:
res[0] += 1
# すべての選択肢を走査
for choice in choices:
# 枝刈りn 段を超えて登ることを許可しない
if state + choice > n:
continue
# 試行:選択を行い、状態を更新
backtrack(choices, state + choice, n, res)
# 撤回
def climbing_stairs_backtrack(n: int) -> int:
"""階段登り:バックトラッキング"""
choices = [1, 2] # 1 段または 2 段登ることを選択可能
state = 0 # 0 段目から登り始める
res = [0] # res[0] を使用して解の数を記録
backtrack(choices, state, n, res)
return res[0]
"""ドライバーコード"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_backtrack(n)
print(f"{n} 段登り、合計 {res} 通りの解がある")

View File

@@ -0,0 +1,29 @@
"""
File: climbing_stairs_constraint_dp.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def climbing_stairs_constraint_dp(n: int) -> int:
"""制約付き階段登り:動的プログラミング"""
if n == 1 or n == 2:
return 1
# dp テーブルを初期化、部分問題の解を格納するために使用
dp = [[0] * 3 for _ in range(n + 1)]
# 初期状態:最小の部分問題の解を事前設定
dp[1][1], dp[1][2] = 1, 0
dp[2][1], dp[2][2] = 0, 1
# 状態遷移:小さい部分問題から大きい部分問題を段階的に解く
for i in range(3, n + 1):
dp[i][1] = dp[i - 1][2]
dp[i][2] = dp[i - 2][1] + dp[i - 2][2]
return dp[n][1] + dp[n][2]
"""ドライバーコード"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_constraint_dp(n)
print(f"{n} 段登り、合計 {res} 通りの解がある")

View File

@@ -0,0 +1,28 @@
"""
File: climbing_stairs_dfs.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def dfs(i: int) -> int:
"""探索"""
# 既知の dp[1] と dp[2] は、それらを返す
if i == 1 or i == 2:
return i
# dp[i] = dp[i-1] + dp[i-2]
count = dfs(i - 1) + dfs(i - 2)
return count
def climbing_stairs_dfs(n: int) -> int:
"""階段登り:探索"""
return dfs(n)
"""ドライバーコード"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dfs(n)
print(f"{n} 段登り、合計 {res} 通りの解がある")

View File

@@ -0,0 +1,35 @@
"""
File: climbing_stairs_dfs_mem.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def dfs(i: int, mem: list[int]) -> int:
"""記憶化探索"""
# 既知の dp[1] と dp[2] は、それらを返す
if i == 1 or i == 2:
return i
# dp[i] の記録がある場合、それを返す
if mem[i] != -1:
return mem[i]
# dp[i] = dp[i-1] + dp[i-2]
count = dfs(i - 1, mem) + dfs(i - 2, mem)
# dp[i] を記録
mem[i] = count
return count
def climbing_stairs_dfs_mem(n: int) -> int:
"""階段登り:記憶化探索"""
# mem[i] は i 段目に登る解の総数を記録、-1 は記録なしを意味する
mem = [-1] * (n + 1)
return dfs(n, mem)
"""ドライバーコード"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dfs_mem(n)
print(f"{n} 段登り、合計 {res} 通りの解がある")

View File

@@ -0,0 +1,40 @@
"""
File: climbing_stairs_dp.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def climbing_stairs_dp(n: int) -> int:
"""階段登り:動的プログラミング"""
if n == 1 or n == 2:
return n
# dp テーブルを初期化、部分問題の解を格納するため使用
dp = [0] * (n + 1)
# 初期状態:最小の部分問題の解を事前設定
dp[1], dp[2] = 1, 2
# 状態遷移:小さい部分問題から大きい部分問題を段階的に解く
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
def climbing_stairs_dp_comp(n: int) -> int:
"""階段登り:空間最適化動的プログラミング"""
if n == 1 or n == 2:
return n
a, b = 1, 2
for _ in range(3, n + 1):
a, b = b, a + b
return b
"""ドライバーコード"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dp(n)
print(f"{n} 段登り、合計 {res} 通りの解がある")
res = climbing_stairs_dp_comp(n)
print(f"{n} 段登り、合計 {res} 通りの解がある")

View File

@@ -0,0 +1,60 @@
"""
File: coin_change.py
Created Time: 2023-07-10
Author: krahets (krahets@163.com)
"""
def coin_change_dp(coins: list[int], amt: int) -> int:
"""硬貨交換:動的プログラミング"""
n = len(coins)
MAX = amt + 1
# dp テーブルを初期化
dp = [[0] * (amt + 1) for _ in range(n + 1)]
# 状態遷移:最初の行と最初の列
for a in range(1, amt + 1):
dp[0][a] = MAX
# 状態遷移:残りの行と列
for i in range(1, n + 1):
for a in range(1, amt + 1):
if coins[i - 1] > a:
# 目標金額を超える場合、硬貨 i を選択しない
dp[i][a] = dp[i - 1][a]
else:
# 硬貨 i を選択しないのと選択するのとで小さい値
dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)
return dp[n][amt] if dp[n][amt] != MAX else -1
def coin_change_dp_comp(coins: list[int], amt: int) -> int:
"""硬貨交換:空間最適化動的プログラミング"""
n = len(coins)
MAX = amt + 1
# dp テーブルを初期化
dp = [MAX] * (amt + 1)
dp[0] = 0
# 状態遷移
for i in range(1, n + 1):
# 順序で走査
for a in range(1, amt + 1):
if coins[i - 1] > a:
# 目標金額を超える場合、硬貨 i を選択しない
dp[a] = dp[a]
else:
# 硬貨 i を選択しないのと選択するのとで小さい値
dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)
return dp[amt] if dp[amt] != MAX else -1
"""ドライバーコード"""
if __name__ == "__main__":
coins = [1, 2, 5]
amt = 4
# 動的プログラミング
res = coin_change_dp(coins, amt)
print(f"目標金額に到達するのに必要な硬貨の最小数 = {res}")
# 空間最適化動的プログラミング
res = coin_change_dp_comp(coins, amt)
print(f"目標金額に到達するのに必要な硬貨の最小数 = {res}")

View File

@@ -0,0 +1,58 @@
"""
File: coin_change_ii.py
Created Time: 2023-07-10
Author: krahets (krahets@163.com)
"""
def coin_change_ii_dp(coins: list[int], amt: int) -> int:
"""硬貨交換 II動的プログラミング"""
n = len(coins)
# dp テーブルを初期化
dp = [[0] * (amt + 1) for _ in range(n + 1)]
# 最初の列を初期化
for i in range(n + 1):
dp[i][0] = 1
# 状態遷移
for i in range(1, n + 1):
for a in range(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]]
return dp[n][amt]
def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int:
"""硬貨交換 II空間最適化動的プログラミング"""
n = len(coins)
# dp テーブルを初期化
dp = [0] * (amt + 1)
dp[0] = 1
# 状態遷移
for i in range(1, n + 1):
# 順序で走査
for a in range(1, amt + 1):
if coins[i - 1] > a:
# 目標金額を超える場合、硬貨 i を選択しない
dp[a] = dp[a]
else:
# 硬貨 i を選択しないのと選択するのとの両方の選択肢の和
dp[a] = dp[a] + dp[a - coins[i - 1]]
return dp[amt]
"""ドライバーコード"""
if __name__ == "__main__":
coins = [1, 2, 5]
amt = 5
# 動的プログラミング
res = coin_change_ii_dp(coins, amt)
print(f"目標金額を構成する硬貨の組み合わせ数は {res}")
# 空間最適化動的プログラミング
res = coin_change_ii_dp_comp(coins, amt)
print(f"目標金額を構成する硬貨の組み合わせ数は {res}")

View File

@@ -0,0 +1,123 @@
"""
File: edit_distance.py
Created Time: 2023-07-04
Author: krahets (krahets@163.com)
"""
def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int:
"""編集距離:ブルートフォース探索"""
# s と t の両方が空の場合、0 を返す
if i == 0 and j == 0:
return 0
# s が空の場合、t の長さを返す
if i == 0:
return j
# t が空の場合、s の長さを返す
if j == 0:
return i
# 2 つの文字が等しい場合、これら 2 つの文字をスキップ
if s[i - 1] == t[j - 1]:
return edit_distance_dfs(s, t, i - 1, 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)
# 最小編集数を返す
return min(insert, delete, replace) + 1
def edit_distance_dfs_mem(s: str, t: str, mem: list[list[int]], i: int, j: int) -> int:
"""編集距離:記憶化探索"""
# s と t の両方が空の場合、0 を返す
if i == 0 and j == 0:
return 0
# s が空の場合、t の長さを返す
if i == 0:
return j
# t が空の場合、s の長さを返す
if j == 0:
return i
# 記録がある場合、それを返す
if mem[i][j] != -1:
return mem[i][j]
# 2 つの文字が等しい場合、これら 2 つの文字をスキップ
if s[i - 1] == t[j - 1]:
return edit_distance_dfs_mem(s, t, mem, i - 1, 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] = min(insert, delete, replace) + 1
return mem[i][j]
def edit_distance_dp(s: str, t: str) -> int:
"""編集距離:動的プログラミング"""
n, m = len(s), len(t)
dp = [[0] * (m + 1) for _ in range(n + 1)]
# 状態遷移:最初の行と最初の列
for i in range(1, n + 1):
dp[i][0] = i
for j in range(1, m + 1):
dp[0][j] = j
# 状態遷移:残りの行と列
for i in range(1, n + 1):
for j in range(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] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1
return dp[n][m]
def edit_distance_dp_comp(s: str, t: str) -> int:
"""編集距離:空間最適化動的プログラミング"""
n, m = len(s), len(t)
dp = [0] * (m + 1)
# 状態遷移:最初の行
for j in range(1, m + 1):
dp[j] = j
# 状態遷移:残りの行
for i in range(1, n + 1):
# 状態遷移:最初の列
leftup = dp[0] # dp[i-1, j-1] を一時的に保存
dp[0] += 1
# 状態遷移:残りの列
for j in range(1, m + 1):
temp = dp[j]
if s[i - 1] == t[j - 1]:
# 2 つの文字が等しい場合、これら 2 つの文字をスキップ
dp[j] = leftup
else:
# 最小編集数 = 3 つの操作(挿入、削除、置換)からの最小編集数 + 1
dp[j] = min(dp[j - 1], dp[j], leftup) + 1
leftup = temp # 次の dp[i-1, j-1] のために更新
return dp[m]
"""ドライバーコード"""
if __name__ == "__main__":
s = "bag"
t = "pack"
n, m = len(s), len(t)
# ブルートフォース探索
res = edit_distance_dfs(s, t, n, m)
print(f"{s}{t} に変更するために必要な最小編集数は {res}")
# 記憶化探索
mem = [[-1] * (m + 1) for _ in range(n + 1)]
res = edit_distance_dfs_mem(s, t, mem, n, m)
print(f"{s}{t} に変更するために必要な最小編集数は {res}")
# 動的プログラミング
res = edit_distance_dp(s, t)
print(f"{s}{t} に変更するために必要な最小編集数は {res}")
# 空間最適化動的プログラミング
res = edit_distance_dp_comp(s, t)
print(f"{s}{t} に変更するために必要な最小編集数は {res}")

View File

@@ -0,0 +1,101 @@
"""
File: knapsack.py
Created Time: 2023-07-03
Author: krahets (krahets@163.com)
"""
def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int:
"""0-1 ナップサック:ブルートフォース探索"""
# すべてのアイテムが選択されたかナップサックに残り容量がない場合、値 0 を返す
if i == 0 or c == 0:
return 0
# ナップサック容量を超える場合、ナップサックに入れないことしか選択できない
if wgt[i - 1] > c:
return knapsack_dfs(wgt, val, 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 つの選択肢のうち大きい値を返す
return max(no, yes)
def knapsack_dfs_mem(
wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int
) -> int:
"""0-1 ナップサック:記憶化探索"""
# すべてのアイテムが選択されたかナップサックに残り容量がない場合、値 0 を返す
if i == 0 or c == 0:
return 0
# 記録がある場合、それを返す
if mem[i][c] != -1:
return mem[i][c]
# ナップサック容量を超える場合、ナップサックに入れないことしか選択できない
if wgt[i - 1] > c:
return knapsack_dfs_mem(wgt, val, mem, 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] = max(no, yes)
return mem[i][c]
def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:
"""0-1 ナップサック:動的プログラミング"""
n = len(wgt)
# dp テーブルを初期化
dp = [[0] * (cap + 1) for _ in range(n + 1)]
# 状態遷移
for i in range(1, n + 1):
for c in range(1, cap + 1):
if wgt[i - 1] > c:
# ナップサック容量を超える場合、アイテム i を選択しない
dp[i][c] = dp[i - 1][c]
else:
# アイテム i を選択しないのと選択するのとで大きい値
dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])
return dp[n][cap]
def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:
"""0-1 ナップサック:空間最適化動的プログラミング"""
n = len(wgt)
# dp テーブルを初期化
dp = [0] * (cap + 1)
# 状態遷移
for i in range(1, n + 1):
# 逆順で走査
for c in range(cap, 0, -1):
if wgt[i - 1] > c:
# ナップサック容量を超える場合、アイテム i を選択しない
dp[c] = dp[c]
else:
# アイテム i を選択しないのと選択するのとで大きい値
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])
return dp[cap]
"""ドライバーコード"""
if __name__ == "__main__":
wgt = [10, 20, 30, 40, 50]
val = [50, 120, 150, 210, 240]
cap = 50
n = len(wgt)
# ブルートフォース探索
res = knapsack_dfs(wgt, val, n, cap)
print(f"ナップサック容量を超えないアイテムの最大値は {res}")
# 記憶化探索
mem = [[-1] * (cap + 1) for _ in range(n + 1)]
res = knapsack_dfs_mem(wgt, val, mem, n, cap)
print(f"ナップサック容量を超えないアイテムの最大値は {res}")
# 動的プログラミング
res = knapsack_dp(wgt, val, cap)
print(f"ナップサック容量を超えないアイテムの最大値は {res}")
# 空間最適化動的プログラミング
res = knapsack_dp_comp(wgt, val, cap)
print(f"ナップサック容量を超えないアイテムの最大値は {res}")

View File

@@ -0,0 +1,43 @@
"""
File: min_cost_climbing_stairs_dp.py
Created Time: 2023-06-30
Author: krahets (krahets@163.com)
"""
def min_cost_climbing_stairs_dp(cost: list[int]) -> int:
"""最小コスト階段登り:動的プログラミング"""
n = len(cost) - 1
if n == 1 or n == 2:
return cost[n]
# dp テーブルを初期化、部分問題の解を格納するために使用
dp = [0] * (n + 1)
# 初期状態:最小の部分問題の解を事前設定
dp[1], dp[2] = cost[1], cost[2]
# 状態遷移:小さい部分問題から大きい部分問題を段階的に解く
for i in range(3, n + 1):
dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]
return dp[n]
def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int:
"""最小コスト階段登り:空間最適化動的プログラミング"""
n = len(cost) - 1
if n == 1 or n == 2:
return cost[n]
a, b = cost[1], cost[2]
for i in range(3, n + 1):
a, b = b, min(a, b) + cost[i]
return b
"""ドライバーコード"""
if __name__ == "__main__":
cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]
print(f"階段コストリストの入力:{cost}")
res = min_cost_climbing_stairs_dp(cost)
print(f"階段を登る最小コスト {res}")
res = min_cost_climbing_stairs_dp_comp(cost)
print(f"階段を登る最小コスト {res}")

View File

@@ -0,0 +1,104 @@
"""
File: min_path_sum.py
Created Time: 2023-07-04
Author: krahets (krahets@163.com)
"""
from math import inf
def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int:
"""最小パス和:ブルートフォース探索"""
# 左上のセルの場合、探索を終了
if i == 0 and j == 0:
return grid[0][0]
# 行または列のインデックスが範囲外の場合、+∞ コストを返す
if i < 0 or j < 0:
return inf
# 左上から (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) への最小パスコストを返す
return min(left, up) + grid[i][j]
def min_path_sum_dfs_mem(
grid: list[list[int]], mem: list[list[int]], i: int, j: int
) -> int:
"""最小パス和:記憶化探索"""
# 左上のセルの場合、探索を終了
if i == 0 and j == 0:
return grid[0][0]
# 行または列のインデックスが範囲外の場合、+∞ コストを返す
if i < 0 or j < 0:
return inf
# 記録がある場合、それを返す
if mem[i][j] != -1:
return mem[i][j]
# 左と上のセルからの最小パスコスト
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] = min(left, up) + grid[i][j]
return mem[i][j]
def min_path_sum_dp(grid: list[list[int]]) -> int:
"""最小パス和:動的プログラミング"""
n, m = len(grid), len(grid[0])
# dp テーブルを初期化
dp = [[0] * m for _ in range(n)]
dp[0][0] = grid[0][0]
# 状態遷移:最初の行
for j in range(1, m):
dp[0][j] = dp[0][j - 1] + grid[0][j]
# 状態遷移:最初の列
for i in range(1, n):
dp[i][0] = dp[i - 1][0] + grid[i][0]
# 状態遷移:残りの行と列
for i in range(1, n):
for j in range(1, m):
dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]
return dp[n - 1][m - 1]
def min_path_sum_dp_comp(grid: list[list[int]]) -> int:
"""最小パス和:空間最適化動的プログラミング"""
n, m = len(grid), len(grid[0])
# dp テーブルを初期化
dp = [0] * m
# 状態遷移:最初の行
dp[0] = grid[0][0]
for j in range(1, m):
dp[j] = dp[j - 1] + grid[0][j]
# 状態遷移:残りの行
for i in range(1, n):
# 状態遷移:最初の列
dp[0] = dp[0] + grid[i][0]
# 状態遷移:残りの列
for j in range(1, m):
dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]
return dp[m - 1]
"""ドライバーコード"""
if __name__ == "__main__":
grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]]
n, m = len(grid), len(grid[0])
# ブルートフォース探索
res = min_path_sum_dfs(grid, n - 1, m - 1)
print(f"左上から右下角への最小パス和は {res}")
# 記憶化探索
mem = [[-1] * m for _ in range(n)]
res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1)
print(f"左上から右下角への最小パス和は {res}")
# 動的プログラミング
res = min_path_sum_dp(grid)
print(f"左上から右下角への最小パス和は {res}")
# 空間最適化動的プログラミング
res = min_path_sum_dp_comp(grid)
print(f"左上から右下角への最小パス和は {res}")

View File

@@ -0,0 +1,55 @@
"""
File: unbounded_knapsack.py
Created Time: 2023-07-10
Author: krahets (krahets@163.com)
"""
def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:
"""完全ナップサック:動的プログラミング"""
n = len(wgt)
# dp テーブルを初期化
dp = [[0] * (cap + 1) for _ in range(n + 1)]
# 状態遷移
for i in range(1, n + 1):
for c in range(1, cap + 1):
if wgt[i - 1] > c:
# ナップサック容量を超える場合、アイテム i を選択しない
dp[i][c] = dp[i - 1][c]
else:
# アイテム i を選択しないのと選択するのとで大きい値
dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])
return dp[n][cap]
def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:
"""完全ナップサック:空間最適化動的プログラミング"""
n = len(wgt)
# dp テーブルを初期化
dp = [0] * (cap + 1)
# 状態遷移
for i in range(1, n + 1):
# 順序で走査
for c in range(1, cap + 1):
if wgt[i - 1] > c:
# ナップサック容量を超える場合、アイテム i を選択しない
dp[c] = dp[c]
else:
# アイテム i を選択しないのと選択するのとで大きい値
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])
return dp[cap]
"""ドライバーコード"""
if __name__ == "__main__":
wgt = [1, 2, 3]
val = [5, 11, 15]
cap = 4
# 動的プログラミング
res = unbounded_knapsack_dp(wgt, val, cap)
print(f"ナップサック容量を超えないアイテムの最大値は {res}")
# 空間最適化動的プログラミング
res = unbounded_knapsack_dp_comp(wgt, val, cap)
print(f"ナップサック容量を超えないアイテムの最大値は {res}")

View File

@@ -0,0 +1,111 @@
"""
File: graph_adjacency_list.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import Vertex, vals_to_vets
class GraphAdjList:
"""隣接リストに基づく無向グラフクラス"""
def __init__(self, edges: list[list[Vertex]]):
"""コンストラクタ"""
# 隣接リスト、キー: 頂点、値: その頂点の隣接する全頂点
self.adj_list = dict[Vertex, list[Vertex]]()
# すべての頂点と辺を追加
for edge in edges:
self.add_vertex(edge[0])
self.add_vertex(edge[1])
self.add_edge(edge[0], edge[1])
def size(self) -> int:
"""頂点数を取得"""
return len(self.adj_list)
def add_edge(self, vet1: Vertex, vet2: Vertex):
"""辺を追加"""
if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:
raise ValueError()
# 辺 vet1 - vet2 を追加
self.adj_list[vet1].append(vet2)
self.adj_list[vet2].append(vet1)
def remove_edge(self, vet1: Vertex, vet2: Vertex):
"""辺を削除"""
if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:
raise ValueError()
# 辺 vet1 - vet2 を削除
self.adj_list[vet1].remove(vet2)
self.adj_list[vet2].remove(vet1)
def add_vertex(self, vet: Vertex):
"""頂点を追加"""
if vet in self.adj_list:
return
# 隣接リストに新しい連結リストを追加
self.adj_list[vet] = []
def remove_vertex(self, vet: Vertex):
"""頂点を削除"""
if vet not in self.adj_list:
raise ValueError()
# 隣接リストから頂点vetに対応する連結リストを削除
self.adj_list.pop(vet)
# 他の頂点の連結リストを走査し、vetを含むすべての辺を削除
for vertex in self.adj_list:
if vet in self.adj_list[vertex]:
self.adj_list[vertex].remove(vet)
def print(self):
"""隣接リストを出力"""
print("隣接リスト =")
for vertex in self.adj_list:
tmp = [v.val for v in self.adj_list[vertex]]
print(f"{vertex.val}: {tmp},")
"""ドライバコード"""
if __name__ == "__main__":
# 無向グラフを初期化
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(edges)
print("\n初期化後、グラフは")
graph.print()
# 辺を追加
# 頂点1、2 つまり v[0], v[2]
graph.add_edge(v[0], v[2])
print("\n辺1-2を追加後、グラフは")
graph.print()
# 辺を削除
# 頂点1、3 つまり v[0], v[1]
graph.remove_edge(v[0], v[1])
print("\n辺1-3を削除後、グラフは")
graph.print()
# 頂点を追加
v5 = Vertex(6)
graph.add_vertex(v5)
print("\n頂点6を追加後、グラフは")
graph.print()
# 頂点を削除
# 頂点3 つまり v[1]
graph.remove_vertex(v[1])
print("\n頂点3を削除後、グラフは")
graph.print()

View File

@@ -0,0 +1,116 @@
"""
File: graph_adjacency_matrix.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import Vertex, print_matrix
class GraphAdjMat:
"""隣接行列に基づく無向グラフクラス"""
def __init__(self, vertices: list[int], edges: list[list[int]]):
"""コンストラクタ"""
# 頂点リスト、要素は「頂点値」を表し、インデックスは「頂点インデックス」を表す
self.vertices: list[int] = []
# 隣接行列、行と列のインデックスは「頂点インデックス」に対応
self.adj_mat: list[list[int]] = []
# 頂点を追加
for val in vertices:
self.add_vertex(val)
# 辺を追加
# edges要素は頂点インデックスを表す
for e in edges:
self.add_edge(e[0], e[1])
def size(self) -> int:
"""頂点数を取得"""
return len(self.vertices)
def add_vertex(self, val: int):
"""頂点を追加"""
n = self.size()
# 頂点リストに新しい頂点値を追加
self.vertices.append(val)
# 隣接行列に行を追加
new_row = [0] * n
self.adj_mat.append(new_row)
# 隣接行列に列を追加
for row in self.adj_mat:
row.append(0)
def remove_vertex(self, index: int):
"""頂点を削除"""
if index >= self.size():
raise IndexError()
# 頂点リストから`index`の頂点を削除
self.vertices.pop(index)
# 隣接行列から`index`の行を削除
self.adj_mat.pop(index)
# 隣接行列から`index`の列を削除
for row in self.adj_mat:
row.pop(index)
def add_edge(self, i: int, j: int):
"""辺を追加"""
# パラメータi、jは頂点要素のインデックスに対応
# インデックスの範囲外と等価性を処理
if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:
raise IndexError()
# 無向グラフでは、隣接行列は主対角線について対称、すなわち (i, j) == (j, i) を満たす
self.adj_mat[i][j] = 1
self.adj_mat[j][i] = 1
def remove_edge(self, i: int, j: int):
"""辺を削除"""
# パラメータi、jは頂点要素のインデックスに対応
# インデックスの範囲外と等価性を処理
if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:
raise IndexError()
self.adj_mat[i][j] = 0
self.adj_mat[j][i] = 0
def print(self):
"""隣接行列を出力"""
print("頂点リスト =", self.vertices)
print("隣接行列 =")
print_matrix(self.adj_mat)
"""ドライバコード"""
if __name__ == "__main__":
# 無向グラフを初期化
# edges要素は頂点インデックスを表す
vertices = [1, 3, 2, 5, 4]
edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]
graph = GraphAdjMat(vertices, edges)
print("\n初期化後、グラフは")
graph.print()
# 辺を追加
# 頂点1、2のインデックスはそれぞれ0、2
graph.add_edge(0, 2)
print("\n辺1-2を追加後、グラフは")
graph.print()
# 辺を削除
# 頂点1、3のインデックスはそれぞれ0、1
graph.remove_edge(0, 1)
print("\n辺1-3を削除後、グラフは")
graph.print()
# 頂点を追加
graph.add_vertex(6)
print("\n頂点6を追加後、グラフは")
graph.print()
# 頂点を削除
# 頂点3のインデックスは1
graph.remove_vertex(1)
print("\n頂点3を削除後、グラフは")
graph.print()

View File

@@ -0,0 +1,64 @@
"""
File: graph_bfs.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import Vertex, vals_to_vets, vets_to_vals
from collections import deque
from graph_adjacency_list import GraphAdjList
def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:
"""幅優先走査"""
# 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得
# 頂点走査シーケンス
res = []
# ハッシュセット、訪問済み頂点を記録するために使用
visited = set[Vertex]([start_vet])
# BFSを実装するために使用されるキュー
que = deque[Vertex]([start_vet])
# 頂点vetから開始し、すべての頂点が訪問されるまでループ
while len(que) > 0:
vet = que.popleft() # キューの先頭の頂点をデキュー
res.append(vet) # 訪問済み頂点を記録
# その頂点のすべての隣接頂点を走査
for adj_vet in graph.adj_list[vet]:
if adj_vet in visited:
continue # 既に訪問済みの頂点をスキップ
que.append(adj_vet) # 未訪問の頂点のみをエンキュー
visited.add(adj_vet) # 頂点を訪問済みとしてマーク
# 頂点走査シーケンスを返す
return res
"""ドライバコード"""
if __name__ == "__main__":
# 無向グラフを初期化
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(edges)
print("\n初期化後、グラフは")
graph.print()
# 幅優先走査
res = graph_bfs(graph, v[0])
print("\n幅優先走査BFSの頂点シーケンスは")
print(vets_to_vals(res))

View File

@@ -0,0 +1,57 @@
"""
File: graph_dfs.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import Vertex, vets_to_vals, vals_to_vets
from graph_adjacency_list import GraphAdjList
def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex):
"""深さ優先走査のヘルパー関数"""
res.append(vet) # 訪問済み頂点を記録
visited.add(vet) # 頂点を訪問済みとしてマーク
# その頂点のすべての隣接頂点を走査
for adjVet in graph.adj_list[vet]:
if adjVet in visited:
continue # 既に訪問済みの頂点をスキップ
# 隣接頂点を再帰的に訪問
dfs(graph, visited, res, adjVet)
def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:
"""深さ優先走査"""
# 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得
# 頂点走査シーケンス
res = []
# ハッシュセット、訪問済み頂点を記録するために使用
visited = set[Vertex]()
dfs(graph, visited, res, start_vet)
return res
"""ドライバコード"""
if __name__ == "__main__":
# 無向グラフを初期化
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(edges)
print("\n初期化後、グラフは")
graph.print()
# 深さ優先走査
res = graph_dfs(graph, v[0])
print("\n深さ優先走査DFSの頂点シーケンスは")
print(vets_to_vals(res))

View File

@@ -0,0 +1,48 @@
"""
File: coin_change_greedy.py
Created Time: 2023-07-18
Author: krahets (krahets@163.com)
"""
def coin_change_greedy(coins: list[int], amt: int) -> int:
"""硬貨交換:貪欲法"""
# coins リストがソートされていると仮定
i = len(coins) - 1
count = 0
# 残り金額がなくなるまで貪欲選択をループ
while amt > 0:
# 残り金額に最も近く、それより小さい硬貨を見つける
while i > 0 and coins[i] > amt:
i -= 1
# coins[i] を選択
amt -= coins[i]
count += 1
# 実行可能な解が見つからない場合、-1 を返す
return count if amt == 0 else -1
"""ドライバーコード"""
if __name__ == "__main__":
# 貪欲法:大域最適解の発見を保証できる
coins = [1, 5, 10, 20, 50, 100]
amt = 186
res = coin_change_greedy(coins, amt)
print(f"\ncoins = {coins}, amt = {amt}")
print(f"{amt} を構成するのに必要な硬貨の最小数は {res}")
# 貪欲法:大域最適解の発見を保証できない
coins = [1, 20, 50]
amt = 60
res = coin_change_greedy(coins, amt)
print(f"\ncoins = {coins}, amt = {amt}")
print(f"{amt} を構成するのに必要な硬貨の最小数は {res}")
print(f"実際には必要な最小数は 3、つまり 20 + 20 + 20")
# 貪欲法:大域最適解の発見を保証できない
coins = [1, 49, 50]
amt = 98
res = coin_change_greedy(coins, amt)
print(f"\ncoins = {coins}, amt = {amt}")
print(f"{amt} を構成するのに必要な硬貨の最小数は {res}")
print(f"実際には必要な最小数は 2、つまり 49 + 49")

View File

@@ -0,0 +1,46 @@
"""
File: fractional_knapsack.py
Created Time: 2023-07-19
Author: krahets (krahets@163.com)
"""
class Item:
"""アイテム"""
def __init__(self, w: int, v: int):
self.w = w # アイテムの重量
self.v = v # アイテムの価値
def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int:
"""分数ナップサック:貪欲法"""
# アイテムリストを作成、2 つの属性を含む:重量、価値
items = [Item(w, v) for w, v in zip(wgt, val)]
# 単位価値 item.v / item.w で高い順にソート
items.sort(key=lambda item: item.v / item.w, reverse=True)
# 貪欲選択をループ
res = 0
for item in items:
if item.w <= cap:
# 残り容量が十分な場合、アイテム全体をナップサックに入れる
res += item.v
cap -= item.w
else:
# 残り容量が不十分な場合、アイテムの一部をナップサックに入れる
res += (item.v / item.w) * cap
# 残り容量がなくなったため、ループを中断
break
return res
"""ドライバーコード"""
if __name__ == "__main__":
wgt = [10, 20, 30, 40, 50]
val = [50, 120, 150, 210, 240]
cap = 50
n = len(wgt)
# 貪欲アルゴリズム
res = fractional_knapsack(wgt, val, cap)
print(f"ナップサック容量を超えないアイテムの最大値は {res}")

View File

@@ -0,0 +1,33 @@
"""
File: max_capacity.py
Created Time: 2023-07-21
Author: krahets (krahets@163.com)
"""
def max_capacity(ht: list[int]) -> int:
"""最大容量:貪欲法"""
# i、j を初期化、配列の両端で分割させる
i, j = 0, len(ht) - 1
# 初期最大容量は 0
res = 0
# 2 つの板が出会うまで貪欲選択をループ
while i < j:
# 最大容量を更新
cap = min(ht[i], ht[j]) * (j - i)
res = max(res, cap)
# 短い板を内側に移動
if ht[i] < ht[j]:
i += 1
else:
j -= 1
return res
"""ドライバーコード"""
if __name__ == "__main__":
ht = [3, 8, 5, 2, 7, 7, 3, 4]
# 貪欲アルゴリズム
res = max_capacity(ht)
print(f"最大容量は {res}")

View File

@@ -0,0 +1,33 @@
"""
File: max_product_cutting.py
Created Time: 2023-07-21
Author: krahets (krahets@163.com)
"""
import math
def max_product_cutting(n: int) -> int:
"""切断の最大積:貪欲法"""
# n <= 3 の場合、1 を切り出す必要がある
if n <= 3:
return 1 * (n - 1)
# 貪欲的に 3 を切り出す、a は 3 の個数、b は余り
a, b = n // 3, n % 3
if b == 1:
# 余りが 1 の場合、1 * 3 のペアを 2 * 2 に変換
return int(math.pow(3, a - 1)) * 2 * 2
if b == 2:
# 余りが 2 の場合、何もしない
return int(math.pow(3, a)) * 2
# 余りが 0 の場合、何もしない
return int(math.pow(3, a))
"""ドライバーコード"""
if __name__ == "__main__":
n = 58
# 貪欲アルゴリズム
res = max_product_cutting(n)
print(f"切断の最大積は {res}")

View File

@@ -0,0 +1,117 @@
"""
File: array_hash_map.py
Created Time: 2022-12-14
Author: msk397 (machangxinq@gmail.com)
"""
class Pair:
"""キー値ペア"""
def __init__(self, key: int, val: str):
self.key = key
self.val = val
class ArrayHashMap:
"""配列実装に基づくハッシュテーブル"""
def __init__(self):
"""コンストラクタ"""
# 100個のバケットを含む配列を初期化
self.buckets: list[Pair | None] = [None] * 100
def hash_func(self, key: int) -> int:
"""ハッシュ関数"""
index = key % 100
return index
def get(self, key: int) -> str:
"""照会操作"""
index: int = self.hash_func(key)
pair: Pair = self.buckets[index]
if pair is None:
return None
return pair.val
def put(self, key: int, val: str):
"""追加操作"""
pair = Pair(key, val)
index: int = self.hash_func(key)
self.buckets[index] = pair
def remove(self, key: int):
"""削除操作"""
index: int = self.hash_func(key)
# None に設定し、削除を表現
self.buckets[index] = None
def entry_set(self) -> list[Pair]:
"""すべてのキー値ペアを取得"""
result: list[Pair] = []
for pair in self.buckets:
if pair is not None:
result.append(pair)
return result
def key_set(self) -> list[int]:
"""すべてのキーを取得"""
result = []
for pair in self.buckets:
if pair is not None:
result.append(pair.key)
return result
def value_set(self) -> list[str]:
"""すべての値を取得"""
result = []
for pair in self.buckets:
if pair is not None:
result.append(pair.val)
return result
def print(self):
"""ハッシュテーブルを出力"""
for pair in self.buckets:
if pair is not None:
print(pair.key, "->", pair.val)
"""Driver Code"""
if __name__ == "__main__":
# ハッシュテーブルを初期化
hmap = ArrayHashMap()
# 追加操作
# キー値ペア (key, value) をハッシュテーブルに追加
hmap.put(12836, "Ha")
hmap.put(15937, "Luo")
hmap.put(16750, "Suan")
hmap.put(13276, "Fa")
hmap.put(10583, "Ya")
print("\n追加後、ハッシュテーブルは\nKey -> Value")
hmap.print()
# 照会操作
# ハッシュテーブルにキーを入力し、値を取得
name = hmap.get(15937)
print("\n学生ID 15937 を入力、名前 " + name + " が見つかりました")
# 削除操作
# ハッシュテーブルからキー値ペア (key, value) を削除
hmap.remove(10583)
print("\n10583 を削除後、ハッシュテーブルは\nKey -> Value")
hmap.print()
# ハッシュテーブルを走査
print("\nキー値ペアを走査 Key->Value")
for pair in hmap.entry_set():
print(pair.key, "->", pair.val)
print("\nキーを個別に走査 Key")
for key in hmap.key_set():
print(key)
print("\n値を個別に走査 Value")
for val in hmap.value_set():
print(val)

View File

@@ -0,0 +1,37 @@
"""
File: built_in_hash.py
Created Time: 2023-06-15
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode
"""Driver Code"""
if __name__ == "__main__":
num = 3
hash_num = hash(num)
print(f"整数 {num} のハッシュ値は {hash_num}")
bol = True
hash_bol = hash(bol)
print(f"ブール値 {bol} のハッシュ値は {hash_bol}")
dec = 3.14159
hash_dec = hash(dec)
print(f"小数 {dec} のハッシュ値は {hash_dec}")
str = "Hello algorithm"
hash_str = hash(str)
print(f"文字列 {str} のハッシュ値は {hash_str}")
tup = (12836, "Ha")
hash_tup = hash(tup)
print(f"タプル {tup} のハッシュ値は {hash(hash_tup)}")
obj = ListNode(0)
hash_obj = hash(obj)
print(f"ノードオブジェクト {obj} のハッシュ値は {hash_obj}")

View File

@@ -0,0 +1,50 @@
"""
File: hash_map.py
Created Time: 2022-12-14
Author: msk397 (machangxinq@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import print_dict
"""Driver Code"""
if __name__ == "__main__":
# ハッシュテーブルを初期化
hmap = dict[int, str]()
# 追加操作
# キー値ペア (key, value) をハッシュテーブルに追加
hmap[12836] = "Ha"
hmap[15937] = "Luo"
hmap[16750] = "Suan"
hmap[13276] = "Fa"
hmap[10583] = "Ya"
print("\n追加後、ハッシュテーブルは\nKey -> Value")
print_dict(hmap)
# 照会操作
# ハッシュテーブルにキーを入力し、値を取得
name: str = hmap[15937]
print("\n学生ID 15937 を入力、名前 " + name + " が見つかりました")
# 削除操作
# ハッシュテーブルからキー値ペア (key, value) を削除
hmap.pop(10583)
print("\n10583 を削除後、ハッシュテーブルは\nKey -> Value")
print_dict(hmap)
# ハッシュテーブルを走査
print("\nキー値ペアを走査 Key->Value")
for key, value in hmap.items():
print(key, "->", value)
print("\nキーを個別に走査 Key")
for key in hmap.keys():
print(key)
print("\n値を個別に走査 Value")
for val in hmap.values():
print(val)

View File

@@ -0,0 +1,118 @@
"""
File: hash_map_chaining.py
Created Time: 2023-06-13
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from chapter_hashing.array_hash_map import Pair
class HashMapChaining:
"""チェーンアドレス法ハッシュテーブル"""
def __init__(self):
"""コンストラクタ"""
self.size = 0 # キー値ペアの数
self.capacity = 4 # ハッシュテーブルの容量
self.load_thres = 2.0 / 3.0 # 拡張をトリガーする負荷率の閾値
self.extend_ratio = 2 # 拡張の倍数
self.buckets = [[] for _ in range(self.capacity)] # バケット配列
def hash_func(self, key: int) -> int:
"""ハッシュ関数"""
return key % self.capacity
def load_factor(self) -> float:
"""負荷率"""
return self.size / self.capacity
def get(self, key: int) -> str | None:
"""照会操作"""
index = self.hash_func(key)
bucket = self.buckets[index]
# バケットを走査し、キーが見つかれば対応する val を返す
for pair in bucket:
if pair.key == key:
return pair.val
# キーが見つからない場合、None を返す
return None
def put(self, key: int, val: str):
"""追加操作"""
# 負荷率が閾値を超えた場合、拡張を実行
if self.load_factor() > self.load_thres:
self.extend()
index = self.hash_func(key)
bucket = self.buckets[index]
# バケットを走査し、指定されたキーに遭遇した場合、対応する val を更新して返す
for pair in bucket:
if pair.key == key:
pair.val = val
return
# キーが見つからない場合、キー値ペアを末尾に追加
pair = Pair(key, val)
bucket.append(pair)
self.size += 1
def remove(self, key: int):
"""削除操作"""
index = self.hash_func(key)
bucket = self.buckets[index]
# バケットを走査し、その中からキー値ペアを削除
for pair in bucket:
if pair.key == key:
bucket.remove(pair)
self.size -= 1
break
def extend(self):
"""ハッシュテーブルを拡張"""
# 元のハッシュテーブルを一時的に保存
buckets = self.buckets
# 拡張された新しいハッシュテーブルを初期化
self.capacity *= self.extend_ratio
self.buckets = [[] for _ in range(self.capacity)]
self.size = 0
# 元のハッシュテーブルから新しいハッシュテーブルにキー値ペアを移動
for bucket in buckets:
for pair in bucket:
self.put(pair.key, pair.val)
def print(self):
"""ハッシュテーブルを出力"""
for bucket in self.buckets:
res = []
for pair in bucket:
res.append(str(pair.key) + " -> " + pair.val)
print(res)
"""Driver Code"""
if __name__ == "__main__":
# ハッシュテーブルを初期化
hashmap = HashMapChaining()
# 追加操作
# キー値ペア (key, value) をハッシュテーブルに追加
hashmap.put(12836, "Ha")
hashmap.put(15937, "Luo")
hashmap.put(16750, "Suan")
hashmap.put(13276, "Fa")
hashmap.put(10583, "Ya")
print("\n追加後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]")
hashmap.print()
# 照会操作
# ハッシュテーブルにキーを入力し、値を取得
name = hashmap.get(13276)
print("\n学生ID 13276 を入力、名前 " + name + " が見つかりました")
# 削除操作
# ハッシュテーブルからキー値ペア (key, value) を削除
hashmap.remove(12836)
print("\n12836 を削除後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]")
hashmap.print()

View File

@@ -0,0 +1,138 @@
"""
File: hash_map_open_addressing.py
Created Time: 2023-06-13
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from chapter_hashing.array_hash_map import Pair
class HashMapOpenAddressing:
"""オープンアドレス法ハッシュテーブル"""
def __init__(self):
"""コンストラクタ"""
self.size = 0 # キー値ペアの数
self.capacity = 4 # ハッシュテーブルの容量
self.load_thres = 2.0 / 3.0 # 拡張をトリガーする負荷率の閾値
self.extend_ratio = 2 # 拡張の倍数
self.buckets: list[Pair | None] = [None] * self.capacity # バケット配列
self.TOMBSTONE = Pair(-1, "-1") # 削除マーク
def hash_func(self, key: int) -> int:
"""ハッシュ関数"""
return key % self.capacity
def load_factor(self) -> float:
"""負荷率"""
return self.size / self.capacity
def find_bucket(self, key: int) -> int:
"""key に対応するバケットインデックスを検索"""
index = self.hash_func(key)
first_tombstone = -1
# 線形探査、空のバケットに遭遇したらブレーク
while self.buckets[index] is not None:
# キーに遭遇した場合、対応するバケットインデックスを返す
if self.buckets[index].key == key:
# 削除マークが以前に遭遇していた場合、キー値ペアをそのインデックスに移動
if first_tombstone != -1:
self.buckets[first_tombstone] = self.buckets[index]
self.buckets[index] = self.TOMBSTONE
return first_tombstone # 移動されたバケットインデックスを返す
return index # バケットインデックスを返す
# 最初に遭遇した削除マークを記録
if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE:
first_tombstone = index
# バケットインデックスを計算、末尾を超えた場合は先頭に戻る
index = (index + 1) % self.capacity
# キーが存在しない場合、挿入ポイントのインデックスを返す
return index if first_tombstone == -1 else first_tombstone
def get(self, key: int) -> str:
"""照会操作"""
# key に対応するバケットインデックスを検索
index = self.find_bucket(key)
# キー値ペアが見つかれば、対応する val を返す
if self.buckets[index] not in [None, self.TOMBSTONE]:
return self.buckets[index].val
# キー値ペアが存在しない場合、None を返す
return None
def put(self, key: int, val: str):
"""追加操作"""
# 負荷率が閾値を超えた場合、拡張を実行
if self.load_factor() > self.load_thres:
self.extend()
# key に対応するバケットインデックスを検索
index = self.find_bucket(key)
# キー値ペアが見つかれば、val を上書きして返す
if self.buckets[index] not in [None, self.TOMBSTONE]:
self.buckets[index].val = val
return
# キー値ペアが存在しない場合、キー値ペアを追加
self.buckets[index] = Pair(key, val)
self.size += 1
def remove(self, key: int):
"""削除操作"""
# key に対応するバケットインデックスを検索
index = self.find_bucket(key)
# キー値ペアが見つかれば、削除マークで覆う
if self.buckets[index] not in [None, self.TOMBSTONE]:
self.buckets[index] = self.TOMBSTONE
self.size -= 1
def extend(self):
"""ハッシュテーブルを拡張"""
# 元のハッシュテーブルを一時的に保存
buckets_tmp = self.buckets
# 拡張された新しいハッシュテーブルを初期化
self.capacity *= self.extend_ratio
self.buckets = [None] * self.capacity
self.size = 0
# 元のハッシュテーブルから新しいハッシュテーブルにキー値ペアを移動
for pair in buckets_tmp:
if pair not in [None, self.TOMBSTONE]:
self.put(pair.key, pair.val)
def print(self):
"""ハッシュテーブルを出力"""
for pair in self.buckets:
if pair is None:
print("None")
elif pair is self.TOMBSTONE:
print("TOMBSTONE")
else:
print(pair.key, "->", pair.val)
"""Driver Code"""
if __name__ == "__main__":
# ハッシュテーブルを初期化
hashmap = HashMapOpenAddressing()
# 追加操作
# キー値ペア (key, val) をハッシュテーブルに追加
hashmap.put(12836, "Ha")
hashmap.put(15937, "Luo")
hashmap.put(16750, "Suan")
hashmap.put(13276, "Fa")
hashmap.put(10583, "Ya")
print("\n追加後、ハッシュテーブルは\nKey -> Value")
hashmap.print()
# 照会操作
# ハッシュテーブルにキーを入力し、値 val を取得
name = hashmap.get(13276)
print("\n学生ID 13276 を入力、名前 " + name + " が見つかりました")
# 削除操作
# ハッシュテーブルからキー値ペア (key, val) を削除
hashmap.remove(16750)
print("\n16750 を削除後、ハッシュテーブルは\nKey -> Value")
hashmap.print()

View File

@@ -0,0 +1,58 @@
"""
File: simple_hash.py
Created Time: 2023-06-15
Author: krahets (krahets@163.com)
"""
def add_hash(key: str) -> int:
"""加法ハッシュ"""
hash = 0
modulus = 1000000007
for c in key:
hash += ord(c)
return hash % modulus
def mul_hash(key: str) -> int:
"""乗法ハッシュ"""
hash = 0
modulus = 1000000007
for c in key:
hash = 31 * hash + ord(c)
return hash % modulus
def xor_hash(key: str) -> int:
"""XORハッシュ"""
hash = 0
modulus = 1000000007
for c in key:
hash ^= ord(c)
return hash % modulus
def rot_hash(key: str) -> int:
"""回転ハッシュ"""
hash = 0
modulus = 1000000007
for c in key:
hash = (hash << 4) ^ (hash >> 28) ^ ord(c)
return hash % modulus
"""Driver Code"""
if __name__ == "__main__":
key = "Hello algorithm"
hash = add_hash(key)
print(f"加法ハッシュ値は {hash}")
hash = mul_hash(key)
print(f"乗法ハッシュ値は {hash}")
hash = xor_hash(key)
print(f"XORハッシュ値は {hash}")
hash = rot_hash(key)
print(f"回転ハッシュ値は {hash}")

View File

@@ -0,0 +1,71 @@
"""
File: heap.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import print_heap
import heapq
def test_push(heap: list, val: int, flag: int = 1):
heapq.heappush(heap, flag * val) # ヒープに要素をプッシュ
print(f"\n要素 {val} をヒープにプッシュ後")
print_heap([flag * val for val in heap])
def test_pop(heap: list, flag: int = 1):
val = flag * heapq.heappop(heap) # ヒープの先頭要素をポップ
print(f"\nヒープの先頭要素 {val} がヒープから出た後")
print_heap([flag * val for val in heap])
"""ドライバコード"""
if __name__ == "__main__":
# 最小ヒープを初期化
min_heap, flag = [], 1
# 最大ヒープを初期化
max_heap, flag = [], -1
print("\n以下のテストケースは最大ヒープ用です")
# PythonのheapqモジュールはデフォルトでMinHeapを実装
# ヒープに入れる前に「要素を反転」することを考慮し、比較演算子を逆転させて最大ヒープを実装
# この例では、flag = 1は最小ヒープに対応し、flag = -1は最大ヒープに対応
# ヒープに要素をプッシュ
test_push(max_heap, 1, flag)
test_push(max_heap, 3, flag)
test_push(max_heap, 2, flag)
test_push(max_heap, 5, flag)
test_push(max_heap, 4, flag)
# ヒープの先頭要素にアクセス
peek: int = flag * max_heap[0]
print(f"\nヒープの先頭要素は {peek}")
# ヒープの先頭要素をポップ
test_pop(max_heap, flag)
test_pop(max_heap, flag)
test_pop(max_heap, flag)
test_pop(max_heap, flag)
test_pop(max_heap, flag)
# ヒープのサイズを取得
size: int = len(max_heap)
print(f"\nヒープの要素数は {size}")
# ヒープが空かどうかを判定
is_empty: bool = not max_heap
print(f"\nヒープは空ですか {is_empty}")
# リストを入力してヒープを構築
# 時間複雑度はO(n)、O(nlogn)ではない
min_heap = [1, 3, 2, 5, 4]
heapq.heapify(min_heap)
print("\nリストを入力して最小ヒープを構築")
print_heap(min_heap)

View File

@@ -0,0 +1,137 @@
"""
File: my_heap.py
Created Time: 2023-02-23
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import print_heap
class MaxHeap:
"""最大ヒープ"""
def __init__(self, nums: list[int]):
"""コンストラクタ、入力リストに基づいてヒープを構築"""
# すべてのリスト要素をヒープに追加
self.max_heap = nums
# 葉以外のすべてのノードをヒープ化
for i in range(self.parent(self.size() - 1), -1, -1):
self.sift_down(i)
def left(self, i: int) -> int:
"""左の子ノードのインデックスを取得"""
return 2 * i + 1
def right(self, i: int) -> int:
"""右の子ノードのインデックスを取得"""
return 2 * i + 2
def parent(self, i: int) -> int:
"""親ノードのインデックスを取得"""
return (i - 1) // 2 # 整数除算で切り下げ
def swap(self, i: int, j: int):
"""要素を交換"""
self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i]
def size(self) -> int:
"""ヒープのサイズを取得"""
return len(self.max_heap)
def is_empty(self) -> bool:
"""ヒープが空かどうかを判定"""
return self.size() == 0
def peek(self) -> int:
"""ヒープの先頭要素にアクセス"""
return self.max_heap[0]
def push(self, val: int):
"""ヒープに要素をプッシュ"""
# ノードを追加
self.max_heap.append(val)
# 下から上へヒープ化
self.sift_up(self.size() - 1)
def sift_up(self, i: int):
"""ードiから開始して、下から上へヒープ化"""
while True:
# ードiの親ードを取得
p = self.parent(i)
# 「ルートノードを越える」または「ノードが修復不要」の場合、ヒープ化を終了
if p < 0 or self.max_heap[i] <= self.max_heap[p]:
break
# 2つのードを交換
self.swap(i, p)
# 上向きのループヒープ化
i = p
def pop(self) -> int:
"""要素をヒープから出す"""
# 空の処理
if self.is_empty():
raise IndexError("Heap is empty")
# ルートノードと最右端の葉ノードを交換(最初の要素と最後の要素を交換)
self.swap(0, self.size() - 1)
# ノードを削除
val = self.max_heap.pop()
# 上から下へヒープ化
self.sift_down(0)
# ヒープの先頭要素を返す
return val
def sift_down(self, i: int):
"""ードiから開始して、上から下へヒープ化"""
while True:
# i、l、rの中で最大のードを決定し、maとする
l, r, ma = self.left(i), self.right(i), i
if l < self.size() and self.max_heap[l] > self.max_heap[ma]:
ma = l
if r < self.size() and self.max_heap[r] > self.max_heap[ma]:
ma = r
# ードiが最大またはインデックスl、rが範囲外の場合、さらなるヒープ化は不要、ブレーク
if ma == i:
break
# 2つのードを交換
self.swap(i, ma)
# 下向きのループヒープ化
i = ma
def print(self):
"""ヒープを出力(二分木)"""
print_heap(self.max_heap)
"""ドライバコード"""
if __name__ == "__main__":
# 最大ヒープを初期化
max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2])
print("\nリストを入力してヒープを構築")
max_heap.print()
# ヒープの先頭要素にアクセス
peek = max_heap.peek()
print(f"\nヒープの先頭要素は {peek}")
# ヒープに要素をプッシュ
val = 7
max_heap.push(val)
print(f"\n要素 {val} をヒープにプッシュ後")
max_heap.print()
# ヒープの先頭要素をポップ
peek = max_heap.pop()
print(f"\nヒープの先頭要素 {peek} がヒープから出た後")
max_heap.print()
# ヒープのサイズを取得
size = max_heap.size()
print(f"\nヒープの要素数は {size}")
# ヒープが空かどうかを判定
is_empty = max_heap.is_empty()
print(f"\nヒープは空ですか {is_empty}")

View File

@@ -0,0 +1,39 @@
"""
File: top_k.py
Created Time: 2023-06-10
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import print_heap
import heapq
def top_k_heap(nums: list[int], k: int) -> list[int]:
"""ヒープを使用して配列内の最大k個の要素を見つける"""
# 最小ヒープを初期化
heap = []
# 配列の最初のk個の要素をヒープに入力
for i in range(k):
heapq.heappush(heap, nums[i])
# k+1番目の要素から、ヒープの長さをkに保つ
for i in range(k, len(nums)):
# 現在の要素がヒープの先頭要素より大きい場合、ヒープの先頭要素を削除し、現在の要素をヒープに入力
if nums[i] > heap[0]:
heapq.heappop(heap)
heapq.heappush(heap, nums[i])
return heap
"""ドライバコード"""
if __name__ == "__main__":
nums = [1, 7, 6, 3, 2]
k = 3
res = top_k_heap(nums, k)
print(f"最大の {k} 個の要素は")
print_heap(res)

View File

@@ -0,0 +1,52 @@
"""
File: binary_search.py
Created Time: 2022-11-26
Author: timi (xisunyy@163.com)
"""
def binary_search(nums: list[int], target: int) -> int:
"""二分探索(両端閉区間)"""
# 両端閉区間 [0, n-1] を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素を指す
i, j = 0, len(nums) - 1
# 検索区間が空になるまでループi > j のとき空)
while i <= j:
# 理論的には、Pythonの数値は無限に大きくなることができるメモリサイズに依存ため、大きな数のオーバーフローを考慮する必要はない
m = i + (j - i) // 2 # 中点インデックス m を計算
if nums[m] < target:
i = m + 1 # この場合、target は区間 [m+1, j] にあることを示す
elif nums[m] > target:
j = m - 1 # この場合、target は区間 [i, m-1] にあることを示す
else:
return m # ターゲット要素が見つかったため、そのインデックスを返す
return -1 # ターゲット要素が見つからなかったため、-1 を返す
def binary_search_lcro(nums: list[int], target: int) -> int:
"""二分探索(左閉右開区間)"""
# 左閉右開区間 [0, n) を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素+1を指す
i, j = 0, len(nums)
# 検索区間が空になるまでループi = j のとき空)
while i < j:
m = i + (j - i) // 2 # 中点インデックス m を計算
if nums[m] < target:
i = m + 1 # この場合、target は区間 [m+1, j) にあることを示す
elif nums[m] > target:
j = m # この場合、target は区間 [i, m) にあることを示す
else:
return m # ターゲット要素が見つかったため、そのインデックスを返す
return -1 # ターゲット要素が見つからなかったため、-1 を返す
"""ドライバーコード"""
if __name__ == "__main__":
target = 6
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
# 二分探索(両端閉区間)
index = binary_search(nums, target)
print("ターゲット要素 6 のインデックス =", index)
# 二分探索(左閉右開区間)
index = binary_search_lcro(nums, target)
print("ターゲット要素 6 のインデックス =", index)

View File

@@ -0,0 +1,49 @@
"""
File: binary_search_edge.py
Created Time: 2023-08-04
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from binary_search_insertion import binary_search_insertion
def binary_search_left_edge(nums: list[int], target: int) -> int:
"""最左端のターゲットの二分探索"""
# ターゲットの挿入位置を見つけることと同等
i = binary_search_insertion(nums, target)
# ターゲットが見つからなかった場合、-1 を返す
if i == len(nums) or nums[i] != target:
return -1
# ターゲットが見つかった場合、インデックス i を返す
return i
def binary_search_right_edge(nums: list[int], target: int) -> int:
"""最右端のターゲットの二分探索"""
# 最左端のターゲット + 1 を見つけることに変換
i = binary_search_insertion(nums, target + 1)
# j は最右端のターゲットを指し、i はターゲットより大きい最初の要素を指す
j = i - 1
# ターゲットが見つからなかった場合、-1 を返す
if j == -1 or nums[j] != target:
return -1
# ターゲットが見つかった場合、インデックス j を返す
return j
"""ドライバーコード"""
if __name__ == "__main__":
# 重複要素のある配列
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]
print(f"\n配列 nums = {nums}")
# 左端と右端の境界の二分探索
for target in [6, 7]:
index = binary_search_left_edge(nums, target)
print(f"最左端の要素 {target} のインデックスは {index}")
index = binary_search_right_edge(nums, target)
print(f"最右端の要素 {target} のインデックスは {index}")

View File

@@ -0,0 +1,54 @@
"""
File: binary_search_insertion.py
Created Time: 2023-08-04
Author: krahets (krahets@163.com)
"""
def binary_search_insertion_simple(nums: list[int], target: int) -> int:
"""挿入位置の二分探索(重複要素なし)"""
i, j = 0, len(nums) - 1 # 両端閉区間 [0, n-1] を初期化
while i <= j:
m = i + (j - i) // 2 # 中点インデックス m を計算
if nums[m] < target:
i = m + 1 # ターゲットは区間 [m+1, j] にある
elif nums[m] > target:
j = m - 1 # ターゲットは区間 [i, m-1] にある
else:
return m # ターゲットが見つかった場合、挿入位置 m を返す
# ターゲットが見つからなかった場合、挿入位置 i を返す
return i
def binary_search_insertion(nums: list[int], target: int) -> int:
"""挿入位置の二分探索(重複要素あり)"""
i, j = 0, len(nums) - 1 # 両端閉区間 [0, n-1] を初期化
while i <= j:
m = i + (j - i) // 2 # 中点インデックス m を計算
if nums[m] < target:
i = m + 1 # ターゲットは区間 [m+1, j] にある
elif nums[m] > target:
j = m - 1 # ターゲットは区間 [i, m-1] にある
else:
j = m - 1 # ターゲット未満の最初の要素は区間 [i, m-1] にある
# 挿入位置 i を返す
return i
"""ドライバーコード"""
if __name__ == "__main__":
# 重複要素のない配列
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
print(f"\n配列 nums = {nums}")
# 挿入位置の二分探索
for target in [6, 9]:
index = binary_search_insertion_simple(nums, target)
print(f"要素 {target} の挿入位置インデックスは {index}")
# 重複要素のある配列
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]
print(f"\n配列 nums = {nums}")
# 挿入位置の二分探索
for target in [2, 6, 20]:
index = binary_search_insertion(nums, target)
print(f"要素 {target} の挿入位置インデックスは {index}")

View File

@@ -0,0 +1,51 @@
"""
File: hashing_search.py
Created Time: 2022-11-26
Author: timi (xisunyy@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode, list_to_linked_list
def hashing_search_array(hmap: dict[int, int], target: int) -> int:
"""ハッシュ探索(配列)"""
# ハッシュテーブルのキー:ターゲット要素、値:インデックス
# ハッシュテーブルがこのキーを含まない場合、-1 を返す
return hmap.get(target, -1)
def hashing_search_linkedlist(
hmap: dict[int, ListNode], target: int
) -> ListNode | None:
"""ハッシュ探索(連結リスト)"""
# ハッシュテーブルのキー:ターゲット要素、値:ノードオブジェクト
# ハッシュテーブルがこのキーを含まない場合、None を返す
return hmap.get(target, None)
"""ドライバーコード"""
if __name__ == "__main__":
target = 3
# ハッシュ探索(配列)
nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]
# ハッシュテーブルを初期化
map0 = dict[int, int]()
for i in range(len(nums)):
map0[nums[i]] = i # キー:要素、値:インデックス
index: int = hashing_search_array(map0, target)
print("ターゲット要素 3 のインデックス =", index)
# ハッシュ探索(連結リスト)
head: ListNode = list_to_linked_list(nums)
# ハッシュテーブルを初期化
map1 = dict[int, ListNode]()
while head:
map1[head.val] = head # キー:ノード値、値:ノード
head = head.next
node: ListNode = hashing_search_linkedlist(map1, target)
print("ターゲットノード値 3 に対応するノードオブジェクトは", node)

View File

@@ -0,0 +1,45 @@
"""
File: linear_search.py
Created Time: 2022-11-26
Author: timi (xisunyy@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode, list_to_linked_list
def linear_search_array(nums: list[int], target: int) -> int:
"""線形探索(配列)"""
# 配列を走査
for i in range(len(nums)):
if nums[i] == target: # ターゲット要素が見つかったため、そのインデックスを返す
return i
return -1 # ターゲット要素が見つからなかったため、-1 を返す
def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None:
"""線形探索(連結リスト)"""
# リストを走査
while head:
if head.val == target: # ターゲットノードが見つかったため、それを返す
return head
head = head.next
return None # ターゲットードが見つからなかったため、None を返す
"""ドライバーコード"""
if __name__ == "__main__":
target = 3
# 配列での線形探索を実行
nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]
index: int = linear_search_array(nums, target)
print("ターゲット要素 3 のインデックス =", index)
# 連結リストでの線形探索を実行
head: ListNode = list_to_linked_list(nums)
node: ListNode | None = linear_search_linkedlist(head, target)
print("ターゲットノード値 3 に対応するノードオブジェクトは", node)

View File

@@ -0,0 +1,42 @@
"""
File: two_sum.py
Created Time: 2022-11-25
Author: krahets (krahets@163.com)
"""
def two_sum_brute_force(nums: list[int], target: int) -> list[int]:
"""方法一:ブルートフォース列挙"""
# 二重ループ、時間計算量は O(n^2)
for i in range(len(nums) - 1):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
return []
def two_sum_hash_table(nums: list[int], target: int) -> list[int]:
"""方法二:補助ハッシュテーブル"""
# 補助ハッシュテーブル、空間計算量は O(n)
dic = {}
# 単一ループ、時間計算量は O(n)
for i in range(len(nums)):
if target - nums[i] in dic:
return [dic[target - nums[i]], i]
dic[nums[i]] = i
return []
"""ドライバーコード"""
if __name__ == "__main__":
# ======= テストケース =======
nums = [2, 7, 11, 15]
target = 13
# ====== ドライバーコード ======
# 方法一
res: list[int] = two_sum_brute_force(nums, target)
print("方法一の結果 =", res)
# 方法二
res: list[int] = two_sum_hash_table(nums, target)
print("方法二の結果 =", res)

View File

@@ -0,0 +1,44 @@
"""
File: bubble_sort.py
Created Time: 2022-11-25
Author: timi (xisunyy@163.com)
"""
def bubble_sort(nums: list[int]):
"""バブルソート"""
n = len(nums)
# 外側のループ:未ソート範囲は [0, i]
for i in range(n - 1, 0, -1):
# 内側のループ:未ソート範囲 [0, i] の最大要素を範囲の右端に移動
for j in range(i):
if nums[j] > nums[j + 1]:
# nums[j] と nums[j + 1] を交換
nums[j], nums[j + 1] = nums[j + 1], nums[j]
def bubble_sort_with_flag(nums: list[int]):
"""バブルソート(フラグによる最適化)"""
n = len(nums)
# 外側のループ:未ソート範囲は [0, i]
for i in range(n - 1, 0, -1):
flag = False # フラグを初期化
# 内側のループ:未ソート範囲 [0, i] の最大要素を範囲の右端に移動
for j in range(i):
if nums[j] > nums[j + 1]:
# nums[j] と nums[j + 1] を交換
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = True # 要素を交換したことを記録
if not flag:
break # この回の「バブリング」で要素が交換されなかった場合、終了
"""ドライバーコード"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
bubble_sort(nums)
print("バブルソート完了 nums =", nums)
nums1 = [4, 1, 3, 1, 5, 2]
bubble_sort_with_flag(nums1)
print("バブルソート完了 nums =", nums1)

View File

@@ -0,0 +1,35 @@
"""
File: bucket_sort.py
Created Time: 2023-03-30
Author: krahets (krahets@163.com)
"""
def bucket_sort(nums: list[float]):
"""バケットソート"""
# k = n/2 個のバケットを初期化、各バケットに平均2個の要素を配置することを期待
k = len(nums) // 2
buckets = [[] for _ in range(k)]
# 1. 配列要素を各バケットに分散
for num in nums:
# 入力データ範囲は [0, 1)、num * k を使用してインデックス範囲 [0, k-1] にマッピング
i = int(num * k)
# num をバケット i に追加
buckets[i].append(num)
# 2. 各バケットをソート
for bucket in buckets:
# 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能
bucket.sort()
# 3. バケットを走査して結果をマージ
i = 0
for bucket in buckets:
for num in bucket:
nums[i] = num
i += 1
if __name__ == "__main__":
# 入力データが浮動小数点数、範囲 [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)
print("バケットソート完了 nums =", nums)

View File

@@ -0,0 +1,64 @@
"""
File: counting_sort.py
Created Time: 2023-03-21
Author: krahets (krahets@163.com)
"""
def counting_sort_naive(nums: list[int]):
"""計数ソート"""
# シンプルな実装、オブジェクトのソートには使用できない
# 1. 配列内の最大要素 m を統計
m = 0
for num in nums:
m = max(m, num)
# 2. 各数字の出現回数を統計
# counter[num] は num の出現回数を表す
counter = [0] * (m + 1)
for num in nums:
counter[num] += 1
# 3. counter を走査し、各要素を元の配列 nums に埋め戻し
i = 0
for num in range(m + 1):
for _ in range(counter[num]):
nums[i] = num
i += 1
def counting_sort(nums: list[int]):
"""計数ソート"""
# 完全な実装、オブジェクトのソートが可能で、安定ソート
# 1. 配列内の最大要素 m を統計
m = max(nums)
# 2. 各数字の出現回数を統計
# counter[num] は num の出現回数を表す
counter = [0] * (m + 1)
for num in nums:
counter[num] += 1
# 3. counter の前置和を計算し、「出現回数」を「末尾インデックス」に変換
# counter[num]-1 は res において num が最後に出現するインデックス
for i in range(m):
counter[i + 1] += counter[i]
# 4. nums を逆順に走査し、各要素を結果配列 res に配置
# 結果を記録するための配列 res を初期化
n = len(nums)
res = [0] * n
for i in range(n - 1, -1, -1):
num = nums[i]
res[counter[num] - 1] = num # num を対応するインデックスに配置
counter[num] -= 1 # 前置和を1減らし、num を配置する次のインデックスを取得
# 結果配列 res を使用して元の配列 nums を上書き
for i in range(n):
nums[i] = res[i]
"""ドライバーコード"""
if __name__ == "__main__":
nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]
counting_sort_naive(nums)
print(f"計数ソート(オブジェクトソート不可)完了 nums = {nums}")
nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]
counting_sort(nums1)
print(f"計数ソート完了 nums1 = {nums1}")

View File

@@ -0,0 +1,45 @@
"""
File: heap_sort.py
Created Time: 2023-05-24
Author: krahets (krahets@163.com)
"""
def sift_down(nums: list[int], n: int, i: int):
"""ヒープの長さが n、ード i から上から下へヒープ化を開始"""
while True:
# i、l、r の中で最大のードを判定し、ma とする
l = 2 * i + 1
r = 2 * i + 2
ma = i
if l < n and nums[l] > nums[ma]:
ma = l
if r < n and nums[r] > nums[ma]:
ma = r
# ノード i が最大または l、r のインデックスが範囲外の場合、さらなるヒープ化は不要、ループを抜ける
if ma == i:
break
# 2つのードを交換
nums[i], nums[ma] = nums[ma], nums[i]
# 下向きにヒープ化をループ
i = ma
def heap_sort(nums: list[int]):
"""ヒープソート"""
# ヒープ構築操作:葉ノード以外のすべてのノードをヒープ化
for i in range(len(nums) // 2 - 1, -1, -1):
sift_down(nums, len(nums), i)
# ヒープから最大要素を抽出し、n-1 回繰り返す
for i in range(len(nums) - 1, 0, -1):
# ルートノードと最も右の葉ノードを交換(最初の要素と最後の要素を交換)
nums[0], nums[i] = nums[i], nums[0]
# ルートノードから上から下へヒープ化を開始
sift_down(nums, i, 0)
"""ドライバーコード"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
heap_sort(nums)
print("ヒープソート完了 nums =", nums)

View File

@@ -0,0 +1,25 @@
"""
File: insertion_sort.py
Created Time: 2022-11-25
Author: timi (xisunyy@163.com)
"""
def insertion_sort(nums: list[int]):
"""挿入ソート"""
# 外側のループ:ソート済み範囲は [0, i-1]
for i in range(1, len(nums)):
base = nums[i]
j = i - 1
# 内側のループbase をソート済み範囲 [0, i-1] の正しい位置に挿入
while j >= 0 and nums[j] > base:
nums[j + 1] = nums[j] # nums[j] を右に1つ移動
j -= 1
nums[j + 1] = base # base を正しい位置に代入
"""ドライバーコード"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
insertion_sort(nums)
print("挿入ソート完了 nums =", nums)

View File

@@ -0,0 +1,55 @@
"""
File: merge_sort.py
Created Time: 2022-11-25
Author: timi (xisunyy@163.com), krahets (krahets@163.com)
"""
def merge(nums: list[int], left: int, mid: int, right: int):
"""左サブ配列と右サブ配列をマージ"""
# 左サブ配列区間は [left, mid]、右サブ配列区間は [mid+1, right]
# 一時配列 tmp を作成してマージ結果を格納
tmp = [0] * (right - left + 1)
# 左右サブ配列の開始インデックスを初期化
i, j, k = left, mid + 1, 0
# 両方のサブ配列に要素が残っている間、より小さい要素を一時配列にコピー
while i <= mid and j <= right:
if nums[i] <= nums[j]:
tmp[k] = nums[i]
i += 1
else:
tmp[k] = nums[j]
j += 1
k += 1
# 残った左右サブ配列の要素を一時配列にコピー
while i <= mid:
tmp[k] = nums[i]
i += 1
k += 1
while j <= right:
tmp[k] = nums[j]
j += 1
k += 1
# 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーバック
for k in range(0, len(tmp)):
nums[left + k] = tmp[k]
def merge_sort(nums: list[int], left: int, right: int):
"""マージソート"""
# 終了条件
if left >= right:
return # サブ配列の長さが1のときに再帰を終了
# 分割段階
mid = left + (right - left) // 2 # 中点を計算
merge_sort(nums, left, mid) # 左サブ配列を再帰的に処理
merge_sort(nums, mid + 1, right) # 右サブ配列を再帰的に処理
# マージ段階
merge(nums, left, mid, right)
"""ドライバーコード"""
if __name__ == "__main__":
nums = [7, 3, 2, 6, 0, 1, 5, 4]
merge_sort(nums, 0, len(nums) - 1)
print("マージソート完了 nums =", nums)

View File

@@ -0,0 +1,129 @@
"""
File: quick_sort.py
Created Time: 2022-11-25
Author: timi (xisunyy@163.com)
"""
class QuickSort:
"""クイックソートクラス"""
def partition(self, nums: list[int], left: int, right: int) -> int:
"""分割"""
# nums[left] をピボットとして使用
i, j = left, right
while i < j:
while i < j and nums[j] >= nums[left]:
j -= 1 # 右から左へピボットより小さい最初の要素を探す
while i < j and nums[i] <= nums[left]:
i += 1 # 左から右へピボットより大きい最初の要素を探す
# 要素を交換
nums[i], nums[j] = nums[j], nums[i]
# ピボットを2つのサブ配列の境界に交換
nums[i], nums[left] = nums[left], nums[i]
return i # ピボットのインデックスを返す
def quick_sort(self, nums: list[int], left: int, right: int):
"""クイックソート"""
# サブ配列の長さが1のときに再帰を終了
if left >= right:
return
# 分割
pivot = self.partition(nums, left, right)
# 左サブ配列と右サブ配列を再帰的に処理
self.quick_sort(nums, left, pivot - 1)
self.quick_sort(nums, pivot + 1, right)
class QuickSortMedian:
"""クイックソートクラス(中央値ピボット最適化)"""
def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int:
"""3つの候補要素の中央値を選択"""
l, m, r = nums[left], nums[mid], nums[right]
if (l <= m <= r) or (r <= m <= l):
return mid # m は l と r の間
if (m <= l <= r) or (r <= l <= m):
return left # l は m と r の間
return right
def partition(self, nums: list[int], left: int, right: int) -> int:
"""分割(三点中央値)"""
# nums[left] をピボットとして使用
med = self.median_three(nums, left, (left + right) // 2, right)
# 中央値を配列の最左端に交換
nums[left], nums[med] = nums[med], nums[left]
# nums[left] をピボットとして使用
i, j = left, right
while i < j:
while i < j and nums[j] >= nums[left]:
j -= 1 # 右から左へピボットより小さい最初の要素を探す
while i < j and nums[i] <= nums[left]:
i += 1 # 左から右へピボットより大きい最初の要素を探す
# 要素を交換
nums[i], nums[j] = nums[j], nums[i]
# ピボットを2つのサブ配列の境界に交換
nums[i], nums[left] = nums[left], nums[i]
return i # ピボットのインデックスを返す
def quick_sort(self, nums: list[int], left: int, right: int):
"""クイックソート"""
# サブ配列の長さが1のときに再帰を終了
if left >= right:
return
# 分割
pivot = self.partition(nums, left, right)
# 左サブ配列と右サブ配列を再帰的に処理
self.quick_sort(nums, left, pivot - 1)
self.quick_sort(nums, pivot + 1, right)
class QuickSortTailCall:
"""クイックソートクラス(末尾再帰最適化)"""
def partition(self, nums: list[int], left: int, right: int) -> int:
"""分割"""
# nums[left] をピボットとして使用
i, j = left, right
while i < j:
while i < j and nums[j] >= nums[left]:
j -= 1 # 右から左へピボットより小さい最初の要素を探す
while i < j and nums[i] <= nums[left]:
i += 1 # 左から右へピボットより大きい最初の要素を探す
# 要素を交換
nums[i], nums[j] = nums[j], nums[i]
# ピボットを2つのサブ配列の境界に交換
nums[i], nums[left] = nums[left], nums[i]
return i # ピボットのインデックスを返す
def quick_sort(self, nums: list[int], left: int, right: int):
"""クイックソート(末尾再帰最適化)"""
# サブ配列の長さが1のときに終了
while left < right:
# 分割操作
pivot = self.partition(nums, left, right)
# 2つのサブ配列のうち短い方に対してクイックソートを実行
if pivot - left < right - pivot:
self.quick_sort(nums, left, pivot - 1) # 左サブ配列を再帰的にソート
left = pivot + 1 # 残りの未ソート区間は [pivot + 1, right]
else:
self.quick_sort(nums, pivot + 1, right) # 右サブ配列を再帰的にソート
right = pivot - 1 # 残りの未ソート区間は [left, pivot - 1]
"""ドライバーコード"""
if __name__ == "__main__":
# クイックソート
nums = [2, 4, 1, 0, 3, 5]
QuickSort().quick_sort(nums, 0, len(nums) - 1)
print("クイックソート完了 nums =", nums)
# クイックソート(中央値ピボット最適化)
nums1 = [2, 4, 1, 0, 3, 5]
QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1)
print("クイックソート(中央値ピボット最適化)完了 nums =", nums1)
# クイックソート(末尾再帰最適化)
nums2 = [2, 4, 1, 0, 3, 5]
QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1)
print("クイックソート(末尾再帰最適化)完了 nums =", nums2)

View File

@@ -0,0 +1,69 @@
"""
File: radix_sort.py
Created Time: 2023-03-26
Author: krahets (krahets@163.com)
"""
def digit(num: int, exp: int) -> int:
"""要素 num の k 番目の桁を取得、exp = 10^(k-1)"""
# k の代わりに exp を渡すことで、ここでコストの高い累乗計算を避けることができる
return (num // exp) % 10
def counting_sort_digit(nums: list[int], exp: int):
"""計数ソートnums の k 番目の桁に基づく)"""
# 10進数の桁の範囲は 0~9、したがって長さ10のバケット配列が必要
counter = [0] * 10
n = len(nums)
# 数字 0~9 の出現回数を統計
for i in range(n):
d = digit(nums[i], exp) # nums[i] の k 番目の桁を取得、d とする
counter[d] += 1 # 数字 d の出現回数を統計
# 前置和を計算し、「出現回数」を「配列インデックス」に変換
for i in range(1, 10):
counter[i] += counter[i - 1]
# 逆順に走査し、バケット統計に基づいて各要素を res に配置
res = [0] * n
for i in range(n - 1, -1, -1):
d = digit(nums[i], exp)
j = counter[d] - 1 # 配列内の d のインデックス j を取得
res[j] = nums[i] # 現在の要素をインデックス j に配置
counter[d] -= 1 # d の数を1減らす
# 結果を使用して元の配列 nums を上書き
for i in range(n):
nums[i] = res[i]
def radix_sort(nums: list[int]):
"""基数ソート"""
# 配列の最大要素を取得し、最大桁数を判定するために使用
m = max(nums)
# 最下位桁から最上位桁まで走査
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
"""ドライバーコード"""
if __name__ == "__main__":
# 基数ソート
nums = [
10546151,
35663510,
42865989,
34862445,
81883077,
88906420,
72429244,
30524779,
82060337,
63832996,
]
radix_sort(nums)
print("基数ソート完了 nums =", nums)

View File

@@ -0,0 +1,26 @@
"""
File: selection_sort.py
Created Time: 2023-05-22
Author: krahets (krahets@163.com)
"""
def selection_sort(nums: list[int]):
"""選択ソート"""
n = len(nums)
# 外側のループ:未ソート範囲は [i, n-1]
for i in range(n - 1):
# 内側のループ:未ソート範囲内で最小要素を見つける
k = i
for j in range(i + 1, n):
if nums[j] < nums[k]:
k = j # 最小要素のインデックスを記録
# 最小要素を未ソート範囲の先頭要素と交換
nums[i], nums[k] = nums[k], nums[i]
"""ドライバーコード"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
selection_sort(nums)
print("選択ソート完了 nums =", nums)

View File

@@ -0,0 +1,129 @@
"""
File: array_deque.py
Created Time: 2023-03-01
Author: krahets (krahets@163.com)
"""
class ArrayDeque:
"""循環配列ベースの双端キュークラス"""
def __init__(self, capacity: int):
"""コンストラクタ"""
self._nums: list[int] = [0] * capacity
self._front: int = 0
self._size: int = 0
def capacity(self) -> int:
"""双端キューの容量を取得"""
return len(self._nums)
def size(self) -> int:
"""双端キューの長さを取得"""
return self._size
def is_empty(self) -> bool:
"""双端キューが空かどうかを判定"""
return self._size == 0
def index(self, i: int) -> int:
"""循環配列のインデックスを計算"""
# モジュロ演算によって循環配列を実装
# i が配列の末尾を超えた場合、先頭に戻る
# i が配列の先頭を超えた場合、末尾に戻る
return (i + self.capacity()) % self.capacity()
def push_first(self, num: int):
"""前端エンキュー"""
if self._size == self.capacity():
print("双端キューが満杯です")
return
# フロントポインタを左に1つ移動
# モジュロ演算によってフロントが配列の先頭を超えて末尾に戻ることを実装
self._front = self.index(self._front - 1)
# num を前端に追加
self._nums[self._front] = num
self._size += 1
def push_last(self, num: int):
"""後端エンキュー"""
if self._size == self.capacity():
print("双端キューが満杯です")
return
# リアポインタを計算、リアインデックス + 1 を指す
rear = self.index(self._front + self._size)
# num を後端に追加
self._nums[rear] = num
self._size += 1
def pop_first(self) -> int:
"""前端デキュー"""
num = self.peek_first()
# フロントポインタを1つ後ろに移動
self._front = self.index(self._front + 1)
self._size -= 1
return num
def pop_last(self) -> int:
"""後端デキュー"""
num = self.peek_last()
self._size -= 1
return num
def peek_first(self) -> int:
"""前端要素にアクセス"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
return self._nums[self._front]
def peek_last(self) -> int:
"""後端要素にアクセス"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
# 後端要素のインデックスを計算
last = self.index(self._front + self._size - 1)
return self._nums[last]
def to_array(self) -> list[int]:
"""出力用の配列を返す"""
# 有効な長さ範囲内の要素のみを変換
res = []
for i in range(self._size):
res.append(self._nums[self.index(self._front + i)])
return res
"""Driver Code"""
if __name__ == "__main__":
# 双端キューを初期化
deque = ArrayDeque(10)
deque.push_last(3)
deque.push_last(2)
deque.push_last(5)
print("双端キュー deque =", deque.to_array())
# 要素にアクセス
peek_first: int = deque.peek_first()
print("前端要素 peek_first =", peek_first)
peek_last: int = deque.peek_last()
print("後端要素 peek_last =", peek_last)
# 要素をエンキュー
deque.push_last(4)
print("要素 4 を後端エンキュー、deque =", deque.to_array())
deque.push_first(1)
print("要素 1 を前端エンキュー、deque =", deque.to_array())
# 要素をデキュー
pop_last: int = deque.pop_last()
print("後端でデキューされた要素 =", pop_last, "、後端デキュー後の deque =", deque.to_array())
pop_first: int = deque.pop_first()
print("前端でデキューされた要素 =", pop_first, "、前端デキュー後の deque =", deque.to_array())
# 双端キューの長さを取得
size: int = deque.size()
print("双端キューの長さ size =", size)
# 双端キューが空かどうかを判定
is_empty: bool = deque.is_empty()
print("双端キューが空かどうか =", is_empty)

View File

@@ -0,0 +1,98 @@
"""
File: array_queue.py
Created Time: 2022-12-01
Author: Peng Chen (pengchzn@gmail.com)
"""
class ArrayQueue:
"""循環配列ベースのキュークラス"""
def __init__(self, size: int):
"""コンストラクタ"""
self._nums: list[int] = [0] * size # キュー要素を格納する配列
self._front: int = 0 # フロントポインタ、フロント要素を指す
self._size: int = 0 # キューの長さ
def capacity(self) -> int:
"""キューの容量を取得"""
return len(self._nums)
def size(self) -> int:
"""キューの長さを取得"""
return self._size
def is_empty(self) -> bool:
"""キューが空かどうかを判定"""
return self._size == 0
def push(self, num: int):
"""エンキュー"""
if self._size == self.capacity():
raise IndexError("Queue is full")
# リアポインタを計算、リアインデックス + 1 を指す
# モジュロ演算を使用してリアポインタを配列の末尾から先頭に戻す
rear: int = (self._front + self._size) % self.capacity()
# num をリアに追加
self._nums[rear] = num
self._size += 1
def pop(self) -> int:
"""デキュー"""
num: int = self.peek()
# フロントポインタを1つ後ろに移動、末尾を超えた場合は配列の先頭に戻る
self._front = (self._front + 1) % self.capacity()
self._size -= 1
return num
def peek(self) -> int:
"""フロント要素にアクセス"""
if self.is_empty():
raise IndexError("Queue is empty")
return self._nums[self._front]
def to_list(self) -> list[int]:
"""出力用の配列を返す"""
res = [0] * self.size()
j: int = self._front
for i in range(self.size()):
res[i] = self._nums[(j % self.capacity())]
j += 1
return res
"""Driver Code"""
if __name__ == "__main__":
# キューを初期化
queue = ArrayQueue(10)
# 要素をエンキュー
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
print("キュー queue =", queue.to_list())
# フロント要素にアクセス
peek: int = queue.peek()
print("フロント要素 peek =", peek)
# 要素をデキュー
pop: int = queue.pop()
print("デキューされた要素 pop =", pop)
print("デキュー後のキュー =", queue.to_list())
# キューの長さを取得
size: int = queue.size()
print("キューの長さ size =", size)
# キューが空かどうかを判定
is_empty: bool = queue.is_empty()
print("キューが空かどうか =", is_empty)
# 循環配列のテスト
for i in range(10):
queue.push(i)
queue.pop()
print("", i, "回目のエンキュー + デキューで、queue =", queue.to_list())

View File

@@ -0,0 +1,72 @@
"""
File: array_stack.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
class ArrayStack:
"""配列ベースのスタッククラス"""
def __init__(self):
"""コンストラクタ"""
self._stack: list[int] = []
def size(self) -> int:
"""スタックの長さを取得"""
return len(self._stack)
def is_empty(self) -> bool:
"""スタックが空かどうかを判定"""
return self.size() == 0
def push(self, item: int):
"""プッシュ"""
self._stack.append(item)
def pop(self) -> int:
"""ポップ"""
if self.is_empty():
raise IndexError("Stack is empty")
return self._stack.pop()
def peek(self) -> int:
"""スタックトップ要素にアクセス"""
if self.is_empty():
raise IndexError("Stack is empty")
return self._stack[-1]
def to_list(self) -> list[int]:
"""出力用の配列を返す"""
return self._stack
"""Driver Code"""
if __name__ == "__main__":
# スタックを初期化
stack = ArrayStack()
# 要素をプッシュ
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
print("スタック stack =", stack.to_list())
# スタックトップ要素にアクセス
peek: int = stack.peek()
print("スタックトップ要素 peek =", peek)
# 要素をポップ
pop: int = stack.pop()
print("ポップされた要素 pop =", pop)
print("ポップ後のスタック =", stack.to_list())
# スタックの長さを取得
size: int = stack.size()
print("スタックの長さ size =", size)
# 空かどうかを判定
is_empty: bool = stack.is_empty()
print("スタックが空かどうか =", is_empty)

View File

@@ -0,0 +1,42 @@
"""
File: deque.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
from collections import deque
"""Driver Code"""
if __name__ == "__main__":
# 双端キューを初期化
deq: deque[int] = deque()
# 要素をエンキュー
deq.append(2) # 後端に追加
deq.append(5)
deq.append(4)
deq.appendleft(3) # 前端に追加
deq.appendleft(1)
print("双端キュー deque =", deq)
# 要素にアクセス
front: int = deq[0] # 前端要素
print("前端要素 front =", front)
rear: int = deq[-1] # 後端要素
print("後端要素 rear =", rear)
# 要素をデキュー
pop_front: int = deq.popleft() # 前端要素のデキュー
print("前端でデキューされた要素 pop_front =", pop_front)
print("前端デキュー後のデック =", deq)
pop_rear: int = deq.pop() # 後端要素のデキュー
print("後端でデキューされた要素 pop_rear =", pop_rear)
print("後端デキュー後のデック =", deq)
# 双端キューの長さを取得
size: int = len(deq)
print("双端キューの長さ size =", size)
# 双端キューが空かどうかを判定
is_empty: bool = len(deq) == 0
print("双端キューが空かどうか =", is_empty)

View File

@@ -0,0 +1,151 @@
"""
File: linkedlist_deque.py
Created Time: 2023-03-01
Author: krahets (krahets@163.com)
"""
class ListNode:
"""双方向連結リストノード"""
def __init__(self, val: int):
"""コンストラクタ"""
self.val: int = val
self.next: ListNode | None = None # 後続ノードへの参照
self.prev: ListNode | None = None # 前駆ノードへの参照
class LinkedListDeque:
"""双方向連結リストベースの双端キュークラス"""
def __init__(self):
"""コンストラクタ"""
self._front: ListNode | None = None # ヘッドノード front
self._rear: ListNode | None = None # テールノード rear
self._size: int = 0 # 双端キューの長さ
def size(self) -> int:
"""双端キューの長さを取得"""
return self._size
def is_empty(self) -> bool:
"""双端キューが空かどうかを判定"""
return self._size == 0
def push(self, num: int, is_front: bool):
"""エンキュー操作"""
node = ListNode(num)
# リストが空の場合、front と rear の両方を node に向ける
if self.is_empty():
self._front = self._rear = node
# 前端エンキュー操作
elif is_front:
# ノードをリストの先頭に追加
self._front.prev = node
node.next = self._front
self._front = node # ヘッドノードを更新
# 後端エンキュー操作
else:
# ノードをリストの末尾に追加
self._rear.next = node
node.prev = self._rear
self._rear = node # テールノードを更新
self._size += 1 # キューの長さを更新
def push_first(self, num: int):
"""前端エンキュー"""
self.push(num, True)
def push_last(self, num: int):
"""後端エンキュー"""
self.push(num, False)
def pop(self, is_front: bool) -> int:
"""デキュー操作"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
# 前端デキュー操作
if is_front:
val: int = self._front.val # ヘッドノードの値を一時的に保存
# ヘッドノードを削除
fnext: ListNode | None = self._front.next
if fnext is not None:
fnext.prev = None
self._front.next = None
self._front = fnext # ヘッドノードを更新
# 後端デキュー操作
else:
val: int = self._rear.val # テールノードの値を一時的に保存
# テールノードを削除
rprev: ListNode | None = self._rear.prev
if rprev is not None:
rprev.next = None
self._rear.prev = None
self._rear = rprev # テールノードを更新
self._size -= 1 # キューの長さを更新
return val
def pop_first(self) -> int:
"""前端デキュー"""
return self.pop(True)
def pop_last(self) -> int:
"""後端デキュー"""
return self.pop(False)
def peek_first(self) -> int:
"""前端要素にアクセス"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
return self._front.val
def peek_last(self) -> int:
"""後端要素にアクセス"""
if self.is_empty():
raise IndexError("Double-ended queue is empty")
return self._rear.val
def to_array(self) -> list[int]:
"""出力用の配列を返す"""
node = self._front
res = [0] * self.size()
for i in range(self.size()):
res[i] = node.val
node = node.next
return res
"""Driver Code"""
if __name__ == "__main__":
# 双端キューを初期化
deque = LinkedListDeque()
deque.push_last(3)
deque.push_last(2)
deque.push_last(5)
print("双端キュー deque =", deque.to_array())
# 要素にアクセス
peek_first: int = deque.peek_first()
print("前端要素 peek_first =", peek_first)
peek_last: int = deque.peek_last()
print("後端要素 peek_last =", peek_last)
# 要素をエンキュー
deque.push_last(4)
print("要素 4 を後端エンキュー、deque =", deque.to_array())
deque.push_first(1)
print("要素 1 を前端エンキュー、deque =", deque.to_array())
# 要素をデキュー
pop_last: int = deque.pop_last()
print("後端でデキューされた要素 =", pop_last, "、後端デキュー後の deque =", deque.to_array())
pop_first: int = deque.pop_first()
print("前端でデキューされた要素 =", pop_first, "、前端デキュー後の deque =", deque.to_array())
# 双端キューの長さを取得
size: int = deque.size()
print("双端キューの長さ size =", size)
# 双端キューが空かどうかを判定
is_empty: bool = deque.is_empty()
print("双端キューが空かどうか =", is_empty)

View File

@@ -0,0 +1,97 @@
"""
File: linkedlist_queue.py
Created Time: 2022-12-01
Author: Peng Chen (pengchzn@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode
class LinkedListQueue:
"""連結リストベースのキュークラス"""
def __init__(self):
"""コンストラクタ"""
self._front: ListNode | None = None # ヘッドノード front
self._rear: ListNode | None = None # テールノード rear
self._size: int = 0
def size(self) -> int:
"""キューの長さを取得"""
return self._size
def is_empty(self) -> bool:
"""キューが空かどうかを判定"""
return self._size == 0
def push(self, num: int):
"""エンキュー"""
# テールノードの後ろに num を追加
node = ListNode(num)
# キューが空の場合、ヘッドとテールノードの両方をそのノードに向ける
if self._front is None:
self._front = node
self._rear = node
# キューが空でない場合、そのノードをテールノードの後ろに追加
else:
self._rear.next = node
self._rear = node
self._size += 1
def pop(self) -> int:
"""デキュー"""
num = self.peek()
# ヘッドノードを削除
self._front = self._front.next
self._size -= 1
return num
def peek(self) -> int:
"""フロント要素にアクセス"""
if self.is_empty():
raise IndexError("Queue is empty")
return self._front.val
def to_list(self) -> list[int]:
"""出力用のリストに変換"""
queue = []
temp = self._front
while temp:
queue.append(temp.val)
temp = temp.next
return queue
"""Driver Code"""
if __name__ == "__main__":
# キューを初期化
queue = LinkedListQueue()
# 要素をエンキュー
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
print("キュー queue =", queue.to_list())
# フロント要素にアクセス
peek: int = queue.peek()
print("フロント要素 front =", peek)
# 要素をデキュー
pop_front: int = queue.pop()
print("デキューされた要素 pop =", pop_front)
print("デキュー後のキュー =", queue.to_list())
# キューの長さを取得
size: int = queue.size()
print("キューの長さ size =", size)
# キューが空かどうかを判定
is_empty: bool = queue.is_empty()
print("キューが空かどうか =", is_empty)

View File

@@ -0,0 +1,89 @@
"""
File: linkedlist_stack.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import ListNode
class LinkedListStack:
"""連結リストベースのスタッククラス"""
def __init__(self):
"""コンストラクタ"""
self._peek: ListNode | None = None
self._size: int = 0
def size(self) -> int:
"""スタックの長さを取得"""
return self._size
def is_empty(self) -> bool:
"""スタックが空かどうかを判定"""
return self._size == 0
def push(self, val: int):
"""プッシュ"""
node = ListNode(val)
node.next = self._peek
self._peek = node
self._size += 1
def pop(self) -> int:
"""ポップ"""
num = self.peek()
self._peek = self._peek.next
self._size -= 1
return num
def peek(self) -> int:
"""スタックトップ要素にアクセス"""
if self.is_empty():
raise IndexError("Stack is empty")
return self._peek.val
def to_list(self) -> list[int]:
"""出力用のリストに変換"""
arr = []
node = self._peek
while node:
arr.append(node.val)
node = node.next
arr.reverse()
return arr
"""Driver Code"""
if __name__ == "__main__":
# スタックを初期化
stack = LinkedListStack()
# 要素をプッシュ
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
print("スタック stack =", stack.to_list())
# スタックトップ要素にアクセス
peek: int = stack.peek()
print("スタックトップ要素 peek =", peek)
# 要素をポップ
pop: int = stack.pop()
print("ポップされた要素 pop =", pop)
print("ポップ後のスタック =", stack.to_list())
# スタックの長さを取得
size: int = stack.size()
print("スタックの長さ size =", size)
# 空かどうかを判定
is_empty: bool = stack.is_empty()
print("スタックが空かどうか =", is_empty)

View File

@@ -0,0 +1,39 @@
"""
File: queue.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
from collections import deque
"""Driver Code"""
if __name__ == "__main__":
# キューを初期化
# Pythonでは、一般的にdequeクラスをキューとして考えます
# queue.Queue()は純粋なキュークラスですが、あまりユーザーフレンドリーではありません
que: deque[int] = deque()
# 要素をエンキュー
que.append(1)
que.append(3)
que.append(2)
que.append(5)
que.append(4)
print("キュー que =", que)
# フロント要素にアクセス
front: int = que[0]
print("フロント要素 front =", front)
# 要素をデキュー
pop: int = que.popleft()
print("デキューされた要素 pop =", pop)
print("デキュー後のキュー =", que)
# キューの長さを取得
size: int = len(que)
print("キューの長さ size =", size)
# キューが空かどうかを判定
is_empty: bool = len(que) == 0
print("キューが空かどうか =", is_empty)

View File

@@ -0,0 +1,36 @@
"""
File: stack.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
"""Driver Code"""
if __name__ == "__main__":
# スタックを初期化
# Pythonには組み込みのスタッククラスはありませんが、リストをスタックとして使用できます
stack: list[int] = []
# 要素をプッシュ
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
print("スタック stack =", stack)
# スタックトップ要素にアクセス
peek: int = stack[-1]
print("スタックトップ要素 peek =", peek)
# 要素をポップ
pop: int = stack.pop()
print("ポップされた要素 pop =", pop)
print("ポップ後のスタック =", stack)
# スタックの長さを取得
size: int = len(stack)
print("スタックの長さ size =", size)
# 空かどうかを判定
is_empty: bool = len(stack) == 0
print("スタックが空かどうか =", is_empty)

View File

@@ -0,0 +1,119 @@
"""
File: array_binary_tree.py
Created Time: 2023-07-19
Author: krahets (krahets@163.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, list_to_tree, print_tree
class ArrayBinaryTree:
"""配列ベースの二分木クラス"""
def __init__(self, arr: list[int | None]):
"""コンストラクタ"""
self._tree = list(arr)
def size(self):
"""リストの容量"""
return len(self._tree)
def val(self, i: int) -> int | None:
"""インデックスiのードの値を取得"""
# インデックスが範囲外の場合、Noneを返し、空席を表す
if i < 0 or i >= self.size():
return None
return self._tree[i]
def left(self, i: int) -> int | None:
"""インデックスiのードの左の子のインデックスを取得"""
return 2 * i + 1
def right(self, i: int) -> int | None:
"""インデックスiのードの右の子のインデックスを取得"""
return 2 * i + 2
def parent(self, i: int) -> int | None:
"""インデックスiのードの親のインデックスを取得"""
return (i - 1) // 2
def level_order(self) -> list[int]:
"""レベル順走査"""
self.res = []
# 配列を走査
for i in range(self.size()):
if self.val(i) is not None:
self.res.append(self.val(i))
return self.res
def dfs(self, i: int, order: str):
"""深さ優先走査"""
if self.val(i) is None:
return
# 前順走査
if order == "pre":
self.res.append(self.val(i))
self.dfs(self.left(i), order)
# 中順走査
if order == "in":
self.res.append(self.val(i))
self.dfs(self.right(i), order)
# 後順走査
if order == "post":
self.res.append(self.val(i))
def pre_order(self) -> list[int]:
"""前順走査"""
self.res = []
self.dfs(0, order="pre")
return self.res
def in_order(self) -> list[int]:
"""中順走査"""
self.res = []
self.dfs(0, order="in")
return self.res
def post_order(self) -> list[int]:
"""後順走査"""
self.res = []
self.dfs(0, order="post")
return self.res
"""ドライバコード"""
if __name__ == "__main__":
# 二分木を初期化
# 特定の関数を使用して配列を二分木に変換
arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
root = list_to_tree(arr)
print("\n二分木を初期化\n")
print("二分木の配列表現:")
print(arr)
print("二分木の連結リスト表現:")
print_tree(root)
# 配列ベースの二分木クラス
abt = ArrayBinaryTree(arr)
# ノードにアクセス
i = 1
l, r, p = abt.left(i), abt.right(i), abt.parent(i)
print(f"\n現在のノードのインデックスは {i}、値は {abt.val(i)}")
print(f"その左の子ノードのインデックスは {l}、値は {abt.val(l)}")
print(f"その右の子ノードのインデックスは {r}、値は {abt.val(r)}")
print(f"その親ノードのインデックスは {p}、値は {abt.val(p)}")
# 木を走査
res = abt.level_order()
print("\nレベル順走査:", res)
res = abt.pre_order()
print("前順走査:", res)
res = abt.in_order()
print("中順走査:", res)
res = abt.post_order()
print("後順走査:", res)

View File

@@ -0,0 +1,200 @@
"""
File: avl_tree.py
Created Time: 2022-12-20
Author: a16su (lpluls001@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
class AVLTree:
"""AVL木"""
def __init__(self):
"""コンストラクタ"""
self._root = None
def get_root(self) -> TreeNode | None:
"""二分木のルートノードを取得"""
return self._root
def height(self, node: TreeNode | None) -> int:
"""ノードの高さを取得"""
# 空ノードの高さは-1、葉ードの高さは0
if node is not None:
return node.height
return -1
def update_height(self, node: TreeNode | None):
"""ノードの高さを更新"""
# ノードの高さ = 最も高い部分木の高さ + 1
node.height = max([self.height(node.left), self.height(node.right)]) + 1
def balance_factor(self, node: TreeNode | None) -> int:
"""バランス因子を取得"""
# 空ードのバランス因子は0
if node is None:
return 0
# ノードのバランス因子 = 左部分木の高さ - 右部分木の高さ
return self.height(node.left) - self.height(node.right)
def right_rotate(self, node: TreeNode | None) -> TreeNode | None:
"""右回転操作"""
child = node.left
grand_child = child.right
# childを中心にnodeを右に回転
child.right = node
node.left = grand_child
# ノードの高さを更新
self.update_height(node)
self.update_height(child)
# 回転後の部分木のルートを返す
return child
def left_rotate(self, node: TreeNode | None) -> TreeNode | None:
"""左回転操作"""
child = node.right
grand_child = child.left
# childを中心にnodeを左に回転
child.left = node
node.right = grand_child
# ノードの高さを更新
self.update_height(node)
self.update_height(child)
# 回転後の部分木のルートを返す
return child
def rotate(self, node: TreeNode | None) -> TreeNode | None:
"""回転操作を実行して部分木のバランスを復元"""
# nodeのバランス因子を取得
balance_factor = self.balance_factor(node)
# 左偏り木
if balance_factor > 1:
if self.balance_factor(node.left) >= 0:
# 右回転
return self.right_rotate(node)
else:
# 左回転してから右回転
node.left = self.left_rotate(node.left)
return self.right_rotate(node)
# 右偏り木
elif balance_factor < -1:
if self.balance_factor(node.right) <= 0:
# 左回転
return self.left_rotate(node)
else:
# 右回転してから左回転
node.right = self.right_rotate(node.right)
return self.left_rotate(node)
# バランスの取れた木、回転不要、戻る
return node
def insert(self, val):
"""ノードを挿入"""
self._root = self.insert_helper(self._root, val)
def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode:
"""再帰的にノードを挿入(ヘルパーメソッド)"""
if node is None:
return TreeNode(val)
# 1. 挿入位置を見つけてノードを挿入
if val < node.val:
node.left = self.insert_helper(node.left, val)
elif val > node.val:
node.right = self.insert_helper(node.right, val)
else:
# 重複ノードは挿入しない、戻る
return node
# ノードの高さを更新
self.update_height(node)
# 2. 回転操作を実行して部分木のバランスを復元
return self.rotate(node)
def remove(self, val: int):
"""ノードを削除"""
self._root = self.remove_helper(self._root, val)
def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None:
"""再帰的にノードを削除(ヘルパーメソッド)"""
if node is None:
return None
# 1. ノードを見つけて削除
if val < node.val:
node.left = self.remove_helper(node.left, val)
elif val > node.val:
node.right = self.remove_helper(node.right, val)
else:
if node.left is None or node.right is None:
child = node.left or node.right
# 子ノード数 = 0、ードを削除して戻る
if child is None:
return None
# 子ノード数 = 1、ードを削除
else:
node = child
else:
# 子ノード数 = 2、中順走査の次のードを削除し、それで現在のードを置き換え
temp = node.right
while temp.left is not None:
temp = temp.left
node.right = self.remove_helper(node.right, temp.val)
node.val = temp.val
# ノードの高さを更新
self.update_height(node)
# 2. 回転操作を実行して部分木のバランスを復元
return self.rotate(node)
def search(self, val: int) -> TreeNode | None:
"""ノードを探索"""
cur = self._root
# ループで探索、葉ノードを通過した後にブレーク
while cur is not None:
# ターゲットードはcurの右部分木にある
if cur.val < val:
cur = cur.right
# ターゲットードはcurの左部分木にある
elif cur.val > val:
cur = cur.left
# ターゲットノードを発見、ループをブレーク
else:
break
# ターゲットノードを返す
return cur
"""ドライバコード"""
if __name__ == "__main__":
def test_insert(tree: AVLTree, val: int):
tree.insert(val)
print("\nノード {} を挿入後、AVL木は".format(val))
print_tree(tree.get_root())
def test_remove(tree: AVLTree, val: int):
tree.remove(val)
print("\nノード {} を削除後、AVL木は".format(val))
print_tree(tree.get_root())
# 空のAVL木を初期化
avl_tree = AVLTree()
# ノードを挿入
# AVL木がード挿入後にバランスを維持する様子に注目
for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]:
test_insert(avl_tree, val)
# 重複ノードを挿入
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)
print("\n発見されたノードオブジェクト: {}、ノードの値 = {}".format(result_node, result_node.val))

View File

@@ -0,0 +1,146 @@
"""
File: binary_search_tree.py
Created Time: 2022-12-20
Author: a16su (lpluls001@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
class BinarySearchTree:
"""二分探索木"""
def __init__(self):
"""コンストラクタ"""
# 空の木を初期化
self._root = None
def get_root(self) -> TreeNode | None:
"""二分木のルートノードを取得"""
return self._root
def search(self, num: int) -> TreeNode | None:
"""ノードを探索"""
cur = self._root
# ループで探索、葉ノードを通過した後にブレーク
while cur is not None:
# ターゲットードはcurの右部分木にある
if cur.val < num:
cur = cur.right
# ターゲットードはcurの左部分木にある
elif cur.val > num:
cur = cur.left
# ターゲットノードを発見、ループをブレーク
else:
break
return cur
def insert(self, num: int):
"""ノードを挿入"""
# 木が空の場合、ルートノードを初期化
if self._root is None:
self._root = TreeNode(num)
return
# ループで探索、葉ノードを通過した後にブレーク
cur, pre = self._root, None
while cur is not None:
# 重複ノードを発見したため、戻る
if cur.val == num:
return
pre = cur
# 挿入位置はcurの右部分木にある
if cur.val < num:
cur = cur.right
# 挿入位置はcurの左部分木にある
else:
cur = cur.left
# ノードを挿入
node = TreeNode(num)
if pre.val < num:
pre.right = node
else:
pre.left = node
def remove(self, num: int):
"""ノードを削除"""
# 木が空の場合、戻る
if self._root is None:
return
# ループで探索、葉ノードを通過した後にブレーク
cur, pre = self._root, None
while cur is not None:
# 削除するノードを発見、ループをブレーク
if cur.val == num:
break
pre = cur
# 削除するードはcurの右部分木にある
if cur.val < num:
cur = cur.right
# 削除するードはcurの左部分木にある
else:
cur = cur.left
# 削除するノードが存在しない場合、戻る
if cur is None:
return
# 子ノード数 = 0 または 1
if cur.left is None or cur.right is None:
# 子ノード数 = 0/1の場合、child = null/その子ノード
child = cur.left or cur.right
# ードcurを削除
if cur != self._root:
if pre.left == cur:
pre.left = child
else:
pre.right = child
else:
# 削除されるノードがルートの場合、ルートを再割り当て
self._root = child
# 子ノード数 = 2
else:
# curの中順走査の次のードを取得
tmp: TreeNode = cur.right
while tmp.left is not None:
tmp = tmp.left
# 再帰的にードtmpを削除
self.remove(tmp.val)
# curをtmpで置き換え
cur.val = tmp.val
"""ドライバコード"""
if __name__ == "__main__":
# 二分探索木を初期化
bst = BinarySearchTree()
nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]
# 注意:異なる挿入順序により、様々な木構造が生成される可能性がある。この特定のシーケンスは完全二分木を作成する
for num in nums:
bst.insert(num)
print("\n初期化された二分木は\n")
print_tree(bst.get_root())
# ノードを探索
node = bst.search(7)
print("\n発見されたノードオブジェクト: {}, ノードの値 = {}".format(node, node.val))
# ノードを挿入
bst.insert(16)
print("\nード16を挿入後の二分木は\n")
print_tree(bst.get_root())
# ノードを削除
bst.remove(1)
print("\nード1を削除後の二分木は\n")
print_tree(bst.get_root())
bst.remove(2)
print("\nード2を削除後の二分木は\n")
print_tree(bst.get_root())
bst.remove(4)
print("\nード4を削除後の二分木は\n")
print_tree(bst.get_root())

View File

@@ -0,0 +1,41 @@
"""
File: binary_tree.py
Created Time: 2022-12-20
Author: a16su (lpluls001@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
"""ドライバコード"""
if __name__ == "__main__":
# 二分木を初期化
# ノードを初期化
n1 = TreeNode(val=1)
n2 = TreeNode(val=2)
n3 = TreeNode(val=3)
n4 = TreeNode(val=4)
n5 = TreeNode(val=5)
# ノードの参照(ポインタ)を構築
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
print("\n二分木を初期化\n")
print_tree(n1)
# ノードの挿入と削除
P = TreeNode(0)
# ードPを n1 -> n2 の間に挿入
n1.left = P
P.left = n2
print("\nードPを挿入後\n")
print_tree(n1)
# ノードを削除
n1.left = n2
print("\nードPを削除後\n")
print_tree(n1)

View File

@@ -0,0 +1,42 @@
"""
File: binary_tree_bfs.py
Created Time: 2022-12-20
Author: a16su (lpluls001@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, list_to_tree, print_tree
from collections import deque
def level_order(root: TreeNode | None) -> list[int]:
"""レベル順走査"""
# キューを初期化し、ルートノードを追加
queue: deque[TreeNode] = deque()
queue.append(root)
# 走査シーケンスを格納するリストを初期化
res = []
while queue:
node: TreeNode = queue.popleft() # キューからデキュー
res.append(node.val) # ノードの値を保存
if node.left is not None:
queue.append(node.left) # 左の子ノードをエンキュー
if node.right is not None:
queue.append(node.right) # 右の子ノードをエンキュー
return res
"""ドライバコード"""
if __name__ == "__main__":
# 二分木を初期化
# 特定の関数を使用して配列を二分木に変換
root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7])
print("\n二分木を初期化\n")
print_tree(root)
# レベル順走査
res: list[int] = level_order(root)
print("\nレベル順走査のノードシーケンスを出力 = ", res)

View File

@@ -0,0 +1,65 @@
"""
File: binary_tree_dfs.py
Created Time: 2022-12-20
Author: a16su (lpluls001@gmail.com)
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, list_to_tree, print_tree
def pre_order(root: TreeNode | None):
"""前順走査"""
if root is None:
return
# 訪問順序: ルートノード -> 左部分木 -> 右部分木
res.append(root.val)
pre_order(root=root.left)
pre_order(root=root.right)
def in_order(root: TreeNode | None):
"""中順走査"""
if root is None:
return
# 訪問順序: 左部分木 -> ルートノード -> 右部分木
in_order(root=root.left)
res.append(root.val)
in_order(root=root.right)
def post_order(root: TreeNode | None):
"""後順走査"""
if root is None:
return
# 訪問順序: 左部分木 -> 右部分木 -> ルートノード
post_order(root=root.left)
post_order(root=root.right)
res.append(root.val)
"""ドライバコード"""
if __name__ == "__main__":
# 二分木を初期化
# 特定の関数を使用して配列を二分木に変換
root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7])
print("\n二分木を初期化\n")
print_tree(root)
# 前順走査
res = []
pre_order(root)
print("\n前順走査のノードシーケンスを出力 = ", res)
# 中順走査
res.clear()
in_order(root)
print("\n中順走査のノードシーケンスを出力 = ", res)
# 後順走査
res.clear()
post_order(root)
print("\n後順走査のノードシーケンスを出力 = ", res)

View File

@@ -0,0 +1,19 @@
# PEP 585に従う - 標準コレクションでの型ヒント
# https://peps.python.org/pep-0585/
from __future__ import annotations
# 共通ライブラリをここでインポートして、`from module import *`でコードを簡潔にする
from .list_node import (
ListNode,
list_to_linked_list,
linked_list_to_list,
)
from .tree_node import TreeNode, list_to_tree, tree_to_list
from .vertex import Vertex, vals_to_vets, vets_to_vals
from .print_util import (
print_matrix,
print_linked_list,
print_tree,
print_dict,
print_heap,
)

View File

@@ -0,0 +1,32 @@
"""
File: list_node.py
Created Time: 2021-12-11
Author: krahets (krahets@163.com)
"""
class ListNode:
"""連結リストのノードクラス"""
def __init__(self, val: int):
self.val: int = val # ノードの値
self.next: ListNode | None = None # 後続ノードへの参照
def list_to_linked_list(arr: list[int]) -> ListNode | None:
"""リストを連結リストにデシリアライズ"""
dum = head = ListNode(0)
for a in arr:
node = ListNode(a)
head.next = node
head = head.next
return dum.next
def linked_list_to_list(head: ListNode | None) -> list[int]:
"""連結リストをリストにシリアライズ"""
arr: list[int] = []
while head:
arr.append(head.val)
head = head.next
return arr

View File

@@ -0,0 +1,81 @@
"""
File: print_util.py
Created Time: 2021-12-11
Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com)
"""
from .tree_node import TreeNode, list_to_tree
from .list_node import ListNode, linked_list_to_list
def print_matrix(mat: list[list[int]]):
"""行列を出力"""
s = []
for arr in mat:
s.append(" " + str(arr))
print("[\n" + ",\n".join(s) + "\n]")
def print_linked_list(head: ListNode | None):
"""連結リストを出力"""
arr: list[int] = linked_list_to_list(head)
print(" -> ".join([str(a) for a in arr]))
class Trunk:
def __init__(self, prev, string: str | None = None):
self.prev = prev
self.str = string
def show_trunks(p: Trunk | None):
if p is None:
return
show_trunks(p.prev)
print(p.str, end="")
def print_tree(
root: TreeNode | None, prev: Trunk | None = None, is_right: bool = False
):
"""
二分木を出力
この木プリンタはTECHIE DELIGHTから借用
https://www.techiedelight.com/c-program-print-binary-tree/
"""
if root is None:
return
prev_str = " "
trunk = Trunk(prev, prev_str)
print_tree(root.right, trunk, True)
if prev is None:
trunk.str = "———"
elif is_right:
trunk.str = "/———"
prev_str = " |"
else:
trunk.str = "\———"
prev.str = prev_str
show_trunks(trunk)
print(" " + str(root.val))
if prev:
prev.str = prev_str
trunk.str = " |"
print_tree(root.left, trunk, False)
def print_dict(hmap: dict):
"""辞書を出力"""
for key, value in hmap.items():
print(key, "->", value)
def print_heap(heap: list[int]):
"""ヒープを出力"""
print("ヒープの配列表現:", heap)
print("ヒープの木表現:")
root: TreeNode | None = list_to_tree(heap)
print_tree(root)

View File

@@ -0,0 +1,69 @@
"""
File: tree_node.py
Created Time: 2021-12-11
Author: krahets (krahets@163.com)
"""
from collections import deque
class TreeNode:
"""二分木のノードクラス"""
def __init__(self, val: int = 0):
self.val: int = val # ノードの値
self.height: int = 0 # ノードの高さ
self.left: TreeNode | None = None # 左の子ノードへの参照
self.right: TreeNode | None = None # 右の子ノードへの参照
# シリアライゼーションのエンコーディングルールについては、以下を参照:
# https://www.hello-algo.com/chapter_tree/array_representation_of_tree/
# 二分木の配列表現:
# [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
# 二分木の連結リスト表現:
# /——— 15
# /——— 7
# /——— 3
# | \——— 6
# | \——— 12
# ——— 1
# \——— 2
# | /——— 9
# \——— 4
# \——— 8
def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None:
"""リストを二分木にデシリアライズ: 再帰的"""
# インデックスが配列の境界外、または対応する要素がNoneの場合、Noneを返す
if i < 0 or i >= len(arr) or arr[i] is None:
return None
# 現在のノードを構築
root = TreeNode(arr[i])
# 左右の部分木を再帰的に構築
root.left = list_to_tree_dfs(arr, 2 * i + 1)
root.right = list_to_tree_dfs(arr, 2 * i + 2)
return root
def list_to_tree(arr: list[int]) -> TreeNode | None:
"""リストを二分木にデシリアライズ"""
return list_to_tree_dfs(arr, 0)
def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]:
"""二分木をリストにシリアライズ: 再帰的"""
if root is None:
return
if i >= len(res):
res += [None] * (i - len(res) + 1)
res[i] = root.val
tree_to_list_dfs(root.left, 2 * i + 1, res)
tree_to_list_dfs(root.right, 2 * i + 2, res)
def tree_to_list(root: TreeNode | None) -> list[int]:
"""二分木をリストにシリアライズ"""
res = []
tree_to_list_dfs(root, 0, res)
return res

View File

@@ -0,0 +1,20 @@
# File: vertex.py
# Created Time: 2023-02-23
# Author: krahets (krahets@163.com)
class Vertex:
"""頂点クラス"""
def __init__(self, val: int):
self.val = val
def vals_to_vets(vals: list[int]) -> list["Vertex"]:
"""値のリストvalsを入力し、頂点のリストvetsを返す"""
return [Vertex(val) for val in vals]
def vets_to_vals(vets: list["Vertex"]) -> list[int]:
"""頂点のリストvetsを入力し、値のリストvalsを返す"""
return [vet.val for vet in vets]

View File

@@ -0,0 +1,33 @@
import os
import glob
import subprocess
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
if __name__ == "__main__":
# ソースコードファイルを検索
src_paths = sorted(glob.glob("ja/codes/python/chapter_*/*.py"))
errors = []
# python コードを実行
for src_path in src_paths:
process = subprocess.Popen(
["python", src_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
env=env,
encoding='utf-8'
)
# プロセスの完了を待ち、出力とエラーメッセージを取得
stdout, stderr = process.communicate()
# 終了ステータスをチェック
exit_status = process.returncode
if exit_status != 0:
errors.append(stderr)
print(f"{len(src_paths)} ファイルをテストしました")
print(f"{len(errors)} ファイルで例外が見つかりました")
if len(errors) > 0:
raise RuntimeError("\n\n".join(errors))