mirror of
https://github.com/krahets/hello-algo.git
synced 2025-11-02 12:58:42 +08:00
Bug fixes and improvements (#1298)
* Fix is_empty() implementation in the stack and queue chapter * Update en/CONTRIBUTING.md * Remove "剩余" from the state definition of knapsack problem * Sync zh and zh-hant versions * Update the stylesheets of code tabs * Fix quick_sort.rb * Fix TS code * Update chapter_paperbook * Upload the manuscript of 0.1 section * Fix binary_tree_dfs.rb * Bug fixes * Update README * Update README * Update README * Update README.md * Update README * Sync zh and zh-hant versions * Bug fixes
This commit is contained in:
@ -64,7 +64,7 @@ $$
|
||||
|
||||
並行最佳化在多核或多處理器的環境中尤其有效,因為系統可以同時處理多個子問題,更加充分地利用計算資源,從而顯著減少總體的執行時間。
|
||||
|
||||
比如在下圖所示的“桶排序”中,我們將海量的資料平均分配到各個桶中,則可所有桶的排序任務分散到各個計算單元,完成後再合併結果。
|
||||
比如在下圖所示的“桶排序”中,我們將海量的資料平均分配到各個桶中,則可將所有桶的排序任務分散到各個計算單元,完成後再合併結果。
|
||||
|
||||

|
||||
|
||||
|
||||
@ -18,15 +18,15 @@
|
||||
|
||||
**第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表**
|
||||
|
||||
對於每個物品來說,不放入背包,背包容量不變;放入背包,背包容量減小。由此可得狀態定義:當前物品編號 $i$ 和剩餘背包容量 $c$ ,記為 $[i, c]$ 。
|
||||
對於每個物品來說,不放入背包,背包容量不變;放入背包,背包容量減小。由此可得狀態定義:當前物品編號 $i$ 和背包容量 $c$ ,記為 $[i, c]$ 。
|
||||
|
||||
狀態 $[i, c]$ 對應的子問題為:**前 $i$ 個物品在剩餘容量為 $c$ 的背包中的最大價值**,記為 $dp[i, c]$ 。
|
||||
狀態 $[i, c]$ 對應的子問題為:**前 $i$ 個物品在容量為 $c$ 的背包中的最大價值**,記為 $dp[i, c]$ 。
|
||||
|
||||
待求解的是 $dp[n, cap]$ ,因此需要一個尺寸為 $(n+1) \times (cap+1)$ 的二維 $dp$ 表。
|
||||
|
||||
**第二步:找出最優子結構,進而推導出狀態轉移方程**
|
||||
|
||||
當我們做出物品 $i$ 的決策後,剩餘的是前 $i-1$ 個物品的決策,可分為以下兩種情況。
|
||||
當我們做出物品 $i$ 的決策後,剩餘的是前 $i-1$ 個物品決策的子問題,可分為以下兩種情況。
|
||||
|
||||
- **不放入物品 $i$** :背包容量不變,狀態變化為 $[i-1, c]$ 。
|
||||
- **放入物品 $i$** :背包容量減少 $wgt[i-1]$ ,價值增加 $val[i-1]$ ,狀態變化為 $[i-1, c-wgt[i-1]]$ 。
|
||||
@ -41,7 +41,7 @@ $$
|
||||
|
||||
**第三步:確定邊界條件和狀態轉移順序**
|
||||
|
||||
當無物品或無剩餘背包容量時最大價值為 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等於 $0$ 。
|
||||
當無物品或背包容量為 $0$ 時最大價值為 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等於 $0$ 。
|
||||
|
||||
當前狀態 $[i, c]$ 從上方的狀態 $[i-1, c]$ 和左上方的狀態 $[i-1, c-wgt[i-1]]$ 轉移而來,因此透過兩層迴圈正序走訪整個 $dp$ 表即可。
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
**背包問題**
|
||||
|
||||
- 背包問題是最典型的動態規劃問題之一,具有 0-1 背包、完全背包、多重背包等變種。
|
||||
- 0-1 背包的狀態定義為前 $i$ 個物品在剩餘容量為 $c$ 的背包中的最大價值。根據不放入背包和放入背包兩種決策,可得到最優子結構,並構建出狀態轉移方程。在空間最佳化中,由於每個狀態依賴正上方和左上方的狀態,因此需要倒序走訪串列,避免左上方狀態被覆蓋。
|
||||
- 0-1 背包的狀態定義為前 $i$ 個物品在容量為 $c$ 的背包中的最大價值。根據不放入背包和放入背包兩種決策,可得到最優子結構,並構建出狀態轉移方程。在空間最佳化中,由於每個狀態依賴正上方和左上方的狀態,因此需要倒序走訪串列,避免左上方狀態被覆蓋。
|
||||
- 完全背包問題的每種物品的選取數量無限制,因此選擇放入物品的狀態轉移與 0-1 背包問題不同。由於狀態依賴正上方和正左方的狀態,因此在空間最佳化中應當正序走訪。
|
||||
- 零錢兌換問題是完全背包問題的一個變種。它從求“最大”價值變為求“最小”硬幣數量,因此狀態轉移方程中的 $\max()$ 應改為 $\min()$ 。從追求“不超過”背包容量到追求“恰好”湊出目標金額,因此使用 $amt + 1$ 來表示“無法湊出目標金額”的無效解。
|
||||
- 零錢兌換問題 II 從求“最少硬幣數量”改為求“硬幣組合數量”,狀態轉移方程相應地從 $\min()$ 改為求和運算子。
|
||||
|
||||
@ -299,7 +299,7 @@ $$
|
||||
```rust title="built_in_hash.rs"
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
|
||||
let num = 3;
|
||||
let mut num_hasher = DefaultHasher::new();
|
||||
num.hash(&mut num_hasher);
|
||||
@ -374,7 +374,29 @@ $$
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="built_in_hash.rb"
|
||||
num = 3
|
||||
hash_num = num.hash
|
||||
# 整數 3 的雜湊值為 -4385856518450339636
|
||||
|
||||
bol = true
|
||||
hash_bol = bol.hash
|
||||
# 布林量 true 的雜湊值為 -1617938112149317027
|
||||
|
||||
dec = 3.14159
|
||||
hash_dec = dec.hash
|
||||
# 小數 3.14159 的雜湊值為 -1479186995943067893
|
||||
|
||||
str = "Hello 演算法"
|
||||
hash_str = str.hash
|
||||
# 字串“Hello 演算法”的雜湊值為 -4075943250025831763
|
||||
|
||||
tup = [12836, '小哈']
|
||||
hash_tup = tup.hash
|
||||
# 元組 (12836, '小哈') 的雜湊值為 1999544809202288822
|
||||
|
||||
obj = ListNode.new(0)
|
||||
hash_obj = obj.hash
|
||||
# 節點物件 #<ListNode:0x000078133140ab70> 的雜湊值為 4302940560806366381
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
```python title="hash_map.py"
|
||||
# 初始化雜湊表
|
||||
hmap: dict = {}
|
||||
|
||||
|
||||
# 新增操作
|
||||
# 在雜湊表中新增鍵值對 (key, value)
|
||||
hmap[12836] = "小哈"
|
||||
@ -39,11 +39,11 @@
|
||||
hmap[16750] = "小算"
|
||||
hmap[13276] = "小法"
|
||||
hmap[10583] = "小鴨"
|
||||
|
||||
|
||||
# 查詢操作
|
||||
# 向雜湊表中輸入鍵 key ,得到值 value
|
||||
name: str = hmap[15937]
|
||||
|
||||
|
||||
# 刪除操作
|
||||
# 在雜湊表中刪除鍵值對 (key, value)
|
||||
hmap.pop(10583)
|
||||
@ -54,7 +54,7 @@
|
||||
```cpp title="hash_map.cpp"
|
||||
/* 初始化雜湊表 */
|
||||
unordered_map<int, string> map;
|
||||
|
||||
|
||||
/* 新增操作 */
|
||||
// 在雜湊表中新增鍵值對 (key, value)
|
||||
map[12836] = "小哈";
|
||||
@ -62,11 +62,11 @@
|
||||
map[16750] = "小算";
|
||||
map[13276] = "小法";
|
||||
map[10583] = "小鴨";
|
||||
|
||||
|
||||
/* 查詢操作 */
|
||||
// 向雜湊表中輸入鍵 key ,得到值 value
|
||||
string name = map[15937];
|
||||
|
||||
|
||||
/* 刪除操作 */
|
||||
// 在雜湊表中刪除鍵值對 (key, value)
|
||||
map.erase(10583);
|
||||
@ -77,19 +77,19 @@
|
||||
```java title="hash_map.java"
|
||||
/* 初始化雜湊表 */
|
||||
Map<Integer, String> map = new HashMap<>();
|
||||
|
||||
|
||||
/* 新增操作 */
|
||||
// 在雜湊表中新增鍵值對 (key, value)
|
||||
map.put(12836, "小哈");
|
||||
map.put(15937, "小囉");
|
||||
map.put(16750, "小算");
|
||||
map.put(12836, "小哈");
|
||||
map.put(15937, "小囉");
|
||||
map.put(16750, "小算");
|
||||
map.put(13276, "小法");
|
||||
map.put(10583, "小鴨");
|
||||
|
||||
|
||||
/* 查詢操作 */
|
||||
// 向雜湊表中輸入鍵 key ,得到值 value
|
||||
String name = map.get(15937);
|
||||
|
||||
|
||||
/* 刪除操作 */
|
||||
// 在雜湊表中刪除鍵值對 (key, value)
|
||||
map.remove(10583);
|
||||
@ -108,11 +108,11 @@
|
||||
{ 13276, "小法" },
|
||||
{ 10583, "小鴨" }
|
||||
};
|
||||
|
||||
|
||||
/* 查詢操作 */
|
||||
// 向雜湊表中輸入鍵 key ,得到值 value
|
||||
string name = map[15937];
|
||||
|
||||
|
||||
/* 刪除操作 */
|
||||
// 在雜湊表中刪除鍵值對 (key, value)
|
||||
map.Remove(10583);
|
||||
@ -123,7 +123,7 @@
|
||||
```go title="hash_map_test.go"
|
||||
/* 初始化雜湊表 */
|
||||
hmap := make(map[int]string)
|
||||
|
||||
|
||||
/* 新增操作 */
|
||||
// 在雜湊表中新增鍵值對 (key, value)
|
||||
hmap[12836] = "小哈"
|
||||
@ -131,11 +131,11 @@
|
||||
hmap[16750] = "小算"
|
||||
hmap[13276] = "小法"
|
||||
hmap[10583] = "小鴨"
|
||||
|
||||
|
||||
/* 查詢操作 */
|
||||
// 向雜湊表中輸入鍵 key ,得到值 value
|
||||
name := hmap[15937]
|
||||
|
||||
|
||||
/* 刪除操作 */
|
||||
// 在雜湊表中刪除鍵值對 (key, value)
|
||||
delete(hmap, 10583)
|
||||
@ -146,7 +146,7 @@
|
||||
```swift title="hash_map.swift"
|
||||
/* 初始化雜湊表 */
|
||||
var map: [Int: String] = [:]
|
||||
|
||||
|
||||
/* 新增操作 */
|
||||
// 在雜湊表中新增鍵值對 (key, value)
|
||||
map[12836] = "小哈"
|
||||
@ -154,11 +154,11 @@
|
||||
map[16750] = "小算"
|
||||
map[13276] = "小法"
|
||||
map[10583] = "小鴨"
|
||||
|
||||
|
||||
/* 查詢操作 */
|
||||
// 向雜湊表中輸入鍵 key ,得到值 value
|
||||
let name = map[15937]!
|
||||
|
||||
|
||||
/* 刪除操作 */
|
||||
// 在雜湊表中刪除鍵值對 (key, value)
|
||||
map.removeValue(forKey: 10583)
|
||||
@ -176,11 +176,11 @@
|
||||
map.set(16750, '小算');
|
||||
map.set(13276, '小法');
|
||||
map.set(10583, '小鴨');
|
||||
|
||||
|
||||
/* 查詢操作 */
|
||||
// 向雜湊表中輸入鍵 key ,得到值 value
|
||||
let name = map.get(15937);
|
||||
|
||||
|
||||
/* 刪除操作 */
|
||||
// 在雜湊表中刪除鍵值對 (key, value)
|
||||
map.delete(10583);
|
||||
@ -200,12 +200,12 @@
|
||||
map.set(10583, '小鴨');
|
||||
console.info('\n新增完成後,雜湊表為\nKey -> Value');
|
||||
console.info(map);
|
||||
|
||||
|
||||
/* 查詢操作 */
|
||||
// 向雜湊表中輸入鍵 key ,得到值 value
|
||||
let name = map.get(15937);
|
||||
console.info('\n輸入學號 15937 ,查詢到姓名 ' + name);
|
||||
|
||||
|
||||
/* 刪除操作 */
|
||||
// 在雜湊表中刪除鍵值對 (key, value)
|
||||
map.delete(10583);
|
||||
@ -240,7 +240,7 @@
|
||||
|
||||
```rust title="hash_map.rs"
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
/* 初始化雜湊表 */
|
||||
let mut map: HashMap<i32, String> = HashMap::new();
|
||||
|
||||
@ -272,7 +272,7 @@
|
||||
```kotlin title="hash_map.kt"
|
||||
/* 初始化雜湊表 */
|
||||
val map = HashMap<Int,String>()
|
||||
|
||||
|
||||
/* 新增操作 */
|
||||
// 在雜湊表中新增鍵值對 (key, value)
|
||||
map[12836] = "小哈"
|
||||
@ -280,11 +280,11 @@
|
||||
map[16750] = "小算"
|
||||
map[13276] = "小法"
|
||||
map[10583] = "小鴨"
|
||||
|
||||
|
||||
/* 查詢操作 */
|
||||
// 向雜湊表中輸入鍵 key ,得到值 value
|
||||
val name = map[15937]
|
||||
|
||||
|
||||
/* 刪除操作 */
|
||||
// 在雜湊表中刪除鍵值對 (key, value)
|
||||
map.remove(10583)
|
||||
@ -293,7 +293,24 @@
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="hash_map.rb"
|
||||
# 初始化雜湊表
|
||||
hmap = {}
|
||||
|
||||
# 新增操作
|
||||
# 在雜湊表中新增鍵值對 (key, value)
|
||||
hmap[12836] = "小哈"
|
||||
hmap[15937] = "小囉"
|
||||
hmap[16750] = "小算"
|
||||
hmap[13276] = "小法"
|
||||
hmap[10583] = "小鴨"
|
||||
|
||||
# 查詢操作
|
||||
# 向雜湊表中輸入鍵 key ,得到值 value
|
||||
name = hmap[15937]
|
||||
|
||||
# 刪除操作
|
||||
# 在雜湊表中刪除鍵值對 (key, value)
|
||||
hmap.delete(10583)
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
@ -476,7 +493,7 @@
|
||||
|
||||
// 單獨走訪鍵 Key
|
||||
for key in map.keys() {
|
||||
println!("{key}");
|
||||
println!("{key}");
|
||||
}
|
||||
|
||||
// 單獨走訪值 Value
|
||||
@ -512,7 +529,15 @@
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="hash_map.rb"
|
||||
# 走訪雜湊表
|
||||
# 走訪鍵值對 key->value
|
||||
hmap.entries.each { |key, value| puts "#{key} -> #{value}" }
|
||||
|
||||
# 單獨走訪鍵 key
|
||||
hmap.keys.each { |key| puts key }
|
||||
|
||||
# 單獨走訪值 value
|
||||
hmap.values.each { |val| puts val }
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
@ -420,7 +420,7 @@
|
||||
|
||||
## 堆積的實現
|
||||
|
||||
下文實現的是大頂堆積。若要將其轉換為小頂堆積,只需將所有大小邏輯判斷取逆(例如,將 $\geq$ 替換為 $\leq$ )。感興趣的讀者可以自行實現。
|
||||
下文實現的是大頂堆積。若要將其轉換為小頂堆積,只需將所有大小邏輯判斷進行逆轉(例如,將 $\geq$ 替換為 $\leq$ )。感興趣的讀者可以自行實現。
|
||||
|
||||
### 堆積的儲存與表示
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@ $$
|
||||
|
||||
## 演算法特性
|
||||
|
||||
- **時間複雜度為 $O(n + m)$** :涉及走訪 `nums` 和走訪 `counter` ,都使用線性時間。一般情況下 $n \gg m$ ,時間複雜度趨於 $O(n)$ 。
|
||||
- **時間複雜度為 $O(n + m)$、非自適應排序** :涉及走訪 `nums` 和走訪 `counter` ,都使用線性時間。一般情況下 $n \gg m$ ,時間複雜度趨於 $O(n)$ 。
|
||||
- **空間複雜度為 $O(n + m)$、非原地排序**:藉助了長度分別為 $n$ 和 $m$ 的陣列 `res` 和 `counter` 。
|
||||
- **穩定排序**:由於向 `res` 中填充元素的順序是“從右向左”的,因此倒序走訪 `nums` 可以避免改變相等元素之間的相對位置,從而實現穩定排序。實際上,正序走訪 `nums` 也可以得到正確的排序結果,但結果是非穩定的。
|
||||
|
||||
|
||||
@ -36,6 +36,6 @@ $$
|
||||
|
||||
相較於計數排序,基數排序適用於數值範圍較大的情況,**但前提是資料必須可以表示為固定位數的格式,且位數不能過大**。例如,浮點數不適合使用基數排序,因為其位數 $k$ 過大,可能導致時間複雜度 $O(nk) \gg O(n^2)$ 。
|
||||
|
||||
- **時間複雜度為 $O(nk)$**:設資料量為 $n$、資料為 $d$ 進位制、最大位數為 $k$ ,則對某一位執行計數排序使用 $O(n + d)$ 時間,排序所有 $k$ 位使用 $O((n + d)k)$ 時間。通常情況下,$d$ 和 $k$ 都相對較小,時間複雜度趨向 $O(n)$ 。
|
||||
- **時間複雜度為 $O(nk)$、非自適應排序**:設資料量為 $n$、資料為 $d$ 進位制、最大位數為 $k$ ,則對某一位執行計數排序使用 $O(n + d)$ 時間,排序所有 $k$ 位使用 $O((n + d)k)$ 時間。通常情況下,$d$ 和 $k$ 都相對較小,時間複雜度趨向 $O(n)$ 。
|
||||
- **空間複雜度為 $O(n + d)$、非原地排序**:與計數排序相同,基數排序需要藉助長度為 $n$ 和 $d$ 的陣列 `res` 和 `counter` 。
|
||||
- **穩定排序**:當計數排序穩定時,基數排序也穩定;當計數排序不穩定時,基數排序無法保證得到正確的排序結果。
|
||||
|
||||
@ -123,7 +123,9 @@
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title=""
|
||||
|
||||
### 二元樹的陣列表示 ###
|
||||
# 使用 nil 來表示空位
|
||||
tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
@ -129,9 +129,9 @@ AVL 樹既是二元搜尋樹,也是平衡二元樹,同時滿足這兩類二
|
||||
right: TreeNode | null; // 右子節點指標
|
||||
constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) {
|
||||
this.val = val === undefined ? 0 : val;
|
||||
this.height = height === undefined ? 0 : height;
|
||||
this.left = left === undefined ? null : left;
|
||||
this.right = right === undefined ? null : right;
|
||||
this.height = height === undefined ? 0 : height;
|
||||
this.left = left === undefined ? null : left;
|
||||
this.right = right === undefined ? null : right;
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -214,7 +214,18 @@ AVL 樹既是二元搜尋樹,也是平衡二元樹,同時滿足這兩類二
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title=""
|
||||
### AVL 樹節點類別 ###
|
||||
class TreeNode
|
||||
attr_accessor :val # 節點值
|
||||
attr_accessor :height # 節點高度
|
||||
attr_accessor :left # 左子節點引用
|
||||
attr_accessor :right # 右子節點引用
|
||||
|
||||
def initialize(val)
|
||||
@val = val
|
||||
@height = 0
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
@ -106,7 +106,7 @@
|
||||
val: number;
|
||||
left: TreeNode | null;
|
||||
right: TreeNode | null;
|
||||
|
||||
|
||||
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
|
||||
this.val = val === undefined ? 0 : val; // 節點值
|
||||
this.left = left === undefined ? null : left; // 左子節點引用
|
||||
@ -189,7 +189,16 @@
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title=""
|
||||
### 二元樹節點類別 ###
|
||||
class TreeNode
|
||||
attr_accessor :val # 節點值
|
||||
attr_accessor :left # 左子節點引用
|
||||
attr_accessor :right # 右子節點引用
|
||||
|
||||
def initialize(val)
|
||||
@val = val
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
@ -432,7 +441,18 @@
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="binary_tree.rb"
|
||||
|
||||
# 初始化二元樹
|
||||
# 初始化節點
|
||||
n1 = TreeNode.new(1)
|
||||
n2 = TreeNode.new(2)
|
||||
n3 = TreeNode.new(3)
|
||||
n4 = TreeNode.new(4)
|
||||
n5 = TreeNode.new(5)
|
||||
# 構建節點之間的引用(指標)
|
||||
n1.left = n2
|
||||
n1.right = n3
|
||||
n2.left = n4
|
||||
n2.right = n5
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
@ -594,7 +614,13 @@
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="binary_tree.rb"
|
||||
|
||||
# 插入與刪除節點
|
||||
_p = TreeNode.new(0)
|
||||
# 在 n1 -> n2 中間插入節點 _p
|
||||
n1.left = _p
|
||||
_p.left = n2
|
||||
# 刪除節點
|
||||
n1.left = n2
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
||||
Reference in New Issue
Block a user