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:
Yudong Jin
2024-04-22 02:26:32 +08:00
committed by GitHub
parent 74f1a63e8c
commit f616dac7da
61 changed files with 1606 additions and 145 deletions

View File

@ -64,7 +64,7 @@ $$
並行最佳化在多核或多處理器的環境中尤其有效,因為系統可以同時處理多個子問題,更加充分地利用計算資源,從而顯著減少總體的執行時間。
比如在下圖所示的“桶排序”中,我們將海量的資料平均分配到各個桶中,則可所有桶的排序任務分散到各個計算單元,完成後再合併結果。
比如在下圖所示的“桶排序”中,我們將海量的資料平均分配到各個桶中,則可所有桶的排序任務分散到各個計算單元,完成後再合併結果。
![桶排序的平行計算](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png)

View File

@ -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$ 表即可。

View File

@ -11,7 +11,7 @@
**背包問題**
- 背包問題是最典型的動態規劃問題之一,具有 0-1 背包、完全背包、多重背包等變種。
- 0-1 背包的狀態定義為前 $i$ 個物品在剩餘容量為 $c$ 的背包中的最大價值。根據不放入背包和放入背包兩種決策,可得到最優子結構,並構建出狀態轉移方程。在空間最佳化中,由於每個狀態依賴正上方和左上方的狀態,因此需要倒序走訪串列,避免左上方狀態被覆蓋。
- 0-1 背包的狀態定義為前 $i$ 個物品在容量為 $c$ 的背包中的最大價值。根據不放入背包和放入背包兩種決策,可得到最優子結構,並構建出狀態轉移方程。在空間最佳化中,由於每個狀態依賴正上方和左上方的狀態,因此需要倒序走訪串列,避免左上方狀態被覆蓋。
- 完全背包問題的每種物品的選取數量無限制,因此選擇放入物品的狀態轉移與 0-1 背包問題不同。由於狀態依賴正上方和正左方的狀態,因此在空間最佳化中應當正序走訪。
- 零錢兌換問題是完全背包問題的一個變種。它從求“最大”價值變為求“最小”硬幣數量,因此狀態轉移方程中的 $\max()$ 應改為 $\min()$ 。從追求“不超過”背包容量到追求“恰好”湊出目標金額,因此使用 $amt + 1$ 來表示“無法湊出目標金額”的無效解。
- 零錢兌換問題 II 從求“最少硬幣數量”改為求“硬幣組合數量”,狀態轉移方程相應地從 $\min()$ 改為求和運算子。

View File

@ -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"

View File

@ -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"

View File

@ -420,7 +420,7 @@
## 堆積的實現
下文實現的是大頂堆積。若要將其轉換為小頂堆積,只需將所有大小邏輯判斷取逆(例如,將 $\geq$ 替換為 $\leq$ )。感興趣的讀者可以自行實現。
下文實現的是大頂堆積。若要將其轉換為小頂堆積,只需將所有大小邏輯判斷進行逆轉(例如,將 $\geq$ 替換為 $\leq$ )。感興趣的讀者可以自行實現。
### 堆積的儲存與表示

View File

@ -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` 也可以得到正確的排序結果,但結果是非穩定的。

View File

@ -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`
- **穩定排序**:當計數排序穩定時,基數排序也穩定;當計數排序不穩定時,基數排序無法保證得到正確的排序結果。

View File

@ -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"

View File

@ -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"

View File

@ -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"