feat: Traditional Chinese version (#1163)

* First commit

* Update mkdocs.yml

* Translate all the docs to traditional Chinese

* Translate the code files.

* Translate the docker file

* Fix mkdocs.yml

* Translate all the figures from SC to TC

* 二叉搜尋樹 -> 二元搜尋樹

* Update terminology.

* Update terminology

* 构造函数/构造方法 -> 建構子
异或 -> 互斥或

* 擴充套件 -> 擴展

* constant - 常量 - 常數

* 類	-> 類別

* AVL -> AVL 樹

* 數組 -> 陣列

* 係統 -> 系統
斐波那契數列 -> 費波那契數列
運算元量 -> 運算量
引數 -> 參數

* 聯絡 -> 關聯

* 麵試 -> 面試

* 面向物件 -> 物件導向
歸併排序 -> 合併排序
范式 -> 範式

* Fix 算法 -> 演算法

* 錶示 -> 表示
反碼 -> 一補數
補碼 -> 二補數
列列尾部 -> 佇列尾部
區域性性 -> 區域性
一摞 -> 一疊

* Synchronize with main branch

* 賬號 -> 帳號
推匯 -> 推導

* Sync with main branch

* First commit

* Update mkdocs.yml

* Translate all the docs to traditional Chinese

* Translate the code files.

* Translate the docker file

* Fix mkdocs.yml

* Translate all the figures from SC to TC

* 二叉搜尋樹 -> 二元搜尋樹

* Update terminology

* 构造函数/构造方法 -> 建構子
异或 -> 互斥或

* 擴充套件 -> 擴展

* constant - 常量 - 常數

* 類	-> 類別

* AVL -> AVL 樹

* 數組 -> 陣列

* 係統 -> 系統
斐波那契數列 -> 費波那契數列
運算元量 -> 運算量
引數 -> 參數

* 聯絡 -> 關聯

* 麵試 -> 面試

* 面向物件 -> 物件導向
歸併排序 -> 合併排序
范式 -> 範式

* Fix 算法 -> 演算法

* 錶示 -> 表示
反碼 -> 一補數
補碼 -> 二補數
列列尾部 -> 佇列尾部
區域性性 -> 區域性
一摞 -> 一疊

* Synchronize with main branch

* 賬號 -> 帳號
推匯 -> 推導

* Sync with main branch

* Update terminology.md

* 操作数量(num. of operations)-> 操作數量

* 字首和->前綴和

* Update figures

* 歸 -> 迴
記憶體洩漏 -> 記憶體流失

* Fix the bug of the file filter

* 支援 -> 支持
Add zh-Hant/README.md

* Add the zh-Hant chapter covers.
Bug fixes.

* 外掛 -> 擴充功能

* Add the landing page for zh-Hant version

* Unify the font of the chapter covers for the zh, en, and zh-Hant version

* Move zh-Hant/ to zh-hant/

* Translate terminology.md to traditional Chinese
This commit is contained in:
Yudong Jin
2024-04-06 02:30:11 +08:00
committed by GitHub
parent 33d7f8a2e5
commit 5f7385c8a3
1875 changed files with 102923 additions and 18 deletions
+1
View File
@@ -0,0 +1 @@
__pycache__
@@ -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 是動態陣列,可以直接擴展
# 為了方便學習,本函式將 list 看作長度不可變的陣列
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 以及之後的所有元素向後移動一位
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 之後的所有元素向前移動一位
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)
@@ -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))
@@ -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
# 拼接兩個串列
nums1 = [6, 8, 7, 10, 9]
nums += nums1
print("\n將串列 nums1 拼接到 nums 之後,得到 nums =", nums)
# 排序串列
nums.sort()
print("\n排序串列後 nums =", nums)
@@ -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("索引越界")
return self._arr[index]
def set(self, num: int, index: int):
"""更新元素"""
if index < 0 or index >= self._size:
raise IndexError("索引越界")
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("索引越界")
# 元素數量超出容量時,觸發擴容機制
if self._size == self.capacity():
self.extend_capacity()
# 將索引 index 以及之後的元素都向後移動一位
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("索引越界")
num = self._arr[index]
# 將索引 index 之後的元素都向前移動一位
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()}")
@@ -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
"""Driver Code"""
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)
@@ -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
"""Driver Code"""
if __name__ == "__main__":
nums = [1, 2, 3]
res = permutations_i(nums)
print(f"輸入陣列 nums = {nums}")
print(f"所有排列 res = {res}")
@@ -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
"""Driver Code"""
if __name__ == "__main__":
nums = [1, 2, 2]
res = permutations_ii(nums)
print(f"輸入陣列 nums = {nums}")
print(f"所有排列 res = {res}")
@@ -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)
"""Driver Code"""
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])
@@ -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()
"""Driver Code"""
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])
@@ -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()
"""Driver Code"""
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])
@@ -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)
"""Driver Code"""
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])
@@ -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
"""Driver Code"""
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}")
@@ -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
"""Driver Code"""
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"請注意,該方法輸出的結果包含重複集合")
@@ -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
"""Driver Code"""
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}")
@@ -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 迴圈(兩次更新)"""
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 迴圈(兩次更新)求和結果 res = {res}")
res = nested_for_loop(n)
print(f"\n雙層 for 迴圈的走訪結果 {res}")
@@ -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}")
@@ -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 長度為 n, n-1, ..., 2, 1
nums = [0] * n
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)
@@ -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
# 細胞每輪一分為二,形成數列 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
"""Driver Code"""
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)
@@ -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)
@@ -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)
"""Driver Code"""
if __name__ == "__main__":
target = 6
nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]
# 二分搜尋(雙閉區間)
index = binary_search(nums, target)
print("目標元素 6 的索引 = ", index)
@@ -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 元素到索引的對映
inorder_map = {val: i for i, val in enumerate(inorder)}
root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1)
return root
"""Driver Code"""
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)
@@ -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 只剩下一個圓盤,則直接將其移到 tar
if i == 1:
move(src, tar)
return
# 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf
dfs(i - 1, src, tar, buf)
# 子問題 f(1) :將 src 剩餘一個圓盤移到 tar
move(src, tar)
# 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar
dfs(i - 1, buf, src, tar)
def solve_hanota(A: list[int], B: list[int], C: list[int]):
"""求解河內塔問題"""
n = len(A)
# 將 A 頂部 n 個圓盤藉助 B 移到 C
dfs(n, A, B, C)
"""Driver Code"""
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}")
@@ -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]
"""Driver Code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_backtrack(n)
print(f"{n} 階樓梯共有 {res} 種方案")
@@ -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]
"""Driver Code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_constraint_dp(n)
print(f"{n} 階樓梯共有 {res} 種方案")
@@ -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)
"""Driver Code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dfs(n)
print(f"{n} 階樓梯共有 {res} 種方案")
@@ -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)
"""Driver Code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dfs_mem(n)
print(f"{n} 階樓梯共有 {res} 種方案")
@@ -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
"""Driver Code"""
if __name__ == "__main__":
n = 9
res = climbing_stairs_dp(n)
print(f"{n} 階樓梯共有 {res} 種方案")
res = climbing_stairs_dp_comp(n)
print(f"{n} 階樓梯共有 {res} 種方案")
@@ -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
"""Driver Code"""
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}")
@@ -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]
"""Driver Code"""
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}")
@@ -0,0 +1,123 @@
"""
File: edit_distancde.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
# 若兩字元相等,則直接跳過此兩字元
if s[i - 1] == t[j - 1]:
return edit_distance_dfs(s, t, i - 1, j - 1)
# 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1
insert = edit_distance_dfs(s, t, i, j - 1)
delete = edit_distance_dfs(s, t, i - 1, j)
replace = edit_distance_dfs(s, t, i - 1, j - 1)
# 返回最少編輯步數
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]
# 若兩字元相等,則直接跳過此兩字元
if s[i - 1] == t[j - 1]:
return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1)
# 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1
insert = edit_distance_dfs_mem(s, t, mem, i, j - 1)
delete = edit_distance_dfs_mem(s, t, mem, i - 1, j)
replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1)
# 記錄並返回最少編輯步數
mem[i][j] = 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]:
# 若兩字元相等,則直接跳過此兩字元
dp[i][j] = dp[i - 1][j - 1]
else:
# 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 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]:
# 若兩字元相等,則直接跳過此兩字元
dp[j] = leftup
else:
# 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1
dp[j] = min(dp[j - 1], dp[j], leftup) + 1
leftup = temp # 更新為下一輪的 dp[i-1, j-1]
return dp[m]
"""Driver Code"""
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}")
@@ -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]
# 返回兩種方案中價值更大的那一個
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]
# 記錄並返回兩種方案中價值更大的那一個
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]
"""Driver Code"""
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}")
@@ -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
"""Driver Code"""
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}")
@@ -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]
"""Driver Code"""
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}")
@@ -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]
"""Driver Code"""
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}")
@@ -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]]):
"""建構子"""
# 鄰接表,key:頂點,value:該頂點的所有鄰接頂點
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},")
"""Driver Code"""
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()
@@ -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 元素代表頂點索引,即對應 vertices 元素索引
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 對應 vertices 元素索引
# 索引越界與相等處理
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 對應 vertices 元素索引
# 索引越界與相等處理
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)
"""Driver Code"""
if __name__ == "__main__":
# 初始化無向圖
# 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引
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()
@@ -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
"""Driver Code"""
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))
@@ -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
"""Driver Code"""
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))
@@ -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
"""Driver Code"""
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")
@@ -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:
"""分數背包:貪婪"""
# 建立物品串列,包含兩個屬性:重量、價值
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
"""Driver Code"""
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}")
@@ -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
# 迴圈貪婪選擇,直至兩板相遇
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
"""Driver Code"""
if __name__ == "__main__":
ht = [3, 8, 5, 2, 7, 7, 3, 4]
# 貪婪演算法
res = max_capacity(ht)
print(f"最大容量為 {res}")
@@ -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))
"""Driver Code"""
if __name__ == "__main__":
n = 58
# 貪婪演算法
res = max_product_cutting(n)
print(f"最大切分乘積為 {res}")
@@ -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, "小哈")
hmap.put(15937, "小囉")
hmap.put(16750, "小算")
hmap.put(13276, "小法")
hmap.put(10583, "小鴨")
print("\n新增完成後,雜湊表為\nKey -> Value")
hmap.print()
# 查詢操作
# 向雜湊表中輸入鍵 key ,得到值 value
name = hmap.get(15937)
print("\n輸入學號 15937 ,查詢到姓名 " + name)
# 刪除操作
# 在雜湊表中刪除鍵值對 (key, value)
hmap.remove(10583)
print("\n刪除 10583 後,雜湊表為\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)
@@ -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 演算法"
hash_str = hash(str)
print(f"字串 {str} 的雜湊值為 {hash_str}")
tup = (12836, "小哈")
hash_tup = hash(tup)
print(f"元組 {tup} 的雜湊值為 {hash(hash_tup)}")
obj = ListNode(0)
hash_obj = hash(obj)
print(f"節點物件 {obj} 的雜湊值為 {hash_obj}")
@@ -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] = "小哈"
hmap[15937] = "小囉"
hmap[16750] = "小算"
hmap[13276] = "小法"
hmap[10583] = "小鴨"
print("\n新增完成後,雜湊表為\nKey -> Value")
print_dict(hmap)
# 查詢操作
# 向雜湊表中輸入鍵 key ,得到值 value
name: str = hmap[15937]
print("\n輸入學號 15937 ,查詢到姓名 " + name)
# 刪除操作
# 在雜湊表中刪除鍵值對 (key, value)
hmap.pop(10583)
print("\n刪除 10583 後,雜湊表為\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)
@@ -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]
# 走訪桶,若找到 key ,則返回對應 val
for pair in bucket:
if pair.key == key:
return pair.val
# 若未找到 key ,則返回 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]
# 走訪桶,若遇到指定 key ,則更新對應 val 並返回
for pair in bucket:
if pair.key == key:
pair.val = val
return
# 若無該 key ,則將鍵值對新增至尾部
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, "小哈")
hashmap.put(15937, "小囉")
hashmap.put(16750, "小算")
hashmap.put(13276, "小法")
hashmap.put(10583, "小鴨")
print("\n新增完成後,雜湊表為\n[Key1 -> Value1, Key2 -> Value2, ...]")
hashmap.print()
# 查詢操作
# 向雜湊表中輸入鍵 key ,得到值 value
name = hashmap.get(13276)
print("\n輸入學號 13276 ,查詢到姓名 " + name)
# 刪除操作
# 在雜湊表中刪除鍵值對 (key, value)
hashmap.remove(12836)
print("\n刪除 12836 後,雜湊表為\n[Key1 -> Value1, Key2 -> Value2, ...]")
hashmap.print()
@@ -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:
# 若遇到 key ,返回對應的桶索引
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
# 若 key 不存在,則返回新增點的索引
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, "小哈")
hashmap.put(15937, "小囉")
hashmap.put(16750, "小算")
hashmap.put(13276, "小法")
hashmap.put(10583, "小鴨")
print("\n新增完成後,雜湊表為\nKey -> Value")
hashmap.print()
# 查詢操作
# 向雜湊表中輸入鍵 key ,得到值 val
name = hashmap.get(13276)
print("\n輸入學號 13276 ,查詢到姓名 " + name)
# 刪除操作
# 在雜湊表中刪除鍵值對 (key, val)
hashmap.remove(16750)
print("\n刪除 16750 後,雜湊表為\nKey -> Value")
hashmap.print()
@@ -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:
"""互斥或雜湊"""
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 演算法"
hash = add_hash(key)
print(f"加法雜湊值為 {hash}")
hash = mul_hash(key)
print(f"乘法雜湊值為 {hash}")
hash = xor_hash(key)
print(f"互斥或雜湊值為 {hash}")
hash = rot_hash(key)
print(f"旋轉雜湊值為 {hash}")
+71
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])
"""Driver Code"""
if __name__ == "__main__":
# 初始化小頂堆積
min_heap, flag = [], 1
# 初始化大頂堆積
max_heap, flag = [], -1
print("\n以下測試樣例為大頂堆積")
# Python 的 heapq 模組預設實現小頂堆積
# 考慮將“元素取負”後再入堆積,這樣就可以將大小關係顛倒,從而實現大頂堆積
# 在本示例中,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)
@@ -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
# 交換兩節點
self.swap(i, p)
# 迴圈向上堆積化
i = p
def pop(self) -> int:
"""元素出堆積"""
# 判空處理
if self.is_empty():
raise IndexError("堆積為空")
# 交換根節點與最右葉節點(交換首元素與尾元素)
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
# 交換兩節點
self.swap(i, ma)
# 迴圈向下堆積化
i = ma
def print(self):
"""列印堆積(二元樹)"""
print_heap(self.max_heap)
"""Driver Code"""
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}")
@@ -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
"""Driver Code"""
if __name__ == "__main__":
nums = [1, 7, 6, 3, 2]
k = 3
res = top_k_heap(nums, k)
print(f"最大的 {k} 個元素為")
print_heap(res)
@@ -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) // 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) // 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
"""Driver Code"""
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)
@@ -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:
"""二分搜尋最左一個 target"""
# 等價於查詢 target 的插入點
i = binary_search_insertion(nums, target)
# 未找到 target ,返回 -1
if i == len(nums) or nums[i] != target:
return -1
# 找到 target ,返回索引 i
return i
def binary_search_right_edge(nums: list[int], target: int) -> int:
"""二分搜尋最右一個 target"""
# 轉化為查詢最左一個 target + 1
i = binary_search_insertion(nums, target + 1)
# j 指向最右一個 target ,i 指向首個大於 target 的元素
j = i - 1
# 未找到 target ,返回 -1
if j == -1 or nums[j] != target:
return -1
# 找到 target ,返回索引 j
return j
"""Driver Code"""
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}")
@@ -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) // 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 # 找到 target ,返回插入點 m
# 未找到 target ,返回插入點 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) // 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:
j = m - 1 # 首個小於 target 的元素在區間 [i, m-1] 中
# 返回插入點 i
return i
"""Driver Code"""
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}")
@@ -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:
"""雜湊查詢(陣列)"""
# 雜湊表的 key: 目標元素,value: 索引
# 若雜湊表中無此 key ,返回 -1
return hmap.get(target, -1)
def hashing_search_linkedlist(
hmap: dict[int, ListNode], target: int
) -> ListNode | None:
"""雜湊查詢(鏈結串列)"""
# 雜湊表的 key: 目標元素,value: 節點物件
# 若雜湊表中無此 key ,返回 None
return hmap.get(target, None)
"""Driver Code"""
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 # key: 元素,value: 索引
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 # key: 節點值,value: 節點
head = head.next
node: ListNode = hashing_search_linkedlist(map1, target)
print("目標節點值 3 的對應節點物件為", node)
@@ -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
"""Driver Code"""
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)
@@ -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 []
"""Driver Code"""
if __name__ == "__main__":
# ======= Test Case =======
nums = [2, 7, 11, 15]
target = 13
# ====== Driver Code ======
# 方法一
res: list[int] = two_sum_brute_force(nums, target)
print("方法一 res =", res)
# 方法二
res: list[int] = two_sum_hash_table(nums, target)
print("方法二 res =", res)
@@ -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 # 此輪“冒泡”未交換任何元素,直接跳出
"""Driver Code"""
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)
@@ -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)
@@ -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 是 num 在 res 中最後一次出現的索引
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]
"""Driver Code"""
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}")
@@ -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
# 交換兩節點
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)
"""Driver Code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
heap_sort(nums)
print("堆積排序完成後 nums =", nums)
@@ -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] 向右移動一位
j -= 1
nums[j + 1] = base # 將 base 賦值到正確位置
"""Driver Code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
insertion_sort(nums)
print("插入排序完成後 nums =", nums)
@@ -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) // 2 # 計算中點
merge_sort(nums, left, mid) # 遞迴左子陣列
merge_sort(nums, mid + 1, right) # 遞迴右子陣列
# 合併階段
merge(nums, left, mid, right)
"""Driver Code"""
if __name__ == "__main__":
nums = [7, 3, 2, 6, 0, 1, 5, 4]
merge_sort(nums, 0, len(nums) - 1)
print("合併排序完成後 nums =", nums)
@@ -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]
# 將基準數交換至兩子陣列的分界線
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:
"""選取三個候選元素的中位數"""
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]
# 將基準數交換至兩子陣列的分界線
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]
# 將基準數交換至兩子陣列的分界線
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)
# 對兩個子陣列中較短的那個執行快速排序
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]
"""Driver Code"""
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)
@@ -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)"""
# 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算
return (num // exp) % 10
def counting_sort_digit(nums: list[int], exp: int):
"""計數排序(根據 nums 第 k 位排序)"""
# 十進位制的位範圍為 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
"""Driver Code"""
if __name__ == "__main__":
# 基數排序
nums = [
10546151,
35663510,
42865989,
34862445,
81883077,
88906420,
72429244,
30524779,
82060337,
63832996,
]
radix_sort(nums)
print("基數排序完成後 nums =", nums)
@@ -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]
"""Driver Code"""
if __name__ == "__main__":
nums = [4, 1, 3, 1, 5, 2]
selection_sort(nums)
print("選擇排序完成後 nums =", nums)
@@ -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
# 佇列首指標向左移動一位
# 透過取餘操作實現 front 越過陣列頭部後回到尾部
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()
# 佇列首指標向後移動一位
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("雙向佇列為空")
return self._nums[self._front]
def peek_last(self) -> int:
"""訪問佇列尾元素"""
if self.is_empty():
raise IndexError("雙向佇列為空")
# 計算尾元素索引
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)
@@ -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("佇列已滿")
# 計算佇列尾指標,指向佇列尾索引 + 1
# 透過取餘操作實現 rear 越過陣列尾部後回到頭部
rear: int = (self._front + self._size) % self.capacity()
# 將 num 新增至佇列尾
self._nums[rear] = num
self._size += 1
def pop(self) -> int:
"""出列"""
num: int = self.peek()
# 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部
self._front = (self._front + 1) % self.capacity()
self._size -= 1
return num
def peek(self) -> int:
"""訪問佇列首元素"""
if self.is_empty():
raise IndexError("佇列為空")
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 =", 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())
@@ -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._stack == []
def push(self, item: int):
"""入堆疊"""
self._stack.append(item)
def pop(self) -> int:
"""出堆疊"""
if self.is_empty():
raise IndexError("堆疊為空")
return self._stack.pop()
def peek(self) -> int:
"""訪問堆疊頂元素"""
if self.is_empty():
raise IndexError("堆疊為空")
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 =", stack.to_list())
# 獲取堆疊的長度
size: int = stack.size()
print("堆疊的長度 size =", size)
# 判斷是否為空
is_empty: bool = stack.is_empty()
print("堆疊是否為空 =", is_empty)
@@ -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("佇列首出列後 deque =", deq)
pop_rear: int = deq.pop() # 佇列尾元素出列
print("佇列尾出列元素 pop_rear =", pop_rear)
print("佇列尾出列後 deque =", deq)
# 獲取雙向佇列的長度
size: int = len(deq)
print("雙向佇列長度 size =", size)
# 判斷雙向佇列是否為空
is_empty: bool = len(deq) == 0
print("雙向佇列是否為空 =", is_empty)
@@ -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:
# 將 node 新增至鏈結串列頭部
self._front.prev = node
node.next = self._front
self._front = node # 更新頭節點
# 佇列尾入列操作
else:
# 將 node 新增至鏈結串列尾部
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("雙向佇列為空")
# 佇列首出列操作
if is_front:
val: int = self._front.val # 暫存頭節點值
# 刪除頭節點
fnext: ListNode | None = self._front.next
if fnext != 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 != 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("雙向佇列為空")
return self._front.val
def peek_last(self) -> int:
"""訪問佇列尾元素"""
if self.is_empty():
raise IndexError("雙向佇列為空")
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)
@@ -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 not self._front
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("佇列為空")
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 =", queue.to_list())
# 獲取佇列的長度
size: int = queue.size()
print("佇列長度 size =", size)
# 判斷佇列是否為空
is_empty: bool = queue.is_empty()
print("佇列是否為空 =", is_empty)
@@ -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 not self._peek
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("堆疊為空")
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 =", stack.to_list())
# 獲取堆疊的長度
size: int = stack.size()
print("堆疊的長度 size =", size)
# 判斷是否為空
is_empty: bool = stack.is_empty()
print("堆疊是否為空 =", is_empty)
@@ -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 =", que)
# 獲取佇列的長度
size: int = len(que)
print("佇列長度 size =", size)
# 判斷佇列是否為空
is_empty: bool = len(que) == 0
print("佇列是否為空 =", is_empty)
@@ -0,0 +1,36 @@
"""
File: stack.py
Created Time: 2022-11-29
Author: Peng Chen (pengchzn@gmail.com)
"""
"""Driver Code"""
if __name__ == "__main__":
# 初始化堆疊
# Python 沒有內建的堆疊類別,可以把 list 當作堆疊來使用
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 =", stack)
# 獲取堆疊的長度
size: int = len(stack)
print("堆疊的長度 size =", size)
# 判斷是否為空
is_empty: bool = len(stack) == 0
print("堆疊是否為空 =", is_empty)
@@ -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:
"""獲取索引為 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
"""Driver Code"""
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)
@@ -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 ,直接刪除 node 並返回
if child is None:
return None
# 子節點數量 = 1 ,直接刪除 node
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
"""Driver Code"""
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))
@@ -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 or 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)
# 用 tmp 覆蓋 cur
cur.val = tmp.val
"""Driver Code"""
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())
@@ -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
"""Driver Code"""
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)
# 在 n1 -> n2 中間插入節點 P
n1.left = P
P.left = n2
print("\n插入節點 P 後\n")
print_tree(n1)
# 刪除節點
n1.left = n2
print("\n刪除節點 P 後\n")
print_tree(n1)
@@ -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
"""Driver Code"""
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)
@@ -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)
"""Driver Code"""
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)
+19
View File
@@ -0,0 +1,19 @@
# Follow the PEP 585 - Type Hinting Generics In Standard Collections
# https://peps.python.org/pep-0585/
from __future__ import annotations
# Import common libs here to simplify the code by `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,
)
+32
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
@@ -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
):
"""
列印二元樹
This tree printer is borrowed from 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)
+69
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
+20
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]
+33
View File
@@ -0,0 +1,33 @@
import os
import glob
import subprocess
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
if __name__ == "__main__":
# find source code files
src_paths = sorted(glob.glob("codes/python/chapter_*/*.py"))
errors = []
# run python code
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'
)
# Wait for the process to complete, and get the output and error messages
stdout, stderr = process.communicate()
# Check the exit status
exit_status = process.returncode
if exit_status != 0:
errors.append(stderr)
print(f"Tested {len(src_paths)} files")
print(f"Found exception in {len(errors)} files")
if len(errors) > 0:
raise RuntimeError("\n\n".join(errors))