diff --git a/docs/index.html b/docs/index.html index ecfd75d8a..53aa17fcf 100644 --- a/docs/index.html +++ b/docs/index.html @@ -8,64 +8,64 @@ style="position: absolute; width: auto; height: 26.445%; left: 28.211%; top: 54.145%;"> - + 初识算法 - + 复杂度 - + 数组与链表 - + 栈与队列 - + 哈希表 - + - + - + - + 搜索 - + 排序 - + 分治 - + 回溯 - + 动态规划 - + 贪心 @@ -80,7 +80,7 @@

动画图解、一键运行的数据结构与算法教程

- +
- +
Cover
@@ -153,7 +153,7 @@
- +
diff --git a/mkdocs.yml b/mkdocs.yml index ae4646668..9a548ce57 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,9 +68,12 @@ theme: extra: alternate: - - name: 中文 + - name: 简体中文 link: / lang: zh + - name: 繁體中文 + link: /zh-hant/ + lang: zh-Hant - name: English link: /en/ lang: en diff --git a/zh-hant/README.md b/zh-hant/README.md new file mode 100644 index 000000000..1f35d148e --- /dev/null +++ b/zh-hant/README.md @@ -0,0 +1,82 @@ +

+ + +

+ +

+ hello-algo-typing-svg +
+ 動畫圖解、一鍵執行的資料結構與演算法教程 +

+ +

+ + + + + + +

+ +

+ + +

+ +

+ + + + + + + + + + + + + +

+ +## 關於本書 + +本專案旨在打造一本開源免費、新手友好的資料結構與演算法入門教程。 + +- 全書採用動畫圖解,內容清晰易懂、學習曲線平滑,引導初學者探索資料結構與演算法的知識地圖。 +- 源程式碼可一鍵執行,幫助讀者在練習中提升程式設計技能,瞭解演算法工作原理和資料結構底層實現。 +- 鼓勵讀者互助學習,提問與評論通常可在兩日內得到回覆。 + +若本書對您有所幫助,請在頁面右上角點個 Star :star: 支持一下,謝謝! + +## 推薦語 + +> “一本通俗易懂的資料結構與演算法入門書,引導讀者手腦並用地學習,強烈推薦演算法初學者閱讀。” +> +> **—— 鄧俊輝,清華大學計算機系教授** + +> “如果我當年學資料結構與演算法的時候有《Hello 演算法》,學起來應該會簡單 10 倍!” +> +> **—— 李沐,亞馬遜資深首席科學家** + +## 貢獻 + +本開源書仍在持續更新之中,歡迎您參與本專案,一同為讀者提供更優質的學習內容。 + +- [內容修正](https://www.hello-algo.com/chapter_appendix/contribution/):請您協助修正或在評論區指出語法錯誤、內容缺失、文字歧義、無效連結或程式碼 bug 等問題。 +- [程式碼轉譯](https://github.com/krahets/hello-algo/issues/15):期待您貢獻各種語言程式碼,已支持 Python、Java、C++、Go、JavaScript 等 12 門程式語言。 +- [中譯英](https://github.com/krahets/hello-algo/issues/914):誠邀您加入我們的翻譯小組,成員主要來自計算機相關專業、英語專業和英文母語者。 + +歡迎您提出寶貴意見和建議,如有任何問題請提交 Issues 或微信關聯 `krahets-jyd` 。 + +感謝本開源書的每一位撰稿人,是他們的無私奉獻讓這本書變得更好,他們是: + +

+ + + +

+ +## License + +The texts, code, images, photos, and videos in this repository are licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). diff --git a/zh-hant/codes/Dockerfile b/zh-hant/codes/Dockerfile new file mode 100644 index 000000000..97d37bbcf --- /dev/null +++ b/zh-hant/codes/Dockerfile @@ -0,0 +1,35 @@ +FROM ubuntu:latest + +# Use Ubuntu image from Aliyun +RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ + sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ + sed -i 's/ports.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list + +RUN apt-get update && apt-get install -y wget + +# Install languages environment +ARG LANGS +RUN for LANG in $LANGS; do \ + case $LANG in \ + python) \ + apt-get install -y python3.10 && \ + update-alternatives --install /usr/bin/python python /usr/bin/python3.10 1 ;; \ + cpp) \ + apt-get install -y g++ gdb ;; \ + java) \ + apt-get install -y openjdk-17-jdk ;; \ + csharp) \ + wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \ + dpkg -i packages-microsoft-prod.deb && \ + apt-get update && \ + apt-get install -y dotnet-sdk-8.0 ;; \ + # More languages... + *) \ + echo "Warning: No installation workflow for $LANG" ;; \ + esac \ + done + +WORKDIR /codes +COPY ./ ./ + +CMD ["/bin/bash"] diff --git a/zh-hant/codes/c/.gitignore b/zh-hant/codes/c/.gitignore new file mode 100644 index 000000000..698ee4e21 --- /dev/null +++ b/zh-hant/codes/c/.gitignore @@ -0,0 +1,9 @@ +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ +*.dSYM/ + +build/ diff --git a/zh-hant/codes/c/CMakeLists.txt b/zh-hant/codes/c/CMakeLists.txt new file mode 100644 index 000000000..bb5f8f6a9 --- /dev/null +++ b/zh-hant/codes/c/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo C) + +set(CMAKE_C_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/zh-hant/codes/c/chapter_array_and_linkedlist/CMakeLists.txt b/zh-hant/codes/c/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 000000000..29677a0be --- /dev/null +++ b/zh-hant/codes/c/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(array array.c) +add_executable(linked_list linked_list.c) +add_executable(my_list my_list.c) \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_array_and_linkedlist/array.c b/zh-hant/codes/c/chapter_array_and_linkedlist/array.c new file mode 100644 index 000000000..dc74b525c --- /dev/null +++ b/zh-hant/codes/c/chapter_array_and_linkedlist/array.c @@ -0,0 +1,114 @@ +/** + * File: array.c + * Created Time: 2022-12-20 + * Author: MolDuM (moldum@163.com) + */ + +#include "../utils/common.h" + +/* 隨機訪問元素 */ +int randomAccess(int *nums, int size) { + // 在區間 [0, size) 中隨機抽取一個數字 + int randomIndex = rand() % size; + // 獲取並返回隨機元素 + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* 擴展陣列長度 */ +int *extend(int *nums, int size, int enlarge) { + // 初始化一個擴展長度後的陣列 + int *res = (int *)malloc(sizeof(int) * (size + enlarge)); + // 將原陣列中的所有元素複製到新陣列 + for (int i = 0; i < size; i++) { + res[i] = nums[i]; + } + // 初始化擴展後的空間 + for (int i = size; i < size + enlarge; i++) { + res[i] = 0; + } + // 返回擴展後的新陣列 + return res; +} + +/* 在陣列的索引 index 處插入元素 num */ +void insert(int *nums, int size, int num, int index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (int i = size - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +/* 刪除索引 index 處的元素 */ +// 注意:stdio.h 佔用了 remove 關鍵詞 +void removeItem(int *nums, int size, int index) { + // 把索引 index 之後的所有元素向前移動一位 + for (int i = index; i < size - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列 */ +void traverse(int *nums, int size) { + int count = 0; + // 透過索引走訪陣列 + for (int i = 0; i < size; i++) { + count += nums[i]; + } +} + +/* 在陣列中查詢指定元素 */ +int find(int *nums, int size, int target) { + for (int i = 0; i < size; i++) { + if (nums[i] == target) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 初始化陣列 */ + int size = 5; + int arr[5]; + printf("陣列 arr = "); + printArray(arr, size); + + int nums[] = {1, 3, 2, 5, 4}; + printf("陣列 nums = "); + printArray(nums, size); + + /* 隨機訪問 */ + int randomNum = randomAccess(nums, size); + printf("在 nums 中獲取隨機元素 %d", randomNum); + + /* 長度擴展 */ + int enlarge = 3; + int *res = extend(nums, size, enlarge); + size += enlarge; + printf("將陣列長度擴展至 8 ,得到 nums = "); + printArray(res, size); + + /* 插入元素 */ + insert(res, size, 6, 3); + printf("在索引 3 處插入數字 6 ,得到 nums = "); + printArray(res, size); + + /* 刪除元素 */ + removeItem(res, size, 2); + printf("刪除索引 2 處的元素,得到 nums = "); + printArray(res, size); + + /* 走訪陣列 */ + traverse(res, size); + + /* 查詢元素 */ + int index = find(res, size, 3); + printf("在 res 中查詢元素 3 ,得到索引 = %d\n", index); + + /* 釋放記憶體 */ + free(res); + return 0; +} diff --git a/zh-hant/codes/c/chapter_array_and_linkedlist/linked_list.c b/zh-hant/codes/c/chapter_array_and_linkedlist/linked_list.c new file mode 100644 index 000000000..047d2832a --- /dev/null +++ b/zh-hant/codes/c/chapter_array_and_linkedlist/linked_list.c @@ -0,0 +1,89 @@ +/** + * File: linked_list.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +// 注意:stdio.h 佔用了 remove 關鍵詞 +void removeItem(ListNode *n0) { + if (!n0->next) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // 釋放記憶體 + free(P); +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == NULL) + return NULL; + head = head->next; + } + return head; +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +int find(ListNode *head, int target) { + int index = 0; + while (head) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 初始化鏈結串列 */ + // 初始化各個節點 + ListNode *n0 = newListNode(1); + ListNode *n1 = newListNode(3); + ListNode *n2 = newListNode(2); + ListNode *n3 = newListNode(5); + ListNode *n4 = newListNode(4); + // 構建節點之間的引用 + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + printf("初始化的鏈結串列為\r\n"); + printLinkedList(n0); + + /* 插入節點 */ + insert(n0, newListNode(0)); + printf("插入節點後的鏈結串列為\r\n"); + printLinkedList(n0); + + /* 刪除節點 */ + removeItem(n0); + printf("刪除節點後的鏈結串列為\r\n"); + printLinkedList(n0); + + /* 訪問節點 */ + ListNode *node = access(n0, 3); + printf("鏈結串列中索引 3 處的節點的值 = %d\r\n", node->val); + + /* 查詢節點 */ + int index = find(n0, 2); + printf("鏈結串列中值為 2 的節點的索引 = %d\r\n", index); + + // 釋放記憶體 + freeMemoryLinkedList(n0); + return 0; +} diff --git a/zh-hant/codes/c/chapter_array_and_linkedlist/my_list.c b/zh-hant/codes/c/chapter_array_and_linkedlist/my_list.c new file mode 100644 index 000000000..7b2742f0a --- /dev/null +++ b/zh-hant/codes/c/chapter_array_and_linkedlist/my_list.c @@ -0,0 +1,163 @@ +/** + * File: my_list.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 串列類別 */ +typedef struct { + int *arr; // 陣列(儲存串列元素) + int capacity; // 串列容量 + int size; // 串列大小 + int extendRatio; // 串列每次擴容的倍數 +} MyList; + +void extendCapacity(MyList *nums); + +/* 建構子 */ +MyList *newMyList() { + MyList *nums = malloc(sizeof(MyList)); + nums->capacity = 10; + nums->arr = malloc(sizeof(int) * nums->capacity); + nums->size = 0; + nums->extendRatio = 2; + return nums; +} + +/* 析構函式 */ +void delMyList(MyList *nums) { + free(nums->arr); + free(nums); +} + +/* 獲取串列長度 */ +int size(MyList *nums) { + return nums->size; +} + +/* 獲取串列容量 */ +int capacity(MyList *nums) { + return nums->capacity; +} + +/* 訪問元素 */ +int get(MyList *nums, int index) { + assert(index >= 0 && index < nums->size); + return nums->arr[index]; +} + +/* 更新元素 */ +void set(MyList *nums, int index, int num) { + assert(index >= 0 && index < nums->size); + nums->arr[index] = num; +} + +/* 在尾部新增元素 */ +void add(MyList *nums, int num) { + if (size(nums) == capacity(nums)) { + extendCapacity(nums); // 擴容 + } + nums->arr[size(nums)] = num; + nums->size++; +} + +/* 在中間插入元素 */ +void insert(MyList *nums, int index, int num) { + assert(index >= 0 && index < size(nums)); + // 元素數量超出容量時,觸發擴容機制 + if (size(nums) == capacity(nums)) { + extendCapacity(nums); // 擴容 + } + for (int i = size(nums); i > index; --i) { + nums->arr[i] = nums->arr[i - 1]; + } + nums->arr[index] = num; + nums->size++; +} + +/* 刪除元素 */ +// 注意:stdio.h 佔用了 remove 關鍵詞 +int removeItem(MyList *nums, int index) { + assert(index >= 0 && index < size(nums)); + int num = nums->arr[index]; + for (int i = index; i < size(nums) - 1; i++) { + nums->arr[i] = nums->arr[i + 1]; + } + nums->size--; + return num; +} + +/* 串列擴容 */ +void extendCapacity(MyList *nums) { + // 先分配空間 + int newCapacity = capacity(nums) * nums->extendRatio; + int *extend = (int *)malloc(sizeof(int) * newCapacity); + int *temp = nums->arr; + + // 複製舊資料到新資料 + for (int i = 0; i < size(nums); i++) + extend[i] = nums->arr[i]; + + // 釋放舊資料 + free(temp); + + // 更新新資料 + nums->arr = extend; + nums->capacity = newCapacity; +} + +/* 將串列轉換為 Array 用於列印 */ +int *toArray(MyList *nums) { + return nums->arr; +} + +/* Driver Code */ +int main() { + /* 初始化串列 */ + MyList *nums = newMyList(); + /* 在尾部新增元素 */ + add(nums, 1); + add(nums, 3); + add(nums, 2); + add(nums, 5); + add(nums, 4); + printf("串列 nums = "); + printArray(toArray(nums), size(nums)); + printf("容量 = %d ,長度 = %d\n", capacity(nums), size(nums)); + + /* 在中間插入元素 */ + insert(nums, 3, 6); + printf("在索引 3 處插入數字 6 ,得到 nums = "); + printArray(toArray(nums), size(nums)); + + /* 刪除元素 */ + removeItem(nums, 3); + printf("刪除索引 3 處的元素,得到 nums = "); + printArray(toArray(nums), size(nums)); + + /* 訪問元素 */ + int num = get(nums, 1); + printf("訪問索引 1 處的元素,得到 num = %d\n", num); + + /* 更新元素 */ + set(nums, 1, 0); + printf("將索引 1 處的元素更新為 0 ,得到 nums = "); + printArray(toArray(nums), size(nums)); + + /* 測試擴容機制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + add(nums, i); + } + + printf("擴容後的串列 nums = "); + printArray(toArray(nums), size(nums)); + printf("容量 = %d ,長度 = %d\n", capacity(nums), size(nums)); + + /* 釋放分配記憶體 */ + delMyList(nums); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/CMakeLists.txt b/zh-hant/codes/c/chapter_backtracking/CMakeLists.txt new file mode 100644 index 000000000..70161b6dd --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(permutations_i permutations_i.c) +add_executable(permutations_ii permutations_ii.c) +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c) +add_executable(subset_sum_i_naive subset_sum_i_naive.c) +add_executable(subset_sum_i subset_sum_i.c) +add_executable(subset_sum_ii subset_sum_ii.c) +add_executable(n_queens n_queens.c) \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_backtracking/n_queens.c b/zh-hant/codes/c/chapter_backtracking/n_queens.c new file mode 100644 index 000000000..f238194fa --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/n_queens.c @@ -0,0 +1,95 @@ +/** + * File : n_queens.c + * Created Time: 2023-09-25 + * Author : lucas (superrat6@gmail.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +/* 回溯演算法:n 皇后 */ +void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], + bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { + // 當放置完所有行時,記錄解 + if (row == n) { + res[*resSize] = (char **)malloc(sizeof(char *) * n); + for (int i = 0; i < n; ++i) { + res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); + strcpy(res[*resSize][i], state[i]); + } + (*resSize)++; + return; + } + // 走訪所有列 + for (int col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +char ***nQueens(int n, int *returnSize) { + char state[MAX_SIZE][MAX_SIZE]; + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + state[i][j] = '#'; + } + state[i][n] = '\0'; + } + bool cols[MAX_SIZE] = {false}; // 記錄列是否有皇后 + bool diags1[2 * MAX_SIZE - 1] = {false}; // 記錄主對角線上是否有皇后 + bool diags2[2 * MAX_SIZE - 1] = {false}; // 記錄次對角線上是否有皇后 + + char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); + *returnSize = 0; + backtrack(0, n, state, res, returnSize, cols, diags1, diags2); + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + int returnSize; + char ***res = nQueens(n, &returnSize); + + printf("輸入棋盤長寬為%d\n", n); + printf("皇后放置方案共有 %d 種\n", returnSize); + for (int i = 0; i < returnSize; ++i) { + for (int j = 0; j < n; ++j) { + printf("["); + for (int k = 0; res[i][j][k] != '\0'; ++k) { + printf("%c", res[i][j][k]); + if (res[i][j][k + 1] != '\0') { + printf(", "); + } + } + printf("]\n"); + } + printf("---------------------\n"); + } + + // 釋放記憶體 + for (int i = 0; i < returnSize; ++i) { + for (int j = 0; j < n; ++j) { + free(res[i][j]); + } + free(res[i]); + } + free(res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/permutations_i.c b/zh-hant/codes/c/chapter_backtracking/permutations_i.c new file mode 100644 index 000000000..cc83abf32 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/permutations_i.c @@ -0,0 +1,79 @@ +/** + * File: permutations_i.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com) + */ + +#include "../utils/common.h" + +// 假設最多有 1000 個排列 +#define MAX_SIZE 1000 + +/* 回溯演算法:全排列 I */ +void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { + // 當狀態長度等於元素數量時,記錄解 + if (stateSize == choicesSize) { + res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); + for (int i = 0; i < choicesSize; i++) { + res[*resSize][i] = state[i]; + } + (*resSize)++; + return; + } + // 走訪所有選擇 + for (int i = 0; i < choicesSize; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state[stateSize] = choice; + // 進行下一輪選擇 + backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + } + } +} + +/* 全排列 I */ +int **permutationsI(int *nums, int numsSize, int *returnSize) { + int *state = (int *)malloc(numsSize * sizeof(int)); + bool *selected = (bool *)malloc(numsSize * sizeof(bool)); + for (int i = 0; i < numsSize; i++) { + selected[i] = false; + } + int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); + *returnSize = 0; + + backtrack(state, 0, nums, numsSize, selected, res, returnSize); + + free(state); + free(selected); + + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 2, 3}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int returnSize; + + int **res = permutationsI(nums, numsSize, &returnSize); + + printf("輸入陣列 nums = "); + printArray(nums, numsSize); + printf("\n所有排列 res = \n"); + for (int i = 0; i < returnSize; i++) { + printArray(res[i], numsSize); + } + + // 釋放記憶體 + for (int i = 0; i < returnSize; i++) { + free(res[i]); + } + free(res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/permutations_ii.c b/zh-hant/codes/c/chapter_backtracking/permutations_ii.c new file mode 100644 index 000000000..da8dff71c --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/permutations_ii.c @@ -0,0 +1,81 @@ +/** + * File: permutations_ii.c + * Created Time: 2023-10-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.h" + +// 假設最多有 1000 個排列,元素最大為 1000 +#define MAX_SIZE 1000 + +/* 回溯演算法:全排列 II */ +void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { + // 當狀態長度等於元素數量時,記錄解 + if (stateSize == choicesSize) { + res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); + for (int i = 0; i < choicesSize; i++) { + res[*resSize][i] = state[i]; + } + (*resSize)++; + return; + } + // 走訪所有選擇 + bool duplicated[MAX_SIZE] = {false}; + for (int i = 0; i < choicesSize; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated[choice]) { + // 嘗試:做出選擇,更新狀態 + duplicated[choice] = true; // 記錄選擇過的元素值 + selected[i] = true; + state[stateSize] = choice; + // 進行下一輪選擇 + backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + } + } +} + +/* 全排列 II */ +int **permutationsII(int *nums, int numsSize, int *returnSize) { + int *state = (int *)malloc(numsSize * sizeof(int)); + bool *selected = (bool *)malloc(numsSize * sizeof(bool)); + for (int i = 0; i < numsSize; i++) { + selected[i] = false; + } + int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); + *returnSize = 0; + + backtrack(state, 0, nums, numsSize, selected, res, returnSize); + + free(state); + free(selected); + + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 1, 2}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int returnSize; + + int **res = permutationsII(nums, numsSize, &returnSize); + + printf("輸入陣列 nums = "); + printArray(nums, numsSize); + printf("\n所有排列 res = \n"); + for (int i = 0; i < returnSize; i++) { + printArray(res[i], numsSize); + } + + // 釋放記憶體 + for (int i = 0; i < returnSize; i++) { + free(res[i]); + } + free(res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/preorder_traversal_i_compact.c b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_i_compact.c new file mode 100644 index 000000000..548e2d8b5 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_i_compact.c @@ -0,0 +1,49 @@ +/** + * File: preorder_traversal_i_compact.c + * Created Time: 2023-05-10 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假設結果長度不超過 100 +#define MAX_SIZE 100 + +TreeNode *res[MAX_SIZE]; +int resSize = 0; + +/* 前序走訪:例題一 */ +void preOrder(TreeNode *root) { + if (root == NULL) { + return; + } + if (root->val == 7) { + // 記錄解 + res[resSize++] = root; + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n初始化二元樹\n"); + printTree(root); + + // 前序走訪 + preOrder(root); + + printf("\n輸出所有值為 7 的節點\n"); + int *vals = malloc(resSize * sizeof(int)); + for (int i = 0; i < resSize; i++) { + vals[i] = res[i]->val; + } + printArray(vals, resSize); + + // 釋放記憶體 + freeMemoryTree(root); + free(vals); + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c new file mode 100644 index 000000000..caac77638 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c @@ -0,0 +1,61 @@ +/** + * File: preorder_traversal_ii_compact.c + * Created Time: 2023-05-28 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假設路徑和結果長度不超過 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 前序走訪:例題二 */ +void preOrder(TreeNode *root) { + if (root == NULL) { + return; + } + // 嘗試 + path[pathSize++] = root; + if (root->val == 7) { + // 記錄解 + for (int i = 0; i < pathSize; ++i) { + res[resSize][i] = path[i]; + } + resSize++; + } + preOrder(root->left); + preOrder(root->right); + // 回退 + pathSize--; +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n初始化二元樹\n"); + printTree(root); + + // 前序走訪 + preOrder(root); + + printf("\n輸出所有根節點到節點 7 的路徑\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // 釋放記憶體 + freeMemoryTree(root); + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c new file mode 100644 index 000000000..d38d05b0d --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c @@ -0,0 +1,62 @@ +/** + * File: preorder_traversal_iii_compact.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假設路徑和結果長度不超過 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 前序走訪:例題三 */ +void preOrder(TreeNode *root) { + // 剪枝 + if (root == NULL || root->val == 3) { + return; + } + // 嘗試 + path[pathSize++] = root; + if (root->val == 7) { + // 記錄解 + for (int i = 0; i < pathSize; i++) { + res[resSize][i] = path[i]; + } + resSize++; + } + preOrder(root->left); + preOrder(root->right); + // 回退 + pathSize--; +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n初始化二元樹\n"); + printTree(root); + + // 前序走訪 + preOrder(root); + + printf("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // 釋放記憶體 + freeMemoryTree(root); + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_template.c b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_template.c new file mode 100644 index 000000000..1648d823d --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/preorder_traversal_iii_template.c @@ -0,0 +1,93 @@ +/** + * File: preorder_traversal_iii_template.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 假設路徑和結果長度不超過 100 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 判斷當前狀態是否為解 */ +bool isSolution(void) { + return pathSize > 0 && path[pathSize - 1]->val == 7; +} + +/* 記錄解 */ +void recordSolution(void) { + for (int i = 0; i < pathSize; i++) { + res[resSize][i] = path[i]; + } + resSize++; +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +bool isValid(TreeNode *choice) { + return choice != NULL && choice->val != 3; +} + +/* 更新狀態 */ +void makeChoice(TreeNode *choice) { + path[pathSize++] = choice; +} + +/* 恢復狀態 */ +void undoChoice(void) { + pathSize--; +} + +/* 回溯演算法:例題三 */ +void backtrack(TreeNode *choices[2]) { + // 檢查是否為解 + if (isSolution()) { + // 記錄解 + recordSolution(); + } + // 走訪所有選擇 + for (int i = 0; i < 2; i++) { + TreeNode *choice = choices[i]; + // 剪枝:檢查選擇是否合法 + if (isValid(choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(choice); + // 進行下一輪選擇 + TreeNode *nextChoices[2] = {choice->left, choice->right}; + backtrack(nextChoices); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(); + } + } +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n初始化二元樹\n"); + printTree(root); + + // 回溯演算法 + TreeNode *choices[2] = {root, NULL}; + backtrack(choices); + + printf("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // 釋放記憶體 + freeMemoryTree(root); + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/subset_sum_i.c b/zh-hant/codes/c/chapter_backtracking/subset_sum_i.c new file mode 100644 index 000000000..661b60886 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/subset_sum_i.c @@ -0,0 +1,78 @@ +/** + * File: subset_sum_i.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 狀態(子集) +int state[MAX_SIZE]; +int stateSize = 0; + +// 結果串列(子集串列) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* 回溯演算法:子集和 I */ +void backtrack(int target, int *choices, int choicesSize, int start) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + for (int i = 0; i < stateSize; ++i) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (int i = start; i < choicesSize; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state[stateSize] = choices[i]; + stateSize++; + // 進行下一輪選擇 + backtrack(target - choices[i], choices, choicesSize, i); + // 回退:撤銷選擇,恢復到之前的狀態 + stateSize--; + } +} + +/* 比較函式 */ +int cmp(const void *a, const void *b) { + return (*(int *)a - *(int *)b); +} + +/* 求解子集和 I */ +void subsetSumI(int *nums, int numsSize, int target) { + qsort(nums, numsSize, sizeof(int), cmp); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + backtrack(target, nums, numsSize, start); +} + +/* Driver Code */ +int main() { + int nums[] = {3, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumI(nums, numsSize, target); + + printf("輸入陣列 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("所有和等於 %d 的子集 res = \n", target); + for (int i = 0; i < resSize; ++i) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/subset_sum_i_naive.c b/zh-hant/codes/c/chapter_backtracking/subset_sum_i_naive.c new file mode 100644 index 000000000..3ca367fa6 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/subset_sum_i_naive.c @@ -0,0 +1,69 @@ +/** + * File: subset_sum_i_naive.c + * Created Time: 2023-07-28 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 狀態(子集) +int state[MAX_SIZE]; +int stateSize = 0; + +// 結果串列(子集串列) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* 回溯演算法:子集和 I */ +void backtrack(int target, int total, int *choices, int choicesSize) { + // 子集和等於 target 時,記錄解 + if (total == target) { + for (int i = 0; i < stateSize; i++) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // 走訪所有選擇 + for (int i = 0; i < choicesSize; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state[stateSize++] = choices[i]; + // 進行下一輪選擇 + backtrack(target, total + choices[i], choices, choicesSize); + // 回退:撤銷選擇,恢復到之前的狀態 + stateSize--; + } +} + +/* 求解子集和 I(包含重複子集) */ +void subsetSumINaive(int *nums, int numsSize, int target) { + resSize = 0; // 初始化解的數量為0 + backtrack(target, 0, nums, numsSize); +} + +/* Driver Code */ +int main() { + int nums[] = {3, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumINaive(nums, numsSize, target); + + printf("輸入陣列 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("所有和等於 %d 的子集 res = \n", target); + for (int i = 0; i < resSize; i++) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/zh-hant/codes/c/chapter_backtracking/subset_sum_ii.c b/zh-hant/codes/c/chapter_backtracking/subset_sum_ii.c new file mode 100644 index 000000000..8064375c7 --- /dev/null +++ b/zh-hant/codes/c/chapter_backtracking/subset_sum_ii.c @@ -0,0 +1,83 @@ +/** + * File: subset_sum_ii.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 狀態(子集) +int state[MAX_SIZE]; +int stateSize = 0; + +// 結果串列(子集串列) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* 回溯演算法:子集和 II */ +void backtrack(int target, int *choices, int choicesSize, int start) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + for (int i = 0; i < stateSize; i++) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (int i = start; i < choicesSize; i++) { + // 剪枝一:若子集和超過 target ,則直接跳過 + if (target - choices[i] < 0) { + continue; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state[stateSize] = choices[i]; + stateSize++; + // 進行下一輪選擇 + backtrack(target - choices[i], choices, choicesSize, i + 1); + // 回退:撤銷選擇,恢復到之前的狀態 + stateSize--; + } +} + +/* 比較函式 */ +int cmp(const void *a, const void *b) { + return (*(int *)a - *(int *)b); +} + +/* 求解子集和 II */ +void subsetSumII(int *nums, int numsSize, int target) { + // 對 nums 進行排序 + qsort(nums, numsSize, sizeof(int), cmp); + // 開始回溯 + backtrack(target, nums, numsSize, 0); +} + +/* Driver Code */ +int main() { + int nums[] = {4, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumII(nums, numsSize, target); + + printf("輸入陣列 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("所有和等於 %d 的子集 res = \n", target); + for (int i = 0; i < resSize; ++i) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/zh-hant/codes/c/chapter_computational_complexity/CMakeLists.txt b/zh-hant/codes/c/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 000000000..dcfa063c3 --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(iteration iteration.c) +add_executable(recursion recursion.c) +add_executable(time_complexity time_complexity.c) +add_executable(worst_best_time_complexity worst_best_time_complexity.c) +add_executable(space_complexity space_complexity.c) diff --git a/zh-hant/codes/c/chapter_computational_complexity/iteration.c b/zh-hant/codes/c/chapter_computational_complexity/iteration.c new file mode 100644 index 000000000..1caf5068a --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/iteration.c @@ -0,0 +1,81 @@ +/** + * File: iteration.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com) + */ + +#include "../utils/common.h" + +/* for 迴圈 */ +int forLoop(int n) { + int res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 迴圈 */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; +} + +/* while 迴圈(兩次更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; +} + +/* 雙層 for 迴圈 */ +char *nestedForLoop(int n) { + // n * n 為對應點數量,"(i, j), " 對應字串長最大為 6+10*2,加上最後一個空字元 \0 的額外空間 + int size = n * n * 26 + 1; + char *res = malloc(size * sizeof(char)); + // 迴圈 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + char tmp[26]; + snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); + strncat(res, tmp, size - strlen(res) - 1); + } + } + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + printf("\nfor 迴圈的求和結果 res = %d\n", res); + + res = whileLoop(n); + printf("\nwhile 迴圈的求和結果 res = %d\n", res); + + res = whileLoopII(n); + printf("\nwhile 迴圈(兩次更新)求和結果 res = %d\n", res); + + char *resStr = nestedForLoop(n); + printf("\n雙層 for 迴圈的走訪結果 %s\r\n", resStr); + free(resStr); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_computational_complexity/recursion.c b/zh-hant/codes/c/chapter_computational_complexity/recursion.c new file mode 100644 index 000000000..6f845c679 --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/recursion.c @@ -0,0 +1,77 @@ +/** + * File: recursion.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 遞迴 */ +int recur(int n) { + // 終止條件 + if (n == 1) + return 1; + // 遞:遞迴呼叫 + int res = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +/* 使用迭代模擬遞迴 */ +int forLoopRecur(int n) { + int stack[1000]; // 藉助一個大陣列來模擬堆疊 + int top = -1; // 堆疊頂索引 + int res = 0; + // 遞:遞迴呼叫 + for (int i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack[1 + top++] = i; + } + // 迴:返回結果 + while (top >= 0) { + // 透過“出堆疊操作”模擬“迴” + res += stack[top--]; + } + // res = 1+2+3+...+n + return res; +} + +/* 尾遞迴 */ +int tailRecur(int n, int res) { + // 終止條件 + if (n == 0) + return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +/* 費波那契數列:遞迴 */ +int fib(int n) { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + printf("\n遞迴函式的求和結果 res = %d\n", res); + + res = forLoopRecur(n); + printf("\n使用迭代模擬遞迴求和結果 res = %d\n", res); + + res = tailRecur(n, 0); + printf("\n尾遞迴函式的求和結果 res = %d\n", res); + + res = fib(n); + printf("\n費波那契數列的第 %d 項為 %d\n", n, res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_computational_complexity/space_complexity.c b/zh-hant/codes/c/chapter_computational_complexity/space_complexity.c new file mode 100644 index 000000000..b44c839a0 --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/space_complexity.c @@ -0,0 +1,141 @@ +/** + * File: space_complexity.c + * Created Time: 2023-04-15 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 函式 */ +int func() { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +void constant(int n) { + // 常數、變數、物件佔用 O(1) 空間 + const int a = 0; + int b = 0; + int nums[1000]; + ListNode *node = newListNode(0); + free(node); + // 迴圈中的變數佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + func(); + } +} + +/* 雜湊表 */ +typedef struct { + int key; + int val; + UT_hash_handle hh; // 基於 uthash.h 實現 +} HashTable; + +/* 線性階 */ +void linear(int n) { + // 長度為 n 的陣列佔用 O(n) 空間 + int *nums = malloc(sizeof(int) * n); + free(nums); + + // 長度為 n 的串列佔用 O(n) 空間 + ListNode **nodes = malloc(sizeof(ListNode *) * n); + for (int i = 0; i < n; i++) { + nodes[i] = newListNode(i); + } + // 記憶體釋放 + for (int i = 0; i < n; i++) { + free(nodes[i]); + } + free(nodes); + + // 長度為 n 的雜湊表佔用 O(n) 空間 + HashTable *h = NULL; + for (int i = 0; i < n; i++) { + HashTable *tmp = malloc(sizeof(HashTable)); + tmp->key = i; + tmp->val = i; + HASH_ADD_INT(h, key, tmp); + } + + // 記憶體釋放 + HashTable *curr, *tmp; + HASH_ITER(hh, h, curr, tmp) { + HASH_DEL(h, curr); + free(curr); + } +} + +/* 線性階(遞迴實現) */ +void linearRecur(int n) { + printf("遞迴 n = %d\r\n", n); + if (n == 1) + return; + linearRecur(n - 1); +} + +/* 平方階 */ +void quadratic(int n) { + // 二維串列佔用 O(n^2) 空間 + int **numMatrix = malloc(sizeof(int *) * n); + for (int i = 0; i < n; i++) { + int *tmp = malloc(sizeof(int) * n); + for (int j = 0; j < n; j++) { + tmp[j] = 0; + } + numMatrix[i] = tmp; + } + + // 記憶體釋放 + for (int i = 0; i < n; i++) { + free(numMatrix[i]); + } + free(numMatrix); +} + +/* 平方階(遞迴實現) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + int *nums = malloc(sizeof(int) * n); + printf("遞迴 n = %d 中的 nums 長度 = %d\r\n", n, n); + int res = quadraticRecur(n - 1); + free(nums); + return res; +} + +/* 指數階(建立滿二元樹) */ +TreeNode *buildTree(int n) { + if (n == 0) + return NULL; + TreeNode *root = newTreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +int main() { + int n = 5; + // 常數階 + constant(n); + // 線性階 + linear(n); + linearRecur(n); + // 平方階 + quadratic(n); + quadraticRecur(n); + // 指數階 + TreeNode *root = buildTree(n); + printTree(root); + + // 釋放記憶體 + freeMemoryTree(root); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_computational_complexity/time_complexity.c b/zh-hant/codes/c/chapter_computational_complexity/time_complexity.c new file mode 100644 index 000000000..280eee938 --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/time_complexity.c @@ -0,0 +1,179 @@ +/** + * File: time_complexity.c + * Created Time: 2023-01-03 + * Author: codingonion (coderonion@gmail.com) + */ + +#include "../utils/common.h" + +/* 常數階 */ +int constant(int n) { + int count = 0; + int size = 100000; + int i = 0; + for (int i = 0; i < size; i++) { + count++; + } + return count; +} + +/* 線性階 */ +int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 線性階(走訪陣列) */ +int arrayTraversal(int *nums, int n) { + int count = 0; + // 迴圈次數與陣列長度成正比 + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 平方階 */ +int quadratic(int n) { + int count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方階(泡沫排序) */ +int bubbleSort(int *nums, int n) { + int count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (int i = n - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +/* 指數階(迴圈實現) */ +int exponential(int n) { + int count = 0; + int bas = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < bas; j++) { + count++; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指數階(遞迴實現) */ +int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 對數階(迴圈實現) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 對數階(遞迴實現) */ +int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; +} + +/* 線性對數階 */ +int linearLogRecur(int n) { + if (n <= 1) + return 1; + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乘階(遞迴實現) */ +int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +int main(int argc, char *argv[]) { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + int n = 8; + printf("輸入資料大小 n = %d\n", n); + + int count = constant(n); + printf("常數階的操作數量 = %d\n", count); + + count = linear(n); + printf("線性階的操作數量 = %d\n", count); + // 分配堆積區記憶體(建立一維可變長陣列:陣列中元素數量為 n ,元素型別為 int ) + int *nums = (int *)malloc(n * sizeof(int)); + count = arrayTraversal(nums, n); + printf("線性階(走訪陣列)的操作數量 = %d\n", count); + + count = quadratic(n); + printf("平方階的操作數量 = %d\n", count); + for (int i = 0; i < n; i++) { + nums[i] = n - i; // [n,n-1,...,2,1] + } + count = bubbleSort(nums, n); + printf("平方階(泡沫排序)的操作數量 = %d\n", count); + + count = exponential(n); + printf("指數階(迴圈實現)的操作數量 = %d\n", count); + count = expRecur(n); + printf("指數階(遞迴實現)的操作數量 = %d\n", count); + + count = logarithmic(n); + printf("對數階(迴圈實現)的操作數量 = %d\n", count); + count = logRecur(n); + printf("對數階(遞迴實現)的操作數量 = %d\n", count); + + count = linearLogRecur(n); + printf("線性對數階(遞迴實現)的操作數量 = %d\n", count); + + count = factorialRecur(n); + printf("階乘階(遞迴實現)的操作數量 = %d\n", count); + + // 釋放堆積區記憶體 + if (nums != NULL) { + free(nums); + nums = NULL; + } + getchar(); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_computational_complexity/worst_best_time_complexity.c b/zh-hant/codes/c/chapter_computational_complexity/worst_best_time_complexity.c new file mode 100644 index 000000000..10435221a --- /dev/null +++ b/zh-hant/codes/c/chapter_computational_complexity/worst_best_time_complexity.c @@ -0,0 +1,57 @@ +/** + * File: worst_best_time_complexity.c + * Created Time: 2023-01-03 + * Author: codingonion (coderonion@gmail.com) + */ + +#include "../utils/common.h" + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +int *randomNumbers(int n) { + // 分配堆積區記憶體(建立一維可變長陣列:陣列中元素數量為 n ,元素型別為 int ) + int *nums = (int *)malloc(n * sizeof(int)); + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 隨機打亂陣列元素 + for (int i = n - 1; i > 0; i--) { + int j = rand() % (i + 1); + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } + return nums; +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +int findOne(int *nums, int n) { + for (int i = 0; i < n; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) + return i; + } + return -1; +} + +/* Driver Code */ +int main(int argc, char *argv[]) { + // 初始化隨機數種子 + srand((unsigned int)time(NULL)); + for (int i = 0; i < 10; i++) { + int n = 100; + int *nums = randomNumbers(n); + int index = findOne(nums, n); + printf("\n陣列 [ 1, 2, ..., n ] 被打亂後 = "); + printArray(nums, n); + printf("數字 1 的索引為 %d\n", index); + // 釋放堆積區記憶體 + if (nums != NULL) { + free(nums); + nums = NULL; + } + } + + return 0; +} diff --git a/zh-hant/codes/c/chapter_divide_and_conquer/CMakeLists.txt b/zh-hant/codes/c/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 000000000..e03b1c588 --- /dev/null +++ b/zh-hant/codes/c/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.c) +add_executable(build_tree build_tree.c) +add_executable(hanota hanota.c) diff --git a/zh-hant/codes/c/chapter_divide_and_conquer/binary_search_recur.c b/zh-hant/codes/c/chapter_divide_and_conquer/binary_search_recur.c new file mode 100644 index 000000000..86582e12a --- /dev/null +++ b/zh-hant/codes/c/chapter_divide_and_conquer/binary_search_recur.c @@ -0,0 +1,47 @@ +/** + * File: binary_search_recur.c + * Created Time: 2023-10-01 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 二分搜尋:問題 f(i, j) */ +int dfs(int nums[], int target, int i, int j) { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } +} + +/* 二分搜尋 */ +int binarySearch(int nums[], int target, int numsSize) { + int n = numsSize; + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + + // 二分搜尋(雙閉區間) + int index = binarySearch(nums, target, numsSize); + printf("目標元素 6 的索引 = %d\n", index); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_divide_and_conquer/build_tree.c b/zh-hant/codes/c/chapter_divide_and_conquer/build_tree.c new file mode 100644 index 000000000..de71c8a94 --- /dev/null +++ b/zh-hant/codes/c/chapter_divide_and_conquer/build_tree.c @@ -0,0 +1,61 @@ +/** + * File : build_tree.c + * Created Time: 2023-10-16 + * Author : lucas (superrat6@gmail.com) + */ + +#include "../utils/common.h" + +// 假設所有元素都小於 1000 +#define MAX_SIZE 1000 + +/* 構建二元樹:分治 */ +TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) { + // 子樹區間為空時終止 + if (r - l < 0) + return NULL; + // 初始化根節點 + TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); + root->val = preorder[i]; + root->left = NULL; + root->right = NULL; + // 查詢 m ,從而劃分左右子樹 + int m = inorderMap[preorder[i]]; + // 子問題:構建左子樹 + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size); + // 子問題:構建右子樹 + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size); + // 返回根節點 + return root; +} + +/* 構建二元樹 */ +TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE); + for (int i = 0; i < inorderSize; i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize); + free(inorderMap); + return root; +} + +/* Driver Code */ +int main() { + int preorder[] = {3, 9, 2, 1, 7}; + int inorder[] = {9, 3, 1, 2, 7}; + int preorderSize = sizeof(preorder) / sizeof(preorder[0]); + int inorderSize = sizeof(inorder) / sizeof(inorder[0]); + printf("前序走訪 = "); + printArray(preorder, preorderSize); + printf("中序走訪 = "); + printArray(inorder, inorderSize); + + TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize); + printf("構建的二元樹為:\n"); + printTree(root); + + freeMemoryTree(root); + return 0; +} diff --git a/zh-hant/codes/c/chapter_divide_and_conquer/hanota.c b/zh-hant/codes/c/chapter_divide_and_conquer/hanota.c new file mode 100644 index 000000000..99a46dde6 --- /dev/null +++ b/zh-hant/codes/c/chapter_divide_and_conquer/hanota.c @@ -0,0 +1,74 @@ +/** + * File: hanota.c + * Created Time: 2023-10-01 + * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com) + */ + +#include "../utils/common.h" + +// 假設最多有 1000 個排列 +#define MAX_SIZE 1000 + +/* 移動一個圓盤 */ +void move(int *src, int *srcSize, int *tar, int *tarSize) { + // 從 src 頂部拿出一個圓盤 + int pan = src[*srcSize - 1]; + src[*srcSize - 1] = 0; + (*srcSize)--; + // 將圓盤放入 tar 頂部 + tar[*tarSize] = pan; + (*tarSize)++; +} + +/* 求解河內塔問題 f(i) */ +void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if (i == 1) { + move(src, srcSize, tar, tarSize); + return; + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize); + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, srcSize, tar, tarSize); + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize); +} + +/* 求解河內塔問題 */ +void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) { + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(*ASize, A, ASize, B, BSize, C, CSize); +} + +/* Driver Code */ +int main() { + // 串列尾部是柱子頂部 + int a[] = {5, 4, 3, 2, 1}; + int b[MAX_SIZE] = {0}; + int c[MAX_SIZE] = {0}; + + int ASize = sizeof(a) / sizeof(a[0]); + int BSize = 0; + int CSize = 0; + + printf("\n初始狀態下:"); + printf("\nA = "); + printArray(a, ASize); + printf("B = "); + printArray(b, BSize); + printf("C = "); + printArray(c, CSize); + + solveHanota(a, &ASize, b, &BSize, c, &CSize); + + printf("\n圓盤移動完成後:"); + printf("A = "); + printArray(a, ASize); + printf("B = "); + printArray(b, BSize); + printf("C = "); + printArray(c, CSize); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/CMakeLists.txt b/zh-hant/codes/c/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 000000000..dd769ebd5 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c) +add_executable(min_path_sum min_path_sum.c) +add_executable(knapsack knapsack.c) +add_executable(unbounded_knapsack unbounded_knapsack.c) +add_executable(coin_change coin_change.c) +add_executable(coin_change_ii coin_change_ii.c) +add_executable(edit_distance edit_distance.c) diff --git a/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c new file mode 100644 index 000000000..28d606d70 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c @@ -0,0 +1,47 @@ +/** + * File: climbing_stairs_backtrack.c + * Created Time: 2023-09-22 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 回溯 */ +void backtrack(int *choices, int state, int n, int *res, int len) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) + res[0]++; + // 走訪所有選擇 + for (int i = 0; i < len; i++) { + int choice = choices[i]; + // 剪枝:不允許越過第 n 階 + if (state + choice > n) + continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res, len); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +int climbingStairsBacktrack(int n) { + int choices[2] = {1, 2}; // 可選擇向上爬 1 階或 2 階 + int state = 0; // 從第 0 階開始爬 + int *res = (int *)malloc(sizeof(int)); + *res = 0; // 使用 res[0] 記錄方案數量 + int len = sizeof(choices) / sizeof(int); + backtrack(choices, state, n, res, len); + int result = *res; + free(res); + return result; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c new file mode 100644 index 000000000..151e36ccd --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c @@ -0,0 +1,46 @@ +/** + * File: climbing_stairs_constraint_dp.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 帶約束爬樓梯:動態規劃 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(3, sizeof(int)); + } + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + int res = dp[n][1] + dp[n][2]; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c new file mode 100644 index 000000000..eaddc6bab --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 搜尋 */ +int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬樓梯:搜尋 */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c new file mode 100644 index 000000000..fe19636de --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_dfs_mem.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 記憶化搜尋 */ +int dfs(int i, int *mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +/* 爬樓梯:記憶化搜尋 */ +int climbingStairsDFSMem(int n) { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + int *mem = (int *)malloc((n + 1) * sizeof(int)); + for (int i = 0; i <= n; i++) { + mem[i] = -1; + } + int result = dfs(n, mem); + free(mem); + return result; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c new file mode 100644 index 000000000..b4a97a557 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c @@ -0,0 +1,51 @@ +/** + * File: climbing_stairs_dp.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 爬樓梯:動態規劃 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用於儲存子問題的解 + int *dp = (int *)malloc((n + 1) * sizeof(int)); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + int result = dp[n]; + free(dp); + return result; +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + res = climbingStairsDPComp(n); + printf("爬 %d 階樓梯共有 %d 種方案\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_dynamic_programming/coin_change.c b/zh-hant/codes/c/chapter_dynamic_programming/coin_change.c new file mode 100644 index 000000000..8cfde5438 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/coin_change.c @@ -0,0 +1,88 @@ +/** + * File: coin_change.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 零錢兌換:動態規劃 */ +int coinChangeDP(int coins[], int amt, int coinsSize) { + int n = coinsSize; + int MAX = amt + 1; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(amt + 1, sizeof(int)); + } + // 狀態轉移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + int res = dp[n][amt] != MAX ? dp[n][amt] : -1; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +int coinChangeDPComp(int coins[], int amt, int coinsSize) { + int n = coinsSize; + int MAX = amt + 1; + // 初始化 dp 表 + int *dp = calloc(amt + 1, sizeof(int)); + dp[0] = 0; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + int res = dp[amt] != MAX ? dp[amt] : -1; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver code */ +int main() { + int coins[] = {1, 2, 5}; + int coinsSize = sizeof(coins) / sizeof(coins[0]); + int amt = 4; + + // 動態規劃 + int res = coinChangeDP(coins, amt, coinsSize); + printf("湊到目標金額所需的最少硬幣數量為 %d\n", res); + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt, coinsSize); + printf("湊到目標金額所需的最少硬幣數量為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/coin_change_ii.c b/zh-hant/codes/c/chapter_dynamic_programming/coin_change_ii.c new file mode 100644 index 000000000..5fbd24048 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/coin_change_ii.c @@ -0,0 +1,81 @@ +/** + * File: coin_change_ii.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 零錢兌換 II:動態規劃 */ +int coinChangeIIDP(int coins[], int amt, int coinsSize) { + int n = coinsSize; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(amt + 1, sizeof(int)); + } + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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]]; + } + } + } + int res = dp[n][amt]; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { + int n = coinsSize; + // 初始化 dp 表 + int *dp = calloc(amt + 1, sizeof(int)); + dp[0] = 1; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + int res = dp[amt]; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver code */ +int main() { + int coins[] = {1, 2, 5}; + int coinsSize = sizeof(coins) / sizeof(coins[0]); + int amt = 5; + + // 動態規劃 + int res = coinChangeIIDP(coins, amt, coinsSize); + printf("湊出目標金額的硬幣組合數量為 %d\n", res); + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins, amt, coinsSize); + printf("湊出目標金額的硬幣組合數量為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/edit_distance.c b/zh-hant/codes/c/chapter_dynamic_programming/edit_distance.c new file mode 100644 index 000000000..16b44e7e0 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/edit_distance.c @@ -0,0 +1,159 @@ +/** + * File: edit_distance.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 編輯距離:暴力搜尋 */ +int editDistanceDFS(char *s, char *t, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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 editDistanceDFS(s, t, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return myMin(myMin(insert, del), replace) + 1; +} + +/* 編輯距離:記憶化搜尋 */ +int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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 editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = myMin(myMin(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 編輯距離:動態規劃 */ +int editDistanceDP(char *s, char *t, int n, int m) { + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(m + 1, sizeof(int)); + } + // 狀態轉移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + int res = dp[n][m]; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +int editDistanceDPComp(char *s, char *t, int n, int m) { + int *dp = calloc(m + 1, sizeof(int)); + // 狀態轉移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (int i = 1; i <= n; i++) { + // 狀態轉移:首列 + int leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + int res = dp[m]; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver Code */ +int main() { + char *s = "bag"; + char *t = "pack"; + int n = strlen(s), m = strlen(t); + + // 暴力搜尋 + int res = editDistanceDFS(s, t, n, m); + printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); + + // 記憶化搜尋 + int **mem = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + mem[i] = malloc((m + 1) * sizeof(int)); + memset(mem[i], -1, (m + 1) * sizeof(int)); + } + res = editDistanceDFSMem(s, t, m + 1, mem, n, m); + printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(mem[i]); + } + free(mem); + + // 動態規劃 + res = editDistanceDP(s, t, n, m); + printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t, n, m); + printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/knapsack.c b/zh-hant/codes/c/chapter_dynamic_programming/knapsack.c new file mode 100644 index 000000000..c27631782 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/knapsack.c @@ -0,0 +1,137 @@ +/** + * File: knapsack.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最大值 */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* 0-1 背包:暴力搜尋 */ +int knapsackDFS(int wgt[], int val[], int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return myMax(no, yes); +} + +/* 0-1 背包:記憶化搜尋 */ +int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = myMax(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:動態規劃 */ +int knapsackDP(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(cap + 1, sizeof(int)); + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[n][cap]; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int *dp = calloc(cap + 1, sizeof(int)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + // 倒序走訪 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[cap]; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int wgt[] = {10, 20, 30, 40, 50}; + int val[] = {50, 120, 150, 210, 240}; + int cap = 50; + int n = sizeof(wgt) / sizeof(wgt[0]); + int wgtSize = n; + + // 暴力搜尋 + int res = knapsackDFS(wgt, val, n, cap); + printf("不超過背包容量的最大物品價值為 %d\n", res); + + // 記憶化搜尋 + int **mem = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + mem[i] = malloc((cap + 1) * sizeof(int)); + memset(mem[i], -1, (cap + 1) * sizeof(int)); + } + res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap); + printf("不超過背包容量的最大物品價值為 %d\n", res); + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(mem[i]); + } + free(mem); + + // 動態規劃 + res = knapsackDP(wgt, val, cap, wgtSize); + printf("不超過背包容量的最大物品價值為 %d\n", res); + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, val, cap, wgtSize); + printf("不超過背包容量的最大物品價值為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c b/zh-hant/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c new file mode 100644 index 000000000..769f63e4c --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c @@ -0,0 +1,62 @@ +/** + * File: min_cost_climbing_stairs_dp.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 爬樓梯最小代價:動態規劃 */ +int minCostClimbingStairsDP(int cost[], int costSize) { + int n = costSize - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用於儲存子問題的解 + int *dp = calloc(n + 1, sizeof(int)); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i]; + } + int res = dp[n]; + // 釋放記憶體 + free(dp); + return res; +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +int minCostClimbingStairsDPComp(int cost[], int costSize) { + int n = costSize - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = myMin(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + int costSize = sizeof(cost) / sizeof(cost[0]); + printf("輸入樓梯的代價串列為:"); + printArray(cost, costSize); + + int res = minCostClimbingStairsDP(cost, costSize); + printf("爬完樓梯的最低代價為 %d\n", res); + + res = minCostClimbingStairsDPComp(cost, costSize); + printf("爬完樓梯的最低代價為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/min_path_sum.c b/zh-hant/codes/c/chapter_dynamic_programming/min_path_sum.c new file mode 100644 index 000000000..b90a5b073 --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/min_path_sum.c @@ -0,0 +1,134 @@ +/** + * File: min_path_sum.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +// 假設矩陣最大行列數為 100 +#define MAX_SIZE 100 + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 最小路徑和:暴力搜尋 */ +int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; +} + +/* 最小路徑和:記憶化搜尋 */ +int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* 最小路徑和:動態規劃 */ +int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { + // 初始化 dp 表 + int **dp = malloc(n * sizeof(int *)); + for (int i = 0; i < n; i++) { + dp[i] = calloc(m, sizeof(int)); + } + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + int res = dp[n - 1][m - 1]; + // 釋放記憶體 + for (int i = 0; i < n; i++) { + free(dp[i]); + } + return res; +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { + // 初始化 dp 表 + int *dp = calloc(m, sizeof(int)); + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (int i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (int j = 1; j < m; j++) { + dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j]; + } + } + int res = dp[m - 1]; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = 4, m = 4; // 矩陣容量為 MAX_SIZE * MAX_SIZE ,有效行列數為 n * m + + // 暴力搜尋 + int res = minPathSumDFS(grid, n - 1, m - 1); + printf("從左上角到右下角的最小路徑和為 %d\n", res); + + // 記憶化搜尋 + int mem[MAX_SIZE][MAX_SIZE]; + memset(mem, -1, sizeof(mem)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + printf("從左上角到右下角的最小路徑和為 %d\n", res); + + // 動態規劃 + res = minPathSumDP(grid, n, m); + printf("從左上角到右下角的最小路徑和為 %d\n", res); + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid, n, m); + printf("從左上角到右下角的最小路徑和為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_dynamic_programming/unbounded_knapsack.c b/zh-hant/codes/c/chapter_dynamic_programming/unbounded_knapsack.c new file mode 100644 index 000000000..3f0e7b91c --- /dev/null +++ b/zh-hant/codes/c/chapter_dynamic_programming/unbounded_knapsack.c @@ -0,0 +1,81 @@ +/** + * File: unbounded_knapsack.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 求最大值 */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* 完全背包:動態規劃 */ +int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(cap + 1, sizeof(int)); + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[n][cap]; + // 釋放記憶體 + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 完全背包:空間最佳化後的動態規劃 */ +int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // 初始化 dp 表 + int *dp = calloc(cap + 1, sizeof(int)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[cap]; + // 釋放記憶體 + free(dp); + return res; +} + +/* Driver code */ +int main() { + int wgt[] = {1, 2, 3}; + int val[] = {5, 11, 15}; + int wgtSize = sizeof(wgt) / sizeof(wgt[0]); + int cap = 4; + + // 動態規劃 + int res = unboundedKnapsackDP(wgt, val, cap, wgtSize); + printf("不超過背包容量的最大物品價值為 %d\n", res); + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize); + printf("不超過背包容量的最大物品價值為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_graph/CMakeLists.txt b/zh-hant/codes/c/chapter_graph/CMakeLists.txt new file mode 100644 index 000000000..28f8470f4 --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(graph_adjacency_matrix graph_adjacency_matrix.c) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.c) +add_executable(graph_bfs graph_bfs.c) +add_executable(graph_dfs graph_dfs.c) diff --git a/zh-hant/codes/c/chapter_graph/graph_adjacency_list.c b/zh-hant/codes/c/chapter_graph/graph_adjacency_list.c new file mode 100644 index 000000000..35236eaa1 --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/graph_adjacency_list.c @@ -0,0 +1,171 @@ +/** + * File: graph_adjacency_list.c + * Created Time: 2023-07-07 + * Author: NI-SW (947743645@qq.com) + */ + +#include "../utils/common.h" + +// 假設節點最大數量為 100 +#define MAX_SIZE 100 + +/* 節點結構體 */ +typedef struct AdjListNode { + Vertex *vertex; // 頂點 + struct AdjListNode *next; // 後繼節點 +} AdjListNode; + +/* 基於鄰接表實現的無向圖類別 */ +typedef struct { + AdjListNode *heads[MAX_SIZE]; // 節點陣列 + int size; // 節點數量 +} GraphAdjList; + +/* 建構子 */ +GraphAdjList *newGraphAdjList() { + GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); + if (!graph) { + return NULL; + } + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + graph->heads[i] = NULL; + } + return graph; +} + +/* 析構函式 */ +void delGraphAdjList(GraphAdjList *graph) { + for (int i = 0; i < graph->size; i++) { + AdjListNode *cur = graph->heads[i]; + while (cur != NULL) { + AdjListNode *next = cur->next; + if (cur != graph->heads[i]) { + free(cur); + } + cur = next; + } + free(graph->heads[i]->vertex); + free(graph->heads[i]); + } + free(graph); +} + +/* 查詢頂點對應的節點 */ +AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { + for (int i = 0; i < graph->size; i++) { + if (graph->heads[i]->vertex == vet) { + return graph->heads[i]; + } + } + return NULL; +} + +/* 新增邊輔助函式 */ +void addEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); + node->vertex = vet; + // 頭插法 + node->next = head->next; + head->next = node; +} + +/* 新增邊 */ +void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL && head1 != head2); + // 新增邊 vet1 - vet2 + addEdgeHelper(head1, vet2); + addEdgeHelper(head2, vet1); +} + +/* 刪除邊輔助函式 */ +void removeEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *pre = head; + AdjListNode *cur = head->next; + // 在鏈結串列中搜索 vet 對應節點 + while (cur != NULL && cur->vertex != vet) { + pre = cur; + cur = cur->next; + } + if (cur == NULL) + return; + // 將 vet 對應節點從鏈結串列中刪除 + pre->next = cur->next; + // 釋放記憶體 + free(cur); +} + +/* 刪除邊 */ +void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL); + // 刪除邊 vet1 - vet2 + removeEdgeHelper(head1, head2->vertex); + removeEdgeHelper(head2, head1->vertex); +} + +/* 新增頂點 */ +void addVertex(GraphAdjList *graph, Vertex *vet) { + assert(graph != NULL && graph->size < MAX_SIZE); + AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); + head->vertex = vet; + head->next = NULL; + // 在鄰接表中新增一個新鏈結串列 + graph->heads[graph->size++] = head; +} + +/* 刪除頂點 */ +void removeVertex(GraphAdjList *graph, Vertex *vet) { + AdjListNode *node = findNode(graph, vet); + assert(node != NULL); + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + AdjListNode *cur = node, *pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + free(pre); + } + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (int i = 0; i < graph->size; i++) { + cur = graph->heads[i]; + pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + if (cur && cur->vertex == vet) { + pre->next = cur->next; + free(cur); + break; + } + } + } + // 將該頂點之後的頂點向前移動,以填補空缺 + int i; + for (i = 0; i < graph->size; i++) { + if (graph->heads[i] == node) + break; + } + for (int j = i; j < graph->size - 1; j++) { + graph->heads[j] = graph->heads[j + 1]; + } + graph->size--; + free(vet); +} + +/* 列印鄰接表 */ +void printGraph(const GraphAdjList *graph) { + printf("鄰接表 =\n"); + for (int i = 0; i < graph->size; ++i) { + AdjListNode *node = graph->heads[i]; + printf("%d: [", node->vertex->val); + node = node->next; + while (node) { + printf("%d, ", node->vertex->val); + node = node->next; + } + printf("]\n"); + } +} diff --git a/zh-hant/codes/c/chapter_graph/graph_adjacency_list_test.c b/zh-hant/codes/c/chapter_graph/graph_adjacency_list_test.c new file mode 100644 index 000000000..b3f539e20 --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/graph_adjacency_list_test.c @@ -0,0 +1,55 @@ +/** + * File: graph_adjacency_list_test.c + * Created Time: 2023-07-11 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +/* Driver Code */ +int main() { + int vals[] = {1, 3, 2, 5, 4}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // 新增所有頂點和邊 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化後,圖為\n"); + printGraph(graph); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + addEdge(graph, v[0], v[2]); + printf("\n新增邊 1-2 後,圖為\n"); + printGraph(graph); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + removeEdge(graph, v[0], v[1]); + printf("\n刪除邊 1-3 後,圖為\n"); + printGraph(graph); + + /* 新增頂點 */ + Vertex *v5 = newVertex(6); + addVertex(graph, v5); + printf("\n新增頂點 6 後,圖為\n"); + printGraph(graph); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + removeVertex(graph, v[1]); + printf("\n刪除頂點 3 後,圖為:\n"); + printGraph(graph); + + // 釋放記憶體 + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/zh-hant/codes/c/chapter_graph/graph_adjacency_matrix.c b/zh-hant/codes/c/chapter_graph/graph_adjacency_matrix.c new file mode 100644 index 000000000..5848dee83 --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/graph_adjacency_matrix.c @@ -0,0 +1,150 @@ +/** + * File: graph_adjacency_matrix.c + * Created Time: 2023-07-06 + * Author: NI-SW (947743645@qq.com) + */ + +#include "../utils/common.h" + +// 假設頂點數量最大為 100 +#define MAX_SIZE 100 + +/* 基於鄰接矩陣實現的無向圖結構體 */ +typedef struct { + int vertices[MAX_SIZE]; + int adjMat[MAX_SIZE][MAX_SIZE]; + int size; +} GraphAdjMat; + +/* 建構子 */ +GraphAdjMat *newGraphAdjMat() { + GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + for (int j = 0; j < MAX_SIZE; j++) { + graph->adjMat[i][j] = 0; + } + } + return graph; +} + +/* 析構函式 */ +void delGraphAdjMat(GraphAdjMat *graph) { + free(graph); +} + +/* 新增頂點 */ +void addVertex(GraphAdjMat *graph, int val) { + if (graph->size == MAX_SIZE) { + fprintf(stderr, "圖的頂點數量已達最大值\n"); + return; + } + // 新增第 n 個頂點,並將第 n 行和列置零 + int n = graph->size; + graph->vertices[n] = val; + for (int i = 0; i <= n; i++) { + graph->adjMat[n][i] = graph->adjMat[i][n] = 0; + } + graph->size++; +} + +/* 刪除頂點 */ +void removeVertex(GraphAdjMat *graph, int index) { + if (index < 0 || index >= graph->size) { + fprintf(stderr, "頂點索引越界\n"); + return; + } + // 在頂點串列中移除索引 index 的頂點 + for (int i = index; i < graph->size - 1; i++) { + graph->vertices[i] = graph->vertices[i + 1]; + } + // 在鄰接矩陣中刪除索引 index 的行 + for (int i = index; i < graph->size - 1; i++) { + for (int j = 0; j < graph->size; j++) { + graph->adjMat[i][j] = graph->adjMat[i + 1][j]; + } + } + // 在鄰接矩陣中刪除索引 index 的列 + for (int i = 0; i < graph->size; i++) { + for (int j = index; j < graph->size - 1; j++) { + graph->adjMat[i][j] = graph->adjMat[i][j + 1]; + } + } + graph->size--; +} + +/* 新增邊 */ +// 參數 i, j 對應 vertices 元素索引 +void addEdge(GraphAdjMat *graph, int i, int j) { + if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { + fprintf(stderr, "邊索引越界或相等\n"); + return; + } + graph->adjMat[i][j] = 1; + graph->adjMat[j][i] = 1; +} + +/* 刪除邊 */ +// 參數 i, j 對應 vertices 元素索引 +void removeEdge(GraphAdjMat *graph, int i, int j) { + if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { + fprintf(stderr, "邊索引越界或相等\n"); + return; + } + graph->adjMat[i][j] = 0; + graph->adjMat[j][i] = 0; +} + +/* 列印鄰接矩陣 */ +void printGraphAdjMat(GraphAdjMat *graph) { + printf("頂點串列 = "); + printArray(graph->vertices, graph->size); + printf("鄰接矩陣 =\n"); + for (int i = 0; i < graph->size; i++) { + printArray(graph->adjMat[i], graph->size); + } +} + +/* Driver Code */ +int main() { + // 初始化無向圖 + GraphAdjMat *graph = newGraphAdjMat(); + int vertices[] = {1, 3, 2, 5, 4}; + for (int i = 0; i < 5; i++) { + addVertex(graph, vertices[i]); + } + int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + for (int i = 0; i < 6; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化後,圖為\n"); + printGraphAdjMat(graph); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + addEdge(graph, 0, 2); + printf("\n新增邊 1-2 後,圖為\n"); + printGraphAdjMat(graph); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + removeEdge(graph, 0, 1); + printf("\n刪除邊 1-3 後,圖為\n"); + printGraphAdjMat(graph); + + /* 新增頂點 */ + addVertex(graph, 6); + printf("\n新增頂點 6 後,圖為\n"); + printGraphAdjMat(graph); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + removeVertex(graph, 1); + printf("\n刪除頂點 3 後,圖為\n"); + printGraphAdjMat(graph); + + // 釋放記憶體 + delGraphAdjMat(graph); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_graph/graph_bfs.c b/zh-hant/codes/c/chapter_graph/graph_bfs.c new file mode 100644 index 000000000..2bc420c81 --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/graph_bfs.c @@ -0,0 +1,116 @@ +/** + * File: graph_bfs.c + * Created Time: 2023-07-11 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +// 假設節點最大數量為 100 +#define MAX_SIZE 100 + +/* 節點佇列結構體 */ +typedef struct { + Vertex *vertices[MAX_SIZE]; + int front, rear, size; +} Queue; + +/* 建構子 */ +Queue *newQueue() { + Queue *q = (Queue *)malloc(sizeof(Queue)); + q->front = q->rear = q->size = 0; + return q; +} + +/* 判斷佇列是否為空 */ +int isEmpty(Queue *q) { + return q->size == 0; +} + +/* 入列操作 */ +void enqueue(Queue *q, Vertex *vet) { + q->vertices[q->rear] = vet; + q->rear = (q->rear + 1) % MAX_SIZE; + q->size++; +} + +/* 出列操作 */ +Vertex *dequeue(Queue *q) { + Vertex *vet = q->vertices[q->front]; + q->front = (q->front + 1) % MAX_SIZE; + q->size--; + return vet; +} + +/* 檢查頂點是否已被訪問 */ +int isVisited(Vertex **visited, int size, Vertex *vet) { + // 走訪查詢節點,使用 O(n) 時間 + for (int i = 0; i < size; i++) { + if (visited[i] == vet) + return 1; + } + return 0; +} + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { + // 佇列用於實現 BFS + Queue *queue = newQueue(); + enqueue(queue, startVet); + visited[(*visitedSize)++] = startVet; + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (!isEmpty(queue)) { + Vertex *vet = dequeue(queue); // 佇列首頂點出隊 + res[(*resSize)++] = vet; // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // 跳過已被訪問的頂點 + if (!isVisited(visited, *visitedSize, node->vertex)) { + enqueue(queue, node->vertex); // 只入列未訪問的頂點 + visited[(*visitedSize)++] = node->vertex; // 標記該頂點已被訪問 + } + node = node->next; + } + } + // 釋放記憶體 + free(queue); +} + +/* Driver Code */ +int main() { + // 初始化無向圖 + int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{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]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // 新增所有頂點和邊 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化後,圖為\n"); + printGraph(graph); + + // 廣度優先走訪 + // 頂點走訪序列 + Vertex *res[MAX_SIZE]; + int resSize = 0; + // 用於記錄已被訪問過的頂點 + Vertex *visited[MAX_SIZE]; + int visitedSize = 0; + graphBFS(graph, v[0], res, &resSize, visited, &visitedSize); + printf("\n廣度優先走訪(BFS)頂點序列為\n"); + printArray(vetsToVals(res, resSize), resSize); + + // 釋放記憶體 + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/zh-hant/codes/c/chapter_graph/graph_dfs.c b/zh-hant/codes/c/chapter_graph/graph_dfs.c new file mode 100644 index 000000000..ca0658a08 --- /dev/null +++ b/zh-hant/codes/c/chapter_graph/graph_dfs.c @@ -0,0 +1,75 @@ +/** + * File: graph_dfs.c + * Created Time: 2023-07-13 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +// 假設節點最大數量為 100 +#define MAX_SIZE 100 + +/* 檢查頂點是否已被訪問 */ +int isVisited(Vertex **res, int size, Vertex *vet) { + // 走訪查詢節點,使用 O(n) 時間 + for (int i = 0; i < size; i++) { + if (res[i] == vet) { + return 1; + } + } + return 0; +} + +/* 深度優先走訪輔助函式 */ +void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { + // 記錄訪問頂點 + res[(*resSize)++] = vet; + // 走訪該頂點的所有鄰接頂點 + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // 跳過已被訪問的頂點 + if (!isVisited(res, *resSize, node->vertex)) { + // 遞迴訪問鄰接頂點 + dfs(graph, res, resSize, node->vertex); + } + node = node->next; + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { + dfs(graph, res, resSize, startVet); +} + +/* Driver Code */ +int main() { + // 初始化無向圖 + int vals[] = {0, 1, 2, 3, 4, 5, 6}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // 新增所有頂點和邊 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初始化後,圖為\n"); + printGraph(graph); + + // 深度優先走訪 + Vertex *res[MAX_SIZE]; + int resSize = 0; + graphDFS(graph, v[0], res, &resSize); + printf("\n深度優先走訪(DFS)頂點序列為\n"); + printArray(vetsToVals(res, resSize), resSize); + + // 釋放記憶體 + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/zh-hant/codes/c/chapter_greedy/CMakeLists.txt b/zh-hant/codes/c/chapter_greedy/CMakeLists.txt new file mode 100644 index 000000000..b8e6ca425 --- /dev/null +++ b/zh-hant/codes/c/chapter_greedy/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(coin_change_greedy coin_change_greedy.c) +add_executable(fractional_knapsack fractional_knapsack.c) +add_executable(max_capacity max_capacity.c) +add_executable(max_product_cutting max_product_cutting.c) + +if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") + target_link_libraries(max_product_cutting m) +endif() diff --git a/zh-hant/codes/c/chapter_greedy/coin_change_greedy.c b/zh-hant/codes/c/chapter_greedy/coin_change_greedy.c new file mode 100644 index 000000000..8df707842 --- /dev/null +++ b/zh-hant/codes/c/chapter_greedy/coin_change_greedy.c @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.c + * Created Time: 2023-09-07 + * Author: lwbaptx (lwbaptx@gmail.com) + */ + +#include "../utils/common.h" + +/* 零錢兌換:貪婪 */ +int coinChangeGreedy(int *coins, int size, int amt) { + // 假設 coins 串列有序 + int i = size - 1; + int count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (amt > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > amt) { + i--; + } + // 選擇 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,則返回 -1 + return amt == 0 ? count : -1; +} + +/* Driver Code */ +int main() { + // 貪婪:能夠保證找到全域性最優解 + int coins1[6] = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins1, 6, amt); + printf("\ncoins = "); + printArray(coins1, 6); + printf("amt = %d\n", amt); + printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res); + + // 貪婪:無法保證找到全域性最優解 + int coins2[3] = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins2, 3, amt); + printf("\ncoins = "); + printArray(coins2, 3); + printf("amt = %d\n", amt); + printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res); + printf("實際上需要的最少數量為 3 ,即 20 + 20 + 20\n"); + + // 貪婪:無法保證找到全域性最優解 + int coins3[3] = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins3, 3, amt); + printf("\ncoins = "); + printArray(coins3, 3); + printf("amt = %d\n", amt); + printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res); + printf("實際上需要的最少數量為 2 ,即 49 + 49\n"); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_greedy/fractional_knapsack.c b/zh-hant/codes/c/chapter_greedy/fractional_knapsack.c new file mode 100644 index 000000000..6475a4efe --- /dev/null +++ b/zh-hant/codes/c/chapter_greedy/fractional_knapsack.c @@ -0,0 +1,60 @@ +/** + * File: fractional_knapsack.c + * Created Time: 2023-09-14 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 物品 */ +typedef struct { + int w; // 物品重量 + int v; // 物品價值 +} Item; + +/* 按照價值密度排序 */ +int sortByValueDensity(const void *a, const void *b) { + Item *t1 = (Item *)a; + Item *t2 = (Item *)b; + return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w; +} + +/* 分數背包:貪婪 */ +float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + Item *items = malloc(sizeof(Item) * itemCount); + for (int i = 0; i < itemCount; i++) { + items[i] = (Item){.w = wgt[i], .v = val[i]}; + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity); + // 迴圈貪婪選擇 + float res = 0.0; + for (int i = 0; i < itemCount; i++) { + if (items[i].w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += items[i].v; + cap -= items[i].w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (float)cap / items[i].w * items[i].v; + cap = 0; + break; + } + } + free(items); + return res; +} + +/* Driver Code */ +int main(void) { + int wgt[] = {10, 20, 30, 40, 50}; + int val[] = {50, 120, 150, 210, 240}; + int capacity = 50; + + // 貪婪演算法 + float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity); + printf("不超過背包容量的最大物品價值為 %0.2f\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_greedy/max_capacity.c b/zh-hant/codes/c/chapter_greedy/max_capacity.c new file mode 100644 index 000000000..04a68f245 --- /dev/null +++ b/zh-hant/codes/c/chapter_greedy/max_capacity.c @@ -0,0 +1,49 @@ +/** + * File: max_capacity.c + * Created Time: 2023-09-15 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 求最小值 */ +int myMin(int a, int b) { + return a < b ? a : b; +} +/* 求最大值 */ +int myMax(int a, int b) { + return a < b ? a : b; +} + +/* 最大容量:貪婪 */ +int maxCapacity(int ht[], int htLength) { + // 初始化 i, j,使其分列陣列兩端 + int i = 0; + int j = htLength - 1; + // 初始最大容量為 0 + int res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + int capacity = myMin(ht[i], ht[j]) * (j - i); + res = myMax(res, capacity); + // 向內移動短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main(void) { + int ht[] = {3, 8, 5, 2, 7, 7, 3, 4}; + + // 貪婪演算法 + int res = maxCapacity(ht, sizeof(ht) / sizeof(int)); + printf("最大容量為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_greedy/max_product_cutting.c b/zh-hant/codes/c/chapter_greedy/max_product_cutting.c new file mode 100644 index 000000000..84cc8dd10 --- /dev/null +++ b/zh-hant/codes/c/chapter_greedy/max_product_cutting.c @@ -0,0 +1,38 @@ +/** + * File: max_product_cutting.c + * Created Time: 2023-09-15 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 最大切分乘積:貪婪 */ +int maxProductCutting(int n) { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + int a = n / 3; + int b = n % 3; + if (b == 1) { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 當餘數為 2 時,不做處理 + return pow(3, a) * 2; + } + // 當餘數為 0 時,不做處理 + return pow(3, a); +} + +/* Driver Code */ +int main(void) { + int n = 58; + // 貪婪演算法 + int res = maxProductCutting(n); + printf("最大切分乘積為 %d\n", res); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_hashing/CMakeLists.txt b/zh-hant/codes/c/chapter_hashing/CMakeLists.txt new file mode 100644 index 000000000..9ac951ae4 --- /dev/null +++ b/zh-hant/codes/c/chapter_hashing/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array_hash_map array_hash_map.c) +add_executable(hash_map_chaining hash_map_chaining.c) +add_executable(hash_map_open_addressing hash_map_open_addressing.c) +add_executable(simple_hash simple_hash.c) diff --git a/zh-hant/codes/c/chapter_hashing/array_hash_map.c b/zh-hant/codes/c/chapter_hashing/array_hash_map.c new file mode 100644 index 000000000..1a44f42d2 --- /dev/null +++ b/zh-hant/codes/c/chapter_hashing/array_hash_map.c @@ -0,0 +1,212 @@ +/** + * File: array_hash_map.c + * Created Time: 2023-03-18 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 雜湊表預設大小 */ +#define HASHTABLE_CAPACITY 100 + +/* 鍵值對 int->string */ +typedef struct { + int key; + char *val; +} Pair; + +/* 鍵值對的集合 */ +typedef struct { + void *set; + int len; +} MapSet; + +/* 基於陣列實現的雜湊表 */ +typedef struct { + Pair *buckets[HASHTABLE_CAPACITY]; +} ArrayHashMap; + +/* 建構子 */ +ArrayHashMap *newArrayHashMap() { + ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap)); + return hmap; +} + +/* 析構函式 */ +void delArrayHashMap(ArrayHashMap *hmap) { + for (int i = 0; i < HASHTABLE_CAPACITY; i++) { + if (hmap->buckets[i] != NULL) { + free(hmap->buckets[i]->val); + free(hmap->buckets[i]); + } + } + free(hmap); +} + +/* 雜湊函式 */ +int hashFunc(int key) { + int index = key % HASHTABLE_CAPACITY; + return index; +} + +/* 查詢操作 */ +const char *get(const ArrayHashMap *hmap, const int key) { + int index = hashFunc(key); + const Pair *Pair = hmap->buckets[index]; + if (Pair == NULL) + return NULL; + return Pair->val; +} + +/* 新增操作 */ +void put(ArrayHashMap *hmap, const int key, const char *val) { + Pair *Pair = malloc(sizeof(Pair)); + Pair->key = key; + Pair->val = malloc(strlen(val) + 1); + strcpy(Pair->val, val); + + int index = hashFunc(key); + hmap->buckets[index] = Pair; +} + +/* 刪除操作 */ +void removeItem(ArrayHashMap *hmap, const int key) { + int index = hashFunc(key); + free(hmap->buckets[index]->val); + free(hmap->buckets[index]); + hmap->buckets[index] = NULL; +} + +/* 獲取所有鍵值對 */ +void pairSet(ArrayHashMap *hmap, MapSet *set) { + Pair *entries; + int i = 0, index = 0; + int total = 0; + /* 統計有效鍵值對數量 */ + for (i = 0; i < HASHTABLE_CAPACITY; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + entries = malloc(sizeof(Pair) * total); + for (i = 0; i < HASHTABLE_CAPACITY; i++) { + if (hmap->buckets[i] != NULL) { + entries[index].key = hmap->buckets[i]->key; + entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1); + strcpy(entries[index].val, hmap->buckets[i]->val); + index++; + } + } + set->set = entries; + set->len = total; +} + +/* 獲取所有鍵 */ +void keySet(ArrayHashMap *hmap, MapSet *set) { + int *keys; + int i = 0, index = 0; + int total = 0; + /* 統計有效鍵值對數量 */ + for (i = 0; i < HASHTABLE_CAPACITY; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + keys = malloc(total * sizeof(int)); + for (i = 0; i < HASHTABLE_CAPACITY; i++) { + if (hmap->buckets[i] != NULL) { + keys[index] = hmap->buckets[i]->key; + index++; + } + } + set->set = keys; + set->len = total; +} + +/* 獲取所有值 */ +void valueSet(ArrayHashMap *hmap, MapSet *set) { + char **vals; + int i = 0, index = 0; + int total = 0; + /* 統計有效鍵值對數量 */ + for (i = 0; i < HASHTABLE_CAPACITY; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + vals = malloc(total * sizeof(char *)); + for (i = 0; i < HASHTABLE_CAPACITY; i++) { + if (hmap->buckets[i] != NULL) { + vals[index] = hmap->buckets[i]->val; + index++; + } + } + set->set = vals; + set->len = total; +} + +/* 列印雜湊表 */ +void print(ArrayHashMap *hmap) { + int i; + MapSet set; + pairSet(hmap, &set); + Pair *entries = (Pair *)set.set; + for (i = 0; i < set.len; i++) { + printf("%d -> %s\n", entries[i].key, entries[i].val); + } + free(set.set); +} + +/* Driver Code */ +int main() { + /* 初始化雜湊表 */ + ArrayHashMap *hmap = newArrayHashMap(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + put(hmap, 12836, "小哈"); + put(hmap, 15937, "小囉"); + put(hmap, 16750, "小算"); + put(hmap, 13276, "小法"); + put(hmap, 10583, "小鴨"); + printf("\n新增完成後,雜湊表為\nKey -> Value\n"); + print(hmap); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + const char *name = get(hmap, 15937); + printf("\n輸入學號 15937 ,查詢到姓名 %s\n", name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + removeItem(hmap, 10583); + printf("\n刪除 10583 後,雜湊表為\nKey -> Value\n"); + print(hmap); + + /* 走訪雜湊表 */ + int i; + + printf("\n走訪鍵值對 Key->Value\n"); + print(hmap); + + MapSet set; + + keySet(hmap, &set); + int *keys = (int *)set.set; + printf("\n單獨走訪鍵 Key\n"); + for (i = 0; i < set.len; i++) { + printf("%d\n", keys[i]); + } + free(set.set); + + valueSet(hmap, &set); + char **vals = (char **)set.set; + printf("\n單獨走訪鍵 Value\n"); + for (i = 0; i < set.len; i++) { + printf("%s\n", vals[i]); + } + free(set.set); + + delArrayHashMap(hmap); + return 0; +} diff --git a/zh-hant/codes/c/chapter_hashing/hash_map_chaining.c b/zh-hant/codes/c/chapter_hashing/hash_map_chaining.c new file mode 100644 index 000000000..94afbb86d --- /dev/null +++ b/zh-hant/codes/c/chapter_hashing/hash_map_chaining.c @@ -0,0 +1,213 @@ +/** + * File: hash_map_chaining.c + * Created Time: 2023-10-13 + * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com) + */ + +#include +#include +#include + +// 假設 val 最大長度為 100 +#define MAX_SIZE 100 + +/* 鍵值對 */ +typedef struct { + int key; + char val[MAX_SIZE]; +} Pair; + +/* 鏈結串列節點 */ +typedef struct Node { + Pair *pair; + struct Node *next; +} Node; + +/* 鏈式位址雜湊表 */ +typedef struct { + int size; // 鍵值對數量 + int capacity; // 雜湊表容量 + double loadThres; // 觸發擴容的負載因子閾值 + int extendRatio; // 擴容倍數 + Node **buckets; // 桶陣列 +} HashMapChaining; + +/* 建構子 */ +HashMapChaining *newHashMapChaining() { + HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); + hashMap->size = 0; + hashMap->capacity = 4; + hashMap->loadThres = 2.0 / 3.0; + hashMap->extendRatio = 2; + hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); + for (int i = 0; i < hashMap->capacity; i++) { + hashMap->buckets[i] = NULL; + } + return hashMap; +} + +/* 析構函式 */ +void delHashMapChaining(HashMapChaining *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Node *cur = hashMap->buckets[i]; + while (cur) { + Node *tmp = cur; + cur = cur->next; + free(tmp->pair); + free(tmp); + } + } + free(hashMap->buckets); + free(hashMap); +} + +/* 雜湊函式 */ +int hashFunc(HashMapChaining *hashMap, int key) { + return key % hashMap->capacity; +} + +/* 負載因子 */ +double loadFactor(HashMapChaining *hashMap) { + return (double)hashMap->size / (double)hashMap->capacity; +} + +/* 查詢操作 */ +char *get(HashMapChaining *hashMap, int key) { + int index = hashFunc(hashMap, key); + // 走訪桶,若找到 key ,則返回對應 val + Node *cur = hashMap->buckets[index]; + while (cur) { + if (cur->pair->key == key) { + return cur->pair->val; + } + cur = cur->next; + } + return ""; // 若未找到 key ,則返回空字串 +} + +/* 新增操作 */ +void put(HashMapChaining *hashMap, int key, const char *val); + +/* 擴容雜湊表 */ +void extend(HashMapChaining *hashMap) { + // 暫存原雜湊表 + int oldCapacity = hashMap->capacity; + Node **oldBuckets = hashMap->buckets; + // 初始化擴容後的新雜湊表 + hashMap->capacity *= hashMap->extendRatio; + hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); + for (int i = 0; i < hashMap->capacity; i++) { + hashMap->buckets[i] = NULL; + } + hashMap->size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (int i = 0; i < oldCapacity; i++) { + Node *cur = oldBuckets[i]; + while (cur) { + put(hashMap, cur->pair->key, cur->pair->val); + Node *temp = cur; + cur = cur->next; + // 釋放記憶體 + free(temp->pair); + free(temp); + } + } + + free(oldBuckets); +} + +/* 新增操作 */ +void put(HashMapChaining *hashMap, int key, const char *val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor(hashMap) > hashMap->loadThres) { + extend(hashMap); + } + int index = hashFunc(hashMap, key); + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + Node *cur = hashMap->buckets[index]; + while (cur) { + if (cur->pair->key == key) { + strcpy(cur->pair->val, val); // 若遇到指定 key ,則更新對應 val 並返回 + return; + } + cur = cur->next; + } + // 若無該 key ,則將鍵值對新增至鏈結串列頭部 + Pair *newPair = (Pair *)malloc(sizeof(Pair)); + newPair->key = key; + strcpy(newPair->val, val); + Node *newNode = (Node *)malloc(sizeof(Node)); + newNode->pair = newPair; + newNode->next = hashMap->buckets[index]; + hashMap->buckets[index] = newNode; + hashMap->size++; +} + +/* 刪除操作 */ +void removeItem(HashMapChaining *hashMap, int key) { + int index = hashFunc(hashMap, key); + Node *cur = hashMap->buckets[index]; + Node *pre = NULL; + while (cur) { + if (cur->pair->key == key) { + // 從中刪除鍵值對 + if (pre) { + pre->next = cur->next; + } else { + hashMap->buckets[index] = cur->next; + } + // 釋放記憶體 + free(cur->pair); + free(cur); + hashMap->size--; + return; + } + pre = cur; + cur = cur->next; + } +} + +/* 列印雜湊表 */ +void print(HashMapChaining *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Node *cur = hashMap->buckets[i]; + printf("["); + while (cur) { + printf("%d -> %s, ", cur->pair->key, cur->pair->val); + cur = cur->next; + } + printf("]\n"); + } +} + +/* Driver Code */ +int main() { + /* 初始化雜湊表 */ + HashMapChaining *hashMap = newHashMapChaining(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + put(hashMap, 12836, "小哈"); + put(hashMap, 15937, "小囉"); + put(hashMap, 16750, "小算"); + put(hashMap, 13276, "小法"); + put(hashMap, 10583, "小鴨"); + printf("\n新增完成後,雜湊表為\nKey -> Value\n"); + print(hashMap); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + char *name = get(hashMap, 13276); + printf("\n輸入學號 13276 ,查詢到姓名 %s\n", name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + removeItem(hashMap, 12836); + printf("\n刪除學號 12836 後,雜湊表為\nKey -> Value\n"); + print(hashMap); + + /* 釋放雜湊表空間 */ + delHashMapChaining(hashMap); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_hashing/hash_map_open_addressing.c b/zh-hant/codes/c/chapter_hashing/hash_map_open_addressing.c new file mode 100644 index 000000000..4c374c199 --- /dev/null +++ b/zh-hant/codes/c/chapter_hashing/hash_map_open_addressing.c @@ -0,0 +1,208 @@ +/** + * File: hash_map_open_addressing.c + * Created Time: 2023-10-6 + * Author: lclc6 (w1929522410@163.com) + */ + +#include "../utils/common.h" + +/* 開放定址雜湊表 */ +typedef struct { + int key; + char *val; +} Pair; + +/* 開放定址雜湊表 */ +typedef struct { + int size; // 鍵值對數量 + int capacity; // 雜湊表容量 + double loadThres; // 觸發擴容的負載因子閾值 + int extendRatio; // 擴容倍數 + Pair **buckets; // 桶陣列 + Pair *TOMBSTONE; // 刪除標記 +} HashMapOpenAddressing; + +// 函式宣告 +void extend(HashMapOpenAddressing *hashMap); + +/* 建構子 */ +HashMapOpenAddressing *newHashMapOpenAddressing() { + HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); + hashMap->size = 0; + hashMap->capacity = 4; + hashMap->loadThres = 2.0 / 3.0; + hashMap->extendRatio = 2; + hashMap->buckets = (Pair **)malloc(sizeof(Pair *) * hashMap->capacity); + hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); + hashMap->TOMBSTONE->key = -1; + hashMap->TOMBSTONE->val = "-1"; + + return hashMap; +} + +/* 析構函式 */ +void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Pair *pair = hashMap->buckets[i]; + if (pair != NULL && pair != hashMap->TOMBSTONE) { + free(pair->val); + free(pair); + } + } +} + +/* 雜湊函式 */ +int hashFunc(HashMapOpenAddressing *hashMap, int key) { + return key % hashMap->capacity; +} + +/* 負載因子 */ +double loadFactor(HashMapOpenAddressing *hashMap) { + return (double)hashMap->size / (double)hashMap->capacity; +} + +/* 搜尋 key 對應的桶索引 */ +int findBucket(HashMapOpenAddressing *hashMap, int key) { + int index = hashFunc(hashMap, key); + int firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (hashMap->buckets[index] != NULL) { + // 若遇到 key ,返回對應的桶索引 + if (hashMap->buckets[index]->key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + hashMap->buckets[firstTombstone] = hashMap->buckets[index]; + hashMap->buckets[index] = hashMap->TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % hashMap->capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone; +} + +/* 查詢操作 */ +char *get(HashMapOpenAddressing *hashMap, int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(hashMap, key); + // 若找到鍵值對,則返回對應 val + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + return hashMap->buckets[index]->val; + } + // 若鍵值對不存在,則返回空字串 + return ""; +} + +/* 新增操作 */ +void put(HashMapOpenAddressing *hashMap, int key, char *val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor(hashMap) > hashMap->loadThres) { + extend(hashMap); + } + // 搜尋 key 對應的桶索引 + int index = findBucket(hashMap, key); + // 若找到鍵值對,則覆蓋 val 並返回 + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + free(hashMap->buckets[index]->val); + hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); + strcpy(hashMap->buckets[index]->val, val); + hashMap->buckets[index]->val[strlen(val)] = '\0'; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + Pair *pair = (Pair *)malloc(sizeof(Pair)); + pair->key = key; + pair->val = (char *)malloc(sizeof(strlen(val) + 1)); + strcpy(pair->val, val); + pair->val[strlen(val)] = '\0'; + + hashMap->buckets[index] = pair; + hashMap->size++; +} + +/* 刪除操作 */ +void removeItem(HashMapOpenAddressing *hashMap, int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(hashMap, key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + Pair *pair = hashMap->buckets[index]; + free(pair->val); + free(pair); + hashMap->buckets[index] = hashMap->TOMBSTONE; + hashMap->size--; + } +} + +/* 擴容雜湊表 */ +void extend(HashMapOpenAddressing *hashMap) { + // 暫存原雜湊表 + Pair **bucketsTmp = hashMap->buckets; + int oldCapacity = hashMap->capacity; + // 初始化擴容後的新雜湊表 + hashMap->capacity *= hashMap->extendRatio; + hashMap->buckets = (Pair **)malloc(sizeof(Pair *) * hashMap->capacity); + hashMap->size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (int i = 0; i < oldCapacity; i++) { + Pair *pair = bucketsTmp[i]; + if (pair != NULL && pair != hashMap->TOMBSTONE) { + put(hashMap, pair->key, pair->val); + free(pair->val); + free(pair); + } + } + free(bucketsTmp); +} + +/* 列印雜湊表 */ +void print(HashMapOpenAddressing *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Pair *pair = hashMap->buckets[i]; + if (pair == NULL) { + printf("NULL\n"); + } else if (pair == hashMap->TOMBSTONE) { + printf("TOMBSTONE\n"); + } else { + printf("%d -> %s\n", pair->key, pair->val); + } + } +} + +/* Driver Code */ +int main() { + // 初始化雜湊表 + HashMapOpenAddressing *hashmap = newHashMapOpenAddressing(); + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, val) + put(hashmap, 12836, "小哈"); + put(hashmap, 15937, "小囉"); + put(hashmap, 16750, "小算"); + put(hashmap, 13276, "小法"); + put(hashmap, 10583, "小鴨"); + printf("\n新增完成後,雜湊表為\nKey -> Value\n"); + print(hashmap); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 val + char *name = get(hashmap, 13276); + printf("\n輸入學號 13276 ,查詢到姓名 %s\n", name); + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, val) + removeItem(hashmap, 16750); + printf("\n刪除 16750 後,雜湊表為\nKey -> Value\n"); + print(hashmap); + + // 銷燬雜湊表 + delHashMapOpenAddressing(hashmap); + return 0; +} diff --git a/zh-hant/codes/c/chapter_hashing/simple_hash.c b/zh-hant/codes/c/chapter_hashing/simple_hash.c new file mode 100644 index 000000000..05d21e7b3 --- /dev/null +++ b/zh-hant/codes/c/chapter_hashing/simple_hash.c @@ -0,0 +1,68 @@ +/** + * File: simple_hash.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 加法雜湊 */ +int addHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = (hash + (unsigned char)key[i]) % MODULUS; + } + return (int)hash; +} + +/* 乘法雜湊 */ +int mulHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = (31 * hash + (unsigned char)key[i]) % MODULUS; + } + return (int)hash; +} + +/* 互斥或雜湊 */ +int xorHash(char *key) { + int hash = 0; + const int MODULUS = 1000000007; + + for (int i = 0; i < strlen(key); i++) { + hash ^= (unsigned char)key[i]; + } + return hash & MODULUS; +} + +/* 旋轉雜湊 */ +int rotHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS; + } + + return (int)hash; +} + +/* Driver Code */ +int main() { + char *key = "Hello dsad3241241dsa算123法"; + + int hash = addHash(key); + printf("加法雜湊值為 %d\n", hash); + + hash = mulHash(key); + printf("乘法雜湊值為 %d\n", hash); + + hash = xorHash(key); + printf("互斥或雜湊值為 %d\n", hash); + + hash = rotHash(key); + printf("旋轉雜湊值為 %d\n", hash); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_heap/CMakeLists.txt b/zh-hant/codes/c/chapter_heap/CMakeLists.txt new file mode 100644 index 000000000..357ec702c --- /dev/null +++ b/zh-hant/codes/c/chapter_heap/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(my_heap_test my_heap_test.c) +add_executable(top_k top_k.c) diff --git a/zh-hant/codes/c/chapter_heap/my_heap.c b/zh-hant/codes/c/chapter_heap/my_heap.c new file mode 100644 index 000000000..17daca231 --- /dev/null +++ b/zh-hant/codes/c/chapter_heap/my_heap.c @@ -0,0 +1,152 @@ +/** + * File: my_heap.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 5000 + +/* 大頂堆積 */ +typedef struct { + // size 代表的是實際元素的個數 + int size; + // 使用預先分配記憶體的陣列,避免擴容 + int data[MAX_SIZE]; +} MaxHeap; + +// 函式宣告 +void siftDown(MaxHeap *maxHeap, int i); +void siftUp(MaxHeap *maxHeap, int i); +int parent(MaxHeap *maxHeap, int i); + +/* 建構子,根據切片建堆積 */ +MaxHeap *newMaxHeap(int nums[], int size) { + // 所有元素入堆積 + MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); + maxHeap->size = size; + memcpy(maxHeap->data, nums, size * sizeof(int)); + for (int i = parent(maxHeap, size - 1); i >= 0; i--) { + // 堆積化除葉節點以外的其他所有節點 + siftDown(maxHeap, i); + } + return maxHeap; +} + +/* 析構函式 */ +void delMaxHeap(MaxHeap *maxHeap) { + // 釋放記憶體 + free(maxHeap); +} + +/* 獲取左子節點的索引 */ +int left(MaxHeap *maxHeap, int i) { + return 2 * i + 1; +} + +/* 獲取右子節點的索引 */ +int right(MaxHeap *maxHeap, int i) { + return 2 * i + 2; +} + +/* 獲取父節點的索引 */ +int parent(MaxHeap *maxHeap, int i) { + return (i - 1) / 2; +} + +/* 交換元素 */ +void swap(MaxHeap *maxHeap, int i, int j) { + int temp = maxHeap->data[i]; + maxHeap->data[i] = maxHeap->data[j]; + maxHeap->data[j] = temp; +} + +/* 獲取堆積大小 */ +int size(MaxHeap *maxHeap) { + return maxHeap->size; +} + +/* 判斷堆積是否為空 */ +int isEmpty(MaxHeap *maxHeap) { + return maxHeap->size == 0; +} + +/* 訪問堆積頂元素 */ +int peek(MaxHeap *maxHeap) { + return maxHeap->data[0]; +} + +/* 元素入堆積 */ +void push(MaxHeap *maxHeap, int val) { + // 預設情況下,不應該新增這麼多節點 + if (maxHeap->size == MAX_SIZE) { + printf("heap is full!"); + return; + } + // 新增節點 + maxHeap->data[maxHeap->size] = val; + maxHeap->size++; + + // 從底至頂堆積化 + siftUp(maxHeap, maxHeap->size - 1); +} + +/* 元素出堆積 */ +int pop(MaxHeap *maxHeap) { + // 判空處理 + if (isEmpty(maxHeap)) { + printf("heap is empty!"); + return INT_MAX; + } + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(maxHeap, 0, size(maxHeap) - 1); + // 刪除節點 + int val = maxHeap->data[maxHeap->size - 1]; + maxHeap->size--; + // 從頂至底堆積化 + siftDown(maxHeap, 0); + + // 返回堆積頂元素 + return val; +} + +/* 從節點 i 開始,從頂至底堆積化 */ +void siftDown(MaxHeap *maxHeap, int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 max + int l = left(maxHeap, i); + int r = right(maxHeap, i); + int max = i; + if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { + max = l; + } + if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { + max = r; + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (max == i) { + break; + } + // 交換兩節點 + swap(maxHeap, i, max); + // 迴圈向下堆積化 + i = max; + } +} + +/* 從節點 i 開始,從底至頂堆積化 */ +void siftUp(MaxHeap *maxHeap, int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = parent(maxHeap, i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { + break; + } + // 交換兩節點 + swap(maxHeap, i, p); + // 迴圈向上堆積化 + i = p; + } +} diff --git a/zh-hant/codes/c/chapter_heap/my_heap_test.c b/zh-hant/codes/c/chapter_heap/my_heap_test.c new file mode 100644 index 000000000..673082a2c --- /dev/null +++ b/zh-hant/codes/c/chapter_heap/my_heap_test.c @@ -0,0 +1,41 @@ +/** + * File: my_heap_test.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "my_heap.c" + +/* Driver Code */ +int main() { + /* 初始化堆積 */ + // 初始化大頂堆積 + int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int)); + printf("輸入陣列並建堆積後\n"); + printHeap(maxHeap->data, maxHeap->size); + + /* 獲取堆積頂元素 */ + printf("\n堆積頂元素為 %d\n", peek(maxHeap)); + + /* 元素入堆積 */ + push(maxHeap, 7); + printf("\n元素 7 入堆積後\n"); + printHeap(maxHeap->data, maxHeap->size); + + /* 堆積頂元素出堆積 */ + int top = pop(maxHeap); + printf("\n堆積頂元素 %d 出堆積後\n", top); + printHeap(maxHeap->data, maxHeap->size); + + /* 獲取堆積大小 */ + printf("\n堆積元素數量為 %d\n", size(maxHeap)); + + /* 判斷堆積是否為空 */ + printf("\n堆積是否為空 %d\n", isEmpty(maxHeap)); + + // 釋放記憶體 + delMaxHeap(maxHeap); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_heap/top_k.c b/zh-hant/codes/c/chapter_heap/top_k.c new file mode 100644 index 000000000..2c78f5979 --- /dev/null +++ b/zh-hant/codes/c/chapter_heap/top_k.c @@ -0,0 +1,73 @@ +/** + * File: top_k.c + * Created Time: 2023-10-26 + * Author: krahets (krahets163.com) + */ + +#include "my_heap.c" + +/* 元素入堆積 */ +void pushMinHeap(MaxHeap *maxHeap, int val) { + // 元素取反 + push(maxHeap, -val); +} + +/* 元素出堆積 */ +int popMinHeap(MaxHeap *maxHeap) { + // 元素取反 + return -pop(maxHeap); +} + +/* 訪問堆積頂元素 */ +int peekMinHeap(MaxHeap *maxHeap) { + // 元素取反 + return -peek(maxHeap); +} + +/* 取出堆積中元素 */ +int *getMinHeap(MaxHeap *maxHeap) { + // 將堆積中所有元素取反並存入 res 陣列 + int *res = (int *)malloc(maxHeap->size * sizeof(int)); + for (int i = 0; i < maxHeap->size; i++) { + res[i] = -maxHeap->data[i]; + } + return res; +} + +// 基於堆積查詢陣列中最大的 k 個元素的函式 +int *topKHeap(int *nums, int sizeNums, int k) { + // 初始化小頂堆積 + // 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 + int *empty = (int *)malloc(0); + MaxHeap *maxHeap = newMaxHeap(empty, 0); + // 將陣列的前 k 個元素入堆積 + for (int i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (int i = k; i < sizeNums; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + int *res = getMinHeap(maxHeap); + // 釋放記憶體 + delMaxHeap(maxHeap); + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 7, 6, 3, 2}; + int k = 3; + int sizeNums = sizeof(nums) / sizeof(nums[0]); + + int *res = topKHeap(nums, sizeNums, k); + printf("最大的 %d 個元素為: ", k); + printArray(res, k); + + free(res); + return 0; +} diff --git a/zh-hant/codes/c/chapter_searching/CMakeLists.txt b/zh-hant/codes/c/chapter_searching/CMakeLists.txt new file mode 100644 index 000000000..7b2a152d5 --- /dev/null +++ b/zh-hant/codes/c/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.c) +add_executable(two_sum two_sum.c) +add_executable(binary_search_edge binary_search_edge.c) +add_executable(binary_search_insertion binary_search_insertion.c) diff --git a/zh-hant/codes/c/chapter_searching/binary_search.c b/zh-hant/codes/c/chapter_searching/binary_search.c new file mode 100644 index 000000000..16abe0dc9 --- /dev/null +++ b/zh-hant/codes/c/chapter_searching/binary_search.c @@ -0,0 +1,59 @@ +/** + * File: binary_search.c + * Created Time: 2023-03-18 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 二分搜尋(雙閉區間) */ +int binarySearch(int *nums, int len, int target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + int i = 0, j = len - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + else // 找到目標元素,返回其索引 + return m; + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 二分搜尋(左閉右開區間) */ +int binarySearchLCRO(int *nums, int len, int target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + int i = 0, j = len; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m) 中 + j = m; + else // 找到目標元素,返回其索引 + return m; + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* Driver Code */ +int main() { + int target = 6; + int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + /* 二分搜尋(雙閉區間) */ + int index = binarySearch(nums, 10, target); + printf("目標元素 6 的索引 = %d\n", index); + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums, 10, target); + printf("目標元素 6 的索引 = %d\n", index); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_searching/binary_search_edge.c b/zh-hant/codes/c/chapter_searching/binary_search_edge.c new file mode 100644 index 000000000..d1285fe9b --- /dev/null +++ b/zh-hant/codes/c/chapter_searching/binary_search_edge.c @@ -0,0 +1,67 @@ +/** + * File: binary_search_edge.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 二分搜尋插入點(存在重複元素) */ +int binarySearchInsertion(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; +} + +/* 二分搜尋最左一個 target */ +int binarySearchLeftEdge(int *nums, int numSize, int target) { + // 等價於查詢 target 的插入點 + int i = binarySearchInsertion(nums, numSize, target); + // 未找到 target ,返回 -1 + if (i == numSize || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分搜尋最右一個 target */ +int binarySearchRightEdge(int *nums, int numSize, int target) { + // 轉化為查詢最左一個 target + 1 + int i = binarySearchInsertion(nums, numSize, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +int main() { + // 包含重複元素的陣列 + int nums[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + printf("\n陣列 nums = "); + printArray(nums, sizeof(nums) / sizeof(nums[0])); + + // 二分搜尋左邊界和右邊界 + int targets[] = {6, 7}; + for (int i = 0; i < sizeof(targets) / sizeof(targets[0]); i++) { + int index = binarySearchLeftEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); + printf("最左一個元素 %d 的索引為 %d\n", targets[i], index); + index = binarySearchRightEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); + printf("最右一個元素 %d 的索引為 %d\n", targets[i], index); + } + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_searching/binary_search_insertion.c b/zh-hant/codes/c/chapter_searching/binary_search_insertion.c new file mode 100644 index 000000000..67ea0784e --- /dev/null +++ b/zh-hant/codes/c/chapter_searching/binary_search_insertion.c @@ -0,0 +1,68 @@ +/** + * File: binary_search_insertion.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 二分搜尋插入點(無重複元素) */ +int binarySearchInsertionSimple(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i; +} + +/* 二分搜尋插入點(存在重複元素) */ +int binarySearchInsertion(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; +} + +/* Driver Code */ +int main() { + // 無重複元素的陣列 + int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + printf("\n陣列 nums = "); + printArray(nums1, sizeof(nums1) / sizeof(nums1[0])); + // 二分搜尋插入點 + int targets1[] = {6, 9}; + for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) { + int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]); + printf("元素 %d 的插入點的索引為 %d\n", targets1[i], index); + } + + // 包含重複元素的陣列 + int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + printf("\n陣列 nums = "); + printArray(nums2, sizeof(nums2) / sizeof(nums2[0])); + // 二分搜尋插入點 + int targets2[] = {2, 6, 20}; + for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) { + int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]); + printf("元素 %d 的插入點的索引為 %d\n", targets2[i], index); + } + + return 0; +} diff --git a/zh-hant/codes/c/chapter_searching/two_sum.c b/zh-hant/codes/c/chapter_searching/two_sum.c new file mode 100644 index 000000000..30f32dd9e --- /dev/null +++ b/zh-hant/codes/c/chapter_searching/two_sum.c @@ -0,0 +1,86 @@ +/** + * File: two_sum.c + * Created Time: 2023-01-19 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 方法一:暴力列舉 */ +int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) { + for (int i = 0; i < numsSize; ++i) { + for (int j = i + 1; j < numsSize; ++j) { + if (nums[i] + nums[j] == target) { + int *res = malloc(sizeof(int) * 2); + res[0] = i, res[1] = j; + *returnSize = 2; + return res; + } + } + } + *returnSize = 0; + return NULL; +} + +/* 雜湊表 */ +typedef struct { + int key; + int val; + UT_hash_handle hh; // 基於 uthash.h 實現 +} HashTable; + +/* 雜湊表查詢 */ +HashTable *find(HashTable *h, int key) { + HashTable *tmp; + HASH_FIND_INT(h, &key, tmp); + return tmp; +} + +/* 雜湊表元素插入 */ +void insert(HashTable *h, int key, int val) { + HashTable *t = find(h, key); + if (t == NULL) { + HashTable *tmp = malloc(sizeof(HashTable)); + tmp->key = key, tmp->val = val; + HASH_ADD_INT(h, key, tmp); + } else { + t->val = val; + } +} + +/* 方法二:輔助雜湊表 */ +int *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) { + HashTable *hashtable = NULL; + for (int i = 0; i < numsSize; i++) { + HashTable *t = find(hashtable, target - nums[i]); + if (t != NULL) { + int *res = malloc(sizeof(int) * 2); + res[0] = t->val, res[1] = i; + *returnSize = 2; + return res; + } + insert(hashtable, nums[i], i); + } + *returnSize = 0; + return NULL; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + int nums[] = {2, 7, 11, 15}; + int target = 13; + // ====== Driver Code ====== + int returnSize; + int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize); + // 方法一 + printf("方法一 res = "); + printArray(res, returnSize); + + // 方法二 + res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize); + printf("方法二 res = "); + printArray(res, returnSize); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/CMakeLists.txt b/zh-hant/codes/c/chapter_sorting/CMakeLists.txt new file mode 100644 index 000000000..88756b4c9 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(bubble_sort bubble_sort.c) +add_executable(insertion_sort insertion_sort.c) +add_executable(quick_sort quick_sort.c) +add_executable(counting_sort counting_sort.c) +add_executable(radix_sort radix_sort.c) +add_executable(merge_sort merge_sort.c) +add_executable(heap_sort heap_sort.c) +add_executable(bucket_sort bucket_sort.c) +add_executable(selection_sort selection_sort.c) diff --git a/zh-hant/codes/c/chapter_sorting/bubble_sort.c b/zh-hant/codes/c/chapter_sorting/bubble_sort.c new file mode 100644 index 000000000..6c6ddff61 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/bubble_sort.c @@ -0,0 +1,61 @@ +/** + * File: bubble_sort.c + * Created Time: 2022-12-26 + * Author: Listening (https://github.com/L-Super) + */ + +#include "../utils/common.h" + +/* 泡沫排序 */ +void bubbleSort(int nums[], int size) { + // 外迴圈:未排序區間為 [0, i] + for (int i = size - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +void bubbleSortWithFlag(int nums[], int size) { + // 外迴圈:未排序區間為 [0, i] + for (int i = size - 1; i > 0; i--) { + bool flag = false; + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + flag = true; + } + } + if (!flag) + break; + } +} + +/* Driver Code */ +int main() { + int nums[6] = {4, 1, 3, 1, 5, 2}; + printf("泡沫排序後: "); + bubbleSort(nums, 6); + for (int i = 0; i < 6; i++) { + printf("%d ", nums[i]); + } + + int nums1[6] = {4, 1, 3, 1, 5, 2}; + printf("\n最佳化版泡沫排序後: "); + bubbleSortWithFlag(nums1, 6); + for (int i = 0; i < 6; i++) { + printf("%d ", nums1[i]); + } + printf("\n"); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/bucket_sort.c b/zh-hant/codes/c/chapter_sorting/bucket_sort.c new file mode 100644 index 000000000..f426a7bc1 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/bucket_sort.c @@ -0,0 +1,82 @@ +/** + * File: bucket_sort.c + * Created Time: 2023-05-30 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define SIZE 10 + +/* 比較兩個浮點數的大小 */ +int compare_float(const void *a, const void *b) { + float fa = *(const float *)a; + float fb = *(const float *)b; + return (fa > fb) - (fa < fb); +} + +/* 交換兩個浮點數 */ +void swap(float *a, float *b) { + float tmp = *a; + *a = *b; + *b = tmp; +} + +/* 桶排序 */ +void bucketSort(float nums[], int size) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + int k = size / 2; + float **buckets = calloc(k, sizeof(float *)); + for (int i = 0; i < k; i++) { + // 每個桶最多可以分配 size 個元素 + buckets[i] = calloc(size, sizeof(float)); + } + + // 1. 將陣列元素分配到各個桶中 + for (int i = 0; i < size; i++) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + int bucket_idx = nums[i] * k; + int j = 0; + // 如果桶中有資料且資料小於當前值 nums[i], 要將其放到當前桶的後面,相當於 cpp 中的 push_back + while (buckets[bucket_idx][j] > 0 && buckets[bucket_idx][j] < nums[i]) { + j++; + } + float temp = nums[i]; + while (j < size && buckets[bucket_idx][j] > 0) { + swap(&temp, &buckets[bucket_idx][j]); + j++; + } + buckets[bucket_idx][j] = temp; + } + + // 2. 對各個桶執行排序 + for (int i = 0; i < k; i++) { + qsort(buckets[i], size, sizeof(float), compare_float); + } + + // 3. 走訪桶合併結果 + for (int i = 0, j = 0; j < k; j++) { + for (int l = 0; l < size; l++) { + if (buckets[j][l] > 0) { + nums[i++] = buckets[j][l]; + } + } + } + + // 釋放上述分配的記憶體 + for (int i = 0; i < k; i++) { + free(buckets[i]); + } + free(buckets); +} + +/* Driver Code */ +int main() { + // 設輸入資料為浮點數,範圍為 [0, 1) + float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums, SIZE); + printf("桶排序完成後 nums = "); + printArrayFloat(nums, SIZE); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_sorting/counting_sort.c b/zh-hant/codes/c/chapter_sorting/counting_sort.c new file mode 100644 index 000000000..3c887a981 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/counting_sort.c @@ -0,0 +1,86 @@ +/** + * File: counting_sort.c + * Created Time: 2023-03-20 + * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +void countingSortNaive(int nums[], int size) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int *counter = calloc(m + 1, sizeof(int)); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + // 4. 釋放記憶體 + free(counter); +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +void countingSort(int nums[], int size) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int *counter = calloc(m, sizeof(int)); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + int *res = malloc(sizeof(int) * size); + for (int i = size - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + memcpy(nums, res, size * sizeof(int)); + // 5. 釋放記憶體 + free(counter); +} + +/* Driver Code */ +int main() { + int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + int size = sizeof(nums) / sizeof(int); + countingSortNaive(nums, size); + printf("計數排序(無法排序物件)完成後 nums = "); + printArray(nums, size); + + int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + int size1 = sizeof(nums1) / sizeof(int); + countingSort(nums1, size1); + printf("計數排序完成後 nums1 = "); + printArray(nums1, size1); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/heap_sort.c b/zh-hant/codes/c/chapter_sorting/heap_sort.c new file mode 100644 index 000000000..17428e404 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/heap_sort.c @@ -0,0 +1,60 @@ +/** + * File: heap_sort.c + * Created Time: 2023-05-30 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +void siftDown(int nums[], int n, int i) { + while (1) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) { + break; + } + // 交換兩節點 + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +void heapSort(int nums[], int n) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (int i = n / 2 - 1; i >= 0; --i) { + siftDown(nums, n, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (int i = n - 1; i > 0; --i) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + int n = sizeof(nums) / sizeof(nums[0]); + + heapSort(nums, n); + printf("堆積排序完成後 nums = "); + printArray(nums, n); + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/c/chapter_sorting/insertion_sort.c b/zh-hant/codes/c/chapter_sorting/insertion_sort.c new file mode 100644 index 000000000..36a683816 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/insertion_sort.c @@ -0,0 +1,36 @@ +/** + * File: insertion_sort.c + * Created Time: 2022-12-29 + * Author: Listening (https://github.com/L-Super) + */ + +#include "../utils/common.h" + +/* 插入排序 */ +void insertionSort(int nums[], int size) { + // 外迴圈:已排序區間為 [0, i-1] + for (int i = 1; i < size; i++) { + int base = nums[i], j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + // 將 nums[j] 向右移動一位 + nums[j + 1] = nums[j]; + j--; + } + // 將 base 賦值到正確位置 + nums[j + 1] = base; + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + insertionSort(nums, 6); + printf("插入排序完成後 nums = "); + for (int i = 0; i < 6; i++) { + printf("%d ", nums[i]); + } + printf("\n"); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/merge_sort.c b/zh-hant/codes/c/chapter_sorting/merge_sort.c new file mode 100644 index 000000000..1ca94cdb2 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/merge_sort.c @@ -0,0 +1,63 @@ +/** + * File: merge_sort.c + * Created Time: 2022-03-21 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 合併左子陣列和右子陣列 */ +void merge(int *nums, int left, int mid, int right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + int tmpSize = right - left + 1; + int *tmp = (int *)malloc(tmpSize * sizeof(int)); + // 初始化左子陣列和右子陣列的起始索引 + int i = left, j = mid + 1, k = 0; + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for (k = 0; k < tmpSize; ++k) { + nums[left + k] = tmp[k]; + } + // 釋放記憶體 + free(tmp); +} + +/* 合併排序 */ +void mergeSort(int *nums, int left, int right) { + // 終止條件 + if (left >= right) + return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + int mid = (left + right) / 2; // 計算中點 + mergeSort(nums, left, mid); // 遞迴左子陣列 + mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +int main() { + /* 合併排序 */ + int nums[] = {7, 3, 2, 6, 0, 1, 5, 4}; + int size = sizeof(nums) / sizeof(int); + mergeSort(nums, 0, size - 1); + printf("合併排序完成後 nums = "); + printArray(nums, size); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/quick_sort.c b/zh-hant/codes/c/chapter_sorting/quick_sort.c new file mode 100644 index 000000000..7ec1fae7c --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/quick_sort.c @@ -0,0 +1,134 @@ +/** + * File: quick_sort.c + * Created Time: 2023-01-18 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 元素交換 */ +void swap(int nums[], int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; +} + +/* 快速排序類別 */ +// 快速排序類別-哨兵劃分 +int partition(int nums[], int left, int right) { + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + // 從右向左找首個小於基準數的元素 + j--; + } + while (i < j && nums[i] <= nums[left]) { + // 從左向右找首個大於基準數的元素 + i++; + } + // 交換這兩個元素 + swap(nums, i, j); + } + // 將基準數交換至兩子陣列的分界線 + swap(nums, i, left); + // 返回基準數的索引 + return i; +} + +// 快速排序類別-快速排序 +void quickSort(int nums[], int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) { + return; + } + // 哨兵劃分 + int pivot = partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); +} + +/* 快速排序類別(中位基準數最佳化) */ +// 選取三個候選元素的中位數 +int medianThree(int nums[], int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m 在 l 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l 在 m 和 r 之間 + return right; +} + +/* 哨兵劃分(三數取中值) */ +int partitionMedian(int nums[], int left, int right) { + // 選取三個候選元素的中位數 + int med = medianThree(nums, left, (left + right) / 2, right); + // 將中位數交換至陣列最左端 + swap(nums, left, med); + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 +} + +// 中位基準數最佳化-快速排序 +void quickSortMedian(int nums[], int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) + return; + // 哨兵劃分 + int pivot = partitionMedian(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSortMedian(nums, left, pivot - 1); + quickSortMedian(nums, pivot + 1, right); +} + +/* 快速排序類別(尾遞迴最佳化) */ +// 快速排序(尾遞迴最佳化) +void quickSortTailCall(int nums[], int left, int right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + int pivot = partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + quickSortTailCall(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSortTailCall(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } +} + +/* Driver Code */ +int main() { + /* 快速排序 */ + int nums[] = {2, 4, 1, 0, 3, 5}; + int size = sizeof(nums) / sizeof(int); + quickSort(nums, 0, size - 1); + printf("快速排序完成後 nums = "); + printArray(nums, size); + + /* 快速排序(中位基準數最佳化) */ + int nums1[] = {2, 4, 1, 0, 3, 5}; + quickSortMedian(nums1, 0, size - 1); + printf("快速排序(中位基準數最佳化)完成後 nums = "); + printArray(nums1, size); + + /* 快速排序(尾遞迴最佳化) */ + int nums2[] = {2, 4, 1, 0, 3, 5}; + quickSortTailCall(nums2, 0, size - 1); + printf("快速排序(尾遞迴最佳化)完成後 nums = "); + printArray(nums1, size); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_sorting/radix_sort.c b/zh-hant/codes/c/chapter_sorting/radix_sort.c new file mode 100644 index 000000000..6598dd0e7 --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/radix_sort.c @@ -0,0 +1,71 @@ +/** + * File: radix_sort.c + * Created Time: 2023-01-18 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +int digit(int num, int exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +void countingSortDigit(int nums[], int size, int exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + int *counter = (int *)malloc((sizeof(int) * 10)); + // 統計 0~9 各數字的出現次數 + for (int i = 0; i < size; i++) { + // 獲取 nums[i] 第 k 位,記為 d + int d = digit(nums[i], exp); + // 統計數字 d 的出現次數 + counter[d]++; + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + int *res = (int *)malloc(sizeof(int) * size); + for (int i = size - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d]--; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for (int i = 0; i < size; i++) { + nums[i] = res[i]; + } +} + +/* 基數排序 */ +void radixSort(int nums[], int size) { + // 獲取陣列的最大元素,用於判斷最大位數 + int max = INT32_MIN; + for (size_t i = 0; i < size - 1; i++) { + if (nums[i] > max) { + max = nums[i]; + } + } + // 按照從低位到高位的順序走訪 + for (int exp = 1; max >= exp; exp *= 10) + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, size, exp); +} + +/* Driver Code */ +int main() { + // 基數排序 + int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + int size = sizeof(nums) / sizeof(int); + radixSort(nums, size); + printf("基數排序完成後 nums = "); + printArray(nums, size); +} diff --git a/zh-hant/codes/c/chapter_sorting/selection_sort.c b/zh-hant/codes/c/chapter_sorting/selection_sort.c new file mode 100644 index 000000000..7ae3c1ffa --- /dev/null +++ b/zh-hant/codes/c/chapter_sorting/selection_sort.c @@ -0,0 +1,37 @@ +/** + * File: selection_sort.c + * Created Time: 2023-05-31 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 選擇排序 */ +void selectionSort(int nums[], int n) { + // 外迴圈:未排序區間為 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 內迴圈:找到未排序區間內的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 記錄最小元素的索引 + } + // 將該最小元素與未排序區間的首個元素交換 + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + int n = sizeof(nums) / sizeof(nums[0]); + + selectionSort(nums, n); + + printf("選擇排序完成後 nums = "); + printArray(nums, n); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_stack_and_queue/CMakeLists.txt b/zh-hant/codes/c/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 000000000..ed3ba840c --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(array_stack array_stack.c) +add_executable(linkedlist_stack linkedlist_stack.c) +add_executable(array_queue array_queue.c) +add_executable(linkedlist_queue linkedlist_queue.c) +add_executable(array_deque array_deque.c) +add_executable(linkedlist_deque linkedlist_deque.c) diff --git a/zh-hant/codes/c/chapter_stack_and_queue/array_deque.c b/zh-hant/codes/c/chapter_stack_and_queue/array_deque.c new file mode 100644 index 000000000..6c828e002 --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/array_deque.c @@ -0,0 +1,159 @@ +/** + * File: array_deque.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基於環形陣列實現的雙向佇列 */ +typedef struct { + int *nums; // 用於儲存佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 尾指標,指向佇列尾 + 1 + int queCapacity; // 佇列容量 +} ArrayDeque; + +/* 建構子 */ +ArrayDeque *newArrayDeque(int capacity) { + ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque)); + // 初始化陣列 + deque->queCapacity = capacity; + deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); + deque->front = deque->queSize = 0; + return deque; +} + +/* 析構函式 */ +void delArrayDeque(ArrayDeque *deque) { + free(deque->nums); + free(deque); +} + +/* 獲取雙向佇列的容量 */ +int capacity(ArrayDeque *deque) { + return deque->queCapacity; +} + +/* 獲取雙向佇列的長度 */ +int size(ArrayDeque *deque) { + return deque->queSize; +} + +/* 判斷雙向佇列是否為空 */ +bool empty(ArrayDeque *deque) { + return deque->queSize == 0; +} + +/* 計算環形陣列索引 */ +int dequeIndex(ArrayDeque *deque, int i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部時,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return ((i + capacity(deque)) % capacity(deque)); +} + +/* 佇列首入列 */ +void pushFirst(ArrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("雙向佇列已滿\r\n"); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部回到尾部 + deque->front = dequeIndex(deque, deque->front - 1); + // 將 num 新增到佇列首 + deque->nums[deque->front] = num; + deque->queSize++; +} + +/* 佇列尾入列 */ +void pushLast(ArrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("雙向佇列已滿\r\n"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + int rear = dequeIndex(deque, deque->front + deque->queSize); + // 將 num 新增至佇列尾 + deque->nums[rear] = num; + deque->queSize++; +} + +/* 訪問佇列首元素 */ +int peekFirst(ArrayDeque *deque) { + // 訪問異常:雙向佇列為空 + assert(empty(deque) == 0); + return deque->nums[deque->front]; +} + +/* 訪問佇列尾元素 */ +int peekLast(ArrayDeque *deque) { + // 訪問異常:雙向佇列為空 + assert(empty(deque) == 0); + int last = dequeIndex(deque, deque->front + deque->queSize - 1); + return deque->nums[last]; +} + +/* 佇列首出列 */ +int popFirst(ArrayDeque *deque) { + int num = peekFirst(deque); + // 佇列首指標向後移動一位 + deque->front = dequeIndex(deque, deque->front + 1); + deque->queSize--; + return num; +} + +/* 佇列尾出列 */ +int popLast(ArrayDeque *deque) { + int num = peekLast(deque); + deque->queSize--; + return num; +} + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + int capacity = 10; + ArrayDeque *deque = newArrayDeque(capacity); + pushLast(deque, 3); + pushLast(deque, 2); + pushLast(deque, 5); + printf("雙向佇列 deque = "); + printArray(deque->nums, deque->queSize); + + /* 訪問元素 */ + int peekFirstNum = peekFirst(deque); + printf("佇列首元素 peekFirst = %d\r\n", peekFirstNum); + int peekLastNum = peekLast(deque); + printf("佇列尾元素 peekLast = %d\r\n", peekLastNum); + + /* 元素入列 */ + pushLast(deque, 4); + printf("元素 4 佇列尾入列後 deque = "); + printArray(deque->nums, deque->queSize); + pushFirst(deque, 1); + printf("元素 1 佇列首入列後 deque = "); + printArray(deque->nums, deque->queSize); + + /* 元素出列 */ + int popLastNum = popLast(deque); + printf("佇列尾出列元素 = %d ,佇列尾出列後 deque= ", popLastNum); + printArray(deque->nums, deque->queSize); + int popFirstNum = popFirst(deque); + printf("佇列首出列元素 = %d ,佇列首出列後 deque= ", popFirstNum); + printArray(deque->nums, deque->queSize); + + /* 獲取佇列的長度 */ + int dequeSize = size(deque); + printf("雙向佇列長度 size = %d\r\n", dequeSize); + + /* 判斷佇列是否為空 */ + bool isEmpty = empty(deque); + printf("佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); + + // 釋放記憶體 + delArrayDeque(deque); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_stack_and_queue/array_queue.c b/zh-hant/codes/c/chapter_stack_and_queue/array_queue.c new file mode 100644 index 000000000..1ed04c4c1 --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/array_queue.c @@ -0,0 +1,121 @@ +/** + * File: array_queue.c + * Created Time: 2023-01-28 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基於環形陣列實現的佇列 */ +typedef struct { + int *nums; // 用於儲存佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 尾指標,指向佇列尾 + 1 + int queCapacity; // 佇列容量 +} ArrayQueue; + +/* 建構子 */ +ArrayQueue *newArrayQueue(int capacity) { + ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue)); + // 初始化陣列 + queue->queCapacity = capacity; + queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); + queue->front = queue->queSize = 0; + return queue; +} + +/* 析構函式 */ +void delArrayQueue(ArrayQueue *queue) { + free(queue->nums); + free(queue); +} + +/* 獲取佇列的容量 */ +int capacity(ArrayQueue *queue) { + return queue->queCapacity; +} + +/* 獲取佇列的長度 */ +int size(ArrayQueue *queue) { + return queue->queSize; +} + +/* 判斷佇列是否為空 */ +bool empty(ArrayQueue *queue) { + return queue->queSize == 0; +} + +/* 訪問佇列首元素 */ +int peek(ArrayQueue *queue) { + assert(size(queue) != 0); + return queue->nums[queue->front]; +} + +/* 入列 */ +void push(ArrayQueue *queue, int num) { + if (size(queue) == capacity(queue)) { + printf("佇列已滿\r\n"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + int rear = (queue->front + queue->queSize) % queue->queCapacity; + // 將 num 新增至佇列尾 + queue->nums[rear] = num; + queue->queSize++; +} + +/* 出列 */ +int pop(ArrayQueue *queue) { + int num = peek(queue); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + queue->front = (queue->front + 1) % queue->queCapacity; + queue->queSize--; + return num; +} + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + int capacity = 10; + ArrayQueue *queue = newArrayQueue(capacity); + + /* 元素入列 */ + push(queue, 1); + push(queue, 3); + push(queue, 2); + push(queue, 5); + push(queue, 4); + printf("佇列 queue = "); + printArray(queue->nums, queue->queSize); + + /* 訪問佇列首元素 */ + int peekNum = peek(queue); + printf("佇列首元素 peek = %d\r\n", peekNum); + + /* 元素出列 */ + peekNum = pop(queue); + printf("出列元素 pop = %d ,出列後 queue = ", peekNum); + printArray(queue->nums, queue->queSize); + + /* 獲取佇列的長度 */ + int queueSize = size(queue); + printf("佇列長度 size = %d\r\n", queueSize); + + /* 判斷佇列是否為空 */ + bool isEmpty = empty(queue); + printf("佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); + + /* 測試環形陣列 */ + for (int i = 0; i < 10; i++) { + push(queue, i); + pop(queue); + printf("第 %d 輪入列 + 出列後 queue = ", i); + printArray(queue->nums, queue->queSize); + } + + // 釋放記憶體 + delArrayQueue(queue); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_stack_and_queue/array_stack.c b/zh-hant/codes/c/chapter_stack_and_queue/array_stack.c new file mode 100644 index 000000000..d70cab7f8 --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/array_stack.c @@ -0,0 +1,103 @@ +/** + * File: array_stack.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 5000 + +/* 基於陣列實現的堆疊 */ +typedef struct { + int *data; + int size; +} ArrayStack; + +/* 建構子 */ +ArrayStack *newArrayStack() { + ArrayStack *stack = malloc(sizeof(ArrayStack)); + // 初始化一個大容量,避免擴容 + stack->data = malloc(sizeof(int) * MAX_SIZE); + stack->size = 0; + return stack; +} + +/* 析構函式 */ +void delArrayStack(ArrayStack *stack) { + free(stack->data); + free(stack); +} + +/* 獲取堆疊的長度 */ +int size(ArrayStack *stack) { + return stack->size; +} + +/* 判斷堆疊是否為空 */ +bool isEmpty(ArrayStack *stack) { + return stack->size == 0; +} + +/* 入堆疊 */ +void push(ArrayStack *stack, int num) { + if (stack->size == MAX_SIZE) { + printf("堆疊已滿\n"); + return; + } + stack->data[stack->size] = num; + stack->size++; +} + +/* 訪問堆疊頂元素 */ +int peek(ArrayStack *stack) { + if (stack->size == 0) { + printf("堆疊為空\n"); + return INT_MAX; + } + return stack->data[stack->size - 1]; +} + +/* 出堆疊 */ +int pop(ArrayStack *stack) { + int val = peek(stack); + stack->size--; + return val; +} + +/* Driver Code */ +int main() { + /* 初始化堆疊 */ + ArrayStack *stack = newArrayStack(); + + /* 元素入堆疊 */ + push(stack, 1); + push(stack, 3); + push(stack, 2); + push(stack, 5); + push(stack, 4); + printf("堆疊 stack = "); + printArray(stack->data, stack->size); + + /* 訪問堆疊頂元素 */ + int val = peek(stack); + printf("堆疊頂元素 top = %d\n", val); + + /* 元素出堆疊 */ + val = pop(stack); + printf("出堆疊元素 pop = %d ,出堆疊後 stack = ", val); + printArray(stack->data, stack->size); + + /* 獲取堆疊的長度 */ + int size = stack->size; + printf("堆疊的長度 size = %d\n", size); + + /* 判斷是否為空 */ + bool empty = isEmpty(stack); + printf("堆疊是否為空 = %stack\n", empty ? "true" : "false"); + + // 釋放記憶體 + delArrayStack(stack); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_deque.c b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_deque.c new file mode 100644 index 000000000..62c9d1436 --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_deque.c @@ -0,0 +1,212 @@ +/** + * File: linkedlist_deque.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 雙向鏈結串列節點 */ +typedef struct DoublyListNode { + int val; // 節點值 + struct DoublyListNode *next; // 後繼節點 + struct DoublyListNode *prev; // 前驅節點 +} DoublyListNode; + +/* 建構子 */ +DoublyListNode *newDoublyListNode(int num) { + DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); + new->val = num; + new->next = NULL; + new->prev = NULL; + return new; +} + +/* 析構函式 */ +void delDoublyListNode(DoublyListNode *node) { + free(node); +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +typedef struct { + DoublyListNode *front, *rear; // 頭節點 front ,尾節點 rear + int queSize; // 雙向佇列的長度 +} LinkedListDeque; + +/* 建構子 */ +LinkedListDeque *newLinkedListDeque() { + LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); + deque->front = NULL; + deque->rear = NULL; + deque->queSize = 0; + return deque; +} + +/* 析構函式 */ +void delLinkedListdeque(LinkedListDeque *deque) { + // 釋放所有節點 + for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { + DoublyListNode *tmp = deque->front; + deque->front = deque->front->next; + free(tmp); + } + // 釋放 deque 結構體 + free(deque); +} + +/* 獲取佇列的長度 */ +int size(LinkedListDeque *deque) { + return deque->queSize; +} + +/* 判斷佇列是否為空 */ +bool empty(LinkedListDeque *deque) { + return (size(deque) == 0); +} + +/* 入列 */ +void push(LinkedListDeque *deque, int num, bool isFront) { + DoublyListNode *node = newDoublyListNode(num); + // 若鏈結串列為空,則令 front 和 rear 都指向node + if (empty(deque)) { + deque->front = deque->rear = node; + } + // 佇列首入列操作 + else if (isFront) { + // 將 node 新增至鏈結串列頭部 + deque->front->prev = node; + node->next = deque->front; + deque->front = node; // 更新頭節點 + } + // 佇列尾入列操作 + else { + // 將 node 新增至鏈結串列尾部 + deque->rear->next = node; + node->prev = deque->rear; + deque->rear = node; + } + deque->queSize++; // 更新佇列長度 +} + +/* 佇列首入列 */ +void pushFirst(LinkedListDeque *deque, int num) { + push(deque, num, true); +} + +/* 佇列尾入列 */ +void pushLast(LinkedListDeque *deque, int num) { + push(deque, num, false); +} + +/* 訪問佇列首元素 */ +int peekFirst(LinkedListDeque *deque) { + assert(size(deque) && deque->front); + return deque->front->val; +} + +/* 訪問佇列尾元素 */ +int peekLast(LinkedListDeque *deque) { + assert(size(deque) && deque->rear); + return deque->rear->val; +} + +/* 出列 */ +int pop(LinkedListDeque *deque, bool isFront) { + if (empty(deque)) + return -1; + int val; + // 佇列首出列操作 + if (isFront) { + val = peekFirst(deque); // 暫存頭節點值 + DoublyListNode *fNext = deque->front->next; + if (fNext) { + fNext->prev = NULL; + deque->front->next = NULL; + delDoublyListNode(deque->front); + } + deque->front = fNext; // 更新頭節點 + } + // 佇列尾出列操作 + else { + val = peekLast(deque); // 暫存尾節點值 + DoublyListNode *rPrev = deque->rear->prev; + if (rPrev) { + rPrev->next = NULL; + deque->rear->prev = NULL; + delDoublyListNode(deque->rear); + } + deque->rear = rPrev; // 更新尾節點 + } + deque->queSize--; // 更新佇列長度 + return val; +} + +/* 佇列首出列 */ +int popFirst(LinkedListDeque *deque) { + return pop(deque, true); +} + +/* 佇列尾出列 */ +int popLast(LinkedListDeque *deque) { + return pop(deque, false); +} + +/* 列印佇列 */ +void printLinkedListDeque(LinkedListDeque *deque) { + int *arr = malloc(sizeof(int) * deque->queSize); + // 複製鏈結串列中的資料到陣列 + int i; + DoublyListNode *node; + for (i = 0, node = deque->front; i < deque->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, deque->queSize); + free(arr); +} + +/* Driver Code */ +int main() { + /* 初始化雙向佇列 */ + LinkedListDeque *deque = newLinkedListDeque(); + pushLast(deque, 3); + pushLast(deque, 2); + pushLast(deque, 5); + printf("雙向佇列 deque = "); + printLinkedListDeque(deque); + + /* 訪問元素 */ + int peekFirstNum = peekFirst(deque); + printf("佇列首元素 peekFirst = %d\r\n", peekFirstNum); + int peekLastNum = peekLast(deque); + printf("佇列首元素 peekLast = %d\r\n", peekLastNum); + + /* 元素入列 */ + pushLast(deque, 4); + printf("元素 4 佇列尾入列後 deque ="); + printLinkedListDeque(deque); + pushFirst(deque, 1); + printf("元素 1 佇列首入列後 deque ="); + printLinkedListDeque(deque); + + /* 元素出列 */ + int popLastNum = popLast(deque); + printf("佇列尾出列元素 popLast = %d ,佇列尾出列後 deque = ", popLastNum); + printLinkedListDeque(deque); + int popFirstNum = popFirst(deque); + printf("佇列首出列元素 popFirst = %d ,佇列首出列後 deque = ", popFirstNum); + printLinkedListDeque(deque); + + /* 獲取佇列的長度 */ + int dequeSize = size(deque); + printf("雙向佇列長度 size = %d\r\n", dequeSize); + + /* 判斷佇列是否為空 */ + bool isEmpty = empty(deque); + printf("雙向佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); + + // 釋放記憶體 + delLinkedListdeque(deque); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_queue.c b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_queue.c new file mode 100644 index 000000000..788c2dd98 --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_queue.c @@ -0,0 +1,128 @@ +/** + * File: linkedlist_queue.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基於鏈結串列實現的佇列 */ +typedef struct { + ListNode *front, *rear; + int queSize; +} LinkedListQueue; + +/* 建構子 */ +LinkedListQueue *newLinkedListQueue() { + LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); + queue->front = NULL; + queue->rear = NULL; + queue->queSize = 0; + return queue; +} + +/* 析構函式 */ +void delLinkedListQueue(LinkedListQueue *queue) { + // 釋放所有節點 + while (queue->front != NULL) { + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + } + // 釋放 queue 結構體 + free(queue); +} + +/* 獲取佇列的長度 */ +int size(LinkedListQueue *queue) { + return queue->queSize; +} + +/* 判斷佇列是否為空 */ +bool empty(LinkedListQueue *queue) { + return (size(queue) == 0); +} + +/* 入列 */ +void push(LinkedListQueue *queue, int num) { + // 尾節點處新增 node + ListNode *node = newListNode(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (queue->front == NULL) { + queue->front = node; + queue->rear = node; + } + // 如果佇列不為空,則將該節點新增到尾節點後 + else { + queue->rear->next = node; + queue->rear = node; + } + queue->queSize++; +} + +/* 訪問佇列首元素 */ +int peek(LinkedListQueue *queue) { + assert(size(queue) && queue->front); + return queue->front->val; +} + +/* 出列 */ +int pop(LinkedListQueue *queue) { + int num = peek(queue); + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + queue->queSize--; + return num; +} + +/* 列印佇列 */ +void printLinkedListQueue(LinkedListQueue *queue) { + int *arr = malloc(sizeof(int) * queue->queSize); + // 複製鏈結串列中的資料到陣列 + int i; + ListNode *node; + for (i = 0, node = queue->front; i < queue->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, queue->queSize); + free(arr); +} + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + LinkedListQueue *queue = newLinkedListQueue(); + + /* 元素入列 */ + push(queue, 1); + push(queue, 3); + push(queue, 2); + push(queue, 5); + push(queue, 4); + printf("佇列 queue = "); + printLinkedListQueue(queue); + + /* 訪問佇列首元素 */ + int peekNum = peek(queue); + printf("佇列首元素 peek = %d\r\n", peekNum); + + /* 元素出列 */ + peekNum = pop(queue); + printf("出列元素 pop = %d ,出列後 queue = ", peekNum); + printLinkedListQueue(queue); + + /* 獲取佇列的長度 */ + int queueSize = size(queue); + printf("佇列長度 size = %d\r\n", queueSize); + + /* 判斷佇列是否為空 */ + bool isEmpty = empty(queue); + printf("佇列是否為空 = %s\r\n", isEmpty ? "true" : "false"); + + // 釋放記憶體 + delLinkedListQueue(queue); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_stack.c b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_stack.c new file mode 100644 index 000000000..9dd3a8d07 --- /dev/null +++ b/zh-hant/codes/c/chapter_stack_and_queue/linkedlist_stack.c @@ -0,0 +1,107 @@ +/** + * File: linkedlist_stack.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 基於鏈結串列實現的堆疊 */ +typedef struct { + ListNode *top; // 將頭節點作為堆疊頂 + int size; // 堆疊的長度 +} LinkedListStack; + +/* 建構子 */ +LinkedListStack *newLinkedListStack() { + LinkedListStack *s = malloc(sizeof(LinkedListStack)); + s->top = NULL; + s->size = 0; + return s; +} + +/* 析構函式 */ +void delLinkedListStack(LinkedListStack *s) { + while (s->top) { + ListNode *n = s->top->next; + free(s->top); + s->top = n; + } + free(s); +} + +/* 獲取堆疊的長度 */ +int size(LinkedListStack *s) { + return s->size; +} + +/* 判斷堆疊是否為空 */ +bool isEmpty(LinkedListStack *s) { + return size(s) == 0; +} + +/* 入堆疊 */ +void push(LinkedListStack *s, int num) { + ListNode *node = (ListNode *)malloc(sizeof(ListNode)); + node->next = s->top; // 更新新加節點指標域 + node->val = num; // 更新新加節點資料域 + s->top = node; // 更新堆疊頂 + s->size++; // 更新堆疊大小 +} + +/* 訪問堆疊頂元素 */ +int peek(LinkedListStack *s) { + if (s->size == 0) { + printf("堆疊為空\n"); + return INT_MAX; + } + return s->top->val; +} + +/* 出堆疊 */ +int pop(LinkedListStack *s) { + int val = peek(s); + ListNode *tmp = s->top; + s->top = s->top->next; + // 釋放記憶體 + free(tmp); + s->size--; + return val; +} + +/* Driver Code */ +int main() { + /* 初始化堆疊 */ + LinkedListStack *stack = newLinkedListStack(); + + /* 元素入堆疊 */ + push(stack, 1); + push(stack, 3); + push(stack, 2); + push(stack, 5); + push(stack, 4); + + printf("堆疊 stack = "); + printLinkedList(stack->top); + + /* 訪問堆疊頂元素 */ + int val = peek(stack); + printf("堆疊頂元素 top = %d\r\n", val); + + /* 元素出堆疊 */ + val = pop(stack); + printf("出堆疊元素 pop = %d, 出堆疊後 stack = ", val); + printLinkedList(stack->top); + + /* 獲取堆疊的長度 */ + printf("堆疊的長度 size = %d\n", size(stack)); + + /* 判斷是否為空 */ + bool empty = isEmpty(stack); + printf("堆疊是否為空 = %s\n", empty ? "true" : "false"); + + // 釋放記憶體 + delLinkedListStack(stack); + + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/CMakeLists.txt b/zh-hant/codes/c/chapter_tree/CMakeLists.txt new file mode 100644 index 000000000..9b4e825ff --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.c) +add_executable(binary_tree binary_tree.c) +add_executable(binary_tree_bfs binary_tree_bfs.c) +add_executable(binary_tree_dfs binary_tree_dfs.c) +add_executable(binary_search_tree binary_search_tree.c) +add_executable(array_binary_tree array_binary_tree.c) diff --git a/zh-hant/codes/c/chapter_tree/array_binary_tree.c b/zh-hant/codes/c/chapter_tree/array_binary_tree.c new file mode 100644 index 000000000..645afe715 --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/array_binary_tree.c @@ -0,0 +1,166 @@ +/** + * File: array_binary_tree.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 陣列表示下的二元樹結構體 */ +typedef struct { + int *tree; + int size; +} ArrayBinaryTree; + +/* 建構子 */ +ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { + ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); + abt->tree = malloc(sizeof(int) * arrSize); + memcpy(abt->tree, arr, sizeof(int) * arrSize); + abt->size = arrSize; + return abt; +} + +/* 析構函式 */ +void delArrayBinaryTree(ArrayBinaryTree *abt) { + free(abt->tree); + free(abt); +} + +/* 串列容量 */ +int size(ArrayBinaryTree *abt) { + return abt->size; +} + +/* 獲取索引為 i 節點的值 */ +int val(ArrayBinaryTree *abt, int i) { + // 若索引越界,則返回 INT_MAX ,代表空位 + if (i < 0 || i >= size(abt)) + return INT_MAX; + return abt->tree[i]; +} + +/* 獲取索引為 i 節點的左子節點的索引 */ +int left(int i) { + return 2 * i + 1; +} + +/* 獲取索引為 i 節點的右子節點的索引 */ +int right(int i) { + return 2 * i + 2; +} + +/* 獲取索引為 i 節點的父節點的索引 */ +int parent(int i) { + return (i - 1) / 2; +} + +/* 層序走訪 */ +int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + // 直接走訪陣列 + for (int i = 0; i < size(abt); i++) { + if (val(abt, i) != INT_MAX) + res[index++] = val(abt, i); + } + *returnSize = index; + return res; +} + +/* 深度優先走訪 */ +void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { + // 若為空位,則返回 + if (val(abt, i) == INT_MAX) + return; + // 前序走訪 + if (strcmp(order, "pre") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, left(i), order, res, index); + // 中序走訪 + if (strcmp(order, "in") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, right(i), order, res, index); + // 後序走訪 + if (strcmp(order, "post") == 0) + res[(*index)++] = val(abt, i); +} + +/* 前序走訪 */ +int *preOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "pre", res, &index); + *returnSize = index; + return res; +} + +/* 中序走訪 */ +int *inOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "in", res, &index); + *returnSize = index; + return res; +} + +/* 後序走訪 */ +int *postOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "post", res, &index); + *returnSize = index; + return res; +} + +/* Driver Code */ +int main() { + // 初始化二元樹 + // 使用 INT_MAX 代表空位 NULL + int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + int arrSize = sizeof(arr) / sizeof(arr[0]); + TreeNode *root = arrayToTree(arr, arrSize); + printf("\n初始化二元樹\n"); + printf("二元樹的陣列表示:\n"); + printArray(arr, arrSize); + printf("二元樹的鏈結串列表示:\n"); + printTree(root); + + ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize); + + // 訪問節點 + int i = 1; + int l = left(i), r = right(i), p = parent(i); + printf("\n當前節點的索引為 %d,值為 %d\n", i, val(abt, i)); + printf("其左子節點的索引為 %d,值為 %d\n", l, l < arrSize ? val(abt, l) : INT_MAX); + printf("其右子節點的索引為 %d,值為 %d\n", r, r < arrSize ? val(abt, r) : INT_MAX); + printf("其父節點的索引為 %d,值為 %d\n", p, p < arrSize ? val(abt, p) : INT_MAX); + + // 走訪樹 + int returnSize; + int *res; + + res = levelOrder(abt, &returnSize); + printf("\n層序走訪為: "); + printArray(res, returnSize); + free(res); + + res = preOrder(abt, &returnSize); + printf("前序走訪為: "); + printArray(res, returnSize); + free(res); + + res = inOrder(abt, &returnSize); + printf("中序走訪為: "); + printArray(res, returnSize); + free(res); + + res = postOrder(abt, &returnSize); + printf("後序走訪為: "); + printArray(res, returnSize); + free(res); + + // 釋放記憶體 + delArrayBinaryTree(abt); + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/avl_tree.c b/zh-hant/codes/c/chapter_tree/avl_tree.c new file mode 100644 index 000000000..660d5df85 --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/avl_tree.c @@ -0,0 +1,259 @@ +/** + * File: avl_tree.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* AVL 樹結構體 */ +typedef struct { + TreeNode *root; +} AVLTree; + +/* 建構子 */ +AVLTree *newAVLTree() { + AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree)); + tree->root = NULL; + return tree; +} + +/* 析構函式 */ +void delAVLTree(AVLTree *tree) { + freeMemoryTree(tree->root); + free(tree); +} + +/* 獲取節點高度 */ +int height(TreeNode *node) { + // 空節點高度為 -1 ,葉節點高度為 0 + if (node != NULL) { + return node->height; + } + return -1; +} + +/* 更新節點高度 */ +void updateHeight(TreeNode *node) { + int lh = height(node->left); + int rh = height(node->right); + // 節點高度等於最高子樹高度 + 1 + if (lh > rh) { + node->height = lh + 1; + } else { + node->height = rh + 1; + } +} + +/* 獲取平衡因子 */ +int balanceFactor(TreeNode *node) { + // 空節點平衡因子為 0 + if (node == NULL) { + return 0; + } + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node->left) - height(node->right); +} + +/* 右旋操作 */ +TreeNode *rightRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->left; + grandChild = child->right; + // 以 child 為原點,將 node 向右旋轉 + child->right = node; + node->left = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; +} + +/* 左旋操作 */ +TreeNode *leftRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->right; + grandChild = child->left; + // 以 child 為原點,將 node 向左旋轉 + child->left = node; + node->right = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; +} + +/* 執行旋轉操作,使該子樹重新恢復平衡 */ +TreeNode *rotate(TreeNode *node) { + // 獲取節點 node 的平衡因子 + int bf = balanceFactor(node); + // 左偏樹 + if (bf > 1) { + if (balanceFactor(node->left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋後右旋 + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // 右偏樹 + if (bf < -1) { + if (balanceFactor(node->right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋後左旋 + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; +} + +/* 遞迴插入節點(輔助函式) */ +TreeNode *insertHelper(TreeNode *node, int val) { + if (node == NULL) { + return newTreeNode(val); + } + /* 1. 查詢插入位置並插入節點 */ + if (val < node->val) { + node->left = insertHelper(node->left, val); + } else if (val > node->val) { + node->right = insertHelper(node->right, val); + } else { + // 重複節點不插入,直接返回 + return node; + } + // 更新節點高度 + updateHeight(node); + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; +} + +/* 插入節點 */ +void insert(AVLTree *tree, int val) { + tree->root = insertHelper(tree->root, val); +} + +/* 遞迴刪除節點(輔助函式) */ +TreeNode *removeHelper(TreeNode *node, int val) { + TreeNode *child, *grandChild; + if (node == NULL) { + return NULL; + } + /* 1. 查詢節點並刪除 */ + if (val < node->val) { + node->left = removeHelper(node->left, val); + } else if (val > node->val) { + node->right = removeHelper(node->right, val); + } else { + if (node->left == NULL || node->right == NULL) { + child = node->left; + if (node->right != NULL) { + child = node->right; + } + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == NULL) { + return NULL; + } else { + // 子節點數量 = 1 ,直接刪除 node + node = child; + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + TreeNode *temp = node->right; + while (temp->left != NULL) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + // 更新節點高度 + updateHeight(node); + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; +} + +/* 刪除節點 */ +// 由於引入了 stdio.h ,此處無法使用 remove 關鍵詞 +void removeItem(AVLTree *tree, int val) { + TreeNode *root = removeHelper(tree->root, val); +} + +/* 查詢節點 */ +TreeNode *search(AVLTree *tree, int val) { + TreeNode *cur = tree->root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != NULL) { + if (cur->val < val) { + // 目標節點在 cur 的右子樹中 + cur = cur->right; + } else if (cur->val > val) { + // 目標節點在 cur 的左子樹中 + cur = cur->left; + } else { + // 找到目標節點,跳出迴圈 + break; + } + } + // 找到目標節點,跳出迴圈 + return cur; +} + +void testInsert(AVLTree *tree, int val) { + insert(tree, val); + printf("\n插入節點 %d 後,AVL 樹為 \n", val); + printTree(tree->root); +} + +void testRemove(AVLTree *tree, int val) { + removeItem(tree, val); + printf("\n刪除節點 %d 後,AVL 樹為 \n", val); + printTree(tree->root); +} + +/* Driver Code */ +int main() { + /* 初始化空 AVL 樹 */ + AVLTree *tree = (AVLTree *)newAVLTree(); + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(tree, 1); + testInsert(tree, 2); + testInsert(tree, 3); + testInsert(tree, 4); + testInsert(tree, 5); + testInsert(tree, 8); + testInsert(tree, 7); + testInsert(tree, 9); + testInsert(tree, 10); + testInsert(tree, 6); + + /* 插入重複節點 */ + testInsert(tree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(tree, 8); // 刪除度為 0 的節點 + testRemove(tree, 5); // 刪除度為 1 的節點 + testRemove(tree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + TreeNode *node = search(tree, 7); + printf("\n查詢到的節點物件節點值 = %d \n", node->val); + + // 釋放記憶體 + delAVLTree(tree); + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/binary_search_tree.c b/zh-hant/codes/c/chapter_tree/binary_search_tree.c new file mode 100644 index 000000000..a20e5f4f0 --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/binary_search_tree.c @@ -0,0 +1,171 @@ +/** + * File: binary_search_tree.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 二元搜尋樹結構體 */ +typedef struct { + TreeNode *root; +} BinarySearchTree; + +/* 建構子 */ +BinarySearchTree *newBinarySearchTree() { + // 初始化空樹 + BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree)); + bst->root = NULL; + return bst; +} + +/* 析構函式 */ +void delBinarySearchTree(BinarySearchTree *bst) { + freeMemoryTree(bst->root); + free(bst); +} + +/* 獲取二元樹根節點 */ +TreeNode *getRoot(BinarySearchTree *bst) { + return bst->root; +} + +/* 查詢節點 */ +TreeNode *search(BinarySearchTree *bst, int num) { + TreeNode *cur = bst->root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != NULL) { + if (cur->val < num) { + // 目標節點在 cur 的右子樹中 + cur = cur->right; + } else if (cur->val > num) { + // 目標節點在 cur 的左子樹中 + cur = cur->left; + } else { + // 找到目標節點,跳出迴圈 + break; + } + } + // 返回目標節點 + return cur; +} + +/* 插入節點 */ +void insert(BinarySearchTree *bst, int num) { + // 若樹為空,則初始化根節點 + if (bst->root == NULL) { + bst->root = newTreeNode(num); + return; + } + TreeNode *cur = bst->root, *pre = NULL; + // 迴圈查詢,越過葉節點後跳出 + while (cur != NULL) { + // 找到重複節點,直接返回 + if (cur->val == num) { + return; + } + pre = cur; + if (cur->val < num) { + // 插入位置在 cur 的右子樹中 + cur = cur->right; + } else { + // 插入位置在 cur 的左子樹中 + cur = cur->left; + } + } + // 插入節點 + TreeNode *node = newTreeNode(num); + if (pre->val < num) { + pre->right = node; + } else { + pre->left = node; + } +} + +/* 刪除節點 */ +// 由於引入了 stdio.h ,此處無法使用 remove 關鍵詞 +void removeItem(BinarySearchTree *bst, int num) { + // 若樹為空,直接提前返回 + if (bst->root == NULL) + return; + TreeNode *cur = bst->root, *pre = NULL; + // 迴圈查詢,越過葉節點後跳出 + while (cur != NULL) { + // 找到待刪除節點,跳出迴圈 + if (cur->val == num) + break; + pre = cur; + if (cur->val < num) { + // 待刪除節點在 root 的右子樹中 + cur = cur->right; + } else { + // 待刪除節點在 root 的左子樹中 + cur = cur->left; + } + } + // 若無待刪除節點,則直接返回 + if (cur == NULL) + return; + // 判斷待刪除節點是否存在子節點 + if (cur->left == NULL || cur->right == NULL) { + /* 子節點數量 = 0 or 1 */ + // 當子節點數量 = 0 / 1 時, child = nullptr / 該子節點 + TreeNode *child = cur->left != NULL ? cur->left : cur->right; + // 刪除節點 cur + if (pre->left == cur) { + pre->left = child; + } else { + pre->right = child; + } + // 釋放記憶體 + free(cur); + } else { + /* 子節點數量 = 2 */ + // 獲取中序走訪中 cur 的下一個節點 + TreeNode *tmp = cur->right; + while (tmp->left != NULL) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // 遞迴刪除節點 tmp + removeItem(bst, tmp->val); + // 用 tmp 覆蓋 cur + cur->val = tmpVal; + } +} + +/* Driver Code */ +int main() { + /* 初始化二元搜尋樹 */ + int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + BinarySearchTree *bst = newBinarySearchTree(); + for (int i = 0; i < sizeof(nums) / sizeof(int); i++) { + insert(bst, nums[i]); + } + printf("初始化的二元樹為\n"); + printTree(getRoot(bst)); + + /* 查詢節點 */ + TreeNode *node = search(bst, 7); + printf("查詢到的節點物件的節點值 = %d\n", node->val); + + /* 插入節點 */ + insert(bst, 16); + printf("插入節點 16 後,二元樹為\n"); + printTree(getRoot(bst)); + + /* 刪除節點 */ + removeItem(bst, 1); + printf("刪除節點 1 後,二元樹為\n"); + printTree(getRoot(bst)); + removeItem(bst, 2); + printf("刪除節點 2 後,二元樹為\n"); + printTree(getRoot(bst)); + removeItem(bst, 4); + printf("刪除節點 4 後,二元樹為\n"); + printTree(getRoot(bst)); + + // 釋放記憶體 + delBinarySearchTree(bst); + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/binary_tree.c b/zh-hant/codes/c/chapter_tree/binary_tree.c new file mode 100644 index 000000000..4dd1b76bb --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/binary_tree.c @@ -0,0 +1,43 @@ +/** + * File: binary_tree.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 初始化節點 + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // 構建節點之間的引用(指標) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + printf("初始化二元樹\n"); + printTree(n1); + + /* 插入與刪除節點 */ + TreeNode *P = newTreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1->left = P; + P->left = n2; + printf("插入節點 P 後\n"); + printTree(n1); + + // 刪除節點 P + n1->left = n2; + // 釋放記憶體 + free(P); + printf("刪除節點 P 後\n"); + printTree(n1); + + freeMemoryTree(n1); + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/binary_tree_bfs.c b/zh-hant/codes/c/chapter_tree/binary_tree_bfs.c new file mode 100644 index 000000000..16fcaa61d --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/binary_tree_bfs.c @@ -0,0 +1,73 @@ +/** + * File: binary_tree_bfs.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +/* 層序走訪 */ +int *levelOrder(TreeNode *root, int *size) { + /* 輔助佇列 */ + int front, rear; + int index, *arr; + TreeNode *node; + TreeNode **queue; + + /* 輔助佇列 */ + queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); + // 佇列指標 + front = 0, rear = 0; + // 加入根節點 + queue[rear++] = root; + // 初始化一個串列,用於儲存走訪序列 + /* 輔助陣列 */ + arr = (int *)malloc(sizeof(int) * MAX_SIZE); + // 陣列指標 + index = 0; + while (front < rear) { + // 隊列出隊 + node = queue[front++]; + // 儲存節點值 + arr[index++] = node->val; + if (node->left != NULL) { + // 左子節點入列 + queue[rear++] = node->left; + } + if (node->right != NULL) { + // 右子節點入列 + queue[rear++] = node->right; + } + } + // 更新陣列長度的值 + *size = index; + arr = realloc(arr, sizeof(int) * (*size)); + + // 釋放輔助陣列空間 + free(queue); + return arr; +} + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + int nums[] = {1, 2, 3, 4, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + printf("初始化二元樹\n"); + printTree(root); + + /* 層序走訪 */ + // 需要傳入陣列的長度 + int *arr = levelOrder(root, &size); + printf("層序走訪的節點列印序列 = "); + printArray(arr, size); + + // 釋放記憶體 + freeMemoryTree(root); + free(arr); + return 0; +} diff --git a/zh-hant/codes/c/chapter_tree/binary_tree_dfs.c b/zh-hant/codes/c/chapter_tree/binary_tree_dfs.c new file mode 100644 index 000000000..d0f89008a --- /dev/null +++ b/zh-hant/codes/c/chapter_tree/binary_tree_dfs.c @@ -0,0 +1,75 @@ +/** + * File: binary_tree_dfs.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +// 輔助陣列,用於儲存走訪序列 +int arr[MAX_SIZE]; + +/* 前序走訪 */ +void preOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + arr[(*size)++] = root->val; + preOrder(root->left, size); + preOrder(root->right, size); +} + +/* 中序走訪 */ +void inOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root->left, size); + arr[(*size)++] = root->val; + inOrder(root->right, size); +} + +/* 後序走訪 */ +void postOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root->left, size); + postOrder(root->right, size); + arr[(*size)++] = root->val; +} + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + int nums[] = {1, 2, 3, 4, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + printf("初始化二元樹\n"); + printTree(root); + + /* 前序走訪 */ + // 初始化輔助陣列 + size = 0; + preOrder(root, &size); + printf("前序走訪的節點列印序列 = "); + printArray(arr, size); + + /* 中序走訪 */ + size = 0; + inOrder(root, &size); + printf("中序走訪的節點列印序列 = "); + printArray(arr, size); + + /* 後序走訪 */ + size = 0; + postOrder(root, &size); + printf("後序走訪的節點列印序列 = "); + printArray(arr, size); + + freeMemoryTree(root); + return 0; +} diff --git a/zh-hant/codes/c/utils/CMakeLists.txt b/zh-hant/codes/c/utils/CMakeLists.txt new file mode 100644 index 000000000..c1ece2e38 --- /dev/null +++ b/zh-hant/codes/c/utils/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(utils + common_test.c + common.h print_util.h + list_node.h tree_node.h + uthash.h) \ No newline at end of file diff --git a/zh-hant/codes/c/utils/common.h b/zh-hant/codes/c/utils/common.h new file mode 100644 index 000000000..0b1ca7af8 --- /dev/null +++ b/zh-hant/codes/c/utils/common.h @@ -0,0 +1,36 @@ +/** + * File: common.h + * Created Time: 2022-12-20 + * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) + */ + +#ifndef C_INCLUDE_H +#define C_INCLUDE_H + +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.h" +#include "print_util.h" +#include "tree_node.h" +#include "vertex.h" + +// hash table lib +#include "uthash.h" + +#include "vector.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif // C_INCLUDE_H diff --git a/zh-hant/codes/c/utils/common_test.c b/zh-hant/codes/c/utils/common_test.c new file mode 100644 index 000000000..a889b423b --- /dev/null +++ b/zh-hant/codes/c/utils/common_test.c @@ -0,0 +1,35 @@ +/** + * File: include_test.c + * Created Time: 2023-01-10 + * Author: Reanon (793584285@qq.com) + */ + +#include "common.h" + +void testListNode() { + int nums[] = {2, 3, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + ListNode *head = arrToLinkedList(nums, size); + printLinkedList(head); +} + +void testTreeNode() { + int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + + // print tree + printTree(root); + + // tree to arr + int *arr = treeToArray(root, &size); + printArray(arr, size); +} + +int main(int argc, char *argv[]) { + printf("==testListNode==\n"); + testListNode(); + printf("==testTreeNode==\n"); + testTreeNode(); + return 0; +} diff --git a/zh-hant/codes/c/utils/list_node.h b/zh-hant/codes/c/utils/list_node.h new file mode 100644 index 000000000..73567a1bc --- /dev/null +++ b/zh-hant/codes/c/utils/list_node.h @@ -0,0 +1,59 @@ +/** + * File: list_node.h + * Created Time: 2023-01-09 + * Author: Reanon (793584285@qq.com) + */ + +#ifndef LIST_NODE_H +#define LIST_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 鏈結串列節點結構體 */ +typedef struct ListNode { + int val; // 節點值 + struct ListNode *next; // 指向下一節點的引用 +} ListNode; + +/* 建構子,初始化一個新節點 */ +ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *)malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; +} + +/* 將陣列反序列化為鏈結串列 */ +ListNode *arrToLinkedList(const int *arr, size_t size) { + if (size <= 0) { + return NULL; + } + + ListNode *dummy = newListNode(0); + ListNode *node = dummy; + for (int i = 0; i < size; i++) { + node->next = newListNode(arr[i]); + node = node->next; + } + return dummy->next; +} + +/* 釋放分配給鏈結串列的記憶體空間 */ +void freeMemoryLinkedList(ListNode *cur) { + // 釋放記憶體 + ListNode *pre; + while (cur != NULL) { + pre = cur; + cur = cur->next; + free(pre); + } +} + +#ifdef __cplusplus +} +#endif + +#endif // LIST_NODE_H diff --git a/zh-hant/codes/c/utils/print_util.h b/zh-hant/codes/c/utils/print_util.h new file mode 100644 index 000000000..aab7f140a --- /dev/null +++ b/zh-hant/codes/c/utils/print_util.h @@ -0,0 +1,131 @@ +/** + * File: print_util.h + * Created Time: 2022-12-21 + * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com) + */ + +#ifndef PRINT_UTIL_H +#define PRINT_UTIL_H + +#include +#include +#include + +#include "list_node.h" +#include "tree_node.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 列印陣列 */ +void printArray(int arr[], int size) { + if (arr == NULL || size == 0) { + printf("[]"); + return; + } + printf("["); + for (int i = 0; i < size - 1; i++) { + printf("%d, ", arr[i]); + } + printf("%d]\n", arr[size - 1]); +} + +/* 列印陣列 */ +void printArrayFloat(float arr[], int size) { + if (arr == NULL || size == 0) { + printf("[]"); + return; + } + printf("["); + for (int i = 0; i < size - 1; i++) { + printf("%.2f, ", arr[i]); + } + printf("%.2f]\n", arr[size - 1]); +} + +/* 列印鏈結串列 */ +void printLinkedList(ListNode *node) { + if (node == NULL) { + return; + } + while (node->next != NULL) { + printf("%d -> ", node->val); + node = node->next; + } + printf("%d\n", node->val); +} + +typedef struct Trunk { + struct Trunk *prev; + char *str; +} Trunk; + +Trunk *newTrunk(Trunk *prev, char *str) { + Trunk *trunk = (Trunk *)malloc(sizeof(Trunk)); + trunk->prev = prev; + trunk->str = (char *)malloc(sizeof(char) * 10); + strcpy(trunk->str, str); + return trunk; +} + +void showTrunks(Trunk *trunk) { + if (trunk == NULL) { + return; + } + showTrunks(trunk->prev); + printf("%s", trunk->str); +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) { + if (node == NULL) { + return; + } + char *prev_str = " "; + Trunk *trunk = newTrunk(prev, prev_str); + printTreeHelper(node->right, trunk, true); + if (prev == NULL) { + trunk->str = "———"; + } else if (isRight) { + trunk->str = "/———"; + prev_str = " |"; + } else { + trunk->str = "\\———"; + prev->str = prev_str; + } + showTrunks(trunk); + printf("%d\n", node->val); + + if (prev != NULL) { + prev->str = prev_str; + } + trunk->str = " |"; + + printTreeHelper(node->left, trunk, false); +} + +/* 列印二元樹 */ +void printTree(TreeNode *root) { + printTreeHelper(root, NULL, false); +} + +/* 列印堆積 */ +void printHeap(int arr[], int size) { + TreeNode *root; + printf("堆積的陣列表示:"); + printArray(arr, size); + printf("堆積的樹狀表示:\n"); + root = arrayToTree(arr, size); + printTree(root); +} + +#ifdef __cplusplus +} +#endif + +#endif // PRINT_UTIL_H diff --git a/zh-hant/codes/c/utils/tree_node.h b/zh-hant/codes/c/utils/tree_node.h new file mode 100644 index 000000000..8d47a07f8 --- /dev/null +++ b/zh-hant/codes/c/utils/tree_node.h @@ -0,0 +1,107 @@ +/** + * File: tree_node.h + * Created Time: 2023-01-09 + * Author: Reanon (793584285@qq.com) + */ + +#ifndef TREE_NODE_H +#define TREE_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define MAX_NODE_SIZE 5000 + +/* 二元樹節點結構體 */ +typedef struct TreeNode { + int val; // 節點值 + int height; // 節點高度 + struct TreeNode *left; // 左子節點指標 + struct TreeNode *right; // 右子節點指標 +} TreeNode; + +/* 建構子 */ +TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; +} + +// 序列化編碼規則請參考: +// 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 + +/* 將串列反序列化為二元樹:遞迴 */ +TreeNode *arrayToTreeDFS(int *arr, int size, int i) { + if (i < 0 || i >= size || arr[i] == INT_MAX) { + return NULL; + } + TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); + root->val = arr[i]; + root->left = arrayToTreeDFS(arr, size, 2 * i + 1); + root->right = arrayToTreeDFS(arr, size, 2 * i + 2); + return root; +} + +/* 將串列反序列化為二元樹 */ +TreeNode *arrayToTree(int *arr, int size) { + return arrayToTreeDFS(arr, size, 0); +} + +/* 將二元樹序列化為串列:遞迴 */ +void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) { + if (root == NULL) { + return; + } + while (i >= *size) { + res = realloc(res, (*size + 1) * sizeof(int)); + res[*size] = INT_MAX; + (*size)++; + } + res[i] = root->val; + treeToArrayDFS(root->left, 2 * i + 1, res, size); + treeToArrayDFS(root->right, 2 * i + 2, res, size); +} + +/* 將二元樹序列化為串列 */ +int *treeToArray(TreeNode *root, int *size) { + *size = 0; + int *res = NULL; + treeToArrayDFS(root, 0, res, size); + return res; +} + +/* 釋放二元樹記憶體 */ +void freeMemoryTree(TreeNode *root) { + if (root == NULL) + return; + freeMemoryTree(root->left); + freeMemoryTree(root->right); + free(root); +} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_NODE_H diff --git a/zh-hant/codes/c/utils/uthash.h b/zh-hant/codes/c/utils/uthash.h new file mode 100644 index 000000000..68693bf39 --- /dev/null +++ b/zh-hant/codes/c/utils/uthash.h @@ -0,0 +1,1140 @@ +/* +Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT +/* This codepath is provided for backward compatibility, but I plan to remove it. */ +#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/zh-hant/codes/c/utils/vector.h b/zh-hant/codes/c/utils/vector.h new file mode 100644 index 000000000..c6eccbd2f --- /dev/null +++ b/zh-hant/codes/c/utils/vector.h @@ -0,0 +1,259 @@ +/** + * File: vector.h + * Created Time: 2023-07-13 + * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com) + */ + +#ifndef VECTOR_H +#define VECTOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 定義向量型別 */ +typedef struct vector { + int size; // 當前向量的大小 + int capacity; // 當前向量的容量 + int depth; // 當前向量的深度 + void **data; // 指向資料的指標陣列 +} vector; + +/* 構造向量 */ +vector *newVector() { + vector *v = malloc(sizeof(vector)); + v->size = 0; + v->capacity = 4; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); + return v; +} + +/* 構造向量,指定大小、元素預設值 */ +vector *_newVector(int size, void *elem, int elemSize) { + vector *v = malloc(sizeof(vector)); + v->size = size; + v->capacity = size; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); + for (int i = 0; i < size; i++) { + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[i] = tmp; + } + return v; +} + +/* 析構向量 */ +void delVector(vector *v) { + if (v) { + if (v->depth == 0) { + return; + } else if (v->depth == 1) { + for (int i = 0; i < v->size; i++) { + free(v->data[i]); + } + free(v); + } else { + for (int i = 0; i < v->size; i++) { + delVector(v->data[i]); + } + v->depth--; + } + } +} + +/* 新增元素(複製方式)到向量尾部 */ +void vectorPushback(vector *v, void *elem, int elemSize) { + if (v->size == v->capacity) { + v->capacity *= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); + } + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[v->size++] = tmp; +} + +/* 從向量尾部彈出元素 */ +void vectorPopback(vector *v) { + if (v->size != 0) { + free(v->data[v->size - 1]); + v->size--; + } +} + +/* 清空向量 */ +void vectorClear(vector *v) { + delVector(v); + v->size = 0; + v->capacity = 4; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); +} + +/* 獲取向量的大小 */ +int vectorSize(vector *v) { + return v->size; +} + +/* 獲取向量的尾元素 */ +void *vectorBack(vector *v) { + int n = v->size; + return n > 0 ? v->data[n - 1] : NULL; +} + +/* 獲取向量的頭元素 */ +void *vectorFront(vector *v) { + return v->size > 0 ? v->data[0] : NULL; +} + +/* 獲取向量下標 pos 的元素 */ +void *vectorAt(vector *v, int pos) { + if (pos < 0 || pos >= v->size) { + printf("vectorAt: out of range\n"); + return NULL; + } + return v->data[pos]; +} + +/* 設定向量下標 pos 的元素 */ +void vectorSet(vector *v, int pos, void *elem, int elemSize) { + if (pos < 0 || pos >= v->size) { + printf("vectorSet: out of range\n"); + return; + } + free(v->data[pos]); + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[pos] = tmp; +} + +/* 向量擴容 */ +void vectorExpand(vector *v) { + v->capacity *= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); +} + +/* 向量縮容 */ +void vectorShrink(vector *v) { + v->capacity /= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); +} + +/* 在向量下標 pos 處插入元素 */ +void vectorInsert(vector *v, int pos, void *elem, int elemSize) { + if (v->size == v->capacity) { + vectorExpand(v); + } + for (int j = v->size; j > pos; j--) { + v->data[j] = v->data[j - 1]; + } + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[pos] = tmp; + v->size++; +} + +/* 刪除向量下標 pos 處的元素 */ +void vectorErase(vector *v, int pos) { + if (v->size != 0) { + free(v->data[pos]); + for (int j = pos; j < v->size - 1; j++) { + v->data[j] = v->data[j + 1]; + } + v->size--; + } +} + +/* 向量交換元素 */ +void vectorSwap(vector *v, int i, int j) { + void *tmp = v->data[i]; + v->data[i] = v->data[j]; + v->data[j] = tmp; +} + +/* 向量是否為空 */ +bool vectorEmpty(vector *v) { + return v->size == 0; +} + +/* 向量是否已滿 */ +bool vectorFull(vector *v) { + return v->size == v->capacity; +} + +/* 向量是否相等 */ +bool vectorEqual(vector *v1, vector *v2) { + if (v1->size != v2->size) { + printf("size not equal\n"); + return false; + } + for (int i = 0; i < v1->size; i++) { + void *a = v1->data[i]; + void *b = v2->data[i]; + if (memcmp(a, b, sizeof(a)) != 0) { + printf("data %d not equal\n", i); + return false; + } + } + return true; +} + +/* 對向量內部進行排序 */ +void vectorSort(vector *v, int (*cmp)(const void *, const void *)) { + qsort(v->data, v->size, sizeof(void *), cmp); +} + +/* 列印函式, 需傳遞一個列印變數的函式進來 */ +/* 當前僅支持列印深度為 1 的 vector */ +void printVector(vector *v, void (*printFunc)(vector *v, void *p)) { + if (v) { + if (v->depth == 0) { + return; + } else if (v->depth == 1) { + if(v->size == 0) { + printf("\n"); + return; + } + for (int i = 0; i < v->size; i++) { + if (i == 0) { + printf("["); + } else if (i == v->size - 1) { + printFunc(v, v->data[i]); + printf("]\r\n"); + break; + } + printFunc(v, v->data[i]); + printf(","); + } + } else { + for (int i = 0; i < v->size; i++) { + printVector(v->data[i], printFunc); + } + v->depth--; + } + } +} + +/* 當前僅支持列印深度為 2 的 vector */ +void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) { + printf("[\n"); + for (int i = 0; i < vv->size; i++) { + vector *v = (vector *)vv->data[i]; + printf(" ["); + for (int j = 0; j < v->size; j++) { + printFunc(v, v->data[j]); + if (j != v->size - 1) + printf(","); + } + printf("],"); + printf("\n"); + } + printf("]\n"); +} + +#ifdef __cplusplus +} +#endif + +#endif // VECTOR_H diff --git a/zh-hant/codes/c/utils/vertex.h b/zh-hant/codes/c/utils/vertex.h new file mode 100644 index 000000000..c277df66d --- /dev/null +++ b/zh-hant/codes/c/utils/vertex.h @@ -0,0 +1,49 @@ +/** + * File: vertex.h + * Created Time: 2023-10-28 + * Author: krahets (krahets@163.com) + */ + +#ifndef VERTEX_H +#define VERTEX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 頂點結構體 */ +typedef struct { + int val; +} Vertex; + +/* 建構子,初始化一個新節點 */ +Vertex *newVertex(int val) { + Vertex *vet; + vet = (Vertex *)malloc(sizeof(Vertex)); + vet->val = val; + return vet; +} + +/* 將值陣列轉換為頂點陣列 */ +Vertex **valsToVets(int *vals, int size) { + Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *)); + for (int i = 0; i < size; ++i) { + vertices[i] = newVertex(vals[i]); + } + return vertices; +} + +/* 將頂點陣列轉換為值陣列 */ +int *vetsToVals(Vertex **vertices, int size) { + int *vals = (int *)malloc(size * sizeof(int)); + for (int i = 0; i < size; ++i) { + vals[i] = vertices[i]->val; + } + return vals; +} + +#ifdef __cplusplus +} +#endif + +#endif // VERTEX_H diff --git a/zh-hant/codes/cpp/.gitignore b/zh-hant/codes/cpp/.gitignore new file mode 100644 index 000000000..dc1ffacf4 --- /dev/null +++ b/zh-hant/codes/cpp/.gitignore @@ -0,0 +1,10 @@ +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ + +*.dSYM/ + +build/ diff --git a/zh-hant/codes/cpp/CMakeLists.txt b/zh-hant/codes/cpp/CMakeLists.txt new file mode 100644 index 000000000..1e80bc4d7 --- /dev/null +++ b/zh-hant/codes/cpp/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/zh-hant/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt b/zh-hant/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 000000000..2e933e016 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array array.cpp) +add_executable(linked_list linked_list.cpp) +add_executable(list list.cpp) +add_executable(my_list my_list.cpp) diff --git a/zh-hant/codes/cpp/chapter_array_and_linkedlist/array.cpp b/zh-hant/codes/cpp/chapter_array_and_linkedlist/array.cpp new file mode 100644 index 000000000..89d3f20b5 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_array_and_linkedlist/array.cpp @@ -0,0 +1,113 @@ +/** + * File: array.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 隨機訪問元素 */ +int randomAccess(int *nums, int size) { + // 在區間 [0, size) 中隨機抽取一個數字 + int randomIndex = rand() % size; + // 獲取並返回隨機元素 + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* 擴展陣列長度 */ +int *extend(int *nums, int size, int enlarge) { + // 初始化一個擴展長度後的陣列 + int *res = new int[size + enlarge]; + // 將原陣列中的所有元素複製到新陣列 + for (int i = 0; i < size; i++) { + res[i] = nums[i]; + } + // 釋放記憶體 + delete[] nums; + // 返回擴展後的新陣列 + return res; +} + +/* 在陣列的索引 index 處插入元素 num */ +void insert(int *nums, int size, int num, int index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (int i = size - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +/* 刪除索引 index 處的元素 */ +void remove(int *nums, int size, int index) { + // 把索引 index 之後的所有元素向前移動一位 + for (int i = index; i < size - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列 */ +void traverse(int *nums, int size) { + int count = 0; + // 透過索引走訪陣列 + for (int i = 0; i < size; i++) { + count += nums[i]; + } +} + +/* 在陣列中查詢指定元素 */ +int find(int *nums, int size, int target) { + for (int i = 0; i < size; i++) { + if (nums[i] == target) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 初始化陣列 */ + int size = 5; + int *arr = new int[size]; + cout << "陣列 arr = "; + printArray(arr, size); + + int *nums = new int[size]{1, 3, 2, 5, 4}; + cout << "陣列 nums = "; + printArray(nums, size); + + /* 隨機訪問 */ + int randomNum = randomAccess(nums, size); + cout << "在 nums 中獲取隨機元素 " << randomNum << endl; + + /* 長度擴展 */ + int enlarge = 3; + nums = extend(nums, size, enlarge); + size += enlarge; + cout << "將陣列長度擴展至 8 ,得到 nums = "; + printArray(nums, size); + + /* 插入元素 */ + insert(nums, size, 6, 3); + cout << "在索引 3 處插入數字 6 ,得到 nums = "; + printArray(nums, size); + + /* 刪除元素 */ + remove(nums, size, 2); + cout << "刪除索引 2 處的元素,得到 nums = "; + printArray(nums, size); + + /* 走訪陣列 */ + traverse(nums, size); + + /* 查詢元素 */ + int index = find(nums, size, 3); + cout << "在 nums 中查詢元素 3 ,得到索引 = " << index << endl; + + // 釋放記憶體 + delete[] arr; + delete[] nums; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp b/zh-hant/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp new file mode 100644 index 000000000..ede559c18 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp @@ -0,0 +1,89 @@ +/** + * File: linked_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +void remove(ListNode *n0) { + if (n0->next == nullptr) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // 釋放記憶體 + delete P; +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == nullptr) + return nullptr; + head = head->next; + } + return head; +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +int find(ListNode *head, int target) { + int index = 0; + while (head != nullptr) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 初始化鏈結串列 */ + // 初始化各個節點 + ListNode *n0 = new ListNode(1); + ListNode *n1 = new ListNode(3); + ListNode *n2 = new ListNode(2); + ListNode *n3 = new ListNode(5); + ListNode *n4 = new ListNode(4); + // 構建節點之間的引用 + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + cout << "初始化的鏈結串列為" << endl; + printLinkedList(n0); + + /* 插入節點 */ + insert(n0, new ListNode(0)); + cout << "插入節點後的鏈結串列為" << endl; + printLinkedList(n0); + + /* 刪除節點 */ + remove(n0); + cout << "刪除節點後的鏈結串列為" << endl; + printLinkedList(n0); + + /* 訪問節點 */ + ListNode *node = access(n0, 3); + cout << "鏈結串列中索引 3 處的節點的值 = " << node->val << endl; + + /* 查詢節點 */ + int index = find(n0, 2); + cout << "鏈結串列中值為 2 的節點的索引 = " << index << endl; + + // 釋放記憶體 + freeMemoryLinkedList(n0); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_array_and_linkedlist/list.cpp b/zh-hant/codes/cpp/chapter_array_and_linkedlist/list.cpp new file mode 100644 index 000000000..efd5d0444 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_array_and_linkedlist/list.cpp @@ -0,0 +1,72 @@ +/** + * File: list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化串列 */ + vector nums = {1, 3, 2, 5, 4}; + cout << "串列 nums = "; + printVector(nums); + + /* 訪問元素 */ + int num = nums[1]; + cout << "訪問索引 1 處的元素,得到 num = " << num << endl; + + /* 更新元素 */ + nums[1] = 0; + cout << "將索引 1 處的元素更新為 0 ,得到 nums = "; + printVector(nums); + + /* 清空串列 */ + nums.clear(); + cout << "清空串列後 nums = "; + printVector(nums); + + /* 在尾部新增元素 */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + cout << "新增元素後 nums = "; + printVector(nums); + + /* 在中間插入元素 */ + nums.insert(nums.begin() + 3, 6); + cout << "在索引 3 處插入數字 6 ,得到 nums = "; + printVector(nums); + + /* 刪除元素 */ + nums.erase(nums.begin() + 3); + cout << "刪除索引 3 處的元素,得到 nums = "; + printVector(nums); + + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; + } + /* 直接走訪串列元素 */ + count = 0; + for (int x : nums) { + count += x; + } + + /* 拼接兩個串列 */ + vector nums1 = {6, 8, 7, 10, 9}; + nums.insert(nums.end(), nums1.begin(), nums1.end()); + cout << "將串列 nums1 拼接到 nums 之後,得到 nums = "; + printVector(nums); + + /* 排序串列 */ + sort(nums.begin(), nums.end()); + cout << "排序串列後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_array_and_linkedlist/my_list.cpp b/zh-hant/codes/cpp/chapter_array_and_linkedlist/my_list.cpp new file mode 100644 index 000000000..6459f0129 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_array_and_linkedlist/my_list.cpp @@ -0,0 +1,171 @@ +/** + * File: my_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 串列類別 */ +class MyList { + private: + int *arr; // 陣列(儲存串列元素) + int arrCapacity = 10; // 串列容量 + int arrSize = 0; // 串列長度(當前元素數量) + int extendRatio = 2; // 每次串列擴容的倍數 + + public: + /* 建構子 */ + MyList() { + arr = new int[arrCapacity]; + } + + /* 析構方法 */ + ~MyList() { + delete[] arr; + } + + /* 獲取串列長度(當前元素數量)*/ + int size() { + return arrSize; + } + + /* 獲取串列容量 */ + int capacity() { + return arrCapacity; + } + + /* 訪問元素 */ + int get(int index) { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + return arr[index]; + } + + /* 更新元素 */ + void set(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + arr[index] = num; + } + + /* 在尾部新增元素 */ + void add(int num) { + // 元素數量超出容量時,觸發擴容機制 + if (size() == capacity()) + extendCapacity(); + arr[size()] = num; + // 更新元素數量 + arrSize++; + } + + /* 在中間插入元素 */ + void insert(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + // 元素數量超出容量時,觸發擴容機制 + if (size() == capacity()) + extendCapacity(); + // 將索引 index 以及之後的元素都向後移動一位 + for (int j = size() - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // 更新元素數量 + arrSize++; + } + + /* 刪除元素 */ + int remove(int index) { + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + int num = arr[index]; + // 將索引 index 之後的元素都向前移動一位 + for (int j = index; j < size() - 1; j++) { + arr[j] = arr[j + 1]; + } + // 更新元素數量 + arrSize--; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + void extendCapacity() { + // 新建一個長度為原陣列 extendRatio 倍的新陣列 + int newCapacity = capacity() * extendRatio; + int *tmp = arr; + arr = new int[newCapacity]; + // 將原陣列中的所有元素複製到新陣列 + for (int i = 0; i < size(); i++) { + arr[i] = tmp[i]; + } + // 釋放記憶體 + delete[] tmp; + arrCapacity = newCapacity; + } + + /* 將串列轉換為 Vector 用於列印 */ + vector toVector() { + // 僅轉換有效長度範圍內的串列元素 + vector vec(size()); + for (int i = 0; i < size(); i++) { + vec[i] = arr[i]; + } + return vec; + } +}; + +/* Driver Code */ +int main() { + /* 初始化串列 */ + MyList *nums = new MyList(); + /* 在尾部新增元素 */ + nums->add(1); + nums->add(3); + nums->add(2); + nums->add(5); + nums->add(4); + cout << "串列 nums = "; + vector vec = nums->toVector(); + printVector(vec); + cout << "容量 = " << nums->capacity() << " ,長度 = " << nums->size() << endl; + + /* 在中間插入元素 */ + nums->insert(3, 6); + cout << "在索引 3 處插入數字 6 ,得到 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 刪除元素 */ + nums->remove(3); + cout << "刪除索引 3 處的元素,得到 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 訪問元素 */ + int num = nums->get(1); + cout << "訪問索引 1 處的元素,得到 num = " << num << endl; + + /* 更新元素 */ + nums->set(1, 0); + cout << "將索引 1 處的元素更新為 0 ,得到 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 測試擴容機制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums->add(i); + } + cout << "擴容後的串列 nums = "; + vec = nums->toVector(); + printVector(vec); + cout << "容量 = " << nums->capacity() << " ,長度 = " << nums->size() << endl; + + // 釋放記憶體 + delete nums; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/CMakeLists.txt b/zh-hant/codes/cpp/chapter_backtracking/CMakeLists.txt new file mode 100644 index 000000000..6c271e330 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) +add_executable(permutations_i permutations_i.cpp) +add_executable(permutations_ii permutations_ii.cpp) +add_executable(n_queens n_queens.cpp) +add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) +add_executable(subset_sum_i subset_sum_i.cpp) +add_executable(subset_sum_ii subset_sum_ii.cpp) diff --git a/zh-hant/codes/cpp/chapter_backtracking/n_queens.cpp b/zh-hant/codes/cpp/chapter_backtracking/n_queens.cpp new file mode 100644 index 000000000..7e151599b --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/n_queens.cpp @@ -0,0 +1,65 @@ +/** + * File: n_queens.cpp + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:n 皇后 */ +void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, + vector &diags1, vector &diags2) { + // 當放置完所有行時,記錄解 + if (row == n) { + res.push_back(state); + return; + } + // 走訪所有列 + for (int col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +vector>> nQueens(int n) { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + vector> state(n, vector(n, "#")); + vector cols(n, false); // 記錄列是否有皇后 + vector diags1(2 * n - 1, false); // 記錄主對角線上是否有皇后 + vector diags2(2 * n - 1, false); // 記錄次對角線上是否有皇后 + vector>> res; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + vector>> res = nQueens(n); + + cout << "輸入棋盤長寬為 " << n << endl; + cout << "皇后放置方案共有 " << res.size() << " 種" << endl; + for (const vector> &state : res) { + cout << "--------------------" << endl; + for (const vector &row : state) { + printVector(row); + } + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/permutations_i.cpp b/zh-hant/codes/cpp/chapter_backtracking/permutations_i.cpp new file mode 100644 index 000000000..36642f37f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/permutations_i.cpp @@ -0,0 +1,54 @@ +/** + * File: permutations_i.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:全排列 I */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.push_back(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop_back(); + } + } +} + +/* 全排列 I */ +vector> permutationsI(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 2, 3}; + + vector> res = permutationsI(nums); + + cout << "輸入陣列 nums = "; + printVector(nums); + cout << "所有排列 res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/permutations_ii.cpp b/zh-hant/codes/cpp/chapter_backtracking/permutations_ii.cpp new file mode 100644 index 000000000..d033f98b3 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/permutations_ii.cpp @@ -0,0 +1,56 @@ +/** + * File: permutations_ii.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:全排列 II */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // 走訪所有選擇 + unordered_set duplicated; + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && duplicated.find(choice) == duplicated.end()) { + // 嘗試:做出選擇,更新狀態 + duplicated.emplace(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.push_back(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop_back(); + } + } +} + +/* 全排列 II */ +vector> permutationsII(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 1, 2}; + + vector> res = permutationsII(nums); + + cout << "輸入陣列 nums = "; + printVector(nums); + cout << "所有排列 res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp new file mode 100644 index 000000000..6b50a2672 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp @@ -0,0 +1,39 @@ +/** + * File: preorder_traversal_i_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector res; + +/* 前序走訪:例題一 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + if (root->val == 7) { + // 記錄解 + res.push_back(root); + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二元樹" << endl; + printTree(root); + + // 前序走訪 + preOrder(root); + + cout << "\n輸出所有值為 7 的節點" << endl; + vector vals; + for (TreeNode *node : res) { + vals.push_back(node->val); + } + printVector(vals); +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp new file mode 100644 index 000000000..7121b5df8 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp @@ -0,0 +1,46 @@ +/** + * File: preorder_traversal_ii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* 前序走訪:例題二 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + // 嘗試 + path.push_back(root); + if (root->val == 7) { + // 記錄解 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 回退 + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二元樹" << endl; + printTree(root); + + // 前序走訪 + preOrder(root); + + cout << "\n輸出所有根節點到節點 7 的路徑" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp new file mode 100644 index 000000000..0a0a535bd --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* 前序走訪:例題三 */ +void preOrder(TreeNode *root) { + // 剪枝 + if (root == nullptr || root->val == 3) { + return; + } + // 嘗試 + path.push_back(root); + if (root->val == 7) { + // 記錄解 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 回退 + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二元樹" << endl; + printTree(root); + + // 前序走訪 + preOrder(root); + + cout << "\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp new file mode 100644 index 000000000..23af8bee7 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 判斷當前狀態是否為解 */ +bool isSolution(vector &state) { + return !state.empty() && state.back()->val == 7; +} + +/* 記錄解 */ +void recordSolution(vector &state, vector> &res) { + res.push_back(state); +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +bool isValid(vector &state, TreeNode *choice) { + return choice != nullptr && choice->val != 3; +} + +/* 更新狀態 */ +void makeChoice(vector &state, TreeNode *choice) { + state.push_back(choice); +} + +/* 恢復狀態 */ +void undoChoice(vector &state, TreeNode *choice) { + state.pop_back(); +} + +/* 回溯演算法:例題三 */ +void backtrack(vector &state, vector &choices, vector> &res) { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + } + // 走訪所有選擇 + for (TreeNode *choice : choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + // 進行下一輪選擇 + vector nextChoices{choice->left, choice->right}; + backtrack(state, nextChoices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二元樹" << endl; + printTree(root); + + // 回溯演算法 + vector state; + vector choices = {root}; + vector> res; + backtrack(state, choices, res); + + cout << "\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i.cpp b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i.cpp new file mode 100644 index 000000000..f705801f7 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i.cpp @@ -0,0 +1,57 @@ +/** + * File: subset_sum_i.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:子集和 I */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.push_back(state); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (int i = start; i < choices.size(); i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.push_back(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop_back(); + } +} + +/* 求解子集和 I */ +vector> subsetSumI(vector &nums, int target) { + vector state; // 狀態(子集) + sort(nums.begin(), nums.end()); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + vector> res; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumI(nums, target); + + cout << "輸入陣列 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等於 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp new file mode 100644 index 000000000..b7ddf663e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:子集和 I */ +void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { + // 子集和等於 target 時,記錄解 + if (total == target) { + res.push_back(state); + return; + } + // 走訪所有選擇 + for (size_t i = 0; i < choices.size(); i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.push_back(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop_back(); + } +} + +/* 求解子集和 I(包含重複子集) */ +vector> subsetSumINaive(vector &nums, int target) { + vector state; // 狀態(子集) + int total = 0; // 子集和 + vector> res; // 結果串列(子集串列) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumINaive(nums, target); + + cout << "輸入陣列 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等於 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_backtracking/subset_sum_ii.cpp b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_ii.cpp new file mode 100644 index 000000000..36041c2cc --- /dev/null +++ b/zh-hant/codes/cpp/chapter_backtracking/subset_sum_ii.cpp @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯演算法:子集和 II */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.push_back(state); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (int i = start; i < choices.size(); i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.push_back(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop_back(); + } +} + +/* 求解子集和 II */ +vector> subsetSumII(vector &nums, int target) { + vector state; // 狀態(子集) + sort(nums.begin(), nums.end()); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + vector> res; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {4, 4, 5}; + int target = 9; + + vector> res = subsetSumII(nums, target); + + cout << "輸入陣列 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等於 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/CMakeLists.txt b/zh-hant/codes/cpp/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 000000000..ea2845b75 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(iteration iteration.cpp) +add_executable(recursion recursion.cpp) +add_executable(space_complexity space_complexity.cpp) +add_executable(time_complexity time_complexity.cpp) +add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/iteration.cpp b/zh-hant/codes/cpp/chapter_computational_complexity/iteration.cpp new file mode 100644 index 000000000..f9a5992fd --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/iteration.cpp @@ -0,0 +1,76 @@ +/** + * File: iteration.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* for 迴圈 */ +int forLoop(int n) { + int res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + res += i; + } + return res; +} + +/* while 迴圈 */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; +} + +/* while 迴圈(兩次更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; +} + +/* 雙層 for 迴圈 */ +string nestedForLoop(int n) { + ostringstream res; + // 迴圈 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + // 迴圈 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; ++j) { + res << "(" << i << ", " << j << "), "; + } + } + return res.str(); +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + cout << "\nfor 迴圈的求和結果 res = " << res << endl; + + res = whileLoop(n); + cout << "\nwhile 迴圈的求和結果 res = " << res << endl; + + res = whileLoopII(n); + cout << "\nwhile 迴圈(兩次更新)求和結果 res = " << res << endl; + + string resStr = nestedForLoop(n); + cout << "\n雙層 for 迴圈的走訪結果 " << resStr << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/recursion.cpp b/zh-hant/codes/cpp/chapter_computational_complexity/recursion.cpp new file mode 100644 index 000000000..b03cf8577 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/recursion.cpp @@ -0,0 +1,78 @@ +/** + * File: recursion.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 遞迴 */ +int recur(int n) { + // 終止條件 + if (n == 1) + return 1; + // 遞:遞迴呼叫 + int res = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +/* 使用迭代模擬遞迴 */ +int forLoopRecur(int n) { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + stack stack; + int res = 0; + // 遞:遞迴呼叫 + for (int i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.push(i); + } + // 迴:返回結果 + while (!stack.empty()) { + // 透過“出堆疊操作”模擬“迴” + res += stack.top(); + stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾遞迴 */ +int tailRecur(int n, int res) { + // 終止條件 + if (n == 0) + return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +/* 費波那契數列:遞迴 */ +int fib(int n) { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + cout << "\n遞迴函式的求和結果 res = " << res << endl; + + res = forLoopRecur(n); + cout << "\n使用迭代模擬遞迴求和結果 res = " << res << endl; + + res = tailRecur(n, 0); + cout << "\n尾遞迴函式的求和結果 res = " << res << endl; + + res = fib(n); + cout << "\n費波那契數列的第 " << n << " 項為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/space_complexity.cpp b/zh-hant/codes/cpp/chapter_computational_complexity/space_complexity.cpp new file mode 100644 index 000000000..9720b5f8b --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/space_complexity.cpp @@ -0,0 +1,107 @@ +/** + * File: space_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 函式 */ +int func() { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +void constant(int n) { + // 常數、變數、物件佔用 O(1) 空間 + const int a = 0; + int b = 0; + vector nums(10000); + ListNode node(0); + // 迴圈中的變數佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + func(); + } +} + +/* 線性階 */ +void linear(int n) { + // 長度為 n 的陣列佔用 O(n) 空間 + vector nums(n); + // 長度為 n 的串列佔用 O(n) 空間 + vector nodes; + for (int i = 0; i < n; i++) { + nodes.push_back(ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + unordered_map map; + for (int i = 0; i < n; i++) { + map[i] = to_string(i); + } +} + +/* 線性階(遞迴實現) */ +void linearRecur(int n) { + cout << "遞迴 n = " << n << endl; + if (n == 1) + return; + linearRecur(n - 1); +} + +/* 平方階 */ +void quadratic(int n) { + // 二維串列佔用 O(n^2) 空間 + vector> numMatrix; + for (int i = 0; i < n; i++) { + vector tmp; + for (int j = 0; j < n; j++) { + tmp.push_back(0); + } + numMatrix.push_back(tmp); + } +} + +/* 平方階(遞迴實現) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + vector nums(n); + cout << "遞迴 n = " << n << " 中的 nums 長度 = " << nums.size() << endl; + return quadraticRecur(n - 1); +} + +/* 指數階(建立滿二元樹) */ +TreeNode *buildTree(int n) { + if (n == 0) + return nullptr; + TreeNode *root = new TreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +int main() { + int n = 5; + // 常數階 + constant(n); + // 線性階 + linear(n); + linearRecur(n); + // 平方階 + quadratic(n); + quadraticRecur(n); + // 指數階 + TreeNode *root = buildTree(n); + printTree(root); + + // 釋放記憶體 + freeMemoryTree(root); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/time_complexity.cpp b/zh-hant/codes/cpp/chapter_computational_complexity/time_complexity.cpp new file mode 100644 index 000000000..c8bf484c1 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/time_complexity.cpp @@ -0,0 +1,168 @@ +/** + * File: time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 常數階 */ +int constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; +} + +/* 線性階 */ +int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; +} + +/* 線性階(走訪陣列) */ +int arrayTraversal(vector &nums) { + int count = 0; + // 迴圈次數與陣列長度成正比 + for (int num : nums) { + count++; + } + return count; +} + +/* 平方階 */ +int quadratic(int n) { + int count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方階(泡沫排序) */ +int bubbleSort(vector &nums) { + int count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +/* 指數階(迴圈實現) */ +int exponential(int n) { + int count = 0, base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指數階(遞迴實現) */ +int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 對數階(迴圈實現) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 對數階(遞迴實現) */ +int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; +} + +/* 線性對數階 */ +int linearLogRecur(int n) { + if (n <= 1) + return 1; + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乘階(遞迴實現) */ +int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + // 從 1 個分裂出 n 個 + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +int main() { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + int n = 8; + cout << "輸入資料大小 n = " << n << endl; + + int count = constant(n); + cout << "常數階的操作數量 = " << count << endl; + + count = linear(n); + cout << "線性階的操作數量 = " << count << endl; + vector arr(n); + count = arrayTraversal(arr); + cout << "線性階(走訪陣列)的操作數量 = " << count << endl; + + count = quadratic(n); + cout << "平方階的操作數量 = " << count << endl; + vector nums(n); + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + cout << "平方階(泡沫排序)的操作數量 = " << count << endl; + + count = exponential(n); + cout << "指數階(迴圈實現)的操作數量 = " << count << endl; + count = expRecur(n); + cout << "指數階(遞迴實現)的操作數量 = " << count << endl; + + count = logarithmic(n); + cout << "對數階(迴圈實現)的操作數量 = " << count << endl; + count = logRecur(n); + cout << "對數階(遞迴實現)的操作數量 = " << count << endl; + + count = linearLogRecur(n); + cout << "線性對數階(遞迴實現)的操作數量 = " << count << endl; + + count = factorialRecur(n); + cout << "階乘階(遞迴實現)的操作數量 = " << count << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp b/zh-hant/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp new file mode 100644 index 000000000..0d1571b9e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp @@ -0,0 +1,45 @@ +/** + * File: worst_best_time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +vector randomNumbers(int n) { + vector nums(n); + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 使用系統時間生成隨機種子 + unsigned seed = chrono::system_clock::now().time_since_epoch().count(); + // 隨機打亂陣列元素 + shuffle(nums.begin(), nums.end(), default_random_engine(seed)); + return nums; +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +int findOne(vector &nums) { + for (int i = 0; i < nums.size(); i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + for (int i = 0; i < 1000; i++) { + int n = 100; + vector nums = randomNumbers(n); + int index = findOne(nums); + cout << "\n陣列 [ 1, 2, ..., n ] 被打亂後 = "; + printVector(nums); + cout << "數字 1 的索引為 " << index << endl; + } + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt b/zh-hant/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 000000000..38dfff710 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.cpp) +add_executable(build_tree build_tree.cpp) +add_executable(hanota hanota.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp b/zh-hant/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp new file mode 100644 index 000000000..5611a3815 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp @@ -0,0 +1,46 @@ +/** + * File: binary_search_recur.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分搜尋:問題 f(i, j) */ +int dfs(vector &nums, int target, int i, int j) { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } +} + +/* 二分搜尋 */ +int binarySearch(vector &nums, int target) { + int n = nums.size(); + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + // 二分搜尋(雙閉區間) + int index = binarySearch(nums, target); + cout << "目標元素 6 的索引 = " << index << endl; + + return 0; +} \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_divide_and_conquer/build_tree.cpp b/zh-hant/codes/cpp/chapter_divide_and_conquer/build_tree.cpp new file mode 100644 index 000000000..5398c8641 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_divide_and_conquer/build_tree.cpp @@ -0,0 +1,51 @@ +/** + * File: build_tree.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 構建二元樹:分治 */ +TreeNode *dfs(vector &preorder, unordered_map &inorderMap, int i, int l, int r) { + // 子樹區間為空時終止 + if (r - l < 0) + return NULL; + // 初始化根節點 + TreeNode *root = new TreeNode(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + int m = inorderMap[preorder[i]]; + // 子問題:構建左子樹 + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; +} + +/* 構建二元樹 */ +TreeNode *buildTree(vector &preorder, vector &inorder) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + unordered_map inorderMap; + for (int i = 0; i < inorder.size(); i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1); + return root; +} + +/* Driver Code */ +int main() { + vector preorder = {3, 9, 2, 1, 7}; + vector inorder = {9, 3, 1, 2, 7}; + cout << "前序走訪 = "; + printVector(preorder); + cout << "中序走訪 = "; + printVector(inorder); + + TreeNode *root = buildTree(preorder, inorder); + cout << "構建的二元樹為:\n"; + printTree(root); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_divide_and_conquer/hanota.cpp b/zh-hant/codes/cpp/chapter_divide_and_conquer/hanota.cpp new file mode 100644 index 000000000..258b3d3a3 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_divide_and_conquer/hanota.cpp @@ -0,0 +1,66 @@ +/** + * File: hanota.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 移動一個圓盤 */ +void move(vector &src, vector &tar) { + // 從 src 頂部拿出一個圓盤 + int pan = src.back(); + src.pop_back(); + // 將圓盤放入 tar 頂部 + tar.push_back(pan); +} + +/* 求解河內塔問題 f(i) */ +void dfs(int i, vector &src, vector &buf, vector &tar) { + // 若 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); +} + +/* 求解河內塔問題 */ +void solveHanota(vector &A, vector &B, vector &C) { + int n = A.size(); + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +int main() { + // 串列尾部是柱子頂部 + vector A = {5, 4, 3, 2, 1}; + vector B = {}; + vector C = {}; + + cout << "初始狀態下:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + solveHanota(A, B, C); + + cout << "圓盤移動完成後:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/CMakeLists.txt b/zh-hant/codes/cpp/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 000000000..ed185458a --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) +add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) +add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) +add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) +add_executable(min_path_sum min_path_sum.cpp) +add_executable(unbounded_knapsack unbounded_knapsack.cpp) +add_executable(coin_change coin_change.cpp) +add_executable(coin_change_ii coin_change_ii.cpp) +add_executable(edit_distance edit_distance.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp new file mode 100644 index 000000000..8eb6e0921 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp @@ -0,0 +1,43 @@ + +/** + * File: climbing_stairs_backtrack.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯 */ +void backtrack(vector &choices, int state, int n, vector &res) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) + res[0]++; + // 走訪所有選擇 + for (auto &choice : choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) + continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +int climbingStairsBacktrack(int n) { + vector choices = {1, 2}; // 可選擇向上爬 1 階或 2 階 + int state = 0; // 從第 0 階開始爬 + vector res = {0}; // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp new file mode 100644 index 000000000..2ec90a71d --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp @@ -0,0 +1,37 @@ +/** + * File: climbing_stairs_constraint_dp.cpp + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 帶約束爬樓梯:動態規劃 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + vector> dp(n + 1, vector(3, 0)); + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + 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 */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp new file mode 100644 index 000000000..a4b1a85b6 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 搜尋 */ +int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬樓梯:搜尋 */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp new file mode 100644 index 000000000..7779e39b3 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_dfs_mem.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 記憶化搜尋 */ +int dfs(int i, vector &mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +/* 爬樓梯:記憶化搜尋 */ +int climbingStairsDFSMem(int n) { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + vector mem(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp new file mode 100644 index 000000000..95d46510a --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 爬樓梯:動態規劃 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用於儲存子問題的解 + vector dp(n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + res = climbingStairsDPComp(n); + cout << "爬 " << n << " 階樓梯共有 " << res << " 種方案" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change.cpp new file mode 100644 index 000000000..75d39c814 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change.cpp @@ -0,0 +1,70 @@ +/** + * File: coin_change.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零錢兌換:動態規劃 */ +int coinChangeDP(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // 初始化 dp 表 + vector> dp(n + 1, vector(amt + 1, 0)); + // 狀態轉移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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] != MAX ? dp[n][amt] : -1; +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +int coinChangeDPComp(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // 初始化 dp 表 + vector dp(amt + 1, MAX); + dp[0] = 0; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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] != MAX ? dp[amt] : -1; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 4; + + // 動態規劃 + int res = coinChangeDP(coins, amt); + cout << "湊到目標金額所需的最少硬幣數量為 " << res << endl; + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt); + cout << "湊到目標金額所需的最少硬幣數量為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp new file mode 100644 index 000000000..7fc199efe --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp @@ -0,0 +1,68 @@ +/** + * File: coin_change_ii.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零錢兌換 II:動態規劃 */ +int coinChangeIIDP(vector &coins, int amt) { + int n = coins.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(amt + 1, 0)); + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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]; +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +int coinChangeIIDPComp(vector &coins, int amt) { + int n = coins.size(); + // 初始化 dp 表 + vector dp(amt + 1, 0); + dp[0] = 1; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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 */ +int main() { + vector coins = {1, 2, 5}; + int amt = 5; + + // 動態規劃 + int res = coinChangeIIDP(coins, amt); + cout << "湊出目標金額的硬幣組合數量為 " << res << endl; + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins, amt); + cout << "湊出目標金額的硬幣組合數量為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/edit_distance.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/edit_distance.cpp new file mode 100644 index 000000000..e6dab8e13 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/edit_distance.cpp @@ -0,0 +1,136 @@ +/** + * File: edit_distance.cpp + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 編輯距離:暴力搜尋 */ +int editDistanceDFS(string s, string t, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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 editDistanceDFS(s, t, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return min(min(insert, del), replace) + 1; +} + +/* 編輯距離:記憶化搜尋 */ +int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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 editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = min(min(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 編輯距離:動態規劃 */ +int editDistanceDP(string s, string t) { + int n = s.length(), m = t.length(); + vector> dp(n + 1, vector(m + 1, 0)); + // 狀態轉移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +int editDistanceDPComp(string s, string t) { + int n = s.length(), m = t.length(); + vector dp(m + 1, 0); + // 狀態轉移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (int i = 1; i <= n; i++) { + // 狀態轉移:首列 + int leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +int main() { + string s = "bag"; + string t = "pack"; + int n = s.length(), m = t.length(); + + // 暴力搜尋 + int res = editDistanceDFS(s, t, n, m); + cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; + + // 記憶化搜尋 + vector> mem(n + 1, vector(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; + + // 動態規劃 + res = editDistanceDP(s, t); + cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t); + cout << "將 " << s << " 更改為 " << t << " 最少需要編輯 " << res << " 步\n"; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/knapsack.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/knapsack.cpp new file mode 100644 index 000000000..6a2fa9943 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/knapsack.cpp @@ -0,0 +1,109 @@ +#include +#include +#include + +using namespace std; + +/* 0-1 背包:暴力搜尋 */ +int knapsackDFS(vector &wgt, vector &val, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return max(no, yes); +} + +/* 0-1 背包:記憶化搜尋 */ +int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:動態規劃 */ +int knapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(cap + 1, 0)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + 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]; +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +int knapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector dp(cap + 1, 0); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + // 倒序走訪 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + int n = wgt.size(); + + // 暴力搜尋 + int res = knapsackDFS(wgt, val, n, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + // 記憶化搜尋 + vector> mem(n + 1, vector(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + // 動態規劃 + res = knapsackDP(wgt, val, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, val, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp new file mode 100644 index 000000000..8b0edf2ca --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 爬樓梯最小代價:動態規劃 */ +int minCostClimbingStairsDP(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用於儲存子問題的解 + vector dp(n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +int minCostClimbingStairsDPComp(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + cout << "輸入樓梯的代價串列為 "; + printVector(cost); + + int res = minCostClimbingStairsDP(cost); + cout << "爬完樓梯的最低代價為 " << res << endl; + + res = minCostClimbingStairsDPComp(cost); + cout << "爬完樓梯的最低代價為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp new file mode 100644 index 000000000..28b8074d6 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp @@ -0,0 +1,116 @@ +/** + * File: min_path_sum.cpp + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最小路徑和:暴力搜尋 */ +int minPathSumDFS(vector> &grid, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; +} + +/* 最小路徑和:記憶化搜尋 */ +int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* 最小路徑和:動態規劃 */ +int minPathSumDP(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // 初始化 dp 表 + vector> dp(n, vector(m)); + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +int minPathSumDPComp(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // 初始化 dp 表 + vector dp(m); + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (int i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +int main() { + vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = grid.size(), m = grid[0].size(); + + // 暴力搜尋 + int res = minPathSumDFS(grid, n - 1, m - 1); + cout << "從左上角到右下角的最小路徑和為 " << res << endl; + + // 記憶化搜尋 + vector> mem(n, vector(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + cout << "從左上角到右下角的最小路徑和為 " << res << endl; + + // 動態規劃 + res = minPathSumDP(grid); + cout << "從左上角到右下角的最小路徑和為 " << res << endl; + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid); + cout << "從左上角到右下角的最小路徑和為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp b/zh-hant/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp new file mode 100644 index 000000000..fae6d7d67 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp @@ -0,0 +1,64 @@ +/** + * File: unbounded_knapsack.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 完全背包:動態規劃 */ +int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(cap + 1, 0)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + 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]; +} + +/* 完全背包:空間最佳化後的動態規劃 */ +int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector dp(cap + 1, 0); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + 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 */ +int main() { + vector wgt = {1, 2, 3}; + vector val = {5, 11, 15}; + int cap = 4; + + // 動態規劃 + int res = unboundedKnapsackDP(wgt, val, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt, val, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_graph/CMakeLists.txt b/zh-hant/codes/cpp/chapter_graph/CMakeLists.txt new file mode 100644 index 000000000..4a56ce35b --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(graph_bfs graph_bfs.cpp) +add_executable(graph_dfs graph_dfs.cpp) +# add_executable(graph_adjacency_list graph_adjacency_list.cpp) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) +add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) diff --git a/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list.cpp b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list.cpp new file mode 100644 index 000000000..2148b4a6e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list.cpp @@ -0,0 +1,90 @@ +/** + * File: graph_adjacency_list.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList { + public: + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + unordered_map> adjList; + + /* 在 vector 中刪除指定節點 */ + void remove(vector &vec, Vertex *vet) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == vet) { + vec.erase(vec.begin() + i); + break; + } + } + } + + /* 建構子 */ + GraphAdjList(const vector> &edges) { + // 新增所有頂點和邊 + for (const vector &edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + int size() { + return adjList.size(); + } + + /* 新增邊 */ + void addEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("不存在頂點"); + // 新增邊 vet1 - vet2 + adjList[vet1].push_back(vet2); + adjList[vet2].push_back(vet1); + } + + /* 刪除邊 */ + void removeEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("不存在頂點"); + // 刪除邊 vet1 - vet2 + remove(adjList[vet1], vet2); + remove(adjList[vet2], vet1); + } + + /* 新增頂點 */ + void addVertex(Vertex *vet) { + if (adjList.count(vet)) + return; + // 在鄰接表中新增一個新鏈結串列 + adjList[vet] = vector(); + } + + /* 刪除頂點 */ + void removeVertex(Vertex *vet) { + if (!adjList.count(vet)) + throw invalid_argument("不存在頂點"); + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.erase(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (auto &adj : adjList) { + remove(adj.second, vet); + } + } + + /* 列印鄰接表 */ + void print() { + cout << "鄰接表 =" << endl; + for (auto &adj : adjList) { + const auto &key = adj.first; + const auto &vec = adj.second; + cout << key->val << ": "; + printVector(vetsToVals(vec)); + } + } +}; + +// 測試樣例請見 graph_adjacency_list_test.cpp diff --git a/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp new file mode 100644 index 000000000..8e3bec669 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp @@ -0,0 +1,49 @@ +/** + * File: graph_adjacency_list_test.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "./graph_adjacency_list.cpp" + +/* Driver Code */ +int main() { + /* 初始化無向圖 */ + vector v = valsToVets(vector{1, 3, 2, 5, 4}); + vector> 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]}}; + GraphAdjList graph(edges); + cout << "\n初始化後,圖為" << endl; + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + cout << "\n新增邊 1-2 後,圖為" << endl; + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + cout << "\n刪除邊 1-3 後,圖為" << endl; + graph.print(); + + /* 新增頂點 */ + Vertex *v5 = new Vertex(6); + graph.addVertex(v5); + cout << "\n新增頂點 6 後,圖為" << endl; + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(v[1]); + cout << "\n刪除頂點 3 後,圖為" << endl; + graph.print(); + + // 釋放記憶體 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp new file mode 100644 index 000000000..2da942a6e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp @@ -0,0 +1,127 @@ +/** + * File: graph_adjacency_matrix.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + vector vertices; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + vector> adjMat; // 鄰接矩陣,行列索引對應“頂點索引” + + public: + /* 建構子 */ + GraphAdjMat(const vector &vertices, const vector> &edges) { + // 新增頂點 + for (int val : vertices) { + addVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (const vector &edge : edges) { + addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + int size() const { + return vertices.size(); + } + + /* 新增頂點 */ + void addVertex(int val) { + int n = size(); + // 向頂點串列中新增新頂點的值 + vertices.push_back(val); + // 在鄰接矩陣中新增一行 + adjMat.emplace_back(vector(n, 0)); + // 在鄰接矩陣中新增一列 + for (vector &row : adjMat) { + row.push_back(0); + } + } + + /* 刪除頂點 */ + void removeVertex(int index) { + if (index >= size()) { + throw out_of_range("頂點不存在"); + } + // 在頂點串列中移除索引 index 的頂點 + vertices.erase(vertices.begin() + index); + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.erase(adjMat.begin() + index); + // 在鄰接矩陣中刪除索引 index 的列 + for (vector &row : adjMat) { + row.erase(row.begin() + index); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + void addEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("頂點不存在"); + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + void removeEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("頂點不存在"); + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + void print() { + cout << "頂點串列 = "; + printVector(vertices); + cout << "鄰接矩陣 =" << endl; + printVectorMatrix(adjMat); + } +}; + +/* Driver Code */ +int main() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + vector vertices = {1, 3, 2, 5, 4}; + vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + GraphAdjMat graph(vertices, edges); + cout << "\n初始化後,圖為" << endl; + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(0, 2); + cout << "\n新增邊 1-2 後,圖為" << endl; + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(0, 1); + cout << "\n刪除邊 1-3 後,圖為" << endl; + graph.print(); + + /* 新增頂點 */ + graph.addVertex(6); + cout << "\n新增頂點 6 後,圖為" << endl; + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(1); + cout << "\n刪除頂點 3 後,圖為" << endl; + graph.print(); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_graph/graph_bfs.cpp b/zh-hant/codes/cpp/chapter_graph/graph_bfs.cpp new file mode 100644 index 000000000..dd70735a4 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/graph_bfs.cpp @@ -0,0 +1,59 @@ +/** + * File: graph_bfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +vector graphBFS(GraphAdjList &graph, Vertex *startVet) { + // 頂點走訪序列 + vector res; + // 雜湊表,用於記錄已被訪問過的頂點 + unordered_set visited = {startVet}; + // 佇列用於實現 BFS + queue que; + que.push(startVet); + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (!que.empty()) { + Vertex *vet = que.front(); + que.pop(); // 佇列首頂點出隊 + res.push_back(vet); // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (auto adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳過已被訪問的頂點 + que.push(adjVet); // 只入列未訪問的頂點 + visited.emplace(adjVet); // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res; +} + +/* Driver Code */ +int main() { + /* 初始化無向圖 */ + vector v = valsToVets({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + vector> 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]}}; + GraphAdjList graph(edges); + cout << "\n初始化後,圖為\\n"; + graph.print(); + + /* 廣度優先走訪 */ + vector res = graphBFS(graph, v[0]); + cout << "\n廣度優先走訪(BFS)頂點序列為" << endl; + printVector(vetsToVals(res)); + + // 釋放記憶體 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_graph/graph_dfs.cpp b/zh-hant/codes/cpp/chapter_graph/graph_dfs.cpp new file mode 100644 index 000000000..f22c851c9 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_graph/graph_dfs.cpp @@ -0,0 +1,55 @@ +/** + * File: graph_dfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* 深度優先走訪輔助函式 */ +void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) { + res.push_back(vet); // 記錄訪問頂點 + visited.emplace(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (Vertex *adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳過已被訪問的頂點 + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +vector graphDFS(GraphAdjList &graph, Vertex *startVet) { + // 頂點走訪序列 + vector res; + // 雜湊表,用於記錄已被訪問過的頂點 + unordered_set visited; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +int main() { + /* 初始化無向圖 */ + vector v = valsToVets(vector{0, 1, 2, 3, 4, 5, 6}); + vector> 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]}}; + GraphAdjList graph(edges); + cout << "\n初始化後,圖為" << endl; + graph.print(); + + /* 深度優先走訪 */ + vector res = graphDFS(graph, v[0]); + cout << "\n深度優先走訪(DFS)頂點序列為" << endl; + printVector(vetsToVals(res)); + + // 釋放記憶體 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_greedy/CMakeLists.txt b/zh-hant/codes/cpp/chapter_greedy/CMakeLists.txt new file mode 100644 index 000000000..91788668d --- /dev/null +++ b/zh-hant/codes/cpp/chapter_greedy/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(coin_change_greedy coin_change_greedy.cpp) +add_executable(fractional_knapsack fractional_knapsack.cpp) +add_executable(max_capacity max_capacity.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_greedy/coin_change_greedy.cpp b/zh-hant/codes/cpp/chapter_greedy/coin_change_greedy.cpp new file mode 100644 index 000000000..78cc54ac6 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_greedy/coin_change_greedy.cpp @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零錢兌換:貪婪 */ +int coinChangeGreedy(vector &coins, int amt) { + // 假設 coins 串列有序 + int i = coins.size() - 1; + int count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (amt > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > amt) { + i--; + } + // 選擇 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,則返回 -1 + return amt == 0 ? count : -1; +} + +/* Driver Code */ +int main() { + // 貪婪:能夠保證找到全域性最優解 + vector coins = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "湊到 " << amt << " 所需的最少硬幣數量為 " << res << endl; + + // 貪婪:無法保證找到全域性最優解 + coins = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "湊到 " << amt << " 所需的最少硬幣數量為 " << res << endl; + cout << "實際上需要的最少數量為 3 ,即 20 + 20 + 20" << endl; + + // 貪婪:無法保證找到全域性最優解 + coins = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "湊到 " << amt << " 所需的最少硬幣數量為 " << res << endl; + cout << "實際上需要的最少數量為 2 ,即 49 + 49" << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_greedy/fractional_knapsack.cpp b/zh-hant/codes/cpp/chapter_greedy/fractional_knapsack.cpp new file mode 100644 index 000000000..579a6b4a2 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_greedy/fractional_knapsack.cpp @@ -0,0 +1,56 @@ +/** + * File: fractional_knapsack.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 物品 */ +class Item { + public: + int w; // 物品重量 + int v; // 物品價值 + + Item(int w, int v) : w(w), v(v) { + } +}; + +/* 分數背包:貪婪 */ +double fractionalKnapsack(vector &wgt, vector &val, int cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + vector items; + for (int i = 0; i < wgt.size(); i++) { + items.push_back(Item(wgt[i], val[i])); + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; }); + // 迴圈貪婪選擇 + double res = 0; + for (auto &item : items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (double)item.v / item.w * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + + // 貪婪演算法 + double res = fractionalKnapsack(wgt, val, cap); + cout << "不超過背包容量的最大物品價值為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_greedy/max_capacity.cpp b/zh-hant/codes/cpp/chapter_greedy/max_capacity.cpp new file mode 100644 index 000000000..05102fa22 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_greedy/max_capacity.cpp @@ -0,0 +1,39 @@ +/** + * File: max_capacity.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最大容量:貪婪 */ +int maxCapacity(vector &ht) { + // 初始化 i, j,使其分列陣列兩端 + int i = 0, j = ht.size() - 1; + // 初始最大容量為 0 + int res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; + + // 貪婪演算法 + int res = maxCapacity(ht); + cout << "最大容量為 " << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_greedy/max_product_cutting.cpp b/zh-hant/codes/cpp/chapter_greedy/max_product_cutting.cpp new file mode 100644 index 000000000..65352c777 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_greedy/max_product_cutting.cpp @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最大切分乘積:貪婪 */ +int maxProductCutting(int n) { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + int a = n / 3; + int b = n % 3; + if (b == 1) { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return (int)pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 當餘數為 2 時,不做處理 + return (int)pow(3, a) * 2; + } + // 當餘數為 0 時,不做處理 + return (int)pow(3, a); +} + +/* Driver Code */ +int main() { + int n = 58; + + // 貪婪演算法 + int res = maxProductCutting(n); + cout << "最大切分乘積為" << res << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_hashing/CMakeLists.txt b/zh-hant/codes/cpp/chapter_hashing/CMakeLists.txt new file mode 100644 index 000000000..6b583ef55 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(hash_map hash_map.cpp) +add_executable(array_hash_map_test array_hash_map_test.cpp) +add_executable(hash_map_chaining hash_map_chaining.cpp) +add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) +add_executable(simple_hash simple_hash.cpp) +add_executable(built_in_hash built_in_hash.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_hashing/array_hash_map.cpp b/zh-hant/codes/cpp/chapter_hashing/array_hash_map.cpp new file mode 100644 index 000000000..60c1f9b66 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/array_hash_map.cpp @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* 鍵值對 */ +struct Pair { + public: + int key; + string val; + Pair(int key, string val) { + this->key = key; + this->val = val; + } +}; + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + private: + vector buckets; + + public: + ArrayHashMap() { + // 初始化陣列,包含 100 個桶 + buckets = vector(100); + } + + ~ArrayHashMap() { + // 釋放記憶體 + for (const auto &bucket : buckets) { + delete bucket; + } + buckets.clear(); + } + + /* 雜湊函式 */ + int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* 查詢操作 */ + string get(int key) { + int index = hashFunc(key); + Pair *pair = buckets[index]; + if (pair == nullptr) + return ""; + return pair->val; + } + + /* 新增操作 */ + void put(int key, string val) { + Pair *pair = new Pair(key, val); + int index = hashFunc(key); + buckets[index] = pair; + } + + /* 刪除操作 */ + void remove(int key) { + int index = hashFunc(key); + // 釋放記憶體並置為 nullptr + delete buckets[index]; + buckets[index] = nullptr; + } + + /* 獲取所有鍵值對 */ + vector pairSet() { + vector pairSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + pairSet.push_back(pair); + } + } + return pairSet; + } + + /* 獲取所有鍵 */ + vector keySet() { + vector keySet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + keySet.push_back(pair->key); + } + } + return keySet; + } + + /* 獲取所有值 */ + vector valueSet() { + vector valueSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + valueSet.push_back(pair->val); + } + } + return valueSet; + } + + /* 列印雜湊表 */ + void print() { + for (Pair *kv : pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + } +}; + +// 測試樣例請見 array_hash_map_test.cpp diff --git a/zh-hant/codes/cpp/chapter_hashing/array_hash_map_test.cpp b/zh-hant/codes/cpp/chapter_hashing/array_hash_map_test.cpp new file mode 100644 index 000000000..341fa40ac --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/array_hash_map_test.cpp @@ -0,0 +1,52 @@ +/** + * File: array_hash_map_test.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "./array_hash_map.cpp" + +/* Driver Code */ +int main() { + /* 初始化雜湊表 */ + ArrayHashMap map = ArrayHashMap(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map.get(15937); + cout << "\n輸入學號 15937 ,查詢到姓名 " << name << endl; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + cout << "\n刪除 10583 後,雜湊表為\nKey -> Value" << endl; + map.print(); + + /* 走訪雜湊表 */ + cout << "\n走訪鍵值對 Key->Value" << endl; + for (auto kv : map.pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + + cout << "\n單獨走訪鍵 Key" << endl; + for (auto key : map.keySet()) { + cout << key << endl; + } + + cout << "\n單獨走訪值 Value" << endl; + for (auto val : map.valueSet()) { + cout << val << endl; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_hashing/built_in_hash.cpp b/zh-hant/codes/cpp/chapter_hashing/built_in_hash.cpp new file mode 100644 index 000000000..6616f5c6e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/built_in_hash.cpp @@ -0,0 +1,29 @@ +/** + * File: built_in_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + int num = 3; + size_t hashNum = hash()(num); + cout << "整數 " << num << " 的雜湊值為 " << hashNum << "\n"; + + bool bol = true; + size_t hashBol = hash()(bol); + cout << "布林量 " << bol << " 的雜湊值為 " << hashBol << "\n"; + + double dec = 3.14159; + size_t hashDec = hash()(dec); + cout << "小數 " << dec << " 的雜湊值為 " << hashDec << "\n"; + + string str = "Hello 演算法"; + size_t hashStr = hash()(str); + cout << "字串 " << str << " 的雜湊值為 " << hashStr << "\n"; + + // 在 C++ 中,內建 std:hash() 僅提供基本資料型別的雜湊值計算 + // 陣列、物件的雜湊值計算需要自行實現 +} diff --git a/zh-hant/codes/cpp/chapter_hashing/hash_map.cpp b/zh-hant/codes/cpp/chapter_hashing/hash_map.cpp new file mode 100644 index 000000000..dfd262abf --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/hash_map.cpp @@ -0,0 +1,46 @@ +/** + * File: hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化雜湊表 */ + unordered_map map; + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈"; + map[15937] = "小囉"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鴨"; + cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; + printHashMap(map); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map[15937]; + cout << "\n輸入學號 15937 ,查詢到姓名 " << name << endl; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.erase(10583); + cout << "\n刪除 10583 後,雜湊表為\nKey -> Value" << endl; + printHashMap(map); + + /* 走訪雜湊表 */ + cout << "\n走訪鍵值對 Key->Value" << endl; + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << endl; + } + cout << "\n使用迭代器走訪 Key->Value" << endl; + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_hashing/hash_map_chaining.cpp b/zh-hant/codes/cpp/chapter_hashing/hash_map_chaining.cpp new file mode 100644 index 000000000..02f4c5400 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/hash_map_chaining.cpp @@ -0,0 +1,150 @@ +/** + * File: hash_map_chaining.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + private: + int size; // 鍵值對數量 + int capacity; // 雜湊表容量 + double loadThres; // 觸發擴容的負載因子閾值 + int extendRatio; // 擴容倍數 + vector> buckets; // 桶陣列 + + public: + /* 建構子 */ + HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) { + buckets.resize(capacity); + } + + /* 析構方法 */ + ~HashMapChaining() { + for (auto &bucket : buckets) { + for (Pair *pair : bucket) { + // 釋放記憶體 + delete pair; + } + } + } + + /* 雜湊函式 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double loadFactor() { + return (double)size / (double)capacity; + } + + /* 查詢操作 */ + string get(int key) { + int index = hashFunc(key); + // 走訪桶,若找到 key ,則返回對應 val + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + return pair->val; + } + } + // 若未找到 key ,則返回空字串 + return ""; + } + + /* 新增操作 */ + void put(int key, string val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + pair->val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + buckets[index].push_back(new Pair(key, val)); + size++; + } + + /* 刪除操作 */ + void remove(int key) { + int index = hashFunc(key); + auto &bucket = buckets[index]; + // 走訪桶,從中刪除鍵值對 + for (int i = 0; i < bucket.size(); i++) { + if (bucket[i]->key == key) { + Pair *tmp = bucket[i]; + bucket.erase(bucket.begin() + i); // 從中刪除鍵值對 + delete tmp; // 釋放記憶體 + size--; + return; + } + } + } + + /* 擴容雜湊表 */ + void extend() { + // 暫存原雜湊表 + vector> bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets.clear(); + buckets.resize(capacity); + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (auto &bucket : bucketsTmp) { + for (Pair *pair : bucket) { + put(pair->key, pair->val); + // 釋放記憶體 + delete pair; + } + } + } + + /* 列印雜湊表 */ + void print() { + for (auto &bucket : buckets) { + cout << "["; + for (Pair *pair : bucket) { + cout << pair->key << " -> " << pair->val << ", "; + } + cout << "]\n"; + } + } +}; + +/* Driver Code */ +int main() { + /* 初始化雜湊表 */ + HashMapChaining map = HashMapChaining(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map.get(13276); + cout << "\n輸入學號 13276 ,查詢到姓名 " << name << endl; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(12836); + cout << "\n刪除 12836 後,雜湊表為\nKey -> Value" << endl; + map.print(); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp b/zh-hant/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp new file mode 100644 index 000000000..4d4625b00 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp @@ -0,0 +1,171 @@ +/** + * File: hash_map_open_addressing.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + private: + int size; // 鍵值對數量 + int capacity = 4; // 雜湊表容量 + const double loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + const int extendRatio = 2; // 擴容倍數 + vector buckets; // 桶陣列 + Pair *TOMBSTONE = new Pair(-1, "-1"); // 刪除標記 + + public: + /* 建構子 */ + HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { + } + + /* 析構方法 */ + ~HashMapOpenAddressing() { + for (Pair *pair : buckets) { + if (pair != nullptr && pair != TOMBSTONE) { + delete pair; + } + } + delete TOMBSTONE; + } + + /* 雜湊函式 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double loadFactor() { + return (double)size / capacity; + } + + /* 搜尋 key 對應的桶索引 */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (buckets[index] != nullptr) { + // 若遇到 key ,返回對應的桶索引 + if (buckets[index]->key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + string get(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則返回對應 val + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + return buckets[index]->val; + } + // 若鍵值對不存在,則返回空字串 + return ""; + } + + /* 新增操作 */ + void put(int key, string val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend(); + } + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + buckets[index]->val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + buckets[index] = new Pair(key, val); + size++; + } + + /* 刪除操作 */ + void remove(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + delete buckets[index]; + buckets[index] = TOMBSTONE; + size--; + } + } + + /* 擴容雜湊表 */ + void extend() { + // 暫存原雜湊表 + vector bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = vector(capacity, nullptr); + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (Pair *pair : bucketsTmp) { + if (pair != nullptr && pair != TOMBSTONE) { + put(pair->key, pair->val); + delete pair; + } + } + } + + /* 列印雜湊表 */ + void print() { + for (Pair *pair : buckets) { + if (pair == nullptr) { + cout << "nullptr" << endl; + } else if (pair == TOMBSTONE) { + cout << "TOMBSTONE" << endl; + } else { + cout << pair->key << " -> " << pair->val << endl; + } + } + } +}; + +/* Driver Code */ +int main() { + // 初始化雜湊表 + HashMapOpenAddressing hashmap; + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, val) + hashmap.put(12836, "小哈"); + hashmap.put(15937, "小囉"); + hashmap.put(16750, "小算"); + hashmap.put(13276, "小法"); + hashmap.put(10583, "小鴨"); + cout << "\n新增完成後,雜湊表為\nKey -> Value" << endl; + hashmap.print(); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 val + string name = hashmap.get(13276); + cout << "\n輸入學號 13276 ,查詢到姓名 " << name << endl; + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, val) + hashmap.remove(16750); + cout << "\n刪除 16750 後,雜湊表為\nKey -> Value" << endl; + hashmap.print(); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp b/zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp new file mode 100644 index 000000000..2b43e166a --- /dev/null +++ b/zh-hant/codes/cpp/chapter_hashing/simple_hash.cpp @@ -0,0 +1,66 @@ +/** + * File: simple_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 加法雜湊 */ +int addHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* 乘法雜湊 */ +int mulHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (31 * hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* 互斥或雜湊 */ +int xorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash ^= (int)c; + } + return hash & MODULUS; +} + +/* 旋轉雜湊 */ +int rotHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS; + } + return (int)hash; +} + +/* Driver Code */ +int main() { + string key = "Hello dsad3241241dsa算123法"; + + int hash = addHash(key); + cout << "加法雜湊值為 " << hash << endl; + + hash = mulHash(key); + cout << "乘法雜湊值為 " << hash << endl; + + hash = xorHash(key); + cout << "互斥或雜湊值為 " << hash << endl; + + hash = rotHash(key); + cout << "旋轉雜湊值為 " << hash << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_heap/CMakeLists.txt b/zh-hant/codes/cpp/chapter_heap/CMakeLists.txt new file mode 100644 index 000000000..1ac33a44f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_heap/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(heap heap.cpp) +add_executable(my_heap my_heap.cpp) +add_executable(top_k top_k.cpp) diff --git a/zh-hant/codes/cpp/chapter_heap/heap.cpp b/zh-hant/codes/cpp/chapter_heap/heap.cpp new file mode 100644 index 000000000..c02463ffe --- /dev/null +++ b/zh-hant/codes/cpp/chapter_heap/heap.cpp @@ -0,0 +1,66 @@ +/** + * File: heap.cpp + * Created Time: 2023-01-19 + * Author: LoneRanger(836253168@qq.com) + */ + +#include "../utils/common.hpp" + +void testPush(priority_queue &heap, int val) { + heap.push(val); // 元素入堆積 + cout << "\n元素 " << val << " 入堆積後" << endl; + printHeap(heap); +} + +void testPop(priority_queue &heap) { + int val = heap.top(); + heap.pop(); + cout << "\n堆積頂元素 " << val << " 出堆積後" << endl; + printHeap(heap); +} + +/* Driver Code */ +int main() { + /* 初始化堆積 */ + // 初始化小頂堆積 + // priority_queue, greater> minHeap; + // 初始化大頂堆積 + priority_queue, less> maxHeap; + + cout << "\n以下測試樣例為大頂堆積" << endl; + + /* 元素入堆積 */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.top(); + cout << "\n堆積頂元素為 " << peek << endl; + + /* 堆積頂元素出堆積 */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + cout << "\n堆積元素數量為 " << size << endl; + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.empty(); + cout << "\n堆積是否為空 " << isEmpty << endl; + + /* 輸入串列並建堆積 */ + // 時間複雜度為 O(n) ,而非 O(nlogn) + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + cout << "輸入串列並建立小頂堆積後" << endl; + printHeap(minHeap); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_heap/my_heap.cpp b/zh-hant/codes/cpp/chapter_heap/my_heap.cpp new file mode 100644 index 000000000..ad36d5656 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_heap/my_heap.cpp @@ -0,0 +1,155 @@ +/** + * File: my_heap.cpp + * Created Time: 2023-02-04 + * Author: LoneRanger (836253168@qq.com), what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* 大頂堆積 */ +class MaxHeap { + private: + // 使用動態陣列,這樣無須考慮擴容問題 + vector maxHeap; + + /* 獲取左子節點的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + int parent(int i) { + return (i - 1) / 2; // 向下整除 + } + + /* 從節點 i 開始,從底至頂堆積化 */ + void siftUp(int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 交換兩節點 + swap(maxHeap[i], maxHeap[p]); + // 迴圈向上堆積化 + i = p; + } + } + + /* 從節點 i 開始,從頂至底堆積化 */ + void siftDown(int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) + break; + swap(maxHeap[i], maxHeap[ma]); + // 迴圈向下堆積化 + i = ma; + } + } + + public: + /* 建構子,根據輸入串列建堆積 */ + MaxHeap(vector nums) { + // 將串列元素原封不動新增進堆積 + maxHeap = nums; + // 堆積化除葉節點以外的其他所有節點 + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 獲取堆積大小 */ + int size() { + return maxHeap.size(); + } + + /* 判斷堆積是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 訪問堆積頂元素 */ + int peek() { + return maxHeap[0]; + } + + /* 元素入堆積 */ + void push(int val) { + // 新增節點 + maxHeap.push_back(val); + // 從底至頂堆積化 + siftUp(size() - 1); + } + + /* 元素出堆積 */ + void pop() { + // 判空處理 + if (isEmpty()) { + throw out_of_range("堆積為空"); + } + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(maxHeap[0], maxHeap[size() - 1]); + // 刪除節點 + maxHeap.pop_back(); + // 從頂至底堆積化 + siftDown(0); + } + + /* 列印堆積(二元樹)*/ + void print() { + cout << "堆積的陣列表示:"; + printVector(maxHeap); + cout << "堆積的樹狀表示:" << endl; + TreeNode *root = vectorToTree(maxHeap); + printTree(root); + freeMemoryTree(root); + } +}; + +/* Driver Code */ +int main() { + /* 初始化大頂堆積 */ + vector vec{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap maxHeap(vec); + cout << "\n輸入串列並建堆積後" << endl; + maxHeap.print(); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.peek(); + cout << "\n堆積頂元素為 " << peek << endl; + + /* 元素入堆積 */ + int val = 7; + maxHeap.push(val); + cout << "\n元素 " << val << " 入堆積後" << endl; + maxHeap.print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.peek(); + maxHeap.pop(); + cout << "\n堆積頂元素 " << peek << " 出堆積後" << endl; + maxHeap.print(); + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + cout << "\n堆積元素數量為 " << size << endl; + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.isEmpty(); + cout << "\n堆積是否為空 " << isEmpty << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_heap/top_k.cpp b/zh-hant/codes/cpp/chapter_heap/top_k.cpp new file mode 100644 index 000000000..96ff263c1 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_heap/top_k.cpp @@ -0,0 +1,38 @@ +/** + * File: top_k.cpp + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +priority_queue, greater> topKHeap(vector &nums, int k) { + // 初始化小頂堆積 + priority_queue, greater> heap; + // 將陣列的前 k 個元素入堆積 + for (int i = 0; i < k; i++) { + heap.push(nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (int i = k; i < nums.size(); i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > heap.top()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +// Driver Code +int main() { + vector nums = {1, 7, 6, 3, 2}; + int k = 3; + + priority_queue, greater> res = topKHeap(nums, k); + cout << "最大的 " << k << " 個元素為: "; + printHeap(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/CMakeLists.txt b/zh-hant/codes/cpp/chapter_searching/CMakeLists.txt new file mode 100644 index 000000000..60a223d83 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.cpp) +add_executable(binary_search_insertion binary_search_insertion.cpp) +add_executable(binary_search_edge binary_search_edge.cpp) +add_executable(two_sum two_sum.cpp) diff --git a/zh-hant/codes/cpp/chapter_searching/binary_search.cpp b/zh-hant/codes/cpp/chapter_searching/binary_search.cpp new file mode 100644 index 000000000..0c2202036 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/binary_search.cpp @@ -0,0 +1,59 @@ +/** + * File: binary_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分搜尋(雙閉區間) */ +int binarySearch(vector &nums, int target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + int i = 0, j = nums.size() - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + else // 找到目標元素,返回其索引 + return m; + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 二分搜尋(左閉右開區間) */ +int binarySearchLCRO(vector &nums, int target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + int i = 0, j = nums.size(); + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m) 中 + j = m; + else // 找到目標元素,返回其索引 + return m; + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + /* 二分搜尋(雙閉區間) */ + int index = binarySearch(nums, target); + cout << "目標元素 6 的索引 = " << index << endl; + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums, target); + cout << "目標元素 6 的索引 = " << index << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/binary_search_edge.cpp b/zh-hant/codes/cpp/chapter_searching/binary_search_edge.cpp new file mode 100644 index 000000000..e39607abe --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/binary_search_edge.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_edge.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分搜尋插入點(存在重複元素) */ +int binarySearchInsertion(const vector &nums, int target) { + int i = 0, j = nums.size() - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; +} + +/* 二分搜尋最左一個 target */ +int binarySearchLeftEdge(vector &nums, int target) { + // 等價於查詢 target 的插入點 + int i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.size() || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分搜尋最右一個 target */ +int binarySearchRightEdge(vector &nums, int target) { + // 轉化為查詢最左一個 target + 1 + int i = binarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +int main() { + // 包含重複元素的陣列 + vector nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\n陣列 nums = "; + printVector(nums); + + // 二分搜尋左邊界和右邊界 + for (int target : {6, 7}) { + int index = binarySearchLeftEdge(nums, target); + cout << "最左一個元素 " << target << " 的索引為 " << index << endl; + index = binarySearchRightEdge(nums, target); + cout << "最右一個元素 " << target << " 的索引為 " << index << endl; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/binary_search_insertion.cpp b/zh-hant/codes/cpp/chapter_searching/binary_search_insertion.cpp new file mode 100644 index 000000000..e0526125d --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/binary_search_insertion.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_insertion.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分搜尋插入點(無重複元素) */ +int binarySearchInsertionSimple(vector &nums, int target) { + int i = 0, j = nums.size() - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i; +} + +/* 二分搜尋插入點(存在重複元素) */ +int binarySearchInsertion(vector &nums, int target) { + int i = 0, j = nums.size() - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; +} + +/* Driver Code */ +int main() { + // 無重複元素的陣列 + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + cout << "\n陣列 nums = "; + printVector(nums); + // 二分搜尋插入點 + for (int target : {6, 9}) { + int index = binarySearchInsertionSimple(nums, target); + cout << "元素 " << target << " 的插入點的索引為 " << index << endl; + } + + // 包含重複元素的陣列 + nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\n陣列 nums = "; + printVector(nums); + // 二分搜尋插入點 + for (int target : {2, 6, 20}) { + int index = binarySearchInsertion(nums, target); + cout << "元素 " << target << " 的插入點的索引為 " << index << endl; + } + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/hashing_search.cpp b/zh-hant/codes/cpp/chapter_searching/hashing_search.cpp new file mode 100644 index 000000000..0e2b0a8ed --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/hashing_search.cpp @@ -0,0 +1,53 @@ +/** + * File: hashing_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 雜湊查詢(陣列) */ +int hashingSearchArray(unordered_map map, int target) { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + if (map.find(target) == map.end()) + return -1; + return map[target]; +} + +/* 雜湊查詢(鏈結串列) */ +ListNode *hashingSearchLinkedList(unordered_map map, int target) { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 nullptr + if (map.find(target) == map.end()) + return nullptr; + return map[target]; +} + +/* Driver Code */ +int main() { + int target = 3; + + /* 雜湊查詢(陣列) */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + // 初始化雜湊表 + unordered_map map; + for (int i = 0; i < nums.size(); i++) { + map[nums[i]] = i; // key: 元素,value: 索引 + } + int index = hashingSearchArray(map, target); + cout << "目標元素 3 的索引 = " << index << endl; + + /* 雜湊查詢(鏈結串列) */ + ListNode *head = vecToLinkedList(nums); + // 初始化雜湊表 + unordered_map map1; + while (head != nullptr) { + map1[head->val] = head; // key: 節點值,value: 節點 + head = head->next; + } + ListNode *node = hashingSearchLinkedList(map1, target); + cout << "目標節點值 3 的對應節點物件為 " << node << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/linear_search.cpp b/zh-hant/codes/cpp/chapter_searching/linear_search.cpp new file mode 100644 index 000000000..c740ad6f7 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/linear_search.cpp @@ -0,0 +1,49 @@ +/** + * File: linear_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 線性查詢(陣列) */ +int linearSearchArray(vector &nums, int target) { + // 走訪陣列 + for (int i = 0; i < nums.size(); i++) { + // 找到目標元素,返回其索引 + if (nums[i] == target) + return i; + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 線性查詢(鏈結串列) */ +ListNode *linearSearchLinkedList(ListNode *head, int target) { + // 走訪鏈結串列 + while (head != nullptr) { + // 找到目標節點,返回之 + if (head->val == target) + return head; + head = head->next; + } + // 未找到目標節點,返回 nullptr + return nullptr; +} + +/* Driver Code */ +int main() { + int target = 3; + + /* 在陣列中執行線性查詢 */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + int index = linearSearchArray(nums, target); + cout << "目標元素 3 的索引 = " << index << endl; + + /* 在鏈結串列中執行線性查詢 */ + ListNode *head = vecToLinkedList(nums); + ListNode *node = linearSearchLinkedList(head, target); + cout << "目標節點值 3 的對應節點物件為 " << node << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_searching/two_sum.cpp b/zh-hant/codes/cpp/chapter_searching/two_sum.cpp new file mode 100644 index 000000000..a20f03c04 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_searching/two_sum.cpp @@ -0,0 +1,54 @@ +/** + * File: two_sum.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 方法一:暴力列舉 */ +vector twoSumBruteForce(vector &nums, int target) { + int size = nums.size(); + // 兩層迴圈,時間複雜度為 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return {i, j}; + } + } + return {}; +} + +/* 方法二:輔助雜湊表 */ +vector twoSumHashTable(vector &nums, int target) { + int size = nums.size(); + // 輔助雜湊表,空間複雜度為 O(n) + unordered_map dic; + // 單層迴圈,時間複雜度為 O(n) + for (int i = 0; i < size; i++) { + if (dic.find(target - nums[i]) != dic.end()) { + return {dic[target - nums[i]], i}; + } + dic.emplace(nums[i], i); + } + return {}; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + vector nums = {2, 7, 11, 15}; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + vector res = twoSumBruteForce(nums, target); + cout << "方法一 res = "; + printVector(res); + // 方法二 + res = twoSumHashTable(nums, target); + cout << "方法二 res = "; + printVector(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/CMakeLists.txt b/zh-hant/codes/cpp/chapter_sorting/CMakeLists.txt new file mode 100644 index 000000000..e6347cf9f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(selection_sort selection_sort.cpp) +add_executable(bubble_sort bubble_sort.cpp) +add_executable(insertion_sort insertion_sort.cpp) +add_executable(merge_sort merge_sort.cpp) +add_executable(quick_sort quick_sort.cpp) +add_executable(heap_sort heap_sort.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_sorting/bubble_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/bubble_sort.cpp new file mode 100644 index 000000000..d29ed4661 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/bubble_sort.cpp @@ -0,0 +1,56 @@ +/** + * File: bubble_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 泡沫排序 */ +void bubbleSort(vector &nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + // 這裡使用了 std::swap() 函式 + swap(nums[j], nums[j + 1]); + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +void bubbleSortWithFlag(vector &nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + bool flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + // 這裡使用了 std::swap() 函式 + swap(nums[j], nums[j + 1]); + flag = true; // 記錄交換元素 + } + } + if (!flag) + break; // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + bubbleSort(nums); + cout << "泡沫排序完成後 nums = "; + printVector(nums); + + vector nums1 = {4, 1, 3, 1, 5, 2}; + bubbleSortWithFlag(nums1); + cout << "泡沫排序完成後 nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/bucket_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/bucket_sort.cpp new file mode 100644 index 000000000..4595aba3d --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/bucket_sort.cpp @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.cpp + * Created Time: 2023-03-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 桶排序 */ +void bucketSort(vector &nums) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + int k = nums.size() / 2; + vector> buckets(k); + // 1. 將陣列元素分配到各個桶中 + for (float num : nums) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + int i = num * k; + // 將 num 新增進桶 bucket_idx + buckets[i].push_back(num); + } + // 2. 對各個桶執行排序 + for (vector &bucket : buckets) { + // 使用內建排序函式,也可以替換成其他排序演算法 + sort(bucket.begin(), bucket.end()); + } + // 3. 走訪桶合併結果 + int i = 0; + for (vector &bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +int main() { + // 設輸入資料為浮點數,範圍為 [0, 1) + vector nums = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums); + cout << "桶排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/counting_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/counting_sort.cpp new file mode 100644 index 000000000..b2ba9ce01 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/counting_sort.cpp @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cpp + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +void countingSortNaive(vector &nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +void countingSort(vector &nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + int n = nums.size(); + vector res(n); + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + nums = res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSortNaive(nums); + cout << "計數排序(無法排序物件)完成後 nums = "; + printVector(nums); + + vector nums1 = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSort(nums1); + cout << "計數排序完成後 nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/heap_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/heap_sort.cpp new file mode 100644 index 000000000..9f10ccfc8 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/heap_sort.cpp @@ -0,0 +1,54 @@ +/** + * File: heap_sort.cpp + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +void siftDown(vector &nums, int n, int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) { + break; + } + // 交換兩節點 + swap(nums[i], nums[ma]); + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +void heapSort(vector &nums) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (int i = nums.size() / 2 - 1; i >= 0; --i) { + siftDown(nums, nums.size(), i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (int i = nums.size() - 1; i > 0; --i) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(nums[0], nums[i]); + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + heapSort(nums); + cout << "堆積排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/insertion_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/insertion_sort.cpp new file mode 100644 index 000000000..f63706e37 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/insertion_sort.cpp @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 插入排序 */ +void insertionSort(vector &nums) { + // 外迴圈:已排序區間為 [0, i-1] + for (int i = 1; i < nums.size(); i++) { + int base = nums[i], j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = base; // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + insertionSort(nums); + cout << "插入排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/merge_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/merge_sort.cpp new file mode 100644 index 000000000..c0586a3a9 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/merge_sort.cpp @@ -0,0 +1,58 @@ +/** + * File: merge_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 合併左子陣列和右子陣列 */ +void merge(vector &nums, int left, int mid, int right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + vector tmp(right - left + 1); + // 初始化左子陣列和右子陣列的起始索引 + int i = left, j = mid + 1, k = 0; + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for (k = 0; k < tmp.size(); k++) { + nums[left + k] = tmp[k]; + } +} + +/* 合併排序 */ +void mergeSort(vector &nums, int left, int right) { + // 終止條件 + if (left >= right) + return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + int mid = (left + right) / 2; // 計算中點 + mergeSort(nums, left, mid); // 遞迴左子陣列 + mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +int main() { + /* 合併排序 */ + vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; + mergeSort(nums, 0, nums.size() - 1); + cout << "合併排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/quick_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/quick_sort.cpp new file mode 100644 index 000000000..ca7994e2f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/quick_sort.cpp @@ -0,0 +1,166 @@ +/** + * File: quick_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 快速排序類別 */ +class QuickSort { + private: + /* 元素交換 */ + static void swap(vector &nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + static int partition(vector &nums, int left, int right) { + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + public: + /* 快速排序 */ + static void quickSort(vector &nums, int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) + return; + // 哨兵劃分 + int pivot = partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + private: + /* 元素交換 */ + static void swap(vector &nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 選取三個候選元素的中位數 */ + static int medianThree(vector &nums, int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m 在 l 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l 在 m 和 r 之間 + return right; + } + + /* 哨兵劃分(三數取中值) */ + static int partition(vector &nums, int left, int right) { + // 選取三個候選元素的中位數 + int med = medianThree(nums, left, (left + right) / 2, right); + // 將中位數交換至陣列最左端 + swap(nums, left, med); + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + public: + /* 快速排序 */ + static void quickSort(vector &nums, int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) + return; + // 哨兵劃分 + int pivot = partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + private: + /* 元素交換 */ + static void swap(vector &nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + static int partition(vector &nums, int left, int right) { + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + public: + /* 快速排序(尾遞迴最佳化) */ + static void quickSort(vector &nums, int left, int right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + int pivot = partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +}; + +/* Driver Code */ +int main() { + /* 快速排序 */ + vector nums{2, 4, 1, 0, 3, 5}; + QuickSort::quickSort(nums, 0, nums.size() - 1); + cout << "快速排序完成後 nums = "; + printVector(nums); + + /* 快速排序(中位基準數最佳化) */ + vector nums1 = {2, 4, 1, 0, 3, 5}; + QuickSortMedian::quickSort(nums1, 0, nums1.size() - 1); + cout << "快速排序(中位基準數最佳化)完成後 nums = "; + printVector(nums1); + + /* 快速排序(尾遞迴最佳化) */ + vector nums2 = {2, 4, 1, 0, 3, 5}; + QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1); + cout << "快速排序(尾遞迴最佳化)完成後 nums = "; + printVector(nums2); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/radix_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/radix_sort.cpp new file mode 100644 index 000000000..28a41da04 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/radix_sort.cpp @@ -0,0 +1,65 @@ +/** + * File: radix_sort.cpp + * Created Time: 2023-03-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +int digit(int num, int exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +void countingSortDigit(vector &nums, int exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + vector counter(10, 0); + int n = nums.size(); + // 統計 0~9 各數字的出現次數 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + vector res(n, 0); + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d]--; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for (int i = 0; i < n; i++) + nums[i] = res[i]; +} + +/* 基數排序 */ +void radixSort(vector &nums) { + // 獲取陣列的最大元素,用於判斷最大位數 + int m = *max_element(nums.begin(), nums.end()); + // 按照從低位到高位的順序走訪 + for (int exp = 1; exp <= m; exp *= 10) + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +int main() { + // 基數排序 + vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + radixSort(nums); + cout << "基數排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_sorting/selection_sort.cpp b/zh-hant/codes/cpp/chapter_sorting/selection_sort.cpp new file mode 100644 index 000000000..846eeaf0a --- /dev/null +++ b/zh-hant/codes/cpp/chapter_sorting/selection_sort.cpp @@ -0,0 +1,34 @@ +/** + * File: selection_sort.cpp + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 選擇排序 */ +void selectionSort(vector &nums) { + int n = nums.size(); + // 外迴圈:未排序區間為 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 內迴圈:找到未排序區間內的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 記錄最小元素的索引 + } + // 將該最小元素與未排序區間的首個元素交換 + swap(nums[i], nums[k]); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + selectionSort(nums); + + cout << "選擇排序完成後 nums = "; + printVector(nums); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/CMakeLists.txt b/zh-hant/codes/cpp/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 000000000..b55878a17 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(array_deque array_deque.cpp) +add_executable(array_queue array_queue.cpp) +add_executable(array_stack array_stack.cpp) +add_executable(deque deque.cpp) +add_executable(linkedlist_deque linkedlist_deque.cpp) +add_executable(linkedlist_queue linkedlist_queue.cpp) +add_executable(linkedlist_stack linkedlist_stack.cpp) +add_executable(queue queue.cpp) +add_executable(stack stack.cpp) diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/array_deque.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/array_deque.cpp new file mode 100644 index 000000000..061506afe --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/array_deque.cpp @@ -0,0 +1,156 @@ +/** + * File: array_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + private: + vector nums; // 用於儲存雙向佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 雙向佇列長度 + + public: + /* 建構子 */ + ArrayDeque(int capacity) { + nums.resize(capacity); + front = queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + int capacity() { + return nums.size(); + } + + /* 獲取雙向佇列的長度 */ + int size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + bool isEmpty() { + return queSize == 0; + } + + /* 計算環形陣列索引 */ + int index(int i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 佇列首入列 */ + void pushFirst(int num) { + if (queSize == capacity()) { + cout << "雙向佇列已滿" << endl; + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + front = index(front - 1); + // 將 num 新增至佇列首 + nums[front] = num; + queSize++; + } + + /* 佇列尾入列 */ + void pushLast(int num) { + if (queSize == capacity()) { + cout << "雙向佇列已滿" << endl; + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + int rear = index(front + queSize); + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 佇列首出列 */ + int popFirst() { + int num = peekFirst(); + // 佇列首指標向後移動一位 + front = index(front + 1); + queSize--; + return num; + } + + /* 佇列尾出列 */ + int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("雙向佇列為空"); + return nums[front]; + } + + /* 訪問佇列尾元素 */ + int peekLast() { + if (isEmpty()) + throw out_of_range("雙向佇列為空"); + // 計算尾元素索引 + int last = index(front + queSize - 1); + return nums[last]; + } + + /* 返回陣列用於列印 */ + vector toVector() { + // 僅轉換有效長度範圍內的串列元素 + vector res(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化雙向佇列 */ + ArrayDeque *deque = new ArrayDeque(10); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "雙向佇列 deque = "; + printVector(deque->toVector()); + + /* 訪問元素 */ + int peekFirst = deque->peekFirst(); + cout << "佇列首元素 peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "佇列尾元素 peekLast = " << peekLast << endl; + + /* 元素入列 */ + deque->pushLast(4); + cout << "元素 4 佇列尾入列後 deque = "; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "元素 1 佇列首入列後 deque = "; + printVector(deque->toVector()); + + /* 元素出列 */ + int popLast = deque->popLast(); + cout << "佇列尾出列元素 = " << popLast << ",佇列尾出列後 deque = "; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "佇列首出列元素 = " << popFirst << ",佇列首出列後 deque = "; + printVector(deque->toVector()); + + /* 獲取雙向佇列的長度 */ + int size = deque->size(); + cout << "雙向佇列長度 size = " << size << endl; + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque->isEmpty(); + cout << "雙向佇列是否為空 = " << boolalpha << isEmpty << endl; + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/array_queue.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/array_queue.cpp new file mode 100644 index 000000000..0a229c7b9 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/array_queue.cpp @@ -0,0 +1,129 @@ +/** + * File: array_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + private: + int *nums; // 用於儲存佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 佇列長度 + int queCapacity; // 佇列容量 + + public: + ArrayQueue(int capacity) { + // 初始化陣列 + nums = new int[capacity]; + queCapacity = capacity; + front = queSize = 0; + } + + ~ArrayQueue() { + delete[] nums; + } + + /* 獲取佇列的容量 */ + int capacity() { + return queCapacity; + } + + /* 獲取佇列的長度 */ + int size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入列 */ + void push(int num) { + if (queSize == queCapacity) { + cout << "佇列已滿" << endl; + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + int rear = (front + queSize) % queCapacity; + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 出列 */ + int pop() { + int num = peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + front = (front + 1) % queCapacity; + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + int peek() { + if (isEmpty()) + throw out_of_range("佇列為空"); + return nums[front]; + } + + /* 將陣列轉化為 Vector 並返回 */ + vector toVector() { + // 僅轉換有效長度範圍內的串列元素 + vector arr(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + arr[i] = nums[j % queCapacity]; + } + return arr; + } +}; + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + int capacity = 10; + ArrayQueue *queue = new ArrayQueue(capacity); + + /* 元素入列 */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "佇列 queue = "; + printVector(queue->toVector()); + + /* 訪問佇列首元素 */ + int peek = queue->peek(); + cout << "佇列首元素 peek = " << peek << endl; + + /* 元素出列 */ + peek = queue->pop(); + cout << "出列元素 pop = " << peek << ",出列後 queue = "; + printVector(queue->toVector()); + + /* 獲取佇列的長度 */ + int size = queue->size(); + cout << "佇列長度 size = " << size << endl; + + /* 判斷佇列是否為空 */ + bool empty = queue->isEmpty(); + cout << "佇列是否為空 = " << empty << endl; + + /* 測試環形陣列 */ + for (int i = 0; i < 10; i++) { + queue->push(i); + queue->pop(); + cout << "第 " << i << " 輪入列 + 出列後 queue = "; + printVector(queue->toVector()); + } + + // 釋放記憶體 + delete queue; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/array_stack.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/array_stack.cpp new file mode 100644 index 000000000..423bf7011 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/array_stack.cpp @@ -0,0 +1,85 @@ +/** + * File: array_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + private: + vector stack; + + public: + /* 獲取堆疊的長度 */ + int size() { + return stack.size(); + } + + /* 判斷堆疊是否為空 */ + bool isEmpty() { + return stack.size() == 0; + } + + /* 入堆疊 */ + void push(int num) { + stack.push_back(num); + } + + /* 出堆疊 */ + int pop() { + int num = top(); + stack.pop_back(); + return num; + } + + /* 訪問堆疊頂元素 */ + int top() { + if (isEmpty()) + throw out_of_range("堆疊為空"); + return stack.back(); + } + + /* 返回 Vector */ + vector toVector() { + return stack; + } +}; + +/* Driver Code */ +int main() { + /* 初始化堆疊 */ + ArrayStack *stack = new ArrayStack(); + + /* 元素入堆疊 */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "堆疊 stack = "; + printVector(stack->toVector()); + + /* 訪問堆疊頂元素 */ + int top = stack->top(); + cout << "堆疊頂元素 top = " << top << endl; + + /* 元素出堆疊 */ + top = stack->pop(); + cout << "出堆疊元素 pop = " << top << ",出堆疊後 stack = "; + printVector(stack->toVector()); + + /* 獲取堆疊的長度 */ + int size = stack->size(); + cout << "堆疊的長度 size = " << size << endl; + + /* 判斷是否為空 */ + bool empty = stack->isEmpty(); + cout << "堆疊是否為空 = " << empty << endl; + + // 釋放記憶體 + delete stack; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/deque.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/deque.cpp new file mode 100644 index 000000000..dab6cdc9f --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/deque.cpp @@ -0,0 +1,46 @@ +/** + * File: deque.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化雙向佇列 */ + deque deque; + + /* 元素入列 */ + deque.push_back(2); + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); + deque.push_front(1); + cout << "雙向佇列 deque = "; + printDeque(deque); + + /* 訪問元素 */ + int front = deque.front(); + cout << "佇列首元素 front = " << front << endl; + int back = deque.back(); + cout << "佇列尾元素 back = " << back << endl; + + /* 元素出列 */ + deque.pop_front(); + cout << "佇列首出列元素 popFront = " << front << ",佇列首出列後 deque = "; + printDeque(deque); + deque.pop_back(); + cout << "佇列尾出列元素 popLast = " << back << ",佇列尾出列後 deque = "; + printDeque(deque); + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + cout << "雙向佇列長度 size = " << size << endl; + + /* 判斷雙向佇列是否為空 */ + bool empty = deque.empty(); + cout << "雙向佇列是否為空 = " << empty << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp new file mode 100644 index 000000000..1767609eb --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp @@ -0,0 +1,194 @@ +/** + * File: linkedlist_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 雙向鏈結串列節點 */ +struct DoublyListNode { + int val; // 節點值 + DoublyListNode *next; // 後繼節點指標 + DoublyListNode *prev; // 前驅節點指標 + DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) { + } +}; + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + private: + DoublyListNode *front, *rear; // 頭節點 front ,尾節點 rear + int queSize = 0; // 雙向佇列的長度 + + public: + /* 建構子 */ + LinkedListDeque() : front(nullptr), rear(nullptr) { + } + + /* 析構方法 */ + ~LinkedListDeque() { + // 走訪鏈結串列刪除節點,釋放記憶體 + DoublyListNode *pre, *cur = front; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } + } + + /* 獲取雙向佇列的長度 */ + int size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入列操作 */ + void push(int num, bool isFront) { + DoublyListNode *node = new DoublyListNode(num); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (isEmpty()) + front = rear = node; + // 佇列首入列操作 + else if (isFront) { + // 將 node 新增至鏈結串列頭部 + front->prev = node; + node->next = front; + front = node; // 更新頭節點 + // 佇列尾入列操作 + } else { + // 將 node 新增至鏈結串列尾部 + rear->next = node; + node->prev = rear; + rear = node; // 更新尾節點 + } + queSize++; // 更新佇列長度 + } + + /* 佇列首入列 */ + void pushFirst(int num) { + push(num, true); + } + + /* 佇列尾入列 */ + void pushLast(int num) { + push(num, false); + } + + /* 出列操作 */ + int pop(bool isFront) { + if (isEmpty()) + throw out_of_range("佇列為空"); + int val; + // 佇列首出列操作 + if (isFront) { + val = front->val; // 暫存頭節點值 + // 刪除頭節點 + DoublyListNode *fNext = front->next; + if (fNext != nullptr) { + fNext->prev = nullptr; + front->next = nullptr; + } + delete front; + front = fNext; // 更新頭節點 + // 佇列尾出列操作 + } else { + val = rear->val; // 暫存尾節點值 + // 刪除尾節點 + DoublyListNode *rPrev = rear->prev; + if (rPrev != nullptr) { + rPrev->next = nullptr; + rear->prev = nullptr; + } + delete rear; + rear = rPrev; // 更新尾節點 + } + queSize--; // 更新佇列長度 + return val; + } + + /* 佇列首出列 */ + int popFirst() { + return pop(true); + } + + /* 佇列尾出列 */ + int popLast() { + return pop(false); + } + + /* 訪問佇列首元素 */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("雙向佇列為空"); + return front->val; + } + + /* 訪問佇列尾元素 */ + int peekLast() { + if (isEmpty()) + throw out_of_range("雙向佇列為空"); + return rear->val; + } + + /* 返回陣列用於列印 */ + vector toVector() { + DoublyListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化雙向佇列 */ + LinkedListDeque *deque = new LinkedListDeque(); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "雙向佇列 deque = "; + printVector(deque->toVector()); + + /* 訪問元素 */ + int peekFirst = deque->peekFirst(); + cout << "佇列首元素 peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "佇列尾元素 peekLast = " << peekLast << endl; + + /* 元素入列 */ + deque->pushLast(4); + cout << "元素 4 佇列尾入列後 deque ="; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "元素 1 佇列首入列後 deque = "; + printVector(deque->toVector()); + + /* 元素出列 */ + int popLast = deque->popLast(); + cout << "佇列尾出列元素 = " << popLast << ",佇列尾出列後 deque = "; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "佇列首出列元素 = " << popFirst << ",佇列首出列後 deque = "; + printVector(deque->toVector()); + + /* 獲取雙向佇列的長度 */ + int size = deque->size(); + cout << "雙向佇列長度 size = " << size << endl; + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque->isEmpty(); + cout << "雙向佇列是否為空 = " << boolalpha << isEmpty << endl; + + // 釋放記憶體 + delete deque; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp new file mode 100644 index 000000000..152620d66 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp @@ -0,0 +1,120 @@ +/** + * File: linkedlist_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + private: + ListNode *front, *rear; // 頭節點 front ,尾節點 rear + int queSize; + + public: + LinkedListQueue() { + front = nullptr; + rear = nullptr; + queSize = 0; + } + + ~LinkedListQueue() { + // 走訪鏈結串列刪除節點,釋放記憶體 + freeMemoryLinkedList(front); + } + + /* 獲取佇列的長度 */ + int size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + bool isEmpty() { + return queSize == 0; + } + + /* 入列 */ + void push(int num) { + // 在尾節點後新增 num + ListNode *node = new ListNode(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (front == nullptr) { + front = node; + rear = node; + } + // 如果佇列不為空,則將該節點新增到尾節點後 + else { + rear->next = node; + rear = node; + } + queSize++; + } + + /* 出列 */ + int pop() { + int num = peek(); + // 刪除頭節點 + ListNode *tmp = front; + front = front->next; + // 釋放記憶體 + delete tmp; + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + int peek() { + if (size() == 0) + throw out_of_range("佇列為空"); + return front->val; + } + + /* 將鏈結串列轉化為 Vector 並返回 */ + vector toVector() { + ListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + LinkedListQueue *queue = new LinkedListQueue(); + + /* 元素入列 */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "佇列 queue = "; + printVector(queue->toVector()); + + /* 訪問佇列首元素 */ + int peek = queue->peek(); + cout << "佇列首元素 peek = " << peek << endl; + + /* 元素出列 */ + peek = queue->pop(); + cout << "出列元素 pop = " << peek << ",出列後 queue = "; + printVector(queue->toVector()); + + /* 獲取佇列的長度 */ + int size = queue->size(); + cout << "佇列長度 size = " << size << endl; + + /* 判斷佇列是否為空 */ + bool empty = queue->isEmpty(); + cout << "佇列是否為空 = " << empty << endl; + + // 釋放記憶體 + delete queue; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp new file mode 100644 index 000000000..3bf1707c0 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp @@ -0,0 +1,109 @@ +/** + * File: linkedlist_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + private: + ListNode *stackTop; // 將頭節點作為堆疊頂 + int stkSize; // 堆疊的長度 + + public: + LinkedListStack() { + stackTop = nullptr; + stkSize = 0; + } + + ~LinkedListStack() { + // 走訪鏈結串列刪除節點,釋放記憶體 + freeMemoryLinkedList(stackTop); + } + + /* 獲取堆疊的長度 */ + int size() { + return stkSize; + } + + /* 判斷堆疊是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入堆疊 */ + void push(int num) { + ListNode *node = new ListNode(num); + node->next = stackTop; + stackTop = node; + stkSize++; + } + + /* 出堆疊 */ + int pop() { + int num = top(); + ListNode *tmp = stackTop; + stackTop = stackTop->next; + // 釋放記憶體 + delete tmp; + stkSize--; + return num; + } + + /* 訪問堆疊頂元素 */ + int top() { + if (isEmpty()) + throw out_of_range("堆疊為空"); + return stackTop->val; + } + + /* 將 List 轉化為 Array 並返回 */ + vector toVector() { + ListNode *node = stackTop; + vector res(size()); + for (int i = res.size() - 1; i >= 0; i--) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化堆疊 */ + LinkedListStack *stack = new LinkedListStack(); + + /* 元素入堆疊 */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "堆疊 stack = "; + printVector(stack->toVector()); + + /* 訪問堆疊頂元素 */ + int top = stack->top(); + cout << "堆疊頂元素 top = " << top << endl; + + /* 元素出堆疊 */ + top = stack->pop(); + cout << "出堆疊元素 pop = " << top << ",出堆疊後 stack = "; + printVector(stack->toVector()); + + /* 獲取堆疊的長度 */ + int size = stack->size(); + cout << "堆疊的長度 size = " << size << endl; + + /* 判斷是否為空 */ + bool empty = stack->isEmpty(); + cout << "堆疊是否為空 = " << empty << endl; + + // 釋放記憶體 + delete stack; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/queue.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/queue.cpp new file mode 100644 index 000000000..78b306d5e --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/queue.cpp @@ -0,0 +1,41 @@ +/** + * File: queue.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化佇列 */ + queue queue; + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + cout << "佇列 queue = "; + printQueue(queue); + + /* 訪問佇列首元素 */ + int front = queue.front(); + cout << "佇列首元素 front = " << front << endl; + + /* 元素出列 */ + queue.pop(); + cout << "出列元素 front = " << front << ",出列後 queue = "; + printQueue(queue); + + /* 獲取佇列的長度 */ + int size = queue.size(); + cout << "佇列長度 size = " << size << endl; + + /* 判斷佇列是否為空 */ + bool empty = queue.empty(); + cout << "佇列是否為空 = " << empty << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_stack_and_queue/stack.cpp b/zh-hant/codes/cpp/chapter_stack_and_queue/stack.cpp new file mode 100644 index 000000000..e046d5c9b --- /dev/null +++ b/zh-hant/codes/cpp/chapter_stack_and_queue/stack.cpp @@ -0,0 +1,41 @@ +/** + * File: stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化堆疊 */ + stack stack; + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + cout << "堆疊 stack = "; + printStack(stack); + + /* 訪問堆疊頂元素 */ + int top = stack.top(); + cout << "堆疊頂元素 top = " << top << endl; + + /* 元素出堆疊 */ + stack.pop(); // 無返回值 + cout << "出堆疊元素 pop = " << top << ",出堆疊後 stack = "; + printStack(stack); + + /* 獲取堆疊的長度 */ + int size = stack.size(); + cout << "堆疊的長度 size = " << size << endl; + + /* 判斷是否為空 */ + bool empty = stack.empty(); + cout << "堆疊是否為空 = " << empty << endl; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_tree/CMakeLists.txt b/zh-hant/codes/cpp/chapter_tree/CMakeLists.txt new file mode 100644 index 000000000..fa7009bcb --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.cpp) +add_executable(binary_search_tree binary_search_tree.cpp) +add_executable(binary_tree binary_tree.cpp) +add_executable(binary_tree_bfs binary_tree_bfs.cpp) +add_executable(binary_tree_dfs binary_tree_dfs.cpp) +add_executable(array_binary_tree array_binary_tree.cpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/chapter_tree/array_binary_tree.cpp b/zh-hant/codes/cpp/chapter_tree/array_binary_tree.cpp new file mode 100644 index 000000000..d0d77b547 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/array_binary_tree.cpp @@ -0,0 +1,137 @@ +/** + * File: array_binary_tree.cpp + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + public: + /* 建構子 */ + ArrayBinaryTree(vector arr) { + tree = arr; + } + + /* 串列容量 */ + int size() { + return tree.size(); + } + + /* 獲取索引為 i 節點的值 */ + int val(int i) { + // 若索引越界,則返回 INT_MAX ,代表空位 + if (i < 0 || i >= size()) + return INT_MAX; + return tree[i]; + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + int parent(int i) { + return (i - 1) / 2; + } + + /* 層序走訪 */ + vector levelOrder() { + vector res; + // 直接走訪陣列 + for (int i = 0; i < size(); i++) { + if (val(i) != INT_MAX) + res.push_back(val(i)); + } + return res; + } + + /* 前序走訪 */ + vector preOrder() { + vector res; + dfs(0, "pre", res); + return res; + } + + /* 中序走訪 */ + vector inOrder() { + vector res; + dfs(0, "in", res); + return res; + } + + /* 後序走訪 */ + vector postOrder() { + vector res; + dfs(0, "post", res); + return res; + } + + private: + vector tree; + + /* 深度優先走訪 */ + void dfs(int i, string order, vector &res) { + // 若為空位,則返回 + if (val(i) == INT_MAX) + return; + // 前序走訪 + if (order == "pre") + res.push_back(val(i)); + dfs(left(i), order, res); + // 中序走訪 + if (order == "in") + res.push_back(val(i)); + dfs(right(i), order, res); + // 後序走訪 + if (order == "post") + res.push_back(val(i)); + } +}; + +/* Driver Code */ +int main() { + // 初始化二元樹 + // 使用 INT_MAX 代表空位 nullptr + vector arr = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + TreeNode *root = vectorToTree(arr); + cout << "\n初始化二元樹\n"; + cout << "二元樹的陣列表示:\n"; + printVector(arr); + cout << "二元樹的鏈結串列表示:\n"; + printTree(root); + + // 陣列表示下的二元樹類別 + ArrayBinaryTree abt(arr); + + // 訪問節點 + int i = 1; + int l = abt.left(i), r = abt.right(i), p = abt.parent(i); + cout << "\n當前節點的索引為 " << i << ",值為 " << abt.val(i) << "\n"; + cout << "其左子節點的索引為 " << l << ",值為 " << (l != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; + cout << "其右子節點的索引為 " << r << ",值為 " << (r != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; + cout << "其父節點的索引為 " << p << ",值為 " << (p != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; + + // 走訪樹 + vector res = abt.levelOrder(); + cout << "\n層序走訪為: "; + printVector(res); + res = abt.preOrder(); + cout << "前序走訪為: "; + printVector(res); + res = abt.inOrder(); + cout << "中序走訪為: "; + printVector(res); + res = abt.postOrder(); + cout << "後序走訪為: "; + printVector(res); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_tree/avl_tree.cpp b/zh-hant/codes/cpp/chapter_tree/avl_tree.cpp new file mode 100644 index 000000000..305a76220 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/avl_tree.cpp @@ -0,0 +1,233 @@ +/** + * File: avl_tree.cpp + * Created Time: 2023-02-03 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* AVL 樹 */ +class AVLTree { + public: + TreeNode *root; // 根節點 + private: + /* 更新節點高度 */ + void updateHeight(TreeNode *node) { + // 節點高度等於最高子樹高度 + 1 + node->height = max(height(node->left), height(node->right)) + 1; + } + + /* 右旋操作 */ + TreeNode *rightRotate(TreeNode *node) { + TreeNode *child = node->left; + TreeNode *grandChild = child->right; + // 以 child 為原點,將 node 向右旋轉 + child->right = node; + node->left = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + TreeNode *leftRotate(TreeNode *node) { + TreeNode *child = node->right; + TreeNode *grandChild = child->left; + // 以 child 為原點,將 node 向左旋轉 + child->left = node; + node->right = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + TreeNode *rotate(TreeNode *node) { + // 獲取節點 node 的平衡因子 + int _balanceFactor = balanceFactor(node); + // 左偏樹 + if (_balanceFactor > 1) { + if (balanceFactor(node->left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋後右旋 + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // 右偏樹 + if (_balanceFactor < -1) { + if (balanceFactor(node->right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋後左旋 + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 遞迴插入節點(輔助方法) */ + TreeNode *insertHelper(TreeNode *node, int val) { + if (node == nullptr) + return new TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node->val) + node->left = insertHelper(node->left, val); + else if (val > node->val) + node->right = insertHelper(node->right, val); + else + return node; // 重複節點不插入,直接返回 + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 遞迴刪除節點(輔助方法) */ + TreeNode *removeHelper(TreeNode *node, int val) { + if (node == nullptr) + return nullptr; + /* 1. 查詢節點並刪除 */ + if (val < node->val) + node->left = removeHelper(node->left, val); + else if (val > node->val) + node->right = removeHelper(node->right, val); + else { + if (node->left == nullptr || node->right == nullptr) { + TreeNode *child = node->left != nullptr ? node->left : node->right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == nullptr) { + delete node; + return nullptr; + } + // 子節點數量 = 1 ,直接刪除 node + else { + delete node; + node = child; + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + TreeNode *temp = node->right; + while (temp->left != nullptr) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + public: + /* 獲取節點高度 */ + int height(TreeNode *node) { + // 空節點高度為 -1 ,葉節點高度為 0 + return node == nullptr ? -1 : node->height; + } + + /* 獲取平衡因子 */ + int balanceFactor(TreeNode *node) { + // 空節點平衡因子為 0 + if (node == nullptr) + return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node->left) - height(node->right); + } + + /* 插入節點 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 刪除節點 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 查詢節點 */ + TreeNode *search(int val) { + TreeNode *cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != nullptr) { + // 目標節點在 cur 的右子樹中 + if (cur->val < val) + cur = cur->right; + // 目標節點在 cur 的左子樹中 + else if (cur->val > val) + cur = cur->left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } + + /*建構子*/ + AVLTree() : root(nullptr) { + } + + /*析構方法*/ + ~AVLTree() { + freeMemoryTree(root); + } +}; + +void testInsert(AVLTree &tree, int val) { + tree.insert(val); + cout << "\n插入節點 " << val << " 後,AVL 樹為" << endl; + printTree(tree.root); +} + +void testRemove(AVLTree &tree, int val) { + tree.remove(val); + cout << "\n刪除節點 " << val << " 後,AVL 樹為" << endl; + printTree(tree.root); +} + +/* Driver Code */ +int main() { + /* 初始化空 AVL 樹 */ + AVLTree avlTree; + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 插入重複節點 */ + testInsert(avlTree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(avlTree, 8); // 刪除度為 0 的節點 + testRemove(avlTree, 5); // 刪除度為 1 的節點 + testRemove(avlTree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + TreeNode *node = avlTree.search(7); + cout << "\n查詢到的節點物件為 " << node << ",節點值 = " << node->val << endl; +} diff --git a/zh-hant/codes/cpp/chapter_tree/binary_search_tree.cpp b/zh-hant/codes/cpp/chapter_tree/binary_search_tree.cpp new file mode 100644 index 000000000..a86e78a44 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/binary_search_tree.cpp @@ -0,0 +1,170 @@ +/** + * File: binary_search_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二元搜尋樹 */ +class BinarySearchTree { + private: + TreeNode *root; + + public: + /* 建構子 */ + BinarySearchTree() { + // 初始化空樹 + root = nullptr; + } + + /* 析構方法 */ + ~BinarySearchTree() { + freeMemoryTree(root); + } + + /* 獲取二元樹根節點 */ + TreeNode *getRoot() { + return root; + } + + /* 查詢節點 */ + TreeNode *search(int num) { + TreeNode *cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != nullptr) { + // 目標節點在 cur 的右子樹中 + if (cur->val < num) + cur = cur->right; + // 目標節點在 cur 的左子樹中 + else if (cur->val > num) + cur = cur->left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + void insert(int num) { + // 若樹為空,則初始化根節點 + if (root == nullptr) { + root = new TreeNode(num); + return; + } + TreeNode *cur = root, *pre = nullptr; + // 迴圈查詢,越過葉節點後跳出 + while (cur != nullptr) { + // 找到重複節點,直接返回 + if (cur->val == num) + return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur->val < num) + cur = cur->right; + // 插入位置在 cur 的左子樹中 + else + cur = cur->left; + } + // 插入節點 + TreeNode *node = new TreeNode(num); + if (pre->val < num) + pre->right = node; + else + pre->left = node; + } + + /* 刪除節點 */ + void remove(int num) { + // 若樹為空,直接提前返回 + if (root == nullptr) + return; + TreeNode *cur = root, *pre = nullptr; + // 迴圈查詢,越過葉節點後跳出 + while (cur != nullptr) { + // 找到待刪除節點,跳出迴圈 + if (cur->val == num) + break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur->val < num) + cur = cur->right; + // 待刪除節點在 cur 的左子樹中 + else + cur = cur->left; + } + // 若無待刪除節點,則直接返回 + if (cur == nullptr) + return; + // 子節點數量 = 0 or 1 + if (cur->left == nullptr || cur->right == nullptr) { + // 當子節點數量 = 0 / 1 時, child = nullptr / 該子節點 + TreeNode *child = cur->left != nullptr ? cur->left : cur->right; + // 刪除節點 cur + if (cur != root) { + if (pre->left == cur) + pre->left = child; + else + pre->right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + root = child; + } + // 釋放記憶體 + delete cur; + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + TreeNode *tmp = cur->right; + while (tmp->left != nullptr) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // 遞迴刪除節點 tmp + remove(tmp->val); + // 用 tmp 覆蓋 cur + cur->val = tmpVal; + } + } +}; + +/* Driver Code */ +int main() { + /* 初始化二元搜尋樹 */ + BinarySearchTree *bst = new BinarySearchTree(); + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + for (int num : nums) { + bst->insert(num); + } + cout << endl << "初始化的二元樹為\n" << endl; + printTree(bst->getRoot()); + + /* 查詢節點 */ + TreeNode *node = bst->search(7); + cout << endl << "查詢到的節點物件為 " << node << ",節點值 = " << node->val << endl; + + /* 插入節點 */ + bst->insert(16); + cout << endl << "插入節點 16 後,二元樹為\n" << endl; + printTree(bst->getRoot()); + + /* 刪除節點 */ + bst->remove(1); + cout << endl << "刪除節點 1 後,二元樹為\n" << endl; + printTree(bst->getRoot()); + bst->remove(2); + cout << endl << "刪除節點 2 後,二元樹為\n" << endl; + printTree(bst->getRoot()); + bst->remove(4); + cout << endl << "刪除節點 4 後,二元樹為\n" << endl; + printTree(bst->getRoot()); + + // 釋放記憶體 + delete bst; + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_tree/binary_tree.cpp b/zh-hant/codes/cpp/chapter_tree/binary_tree.cpp new file mode 100644 index 000000000..56b7c8ec6 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/binary_tree.cpp @@ -0,0 +1,43 @@ +/** + * File: binary_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 初始化節點 + TreeNode *n1 = new TreeNode(1); + TreeNode *n2 = new TreeNode(2); + TreeNode *n3 = new TreeNode(3); + TreeNode *n4 = new TreeNode(4); + TreeNode *n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + cout << endl << "初始化二元樹\n" << endl; + printTree(n1); + + /* 插入與刪除節點 */ + TreeNode *P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1->left = P; + P->left = n2; + cout << endl << "插入節點 P 後\n" << endl; + printTree(n1); + // 刪除節點 P + n1->left = n2; + delete P; // 釋放記憶體 + cout << endl << "刪除節點 P 後\n" << endl; + printTree(n1); + + // 釋放記憶體 + freeMemoryTree(n1); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_tree/binary_tree_bfs.cpp b/zh-hant/codes/cpp/chapter_tree/binary_tree_bfs.cpp new file mode 100644 index 000000000..a22509114 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/binary_tree_bfs.cpp @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 層序走訪 */ +vector levelOrder(TreeNode *root) { + // 初始化佇列,加入根節點 + queue queue; + queue.push(root); + // 初始化一個串列,用於儲存走訪序列 + vector vec; + while (!queue.empty()) { + TreeNode *node = queue.front(); + queue.pop(); // 隊列出隊 + vec.push_back(node->val); // 儲存節點值 + if (node->left != nullptr) + queue.push(node->left); // 左子節點入列 + if (node->right != nullptr) + queue.push(node->right); // 右子節點入列 + } + return vec; +} + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); + cout << endl << "初始化二元樹\n" << endl; + printTree(root); + + /* 層序走訪 */ + vector vec = levelOrder(root); + cout << endl << "層序走訪的節點列印序列 = "; + printVector(vec); + + return 0; +} diff --git a/zh-hant/codes/cpp/chapter_tree/binary_tree_dfs.cpp b/zh-hant/codes/cpp/chapter_tree/binary_tree_dfs.cpp new file mode 100644 index 000000000..e4ebf7a44 --- /dev/null +++ b/zh-hant/codes/cpp/chapter_tree/binary_tree_dfs.cpp @@ -0,0 +1,69 @@ +/** + * File: binary_tree_dfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +// 初始化串列,用於儲存走訪序列 +vector vec; + +/* 前序走訪 */ +void preOrder(TreeNode *root) { + if (root == nullptr) + return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + vec.push_back(root->val); + preOrder(root->left); + preOrder(root->right); +} + +/* 中序走訪 */ +void inOrder(TreeNode *root) { + if (root == nullptr) + return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root->left); + vec.push_back(root->val); + inOrder(root->right); +} + +/* 後序走訪 */ +void postOrder(TreeNode *root) { + if (root == nullptr) + return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root->left); + postOrder(root->right); + vec.push_back(root->val); +} + +/* Driver Code */ +int main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); + cout << endl << "初始化二元樹\n" << endl; + printTree(root); + + /* 前序走訪 */ + vec.clear(); + preOrder(root); + cout << endl << "前序走訪的節點列印序列 = "; + printVector(vec); + + /* 中序走訪 */ + vec.clear(); + inOrder(root); + cout << endl << "中序走訪的節點列印序列 = "; + printVector(vec); + + /* 後序走訪 */ + vec.clear(); + postOrder(root); + cout << endl << "後序走訪的節點列印序列 = "; + printVector(vec); + + return 0; +} diff --git a/zh-hant/codes/cpp/utils/CMakeLists.txt b/zh-hant/codes/cpp/utils/CMakeLists.txt new file mode 100644 index 000000000..775a55869 --- /dev/null +++ b/zh-hant/codes/cpp/utils/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(utils + common.hpp print_utils.hpp + list_node.hpp tree_node.hpp + vertex.hpp) \ No newline at end of file diff --git a/zh-hant/codes/cpp/utils/common.hpp b/zh-hant/codes/cpp/utils/common.hpp new file mode 100644 index 000000000..c72dabd88 --- /dev/null +++ b/zh-hant/codes/cpp/utils/common.hpp @@ -0,0 +1,28 @@ +/** + * File: common.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.hpp" +#include "print_utils.hpp" +#include "tree_node.hpp" +#include "vertex.hpp" + +using namespace std; diff --git a/zh-hant/codes/cpp/utils/list_node.hpp b/zh-hant/codes/cpp/utils/list_node.hpp new file mode 100644 index 000000000..e3b7e2389 --- /dev/null +++ b/zh-hant/codes/cpp/utils/list_node.hpp @@ -0,0 +1,42 @@ +/** + * File: list_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* 鏈結串列節點 */ +struct ListNode { + int val; + ListNode *next; + ListNode(int x) : val(x), next(nullptr) { + } +}; + +/* 將串列反序列化為鏈結串列 */ +ListNode *vecToLinkedList(vector list) { + ListNode *dum = new ListNode(0); + ListNode *head = dum; + for (int val : list) { + head->next = new ListNode(val); + head = head->next; + } + return dum->next; +} + +/* 釋放分配給鏈結串列的記憶體空間 */ +void freeMemoryLinkedList(ListNode *cur) { + // 釋放記憶體 + ListNode *pre; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } +} diff --git a/zh-hant/codes/cpp/utils/print_utils.hpp b/zh-hant/codes/cpp/utils/print_utils.hpp new file mode 100644 index 000000000..a3bc4a6b3 --- /dev/null +++ b/zh-hant/codes/cpp/utils/print_utils.hpp @@ -0,0 +1,228 @@ +/** + * File: print_utils.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com), LoneRanger(836253168@qq.com) + */ + +#pragma once + +#include "list_node.hpp" +#include "tree_node.hpp" +#include +#include +#include +#include + +/* Find an element in a vector */ +template int vecFind(const vector &vec, T ele) { + int j = INT_MAX; + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == ele) { + j = i; + } + } + return j; +} + +/* Concatenate a vector with a delim */ +template string strJoin(const string &delim, const T &vec) { + ostringstream s; + for (const auto &i : vec) { + if (&i != &vec[0]) { + s << delim; + } + s << i; + } + return s.str(); +} + +/* Repeat a string for n times */ +string strRepeat(string str, int n) { + ostringstream os; + for (int i = 0; i < n; i++) + os << str; + return os.str(); +} + +/* 列印陣列 */ +template void printArray(T *arr, int n) { + cout << "["; + for (int i = 0; i < n - 1; i++) { + cout << arr[i] << ", "; + } + if (n >= 1) + cout << arr[n - 1] << "]" << endl; + else + cout << "]" << endl; +} + +/* Get the Vector String object */ +template string getVectorString(vector &list) { + return "[" + strJoin(", ", list) + "]"; +} + +/* 列印串列 */ +template void printVector(vector list) { + cout << getVectorString(list) << '\n'; +} + +/* 列印矩陣 */ +template void printVectorMatrix(vector> &matrix) { + cout << "[" << '\n'; + for (vector &list : matrix) + cout << " " + getVectorString(list) + "," << '\n'; + cout << "]" << '\n'; +} + +/* 列印鏈結串列 */ +void printLinkedList(ListNode *head) { + vector list; + while (head != nullptr) { + list.push_back(head->val); + head = head->next; + } + + cout << strJoin(" -> ", list) << '\n'; +} + +struct Trunk { + Trunk *prev; + string str; + Trunk(Trunk *prev, string str) { + this->prev = prev; + this->str = str; + } +}; + +void showTrunks(Trunk *p) { + if (p == nullptr) { + return; + } + + showTrunks(p->prev); + cout << p->str; +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode *root, Trunk *prev, bool isRight) { + if (root == nullptr) { + return; + } + + string prev_str = " "; + Trunk trunk(prev, prev_str); + + printTree(root->right, &trunk, true); + + if (!prev) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev->str = prev_str; + } + + showTrunks(&trunk); + cout << " " << root->val << endl; + + if (prev) { + prev->str = prev_str; + } + trunk.str = " |"; + + printTree(root->left, &trunk, false); +} + +/* 列印二元樹 */ +void printTree(TreeNode *root) { + printTree(root, nullptr, false); +} + +/* 列印堆疊 */ +template void printStack(stack stk) { + // Reverse the input stack + stack tmp; + while (!stk.empty()) { + tmp.push(stk.top()); + stk.pop(); + } + // Generate the string to print + ostringstream s; + bool flag = true; + while (!tmp.empty()) { + if (flag) { + s << tmp.top(); + flag = false; + } else + s << ", " << tmp.top(); + tmp.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* 列印佇列 */ +template void printQueue(queue queue) { + // Generate the string to print + ostringstream s; + bool flag = true; + while (!queue.empty()) { + if (flag) { + s << queue.front(); + flag = false; + } else + s << ", " << queue.front(); + queue.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* 列印雙向佇列 */ +template void printDeque(deque deque) { + // Generate the string to print + ostringstream s; + bool flag = true; + while (!deque.empty()) { + if (flag) { + s << deque.front(); + flag = false; + } else + s << ", " << deque.front(); + deque.pop_front(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* 列印雜湊表 */ +// 定義模板參數 TKey 和 TValue ,用於指定鍵值對的型別 +template void printHashMap(unordered_map map) { + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << '\n'; + } +} + +/* Expose the underlying storage of the priority_queue container */ +template S &Container(priority_queue &pq) { + struct HackedQueue : private priority_queue { + static S &Container(priority_queue &pq) { + return pq.*&HackedQueue::c; + } + }; + return HackedQueue::Container(pq); +} + +/* 列印堆積(優先佇列) */ +template void printHeap(priority_queue &heap) { + vector vec = Container(heap); + cout << "堆積的陣列表示:"; + printVector(vec); + cout << "堆積的樹狀表示:" << endl; + TreeNode *root = vectorToTree(vec); + printTree(root); + freeMemoryTree(root); +} diff --git a/zh-hant/codes/cpp/utils/tree_node.hpp b/zh-hant/codes/cpp/utils/tree_node.hpp new file mode 100644 index 000000000..266c5a183 --- /dev/null +++ b/zh-hant/codes/cpp/utils/tree_node.hpp @@ -0,0 +1,84 @@ +/** + * File: tree_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* 二元樹節點結構體 */ +struct TreeNode { + int val{}; + int height = 0; + TreeNode *parent{}; + TreeNode *left{}; + TreeNode *right{}; + TreeNode() = default; + explicit TreeNode(int x, TreeNode *parent = nullptr) : val(x), parent(parent) { + } +}; + +// 序列化編碼規則請參考: +// 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 + +/* 將串列反序列化為二元樹:遞迴 */ +TreeNode *vectorToTreeDFS(vector &arr, int i) { + if (i < 0 || i >= arr.size() || arr[i] == INT_MAX) { + return nullptr; + } + TreeNode *root = new TreeNode(arr[i]); + root->left = vectorToTreeDFS(arr, 2 * i + 1); + root->right = vectorToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* 將串列反序列化為二元樹 */ +TreeNode *vectorToTree(vector arr) { + return vectorToTreeDFS(arr, 0); +} + +/* 將二元樹序列化為串列:遞迴 */ +void treeToVecorDFS(TreeNode *root, int i, vector &res) { + if (root == nullptr) + return; + while (i >= res.size()) { + res.push_back(INT_MAX); + } + res[i] = root->val; + treeToVecorDFS(root->left, 2 * i + 1, res); + treeToVecorDFS(root->right, 2 * i + 2, res); +} + +/* 將二元樹序列化為串列 */ +vector treeToVecor(TreeNode *root) { + vector res; + treeToVecorDFS(root, 0, res); + return res; +} + +/* 釋放二元樹記憶體 */ +void freeMemoryTree(TreeNode *root) { + if (root == nullptr) + return; + freeMemoryTree(root->left); + freeMemoryTree(root->right); + delete root; +} diff --git a/zh-hant/codes/cpp/utils/vertex.hpp b/zh-hant/codes/cpp/utils/vertex.hpp new file mode 100644 index 000000000..9b78144d9 --- /dev/null +++ b/zh-hant/codes/cpp/utils/vertex.hpp @@ -0,0 +1,36 @@ +/** + * File: vertex.hpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include + +using namespace std; + +/* 頂點類別 */ +struct Vertex { + int val; + Vertex(int x) : val(x) { + } +}; + +/* 輸入值串列 vals ,返回頂點串列 vets */ +vector valsToVets(vector vals) { + vector vets; + for (int val : vals) { + vets.push_back(new Vertex(val)); + } + return vets; +} + +/* 輸入頂點串列 vets ,返回值串列 vals */ +vector vetsToVals(vector vets) { + vector vals; + for (Vertex *vet : vets) { + vals.push_back(vet->val); + } + return vals; +} diff --git a/zh-hant/codes/csharp/.editorconfig b/zh-hant/codes/csharp/.editorconfig new file mode 100644 index 000000000..0a2c1df58 --- /dev/null +++ b/zh-hant/codes/csharp/.editorconfig @@ -0,0 +1,88 @@ +# CSharp formatting rules +[*.cs] +csharp_new_line_before_open_brace = none +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent + +# CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language. +dotnet_diagnostic.CS8981.severity = silent + +# IDE1006: Naming Styles +dotnet_diagnostic.IDE1006.severity = silent + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = silent + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = silent + +# IDE0044: Add readonly modifier +dotnet_diagnostic.IDE0044.severity = silent diff --git a/zh-hant/codes/csharp/.gitignore b/zh-hant/codes/csharp/.gitignore new file mode 100644 index 000000000..a4b66a94a --- /dev/null +++ b/zh-hant/codes/csharp/.gitignore @@ -0,0 +1,5 @@ +.idea/ +.vs/ +obj/ +.Debug +bin/ diff --git a/zh-hant/codes/csharp/GlobalUsing.cs b/zh-hant/codes/csharp/GlobalUsing.cs new file mode 100644 index 000000000..402066ff4 --- /dev/null +++ b/zh-hant/codes/csharp/GlobalUsing.cs @@ -0,0 +1,3 @@ +global using NUnit.Framework; +global using hello_algo.utils; +global using System.Text; \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_array_and_linkedlist/array.cs b/zh-hant/codes/csharp/chapter_array_and_linkedlist/array.cs new file mode 100644 index 000000000..328aa58c9 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_array_and_linkedlist/array.cs @@ -0,0 +1,107 @@ +// File: array.cs +// Created Time: 2022-12-14 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.chapter_array_and_linkedlist; + +public class array { + /* 隨機訪問元素 */ + int RandomAccess(int[] nums) { + Random random = new(); + // 在區間 [0, nums.Length) 中隨機抽取一個數字 + int randomIndex = random.Next(nums.Length); + // 獲取並返回隨機元素 + int randomNum = nums[randomIndex]; + return randomNum; + } + + /* 擴展陣列長度 */ + int[] Extend(int[] nums, int enlarge) { + // 初始化一個擴展長度後的陣列 + int[] res = new int[nums.Length + enlarge]; + // 將原陣列中的所有元素複製到新陣列 + for (int i = 0; i < nums.Length; i++) { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + return res; + } + + /* 在陣列的索引 index 處插入元素 num */ + void Insert(int[] nums, int num, int index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (int i = nums.Length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; + } + + /* 刪除索引 index 處的元素 */ + void Remove(int[] nums, int index) { + // 把索引 index 之後的所有元素向前移動一位 + for (int i = index; i < nums.Length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + + /* 走訪陣列 */ + void Traverse(int[] nums) { + int count = 0; + // 透過索引走訪陣列 + for (int i = 0; i < nums.Length; i++) { + count += nums[i]; + } + // 直接走訪陣列元素 + foreach (int num in nums) { + count += num; + } + } + + /* 在陣列中查詢指定元素 */ + int Find(int[] nums, int target) { + for (int i = 0; i < nums.Length; i++) { + if (nums[i] == target) + return i; + } + return -1; + } + + /* 輔助函式,陣列轉字串 */ + string ToString(int[] nums) { + return string.Join(",", nums); + } + + + [Test] + public void Test() { + // 初始化陣列 + int[] arr = new int[5]; + Console.WriteLine("陣列 arr = " + ToString(arr)); + int[] nums = [1, 3, 2, 5, 4]; + Console.WriteLine("陣列 nums = " + ToString(nums)); + + // 隨機訪問 + int randomNum = RandomAccess(nums); + Console.WriteLine("在 nums 中獲取隨機元素 " + randomNum); + + // 長度擴展 + nums = Extend(nums, 3); + Console.WriteLine("將陣列長度擴展至 8 ,得到 nums = " + ToString(nums)); + + // 插入元素 + Insert(nums, 6, 3); + Console.WriteLine("在索引 3 處插入數字 6 ,得到 nums = " + ToString(nums)); + + // 刪除元素 + Remove(nums, 2); + Console.WriteLine("刪除索引 2 處的元素,得到 nums = " + ToString(nums)); + + // 走訪陣列 + Traverse(nums); + + // 查詢元素 + int index = Find(nums, 3); + Console.WriteLine("在 nums 中查詢元素 3 ,得到索引 = " + index); + } +} diff --git a/zh-hant/codes/csharp/chapter_array_and_linkedlist/linked_list.cs b/zh-hant/codes/csharp/chapter_array_and_linkedlist/linked_list.cs new file mode 100644 index 000000000..f4fb0965c --- /dev/null +++ b/zh-hant/codes/csharp/chapter_array_and_linkedlist/linked_list.cs @@ -0,0 +1,80 @@ +// File: linked_list.cs +// Created Time: 2022-12-16 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.chapter_array_and_linkedlist; + +public class linked_list { + /* 在鏈結串列的節點 n0 之後插入節點 P */ + void Insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; + } + + /* 刪除鏈結串列的節點 n0 之後的首個節點 */ + void Remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode? n1 = P.next; + n0.next = n1; + } + + /* 訪問鏈結串列中索引為 index 的節點 */ + ListNode? Access(ListNode? head, int index) { + for (int i = 0; i < index; i++) { + if (head == null) + return null; + head = head.next; + } + return head; + } + + /* 在鏈結串列中查詢值為 target 的首個節點 */ + int Find(ListNode? head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) + return index; + head = head.next; + index++; + } + return -1; + } + + + [Test] + public void Test() { + // 初始化鏈結串列 + // 初始化各個節點 + ListNode n0 = new(1); + ListNode n1 = new(3); + ListNode n2 = new(2); + ListNode n3 = new(5); + ListNode n4 = new(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + Console.WriteLine($"初始化的鏈結串列為{n0}"); + + // 插入節點 + Insert(n0, new ListNode(0)); + Console.WriteLine($"插入節點後的鏈結串列為{n0}"); + + // 刪除節點 + Remove(n0); + Console.WriteLine($"刪除節點後的鏈結串列為{n0}"); + + // 訪問節點 + ListNode? node = Access(n0, 3); + Console.WriteLine($"鏈結串列中索引 3 處的節點的值 = {node?.val}"); + + // 查詢節點 + int index = Find(n0, 2); + Console.WriteLine($"鏈結串列中值為 2 的節點的索引 = {index}"); + } +} diff --git a/zh-hant/codes/csharp/chapter_array_and_linkedlist/list.cs b/zh-hant/codes/csharp/chapter_array_and_linkedlist/list.cs new file mode 100644 index 000000000..71bbc2050 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_array_and_linkedlist/list.cs @@ -0,0 +1,66 @@ +/** + * File: list.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_array_and_linkedlist; + +public class list { + [Test] + public void Test() { + + /* 初始化串列 */ + int[] numbers = [1, 3, 2, 5, 4]; + List nums = [.. numbers]; + Console.WriteLine("串列 nums = " + string.Join(",", nums)); + + /* 訪問元素 */ + int num = nums[1]; + Console.WriteLine("訪問索引 1 處的元素,得到 num = " + num); + + /* 更新元素 */ + nums[1] = 0; + Console.WriteLine("將索引 1 處的元素更新為 0 ,得到 nums = " + string.Join(",", nums)); + + /* 清空串列 */ + nums.Clear(); + Console.WriteLine("清空串列後 nums = " + string.Join(",", nums)); + + /* 在尾部新增元素 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + Console.WriteLine("新增元素後 nums = " + string.Join(",", nums)); + + /* 在中間插入元素 */ + nums.Insert(3, 6); + Console.WriteLine("在索引 3 處插入數字 6 ,得到 nums = " + string.Join(",", nums)); + + /* 刪除元素 */ + nums.RemoveAt(3); + Console.WriteLine("刪除索引 3 處的元素,得到 nums = " + string.Join(",", nums)); + + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.Count; i++) { + count += nums[i]; + } + /* 直接走訪串列元素 */ + count = 0; + foreach (int x in nums) { + count += x; + } + + /* 拼接兩個串列 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.AddRange(nums1); + Console.WriteLine("將串列 nums1 拼接到 nums 之後,得到 nums = " + string.Join(",", nums)); + + /* 排序串列 */ + nums.Sort(); // 排序後,串列元素從小到大排列 + Console.WriteLine("排序串列後 nums = " + string.Join(",", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_array_and_linkedlist/my_list.cs b/zh-hant/codes/csharp/chapter_array_and_linkedlist/my_list.cs new file mode 100644 index 000000000..55cb27ccb --- /dev/null +++ b/zh-hant/codes/csharp/chapter_array_and_linkedlist/my_list.cs @@ -0,0 +1,144 @@ +/** + * File: my_list.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_array_and_linkedlist; + +/* 串列類別 */ +class MyList { + private int[] arr; // 陣列(儲存串列元素) + private int arrCapacity = 10; // 串列容量 + private int arrSize = 0; // 串列長度(當前元素數量) + private readonly int extendRatio = 2; // 每次串列擴容的倍數 + + /* 建構子 */ + public MyList() { + arr = new int[arrCapacity]; + } + + /* 獲取串列長度(當前元素數量)*/ + public int Size() { + return arrSize; + } + + /* 獲取串列容量 */ + public int Capacity() { + return arrCapacity; + } + + /* 訪問元素 */ + public int Get(int index) { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + return arr[index]; + } + + /* 更新元素 */ + public void Set(int index, int num) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + arr[index] = num; + } + + /* 在尾部新增元素 */ + public void Add(int num) { + // 元素數量超出容量時,觸發擴容機制 + if (arrSize == arrCapacity) + ExtendCapacity(); + arr[arrSize] = num; + // 更新元素數量 + arrSize++; + } + + /* 在中間插入元素 */ + public void Insert(int index, int num) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + // 元素數量超出容量時,觸發擴容機制 + if (arrSize == arrCapacity) + ExtendCapacity(); + // 將索引 index 以及之後的元素都向後移動一位 + for (int j = arrSize - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // 更新元素數量 + arrSize++; + } + + /* 刪除元素 */ + public int Remove(int index) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("索引越界"); + int num = arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for (int j = index; j < arrSize - 1; j++) { + arr[j] = arr[j + 1]; + } + // 更新元素數量 + arrSize--; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + public void ExtendCapacity() { + // 新建一個長度為 arrCapacity * extendRatio 的陣列,並將原陣列複製到新陣列 + Array.Resize(ref arr, arrCapacity * extendRatio); + // 更新串列容量 + arrCapacity = arr.Length; + } + + /* 將串列轉換為陣列 */ + public int[] ToArray() { + // 僅轉換有效長度範圍內的串列元素 + int[] arr = new int[arrSize]; + for (int i = 0; i < arrSize; i++) { + arr[i] = Get(i); + } + return arr; + } +} + +public class my_list { + [Test] + public void Test() { + /* 初始化串列 */ + MyList nums = new(); + /* 在尾部新增元素 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + Console.WriteLine("串列 nums = " + string.Join(",", nums.ToArray()) + + " ,容量 = " + nums.Capacity() + " ,長度 = " + nums.Size()); + + /* 在中間插入元素 */ + nums.Insert(3, 6); + Console.WriteLine("在索引 3 處插入數字 6 ,得到 nums = " + string.Join(",", nums.ToArray())); + + /* 刪除元素 */ + nums.Remove(3); + Console.WriteLine("刪除索引 3 處的元素,得到 nums = " + string.Join(",", nums.ToArray())); + + /* 訪問元素 */ + int num = nums.Get(1); + Console.WriteLine("訪問索引 1 處的元素,得到 num = " + num); + + /* 更新元素 */ + nums.Set(1, 0); + Console.WriteLine("將索引 1 處的元素更新為 0 ,得到 nums = " + string.Join(",", nums.ToArray())); + + /* 測試擴容機制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.Add(i); + } + Console.WriteLine("擴容後的串列 nums = " + string.Join(",", nums.ToArray()) + + " ,容量 = " + nums.Capacity() + " ,長度 = " + nums.Size()); + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/n_queens.cs b/zh-hant/codes/csharp/chapter_backtracking/n_queens.cs new file mode 100644 index 000000000..611203588 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/n_queens.cs @@ -0,0 +1,76 @@ +/** + * File: n_queens.cs + * Created Time: 2023-05-04 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class n_queens { + /* 回溯演算法:n 皇后 */ + void Backtrack(int row, int n, List> state, List>> res, + bool[] cols, bool[] diags1, bool[] diags2) { + // 當放置完所有行時,記錄解 + if (row == n) { + List> copyState = []; + foreach (List sRow in state) { + copyState.Add(new List(sRow)); + } + res.Add(copyState); + return; + } + // 走訪所有列 + for (int col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + Backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } + } + + /* 求解 n 皇后 */ + List>> NQueens(int n) { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + List> state = []; + for (int i = 0; i < n; i++) { + List row = []; + for (int j = 0; j < n; j++) { + row.Add("#"); + } + state.Add(row); + } + bool[] cols = new bool[n]; // 記錄列是否有皇后 + bool[] diags1 = new bool[2 * n - 1]; // 記錄主對角線上是否有皇后 + bool[] diags2 = new bool[2 * n - 1]; // 記錄次對角線上是否有皇后 + List>> res = []; + + Backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + + [Test] + public void Test() { + int n = 4; + List>> res = NQueens(n); + + Console.WriteLine("輸入棋盤長寬為 " + n); + Console.WriteLine("皇后放置方案共有 " + res.Count + " 種"); + foreach (List> state in res) { + Console.WriteLine("--------------------"); + foreach (List row in state) { + PrintUtil.PrintList(row); + } + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/permutations_i.cs b/zh-hant/codes/csharp/chapter_backtracking/permutations_i.cs new file mode 100644 index 000000000..b61fc0c95 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/permutations_i.cs @@ -0,0 +1,53 @@ +/** + * File: permutations_i.cs + * Created Time: 2023-04-24 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class permutations_i { + /* 回溯演算法:全排列 I */ + void Backtrack(List state, int[] choices, bool[] selected, List> res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.Add(choice); + // 進行下一輪選擇 + Backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* 全排列 I */ + List> PermutationsI(int[] nums) { + List> res = []; + Backtrack([], nums, new bool[nums.Length], res); + return res; + } + + [Test] + public void Test() { + int[] nums = [1, 2, 3]; + + List> res = PermutationsI(nums); + + Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums)); + Console.WriteLine("所有排列 res = "); + foreach (List permutation in res) { + PrintUtil.PrintList(permutation); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/permutations_ii.cs b/zh-hant/codes/csharp/chapter_backtracking/permutations_ii.cs new file mode 100644 index 000000000..dd286058f --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/permutations_ii.cs @@ -0,0 +1,55 @@ +/** + * File: permutations_ii.cs + * Created Time: 2023-04-24 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class permutations_ii { + /* 回溯演算法:全排列 II */ + void Backtrack(List state, int[] choices, bool[] selected, List> res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // 走訪所有選擇 + HashSet duplicated = []; + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.Contains(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.Add(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.Add(choice); + // 進行下一輪選擇 + Backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* 全排列 II */ + List> PermutationsII(int[] nums) { + List> res = []; + Backtrack([], nums, new bool[nums.Length], res); + return res; + } + + [Test] + public void Test() { + int[] nums = [1, 2, 2]; + + List> res = PermutationsII(nums); + + Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums)); + Console.WriteLine("所有排列 res = "); + foreach (List permutation in res) { + PrintUtil.PrintList(permutation); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs new file mode 100644 index 000000000..938a00b0d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs @@ -0,0 +1,37 @@ +/** + * File: preorder_traversal_i_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_i_compact { + List res = []; + + /* 前序走訪:例題一 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 記錄解 + res.Add(root); + } + PreOrder(root.left); + PreOrder(root.right); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹"); + PrintUtil.PrintTree(root); + + // 前序走訪 + PreOrder(root); + + Console.WriteLine("\n輸出所有值為 7 的節點"); + PrintUtil.PrintList(res.Select(p => p.val).ToList()); + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs new file mode 100644 index 000000000..145593f22 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_ii_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_ii_compact { + List path = []; + List> res = []; + + /* 前序走訪:例題二 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + // 嘗試 + path.Add(root); + if (root.val == 7) { + // 記錄解 + res.Add(new List(path)); + } + PreOrder(root.left); + PreOrder(root.right); + // 回退 + path.RemoveAt(path.Count - 1); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹"); + PrintUtil.PrintTree(root); + + // 前序走訪 + PreOrder(root); + + Console.WriteLine("\n輸出所有根節點到節點 7 的路徑"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs new file mode 100644 index 000000000..7aa13837d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs @@ -0,0 +1,45 @@ +/** + * File: preorder_traversal_iii_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_iii_compact { + List path = []; + List> res = []; + + /* 前序走訪:例題三 */ + void PreOrder(TreeNode? root) { + // 剪枝 + if (root == null || root.val == 3) { + return; + } + // 嘗試 + path.Add(root); + if (root.val == 7) { + // 記錄解 + res.Add(new List(path)); + } + PreOrder(root.left); + PreOrder(root.right); + // 回退 + path.RemoveAt(path.Count - 1); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹"); + PrintUtil.PrintTree(root); + + // 前序走訪 + PreOrder(root); + + Console.WriteLine("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs new file mode 100644 index 000000000..3d8c1a57b --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs @@ -0,0 +1,72 @@ +/** + * File: preorder_traversal_iii_template.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_iii_template { + /* 判斷當前狀態是否為解 */ + bool IsSolution(List state) { + return state.Count != 0 && state[^1].val == 7; + } + + /* 記錄解 */ + void RecordSolution(List state, List> res) { + res.Add(new List(state)); + } + + /* 判斷在當前狀態下,該選擇是否合法 */ + bool IsValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* 更新狀態 */ + void MakeChoice(List state, TreeNode choice) { + state.Add(choice); + } + + /* 恢復狀態 */ + void UndoChoice(List state, TreeNode choice) { + state.RemoveAt(state.Count - 1); + } + + /* 回溯演算法:例題三 */ + void Backtrack(List state, List choices, List> res) { + // 檢查是否為解 + if (IsSolution(state)) { + // 記錄解 + RecordSolution(state, res); + } + // 走訪所有選擇 + foreach (TreeNode choice in choices) { + // 剪枝:檢查選擇是否合法 + if (IsValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + MakeChoice(state, choice); + // 進行下一輪選擇 + Backtrack(state, [choice.left!, choice.right!], res); + // 回退:撤銷選擇,恢復到之前的狀態 + UndoChoice(state, choice); + } + } + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹"); + PrintUtil.PrintTree(root); + + // 回溯演算法 + List> res = []; + List choices = [root!]; + Backtrack([], choices, res); + + Console.WriteLine("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i.cs b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i.cs new file mode 100644 index 000000000..817994662 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i.cs @@ -0,0 +1,55 @@ +/** +* File: subset_sum_i.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_i { + /* 回溯演算法:子集和 I */ + void Backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.Add(new List(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (int i = start; i < choices.Length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.Add(choices[i]); + // 進行下一輪選擇 + Backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 I */ + List> SubsetSumI(int[] nums, int target) { + List state = []; // 狀態(子集) + Array.Sort(nums); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = []; // 結果串列(子集串列) + Backtrack(state, target, nums, start, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [3, 4, 5]; + int target = 9; + List> res = SubsetSumI(nums, target); + Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("所有和等於 " + target + " 的子集 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs new file mode 100644 index 000000000..cf93538db --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs @@ -0,0 +1,53 @@ +/** +* File: subset_sum_i_naive.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_i_naive { + /* 回溯演算法:子集和 I */ + void Backtrack(List state, int target, int total, int[] choices, List> res) { + // 子集和等於 target 時,記錄解 + if (total == target) { + res.Add(new List(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.Length; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.Add(choices[i]); + // 進行下一輪選擇 + Backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 I(包含重複子集) */ + List> SubsetSumINaive(int[] nums, int target) { + List state = []; // 狀態(子集) + int total = 0; // 子集和 + List> res = []; // 結果串列(子集串列) + Backtrack(state, target, total, nums, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [3, 4, 5]; + int target = 9; + List> res = SubsetSumINaive(nums, target); + Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("所有和等於 " + target + " 的子集 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + Console.WriteLine("請注意,該方法輸出的結果包含重複集合"); + } +} diff --git a/zh-hant/codes/csharp/chapter_backtracking/subset_sum_ii.cs b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_ii.cs new file mode 100644 index 000000000..bd442b66b --- /dev/null +++ b/zh-hant/codes/csharp/chapter_backtracking/subset_sum_ii.cs @@ -0,0 +1,60 @@ +/** +* File: subset_sum_ii.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_ii { + /* 回溯演算法:子集和 II */ + void Backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.Add(new List(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (int i = start; i < choices.Length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.Add(choices[i]); + // 進行下一輪選擇 + Backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.RemoveAt(state.Count - 1); + } + } + + /* 求解子集和 II */ + List> SubsetSumII(int[] nums, int target) { + List state = []; // 狀態(子集) + Array.Sort(nums); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = []; // 結果串列(子集串列) + Backtrack(state, target, nums, start, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [4, 4, 5]; + int target = 9; + List> res = SubsetSumII(nums, target); + Console.WriteLine("輸入陣列 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("所有和等於 " + target + " 的子集 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_computational_complexity/iteration.cs b/zh-hant/codes/csharp/chapter_computational_complexity/iteration.cs new file mode 100644 index 000000000..6065cd266 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_computational_complexity/iteration.cs @@ -0,0 +1,77 @@ +/** +* File: iteration.cs +* Created Time: 2023-08-28 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_computational_complexity; + +public class iteration { + /* for 迴圈 */ + int ForLoop(int n) { + int res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } + + /* while 迴圈 */ + int WhileLoop(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i += 1; // 更新條件變數 + } + return res; + } + + /* while 迴圈(兩次更新) */ + int WhileLoopII(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i += 1; + i *= 2; + } + return res; + } + + /* 雙層 for 迴圈 */ + string NestedForLoop(int n) { + StringBuilder res = new(); + // 迴圈 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.Append($"({i}, {j}), "); + } + } + return res.ToString(); + } + + /* Driver Code */ + [Test] + public void Test() { + int n = 5; + int res; + + res = ForLoop(n); + Console.WriteLine("\nfor 迴圈的求和結果 res = " + res); + + res = WhileLoop(n); + Console.WriteLine("\nwhile 迴圈的求和結果 res = " + res); + + res = WhileLoopII(n); + Console.WriteLine("\nwhile 迴圈(兩次更新)求和結果 res = " + res); + + string resStr = NestedForLoop(n); + Console.WriteLine("\n雙層 for 迴圈的走訪結果 " + resStr); + } +} diff --git a/zh-hant/codes/csharp/chapter_computational_complexity/recursion.cs b/zh-hant/codes/csharp/chapter_computational_complexity/recursion.cs new file mode 100644 index 000000000..159b7aae8 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_computational_complexity/recursion.cs @@ -0,0 +1,78 @@ +/** +* File: recursion.cs +* Created Time: 2023-08-28 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_computational_complexity; + +public class recursion { + /* 遞迴 */ + int Recur(int n) { + // 終止條件 + if (n == 1) + return 1; + // 遞:遞迴呼叫 + int res = Recur(n - 1); + // 迴:返回結果 + return n + res; + } + + /* 使用迭代模擬遞迴 */ + int ForLoopRecur(int n) { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + Stack stack = new(); + int res = 0; + // 遞:遞迴呼叫 + for (int i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.Push(i); + } + // 迴:返回結果 + while (stack.Count > 0) { + // 透過“出堆疊操作”模擬“迴” + res += stack.Pop(); + } + // res = 1+2+3+...+n + return res; + } + + /* 尾遞迴 */ + int TailRecur(int n, int res) { + // 終止條件 + if (n == 0) + return res; + // 尾遞迴呼叫 + return TailRecur(n - 1, res + n); + } + + /* 費波那契數列:遞迴 */ + int Fib(int n) { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + int res = Fib(n - 1) + Fib(n - 2); + // 返回結果 f(n) + return res; + } + + /* Driver Code */ + [Test] + public void Test() { + int n = 5; + int res; + + res = Recur(n); + Console.WriteLine("\n遞迴函式的求和結果 res = " + res); + + res = ForLoopRecur(n); + Console.WriteLine("\n使用迭代模擬遞迴求和結果 res = " + res); + + res = TailRecur(n, 0); + Console.WriteLine("\n尾遞迴函式的求和結果 res = " + res); + + res = Fib(n); + Console.WriteLine("\n費波那契數列的第 " + n + " 項為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_computational_complexity/space_complexity.cs b/zh-hant/codes/csharp/chapter_computational_complexity/space_complexity.cs new file mode 100644 index 000000000..e9b065ff8 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_computational_complexity/space_complexity.cs @@ -0,0 +1,104 @@ +/** + * File: space_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class space_complexity { + /* 函式 */ + int Function() { + // 執行某些操作 + return 0; + } + + /* 常數階 */ + void Constant(int n) { + // 常數、變數、物件佔用 O(1) 空間 + int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new(0); + // 迴圈中的變數佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + Function(); + } + } + + /* 線性階 */ + void Linear(int n) { + // 長度為 n 的陣列佔用 O(n) 空間 + int[] nums = new int[n]; + // 長度為 n 的串列佔用 O(n) 空間 + List nodes = []; + for (int i = 0; i < n; i++) { + nodes.Add(new ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + Dictionary map = []; + for (int i = 0; i < n; i++) { + map.Add(i, i.ToString()); + } + } + + /* 線性階(遞迴實現) */ + void LinearRecur(int n) { + Console.WriteLine("遞迴 n = " + n); + if (n == 1) return; + LinearRecur(n - 1); + } + + /* 平方階 */ + void Quadratic(int n) { + // 矩陣佔用 O(n^2) 空間 + int[,] numMatrix = new int[n, n]; + // 二維串列佔用 O(n^2) 空間 + List> numList = []; + for (int i = 0; i < n; i++) { + List tmp = []; + for (int j = 0; j < n; j++) { + tmp.Add(0); + } + numList.Add(tmp); + } + } + + /* 平方階(遞迴實現) */ + int QuadraticRecur(int n) { + if (n <= 0) return 0; + int[] nums = new int[n]; + Console.WriteLine("遞迴 n = " + n + " 中的 nums 長度 = " + nums.Length); + return QuadraticRecur(n - 1); + } + + /* 指數階(建立滿二元樹) */ + TreeNode? BuildTree(int n) { + if (n == 0) return null; + TreeNode root = new(0) { + left = BuildTree(n - 1), + right = BuildTree(n - 1) + }; + return root; + } + + [Test] + public void Test() { + int n = 5; + // 常數階 + Constant(n); + // 線性階 + Linear(n); + LinearRecur(n); + // 平方階 + Quadratic(n); + QuadraticRecur(n); + // 指數階 + TreeNode? root = BuildTree(n); + PrintUtil.PrintTree(root); + } +} diff --git a/zh-hant/codes/csharp/chapter_computational_complexity/time_complexity.cs b/zh-hant/codes/csharp/chapter_computational_complexity/time_complexity.cs new file mode 100644 index 000000000..5fd3021e0 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_computational_complexity/time_complexity.cs @@ -0,0 +1,195 @@ +/** + * File: time_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class time_complexity { + void Algorithm(int n) { + int a = 1; // +0(技巧 1) + a += n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + Console.WriteLine(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + Console.WriteLine(0); + } + } + } + + // 演算法 A 時間複雜度:常數階 + void AlgorithmA(int n) { + Console.WriteLine(0); + } + + // 演算法 B 時間複雜度:線性階 + void AlgorithmB(int n) { + for (int i = 0; i < n; i++) { + Console.WriteLine(0); + } + } + + // 演算法 C 時間複雜度:常數階 + void AlgorithmC(int n) { + for (int i = 0; i < 1000000; i++) { + Console.WriteLine(0); + } + } + + /* 常數階 */ + int Constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; + } + + /* 線性階 */ + int Linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } + + /* 線性階(走訪陣列) */ + int ArrayTraversal(int[] nums) { + int count = 0; + // 迴圈次數與陣列長度成正比 + foreach (int num in nums) { + count++; + } + return count; + } + + /* 平方階 */ + int Quadratic(int n) { + int count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; + } + + /* 平方階(泡沫排序) */ + int BubbleSort(int[] nums) { + int count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; + } + + /* 指數階(迴圈實現) */ + int Exponential(int n) { + int count = 0, bas = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < bas; j++) { + count++; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; + } + + /* 指數階(遞迴實現) */ + int ExpRecur(int n) { + if (n == 1) return 1; + return ExpRecur(n - 1) + ExpRecur(n - 1) + 1; + } + + /* 對數階(迴圈實現) */ + int Logarithmic(int n) { + int count = 0; + while (n > 1) { + n /= 2; + count++; + } + return count; + } + + /* 對數階(遞迴實現) */ + int LogRecur(int n) { + if (n <= 1) return 0; + return LogRecur(n / 2) + 1; + } + + /* 線性對數階 */ + int LinearLogRecur(int n) { + if (n <= 1) return 1; + int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; + } + + /* 階乘階(遞迴實現) */ + int FactorialRecur(int n) { + if (n == 0) return 1; + int count = 0; + // 從 1 個分裂出 n 個 + for (int i = 0; i < n; i++) { + count += FactorialRecur(n - 1); + } + return count; + } + + [Test] + public void Test() { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + int n = 8; + Console.WriteLine("輸入資料大小 n = " + n); + + int count = Constant(n); + Console.WriteLine("常數階的操作數量 = " + count); + + count = Linear(n); + Console.WriteLine("線性階的操作數量 = " + count); + count = ArrayTraversal(new int[n]); + Console.WriteLine("線性階(走訪陣列)的操作數量 = " + count); + + count = Quadratic(n); + Console.WriteLine("平方階的操作數量 = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = BubbleSort(nums); + Console.WriteLine("平方階(泡沫排序)的操作數量 = " + count); + + count = Exponential(n); + Console.WriteLine("指數階(迴圈實現)的操作數量 = " + count); + count = ExpRecur(n); + Console.WriteLine("指數階(遞迴實現)的操作數量 = " + count); + + count = Logarithmic(n); + Console.WriteLine("對數階(迴圈實現)的操作數量 = " + count); + count = LogRecur(n); + Console.WriteLine("對數階(遞迴實現)的操作數量 = " + count); + + count = LinearLogRecur(n); + Console.WriteLine("線性對數階(遞迴實現)的操作數量 = " + count); + + count = FactorialRecur(n); + Console.WriteLine("階乘階(遞迴實現)的操作數量 = " + count); + } +} diff --git a/zh-hant/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs b/zh-hant/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs new file mode 100644 index 000000000..5359ae3f8 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs @@ -0,0 +1,49 @@ +/** + * File: worst_best_time_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class worst_best_time_complexity { + /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ + int[] RandomNumbers(int n) { + int[] nums = new int[n]; + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + + // 隨機打亂陣列元素 + for (int i = 0; i < nums.Length; i++) { + int index = new Random().Next(i, nums.Length); + (nums[i], nums[index]) = (nums[index], nums[i]); + } + return nums; + } + + /* 查詢陣列 nums 中數字 1 所在索引 */ + int FindOne(int[] nums) { + for (int i = 0; i < nums.Length; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) + return i; + } + return -1; + } + + + /* Driver Code */ + [Test] + public void Test() { + for (int i = 0; i < 10; i++) { + int n = 100; + int[] nums = RandomNumbers(n); + int index = FindOne(nums); + Console.WriteLine("\n陣列 [ 1, 2, ..., n ] 被打亂後 = " + string.Join(",", nums)); + Console.WriteLine("數字 1 的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs b/zh-hant/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs new file mode 100644 index 000000000..5832ee668 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs @@ -0,0 +1,46 @@ +/** +* File: binary_search_recur.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class binary_search_recur { + /* 二分搜尋:問題 f(i, j) */ + int DFS(int[] nums, int target, int i, int j) { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + return DFS(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + return DFS(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } + } + + /* 二分搜尋 */ + int BinarySearch(int[] nums, int target) { + int n = nums.Length; + // 求解問題 f(0, n-1) + return DFS(nums, target, 0, n - 1); + } + + [Test] + public void Test() { + int target = 6; + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分搜尋(雙閉區間) + int index = BinarySearch(nums, target); + Console.WriteLine("目標元素 6 的索引 = " + index); + } +} diff --git a/zh-hant/codes/csharp/chapter_divide_and_conquer/build_tree.cs b/zh-hant/codes/csharp/chapter_divide_and_conquer/build_tree.cs new file mode 100644 index 000000000..8c07ec787 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_divide_and_conquer/build_tree.cs @@ -0,0 +1,49 @@ +/** +* File: build_tree.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class build_tree { + /* 構建二元樹:分治 */ + TreeNode? DFS(int[] preorder, Dictionary inorderMap, int i, int l, int r) { + // 子樹區間為空時終止 + if (r - l < 0) + return null; + // 初始化根節點 + TreeNode root = new(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + int m = inorderMap[preorder[i]]; + // 子問題:構建左子樹 + root.left = DFS(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; + } + + /* 構建二元樹 */ + TreeNode? BuildTree(int[] preorder, int[] inorder) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + Dictionary inorderMap = []; + for (int i = 0; i < inorder.Length; i++) { + inorderMap.TryAdd(inorder[i], i); + } + TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1); + return root; + } + + [Test] + public void Test() { + int[] preorder = [3, 9, 2, 1, 7]; + int[] inorder = [9, 3, 1, 2, 7]; + Console.WriteLine("前序走訪 = " + string.Join(", ", preorder)); + Console.WriteLine("中序走訪 = " + string.Join(", ", inorder)); + + TreeNode? root = BuildTree(preorder, inorder); + Console.WriteLine("構建的二元樹為:"); + PrintUtil.PrintTree(root); + } +} diff --git a/zh-hant/codes/csharp/chapter_divide_and_conquer/hanota.cs b/zh-hant/codes/csharp/chapter_divide_and_conquer/hanota.cs new file mode 100644 index 000000000..404f7dde0 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_divide_and_conquer/hanota.cs @@ -0,0 +1,59 @@ +/** +* File: hanota.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class hanota { + /* 移動一個圓盤 */ + void Move(List src, List tar) { + // 從 src 頂部拿出一個圓盤 + int pan = src[^1]; + src.RemoveAt(src.Count - 1); + // 將圓盤放入 tar 頂部 + tar.Add(pan); + } + + /* 求解河內塔問題 f(i) */ + void DFS(int i, List src, List buf, List tar) { + // 若 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); + } + + /* 求解河內塔問題 */ + void SolveHanota(List A, List B, List C) { + int n = A.Count; + // 將 A 頂部 n 個圓盤藉助 B 移到 C + DFS(n, A, B, C); + } + + [Test] + public void Test() { + // 串列尾部是柱子頂部 + List A = [5, 4, 3, 2, 1]; + List B = []; + List C = []; + Console.WriteLine("初始狀態下:"); + Console.WriteLine("A = " + string.Join(", ", A)); + Console.WriteLine("B = " + string.Join(", ", B)); + Console.WriteLine("C = " + string.Join(", ", C)); + + SolveHanota(A, B, C); + + Console.WriteLine("圓盤移動完成後:"); + Console.WriteLine("A = " + string.Join(", ", A)); + Console.WriteLine("B = " + string.Join(", ", B)); + Console.WriteLine("C = " + string.Join(", ", C)); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs new file mode 100644 index 000000000..bb7f384dd --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs @@ -0,0 +1,41 @@ +/** +* File: climbing_stairs_backtrack.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_backtrack { + /* 回溯 */ + void Backtrack(List choices, int state, int n, List res) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) + res[0]++; + // 走訪所有選擇 + foreach (int choice in choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) + continue; + // 嘗試:做出選擇,更新狀態 + Backtrack(choices, state + choice, n, res); + // 回退 + } + } + + /* 爬樓梯:回溯 */ + int ClimbingStairsBacktrack(int n) { + List choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 + int state = 0; // 從第 0 階開始爬 + List res = [0]; // 使用 res[0] 記錄方案數量 + Backtrack(choices, state, n, res); + return res[0]; + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsBacktrack(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs new file mode 100644 index 000000000..170735b82 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs @@ -0,0 +1,36 @@ +/** +* File: climbing_stairs_constraint_dp.cs +* Created Time: 2023-07-03 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* 帶約束爬樓梯:動態規劃 */ + int ClimbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + int[,] dp = new int[n + 1, 3]; + // 初始狀態:預設最小子問題的解 + dp[1, 1] = 1; + dp[1, 2] = 0; + dp[2, 1] = 0; + dp[2, 2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + 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]; + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsConstraintDP(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs new file mode 100644 index 000000000..cdd0e8d07 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs @@ -0,0 +1,31 @@ +/** +* File: climbing_stairs_dfs.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* 搜尋 */ + int DFS(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = DFS(i - 1) + DFS(i - 2); + return count; + } + + /* 爬樓梯:搜尋 */ + int ClimbingStairsDFS(int n) { + return DFS(n); + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsDFS(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs new file mode 100644 index 000000000..28d7864a3 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs @@ -0,0 +1,39 @@ +/** +* File: climbing_stairs_dfs_mem.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dfs_mem { + /* 記憶化搜尋 */ + int DFS(int i, int[] mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = DFS(i - 1, mem) + DFS(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; + } + + /* 爬樓梯:記憶化搜尋 */ + int ClimbingStairsDFSMem(int n) { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + int[] mem = new int[n + 1]; + Array.Fill(mem, -1); + return DFS(n, mem); + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsDFSMem(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs new file mode 100644 index 000000000..84b62668e --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs @@ -0,0 +1,49 @@ +/** +* File: climbing_stairs_dp.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* 爬樓梯:動態規劃 */ + int ClimbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用於儲存子問題的解 + int[] dp = new int[n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + /* 爬樓梯:空間最佳化後的動態規劃 */ + int ClimbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; + } + + [Test] + public void Test() { + int n = 9; + + int res = ClimbingStairsDP(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + + res = ClimbingStairsDPComp(n); + Console.WriteLine($"爬 {n} 階樓梯共有 {res} 種方案"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change.cs new file mode 100644 index 000000000..724ac57b2 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change.cs @@ -0,0 +1,71 @@ +/** +* File: coin_change.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class coin_change { + /* 零錢兌換:動態規劃 */ + int CoinChangeDP(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // 初始化 dp 表 + int[,] dp = new int[n + 1, amt + 1]; + // 狀態轉移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0, a] = MAX; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i, a] = dp[i - 1, a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); + } + } + } + return dp[n, amt] != MAX ? dp[n, amt] : -1; + } + + /* 零錢兌換:空間最佳化後的動態規劃 */ + int CoinChangeDPComp(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + Array.Fill(dp, MAX); + dp[0] = 0; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + + [Test] + public void Test() { + int[] coins = [1, 2, 5]; + int amt = 4; + + // 動態規劃 + int res = CoinChangeDP(coins, amt); + Console.WriteLine("湊到目標金額所需的最少硬幣數量為 " + res); + + // 空間最佳化後的動態規劃 + res = CoinChangeDPComp(coins, amt); + Console.WriteLine("湊到目標金額所需的最少硬幣數量為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs new file mode 100644 index 000000000..e25453fb0 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs @@ -0,0 +1,68 @@ +/** +* File: coin_change_ii.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class coin_change_ii { + /* 零錢兌換 II:動態規劃 */ + int CoinChangeIIDP(int[] coins, int amt) { + int n = coins.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, amt + 1]; + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i, 0] = 1; + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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]; + } + + /* 零錢兌換 II:空間最佳化後的動態規劃 */ + int CoinChangeIIDPComp(int[] coins, int amt) { + int n = coins.Length; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + dp[0] = 1; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + [Test] + public void Test() { + int[] coins = [1, 2, 5]; + int amt = 5; + + // 動態規劃 + int res = CoinChangeIIDP(coins, amt); + Console.WriteLine("湊出目標金額的硬幣組合數量為 " + res); + + // 空間最佳化後的動態規劃 + res = CoinChangeIIDPComp(coins, amt); + Console.WriteLine("湊出目標金額的硬幣組合數量為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/edit_distance.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/edit_distance.cs new file mode 100644 index 000000000..59f4bd05e --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/edit_distance.cs @@ -0,0 +1,141 @@ +/** +* File: edit_distance.cs +* Created Time: 2023-07-14 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class edit_distance { + /* 編輯距離:暴力搜尋 */ + int EditDistanceDFS(string s, string t, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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 EditDistanceDFS(s, t, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = EditDistanceDFS(s, t, i, j - 1); + int delete = EditDistanceDFS(s, t, i - 1, j); + int replace = EditDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return Math.Min(Math.Min(insert, delete), replace) + 1; + } + + /* 編輯距離:記憶化搜尋 */ + int EditDistanceDFSMem(string s, string t, int[][] mem, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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 EditDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = EditDistanceDFSMem(s, t, mem, i, j - 1); + int delete = EditDistanceDFSMem(s, t, mem, i - 1, j); + int replace = EditDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1; + return mem[i][j]; + } + + /* 編輯距離:動態規劃 */ + int EditDistanceDP(string s, string t) { + int n = s.Length, m = t.Length; + int[,] dp = new int[n + 1, m + 1]; + // 狀態轉移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i, 0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0, j] = j; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i, j] = dp[i - 1, j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; + } + } + } + return dp[n, m]; + } + + /* 編輯距離:空間最佳化後的動態規劃 */ + int EditDistanceDPComp(string s, string t) { + int n = s.Length, m = t.Length; + int[] dp = new int[m + 1]; + // 狀態轉移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (int i = 1; i <= n; i++) { + // 狀態轉移:首列 + int leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; + } + + [Test] + public void Test() { + string s = "bag"; + string t = "pack"; + int n = s.Length, m = t.Length; + + // 暴力搜尋 + int res = EditDistanceDFS(s, t, n, m); + Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 記憶化搜尋 + int[][] mem = new int[n + 1][]; + for (int i = 0; i <= n; i++) { + mem[i] = new int[m + 1]; + Array.Fill(mem[i], -1); + } + + res = EditDistanceDFSMem(s, t, mem, n, m); + Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 動態規劃 + res = EditDistanceDP(s, t); + Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 空間最佳化後的動態規劃 + res = EditDistanceDPComp(s, t); + Console.WriteLine("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/knapsack.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/knapsack.cs new file mode 100644 index 000000000..c3cf2d710 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/knapsack.cs @@ -0,0 +1,118 @@ +/** +* File: knapsack.cs +* Created Time: 2023-07-07 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class knapsack { + /* 0-1 背包:暴力搜尋 */ + int KnapsackDFS(int[] weight, int[] val, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (weight[i - 1] > c) { + return KnapsackDFS(weight, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = KnapsackDFS(weight, val, i - 1, c); + int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return Math.Max(no, yes); + } + + /* 0-1 背包:記憶化搜尋 */ + int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (weight[i - 1] > c) { + return KnapsackDFSMem(weight, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = KnapsackDFSMem(weight, val, mem, i - 1, c); + int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = Math.Max(no, yes); + return mem[i][c]; + } + + /* 0-1 背包:動態規劃 */ + int KnapsackDP(int[] weight, int[] val, int cap) { + int n = weight.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (weight[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i, c] = dp[i - 1, c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); + } + } + } + return dp[n, cap]; + } + + /* 0-1 背包:空間最佳化後的動態規劃 */ + int KnapsackDPComp(int[] weight, int[] val, int cap) { + int n = weight.Length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + // 倒序走訪 + for (int c = cap; c > 0; c--) { + if (weight[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + [Test] + public void Test() { + int[] weight = [10, 20, 30, 40, 50]; + int[] val = [50, 120, 150, 210, 240]; + int cap = 50; + int n = weight.Length; + + // 暴力搜尋 + int res = KnapsackDFS(weight, val, n, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + + // 記憶化搜尋 + int[][] mem = new int[n + 1][]; + for (int i = 0; i <= n; i++) { + mem[i] = new int[cap + 1]; + Array.Fill(mem[i], -1); + } + res = KnapsackDFSMem(weight, val, mem, n, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + + // 動態規劃 + res = KnapsackDP(weight, val, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + + // 空間最佳化後的動態規劃 + res = KnapsackDPComp(weight, val, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs new file mode 100644 index 000000000..d43d96563 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs @@ -0,0 +1,53 @@ +/** +* File: min_cost_climbing_stairs_dp.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class min_cost_climbing_stairs_dp { + /* 爬樓梯最小代價:動態規劃 */ + int MinCostClimbingStairsDP(int[] cost) { + int n = cost.Length - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用於儲存子問題的解 + int[] dp = new int[n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + + /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ + int MinCostClimbingStairsDPComp(int[] cost) { + int n = cost.Length - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = Math.Min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + + [Test] + public void Test() { + int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + Console.WriteLine("輸入樓梯的代價串列為"); + PrintUtil.PrintList(cost); + + int res = MinCostClimbingStairsDP(cost); + Console.WriteLine($"爬完樓梯的最低代價為 {res}"); + + res = MinCostClimbingStairsDPComp(cost); + Console.WriteLine($"爬完樓梯的最低代價為 {res}"); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/min_path_sum.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/min_path_sum.cs new file mode 100644 index 000000000..c25f149d8 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/min_path_sum.cs @@ -0,0 +1,127 @@ +/** +* File: min_path_sum.cs +* Created Time: 2023-07-10 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class min_path_sum { + /* 最小路徑和:暴力搜尋 */ + int MinPathSumDFS(int[][] grid, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return int.MaxValue; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + int up = MinPathSumDFS(grid, i - 1, j); + int left = MinPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return Math.Min(left, up) + grid[i][j]; + } + + /* 最小路徑和:記憶化搜尋 */ + int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return int.MaxValue; + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + int up = MinPathSumDFSMem(grid, mem, i - 1, j); + int left = MinPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = Math.Min(left, up) + grid[i][j]; + return mem[i][j]; + } + + /* 最小路徑和:動態規劃 */ + int MinPathSumDP(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // 初始化 dp 表 + int[,] dp = new int[n, m]; + dp[0, 0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[0, j] = dp[0, j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (int i = 1; i < n; i++) { + dp[i, 0] = dp[i - 1, 0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; + } + } + return dp[n - 1, m - 1]; + } + + /* 最小路徑和:空間最佳化後的動態規劃 */ + int MinPathSumDPComp(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // 初始化 dp 表 + int[] dp = new int[m]; + dp[0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (int i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (int j = 1; j < m; j++) { + dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + + [Test] + public void Test() { + int[][] grid = + [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2] + ]; + + int n = grid.Length, m = grid[0].Length; + + // 暴力搜尋 + int res = MinPathSumDFS(grid, n - 1, m - 1); + Console.WriteLine("從左上角到右下角的做小路徑和為 " + res); + + // 記憶化搜尋 + int[][] mem = new int[n][]; + for (int i = 0; i < n; i++) { + mem[i] = new int[m]; + Array.Fill(mem[i], -1); + } + res = MinPathSumDFSMem(grid, mem, n - 1, m - 1); + Console.WriteLine("從左上角到右下角的做小路徑和為 " + res); + + // 動態規劃 + res = MinPathSumDP(grid); + Console.WriteLine("從左上角到右下角的做小路徑和為 " + res); + + // 空間最佳化後的動態規劃 + res = MinPathSumDPComp(grid); + Console.WriteLine("從左上角到右下角的做小路徑和為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs b/zh-hant/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs new file mode 100644 index 000000000..c917b941a --- /dev/null +++ b/zh-hant/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs @@ -0,0 +1,64 @@ +/** +* File: unbounded_knapsack.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class unbounded_knapsack { + /* 完全背包:動態規劃 */ + int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // 初始化 dp 表 + int[,] dp = new int[n + 1, cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i, c] = dp[i - 1, c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n, cap]; + } + + /* 完全背包:空間最佳化後的動態規劃 */ + int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + [Test] + public void Test() { + int[] wgt = [1, 2, 3]; + int[] val = [5, 11, 15]; + int cap = 4; + + // 動態規劃 + int res = UnboundedKnapsackDP(wgt, val, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + + // 空間最佳化後的動態規劃 + res = UnboundedKnapsackDPComp(wgt, val, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + } +} diff --git a/zh-hant/codes/csharp/chapter_graph/graph_adjacency_list.cs b/zh-hant/codes/csharp/chapter_graph/graph_adjacency_list.cs new file mode 100644 index 000000000..7ad6e84ec --- /dev/null +++ b/zh-hant/codes/csharp/chapter_graph/graph_adjacency_list.cs @@ -0,0 +1,122 @@ +/** + * File: graph_adjacency_list.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_graph; + +/* 基於鄰接表實現的無向圖類別 */ +public class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + public Dictionary> adjList; + + /* 建構子 */ + public GraphAdjList(Vertex[][] edges) { + adjList = []; + // 新增所有頂點和邊 + foreach (Vertex[] edge in edges) { + AddVertex(edge[0]); + AddVertex(edge[1]); + AddEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + int Size() { + return adjList.Count; + } + + /* 新增邊 */ + public void AddEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 新增邊 vet1 - vet2 + adjList[vet1].Add(vet2); + adjList[vet2].Add(vet1); + } + + /* 刪除邊 */ + public void RemoveEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 刪除邊 vet1 - vet2 + adjList[vet1].Remove(vet2); + adjList[vet2].Remove(vet1); + } + + /* 新增頂點 */ + public void AddVertex(Vertex vet) { + if (adjList.ContainsKey(vet)) + return; + // 在鄰接表中新增一個新鏈結串列 + adjList.Add(vet, []); + } + + /* 刪除頂點 */ + public void RemoveVertex(Vertex vet) { + if (!adjList.ContainsKey(vet)) + throw new InvalidOperationException(); + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.Remove(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + foreach (List list in adjList.Values) { + list.Remove(vet); + } + } + + /* 列印鄰接表 */ + public void Print() { + Console.WriteLine("鄰接表 ="); + foreach (KeyValuePair> pair in adjList) { + List tmp = []; + foreach (Vertex vertex in pair.Value) + tmp.Add(vertex.val); + Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); + } + } +} + +public class graph_adjacency_list { + [Test] + public void Test() { + /* 初始化無向圖 */ + Vertex[] v = Vertex.ValsToVets([1, 3, 2, 5, 4]); + Vertex[][] 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]] + ]; + GraphAdjList graph = new(edges); + Console.WriteLine("\n初始化後,圖為"); + graph.Print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.AddEdge(v[0], v[2]); + Console.WriteLine("\n新增邊 1-2 後,圖為"); + graph.Print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.RemoveEdge(v[0], v[1]); + Console.WriteLine("\n刪除邊 1-3 後,圖為"); + graph.Print(); + + /* 新增頂點 */ + Vertex v5 = new(6); + graph.AddVertex(v5); + Console.WriteLine("\n新增頂點 6 後,圖為"); + graph.Print(); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.RemoveVertex(v[1]); + Console.WriteLine("\n刪除頂點 3 後,圖為"); + graph.Print(); + } +} diff --git a/zh-hant/codes/csharp/chapter_graph/graph_adjacency_matrix.cs b/zh-hant/codes/csharp/chapter_graph/graph_adjacency_matrix.cs new file mode 100644 index 000000000..6e900e178 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_graph/graph_adjacency_matrix.cs @@ -0,0 +1,137 @@ +/** + * File: graph_adjacency_matrix.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_graph; + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + List vertices; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + List> adjMat; // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = []; + this.adjMat = []; + // 新增頂點 + foreach (int val in vertices) { + AddVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + foreach (int[] e in edges) { + AddEdge(e[0], e[1]); + } + } + + /* 獲取頂點數量 */ + int Size() { + return vertices.Count; + } + + /* 新增頂點 */ + public void AddVertex(int val) { + int n = Size(); + // 向頂點串列中新增新頂點的值 + vertices.Add(val); + // 在鄰接矩陣中新增一行 + List newRow = new(n); + for (int j = 0; j < n; j++) { + newRow.Add(0); + } + adjMat.Add(newRow); + // 在鄰接矩陣中新增一列 + foreach (List row in adjMat) { + row.Add(0); + } + } + + /* 刪除頂點 */ + public void RemoveVertex(int index) { + if (index >= Size()) + throw new IndexOutOfRangeException(); + // 在頂點串列中移除索引 index 的頂點 + vertices.RemoveAt(index); + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.RemoveAt(index); + // 在鄰接矩陣中刪除索引 index 的列 + foreach (List row in adjMat) { + row.RemoveAt(index); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + public void AddEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + public void RemoveEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + public void Print() { + Console.Write("頂點串列 = "); + PrintUtil.PrintList(vertices); + Console.WriteLine("鄰接矩陣 ="); + PrintUtil.PrintMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + [Test] + public void Test() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + int[] vertices = [1, 3, 2, 5, 4]; + int[][] edges = + [ + [0, 1], + [0, 3], + [1, 2], + [2, 3], + [2, 4], + [3, 4] + ]; + GraphAdjMat graph = new(vertices, edges); + Console.WriteLine("\n初始化後,圖為"); + graph.Print(); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.AddEdge(0, 2); + Console.WriteLine("\n新增邊 1-2 後,圖為"); + graph.Print(); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.RemoveEdge(0, 1); + Console.WriteLine("\n刪除邊 1-3 後,圖為"); + graph.Print(); + + /* 新增頂點 */ + graph.AddVertex(6); + Console.WriteLine("\n新增頂點 6 後,圖為"); + graph.Print(); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.RemoveVertex(1); + Console.WriteLine("\n刪除頂點 3 後,圖為"); + graph.Print(); + } +} diff --git a/zh-hant/codes/csharp/chapter_graph/graph_bfs.cs b/zh-hant/codes/csharp/chapter_graph/graph_bfs.cs new file mode 100644 index 000000000..7c1a3d69d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_graph/graph_bfs.cs @@ -0,0 +1,58 @@ +/** + * File: graph_bfs.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_graph; + +public class graph_bfs { + /* 廣度優先走訪 */ + // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + List GraphBFS(GraphAdjList graph, Vertex startVet) { + // 頂點走訪序列 + List res = []; + // 雜湊表,用於記錄已被訪問過的頂點 + HashSet visited = [startVet]; + // 佇列用於實現 BFS + Queue que = new(); + que.Enqueue(startVet); + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (que.Count > 0) { + Vertex vet = que.Dequeue(); // 佇列首頂點出隊 + res.Add(vet); // 記錄訪問頂點 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + que.Enqueue(adjVet); // 只入列未訪問的頂點 + visited.Add(adjVet); // 標記該頂點已被訪問 + } + } + + // 返回頂點走訪序列 + return res; + } + + [Test] + public void Test() { + /* 初始化無向圖 */ + Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + Vertex[][] 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]] + ]; + + GraphAdjList graph = new(edges); + Console.WriteLine("\n初始化後,圖為"); + graph.Print(); + + /* 廣度優先走訪 */ + List res = GraphBFS(graph, v[0]); + Console.WriteLine("\n廣度優先走訪(BFS)頂點序列為"); + Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); + } +} diff --git a/zh-hant/codes/csharp/chapter_graph/graph_dfs.cs b/zh-hant/codes/csharp/chapter_graph/graph_dfs.cs new file mode 100644 index 000000000..cbeb7eab9 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_graph/graph_dfs.cs @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_graph; + +public class graph_dfs { + /* 深度優先走訪輔助函式 */ + void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { + res.Add(vet); // 記錄訪問頂點 + visited.Add(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + DFS(graph, visited, res, adjVet); + } + } + + /* 深度優先走訪 */ + // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + List GraphDFS(GraphAdjList graph, Vertex startVet) { + // 頂點走訪序列 + List res = []; + // 雜湊表,用於記錄已被訪問過的頂點 + HashSet visited = []; + DFS(graph, visited, res, startVet); + return res; + } + + [Test] + public void Test() { + /* 初始化無向圖 */ + Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6]); + Vertex[][] 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]], + ]; + + GraphAdjList graph = new(edges); + Console.WriteLine("\n初始化後,圖為"); + graph.Print(); + + /* 深度優先走訪 */ + List res = GraphDFS(graph, v[0]); + Console.WriteLine("\n深度優先走訪(DFS)頂點序列為"); + Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); + } +} diff --git a/zh-hant/codes/csharp/chapter_greedy/coin_change_greedy.cs b/zh-hant/codes/csharp/chapter_greedy/coin_change_greedy.cs new file mode 100644 index 000000000..85a37a0a7 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_greedy/coin_change_greedy.cs @@ -0,0 +1,54 @@ +/** +* File: coin_change_greedy.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class coin_change_greedy { + /* 零錢兌換:貪婪 */ + int CoinChangeGreedy(int[] coins, int amt) { + // 假設 coins 串列有序 + int i = coins.Length - 1; + int count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (amt > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > amt) { + i--; + } + // 選擇 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,則返回 -1 + return amt == 0 ? count : -1; + } + + [Test] + public void Test() { + // 貪婪:能夠保證找到全域性最優解 + int[] coins = [1, 5, 10, 20, 50, 100]; + int amt = 186; + int res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 20, 50]; + amt = 60; + res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + Console.WriteLine("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 49, 50]; + amt = 98; + res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + Console.WriteLine("實際上需要的最少數量為 2 ,即 49 + 49"); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_greedy/fractional_knapsack.cs b/zh-hant/codes/csharp/chapter_greedy/fractional_knapsack.cs new file mode 100644 index 000000000..d88bb3e81 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_greedy/fractional_knapsack.cs @@ -0,0 +1,52 @@ +/** +* File: fractional_knapsack.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +/* 物品 */ +class Item(int w, int v) { + public int w = w; // 物品重量 + public int v = v; // 物品價值 +} + +public class fractional_knapsack { + /* 分數背包:貪婪 */ + double FractionalKnapsack(int[] wgt, int[] val, int cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + Item[] items = new Item[wgt.Length]; + for (int i = 0; i < wgt.Length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); + // 迴圈貪婪選擇 + double res = 0; + foreach (Item item in items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (double)item.v / item.w * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; + } + + [Test] + public void Test() { + int[] wgt = [10, 20, 30, 40, 50]; + int[] val = [50, 120, 150, 210, 240]; + int cap = 50; + + // 貪婪演算法 + double res = FractionalKnapsack(wgt, val, cap); + Console.WriteLine("不超過背包容量的最大物品價值為 " + res); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_greedy/max_capacity.cs b/zh-hant/codes/csharp/chapter_greedy/max_capacity.cs new file mode 100644 index 000000000..da2f8c588 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_greedy/max_capacity.cs @@ -0,0 +1,39 @@ +/** +* File: max_capacity.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class max_capacity { + /* 最大容量:貪婪 */ + int MaxCapacity(int[] ht) { + // 初始化 i, j,使其分列陣列兩端 + int i = 0, j = ht.Length - 1; + // 初始最大容量為 0 + int res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + int cap = Math.Min(ht[i], ht[j]) * (j - i); + res = Math.Max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + + [Test] + public void Test() { + int[] ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 貪婪演算法 + int res = MaxCapacity(ht); + Console.WriteLine("最大容量為 " + res); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_greedy/max_product_cutting.cs b/zh-hant/codes/csharp/chapter_greedy/max_product_cutting.cs new file mode 100644 index 000000000..e631bde78 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_greedy/max_product_cutting.cs @@ -0,0 +1,39 @@ +/** +* File: max_product_cutting.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class max_product_cutting { + /* 最大切分乘積:貪婪 */ + int MaxProductCutting(int n) { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + int a = n / 3; + int b = 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); + } + + [Test] + public void Test() { + int n = 58; + + // 貪婪演算法 + int res = MaxProductCutting(n); + Console.WriteLine("最大切分乘積為" + res); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_hashing/array_hash_map.cs b/zh-hant/codes/csharp/chapter_hashing/array_hash_map.cs new file mode 100644 index 000000000..d7b83279c --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/array_hash_map.cs @@ -0,0 +1,134 @@ +/** + * File: array_hash_map.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_hashing; + +/* 鍵值對 int->string */ +class Pair(int key, string val) { + public int key = key; + public string val = val; +} + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + List buckets; + public ArrayHashMap() { + // 初始化陣列,包含 100 個桶 + buckets = []; + for (int i = 0; i < 100; i++) { + buckets.Add(null); + } + } + + /* 雜湊函式 */ + int HashFunc(int key) { + int index = key % 100; + return index; + } + + /* 查詢操作 */ + public string? Get(int key) { + int index = HashFunc(key); + Pair? pair = buckets[index]; + if (pair == null) return null; + return pair.val; + } + + /* 新增操作 */ + public void Put(int key, string val) { + Pair pair = new(key, val); + int index = HashFunc(key); + buckets[index] = pair; + } + + /* 刪除操作 */ + public void Remove(int key) { + int index = HashFunc(key); + // 置為 null ,代表刪除 + buckets[index] = null; + } + + /* 獲取所有鍵值對 */ + public List PairSet() { + List pairSet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + pairSet.Add(pair); + } + return pairSet; + } + + /* 獲取所有鍵 */ + public List KeySet() { + List keySet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + keySet.Add(pair.key); + } + return keySet; + } + + /* 獲取所有值 */ + public List ValueSet() { + List valueSet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + valueSet.Add(pair.val); + } + return valueSet; + } + + /* 列印雜湊表 */ + public void Print() { + foreach (Pair kv in PairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); + } + } +} + + +public class array_hash_map { + [Test] + public void Test() { + /* 初始化雜湊表 */ + ArrayHashMap map = new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.Put(12836, "小哈"); + map.Put(15937, "小囉"); + map.Put(16750, "小算"); + map.Put(13276, "小法"); + map.Put(10583, "小鴨"); + Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); + map.Print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string? name = map.Get(15937); + Console.WriteLine("\n輸入學號 15937 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.Remove(10583); + Console.WriteLine("\n刪除 10583 後,雜湊表為\nKey -> Value"); + map.Print(); + + /* 走訪雜湊表 */ + Console.WriteLine("\n走訪鍵值對 Key->Value"); + foreach (Pair kv in map.PairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); + } + Console.WriteLine("\n單獨走訪鍵 Key"); + foreach (int key in map.KeySet()) { + Console.WriteLine(key); + } + Console.WriteLine("\n單獨走訪值 Value"); + foreach (string val in map.ValueSet()) { + Console.WriteLine(val); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_hashing/built_in_hash.cs b/zh-hant/codes/csharp/chapter_hashing/built_in_hash.cs new file mode 100644 index 000000000..73b7e188e --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/built_in_hash.cs @@ -0,0 +1,36 @@ +/** +* File: built_in_hash.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +public class built_in_hash { + [Test] + public void Test() { + int num = 3; + int hashNum = num.GetHashCode(); + Console.WriteLine("整數 " + num + " 的雜湊值為 " + hashNum); + + bool bol = true; + int hashBol = bol.GetHashCode(); + Console.WriteLine("布林量 " + bol + " 的雜湊值為 " + hashBol); + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + Console.WriteLine("小數 " + dec + " 的雜湊值為 " + hashDec); + + string str = "Hello 演算法"; + int hashStr = str.GetHashCode(); + Console.WriteLine("字串 " + str + " 的雜湊值為 " + hashStr); + + object[] arr = [12836, "小哈"]; + int hashTup = arr.GetHashCode(); + Console.WriteLine("陣列 [" + string.Join(", ", arr) + "] 的雜湊值為 " + hashTup); + + ListNode obj = new(0); + int hashObj = obj.GetHashCode(); + Console.WriteLine("節點物件 " + obj + " 的雜湊值為 " + hashObj); + } +} diff --git a/zh-hant/codes/csharp/chapter_hashing/hash_map.cs b/zh-hant/codes/csharp/chapter_hashing/hash_map.cs new file mode 100644 index 000000000..7266bd103 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/hash_map.cs @@ -0,0 +1,51 @@ + +/** + * File: hash_map.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_hashing; + +public class hash_map { + [Test] + public void Test() { + /* 初始化雜湊表 */ + Dictionary map = new() { + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + { 12836, "小哈" }, + { 15937, "小囉" }, + { 16750, "小算" }, + { 13276, "小法" }, + { 10583, "小鴨" } + }; + Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); + PrintUtil.PrintHashMap(map); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map[15937]; + Console.WriteLine("\n輸入學號 15937 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.Remove(10583); + Console.WriteLine("\n刪除 10583 後,雜湊表為\nKey -> Value"); + PrintUtil.PrintHashMap(map); + + /* 走訪雜湊表 */ + Console.WriteLine("\n走訪鍵值對 Key->Value"); + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + Console.WriteLine("\n單獨走訪鍵 Key"); + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + Console.WriteLine("\n單獨走訪值 Value"); + foreach (string val in map.Values) { + Console.WriteLine(val); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_hashing/hash_map_chaining.cs b/zh-hant/codes/csharp/chapter_hashing/hash_map_chaining.cs new file mode 100644 index 000000000..0e9fc6dd0 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/hash_map_chaining.cs @@ -0,0 +1,144 @@ +/** +* File: hash_map_chaining.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + int size; // 鍵值對數量 + int capacity; // 雜湊表容量 + double loadThres; // 觸發擴容的負載因子閾值 + int extendRatio; // 擴容倍數 + List> buckets; // 桶陣列 + + /* 建構子 */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add([]); + } + } + + /* 雜湊函式 */ + int HashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double LoadFactor() { + return (double)size / capacity; + } + + /* 查詢操作 */ + public string? Get(int key) { + int index = HashFunc(key); + // 走訪桶,若找到 key ,則返回對應 val + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key ,則返回 null + return null; + } + + /* 新增操作 */ + public void Put(int key, string val) { + // 當負載因子超過閾值時,執行擴容 + if (LoadFactor() > loadThres) { + Extend(); + } + int index = HashFunc(key); + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + buckets[index].Add(new Pair(key, val)); + size++; + } + + /* 刪除操作 */ + public void Remove(int key) { + int index = HashFunc(key); + // 走訪桶,從中刪除鍵值對 + foreach (Pair pair in buckets[index].ToList()) { + if (pair.key == key) { + buckets[index].Remove(pair); + size--; + break; + } + } + } + + /* 擴容雜湊表 */ + void Extend() { + // 暫存原雜湊表 + List> bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add([]); + } + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + foreach (List bucket in bucketsTmp) { + foreach (Pair pair in bucket) { + Put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + public void Print() { + foreach (List bucket in buckets) { + List res = []; + foreach (Pair pair in bucket) { + res.Add(pair.key + " -> " + pair.val); + } + foreach (string kv in res) { + Console.WriteLine(kv); + } + } + } +} + +public class hash_map_chaining { + [Test] + public void Test() { + /* 初始化雜湊表 */ + HashMapChaining map = new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.Put(12836, "小哈"); + map.Put(15937, "小囉"); + map.Put(16750, "小算"); + map.Put(13276, "小法"); + map.Put(10583, "小鴨"); + Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); + map.Print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string? name = map.Get(13276); + Console.WriteLine("\n輸入學號 13276 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.Remove(12836); + Console.WriteLine("\n刪除 12836 後,雜湊表為\nKey -> Value"); + map.Print(); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_hashing/hash_map_open_addressing.cs b/zh-hant/codes/csharp/chapter_hashing/hash_map_open_addressing.cs new file mode 100644 index 000000000..3e393266c --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/hash_map_open_addressing.cs @@ -0,0 +1,159 @@ +/** +* File: hash_map_open_addressing.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + int size; // 鍵值對數量 + int capacity = 4; // 雜湊表容量 + double loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + int extendRatio = 2; // 擴容倍數 + Pair[] buckets; // 桶陣列 + Pair TOMBSTONE = new(-1, "-1"); // 刪除標記 + + /* 建構子 */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* 雜湊函式 */ + int HashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double LoadFactor() { + return (double)size / capacity; + } + + /* 搜尋 key 對應的桶索引 */ + int FindBucket(int key) { + int index = HashFunc(key); + int firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (buckets[index] != null) { + // 若遇到 key ,返回對應的桶索引 + if (buckets[index].key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + public string? Get(int key) { + // 搜尋 key 對應的桶索引 + int index = FindBucket(key); + // 若找到鍵值對,則返回對應 val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index].val; + } + // 若鍵值對不存在,則返回 null + return null; + } + + /* 新增操作 */ + public void Put(int key, string val) { + // 當負載因子超過閾值時,執行擴容 + if (LoadFactor() > loadThres) { + Extend(); + } + // 搜尋 key 對應的桶索引 + int index = FindBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index].val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + buckets[index] = new Pair(key, val); + size++; + } + + /* 刪除操作 */ + public void Remove(int key) { + // 搜尋 key 對應的桶索引 + int index = FindBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE; + size--; + } + } + + /* 擴容雜湊表 */ + void Extend() { + // 暫存原雜湊表 + Pair[] bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + foreach (Pair pair in bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + Put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + public void Print() { + foreach (Pair pair in buckets) { + if (pair == null) { + Console.WriteLine("null"); + } else if (pair == TOMBSTONE) { + Console.WriteLine("TOMBSTONE"); + } else { + Console.WriteLine(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + [Test] + public void Test() { + /* 初始化雜湊表 */ + HashMapOpenAddressing map = new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.Put(12836, "小哈"); + map.Put(15937, "小囉"); + map.Put(16750, "小算"); + map.Put(13276, "小法"); + map.Put(10583, "小鴨"); + Console.WriteLine("\n新增完成後,雜湊表為\nKey -> Value"); + map.Print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string? name = map.Get(13276); + Console.WriteLine("\n輸入學號 13276 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.Remove(16750); + Console.WriteLine("\n刪除 16750 後,雜湊表為\nKey -> Value"); + map.Print(); + } +} diff --git a/zh-hant/codes/csharp/chapter_hashing/simple_hash.cs b/zh-hant/codes/csharp/chapter_hashing/simple_hash.cs new file mode 100644 index 000000000..9ab74faf2 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_hashing/simple_hash.cs @@ -0,0 +1,66 @@ +/** +* File: simple_hash.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +public class simple_hash { + /* 加法雜湊 */ + int AddHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (hash + c) % MODULUS; + } + return (int)hash; + } + + /* 乘法雜湊 */ + int MulHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (31 * hash + c) % MODULUS; + } + return (int)hash; + } + + /* 互斥或雜湊 */ + int XorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash ^= c; + } + return hash & MODULUS; + } + + /* 旋轉雜湊 */ + int RotHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; + } + return (int)hash; + } + + [Test] + public void Test() { + string key = "Hello 演算法"; + + int hash = AddHash(key); + Console.WriteLine("加法雜湊值為 " + hash); + + hash = MulHash(key); + Console.WriteLine("乘法雜湊值為 " + hash); + + hash = XorHash(key); + Console.WriteLine("互斥或雜湊值為 " + hash); + + hash = RotHash(key); + Console.WriteLine("旋轉雜湊值為 " + hash); + } +} diff --git a/zh-hant/codes/csharp/chapter_heap/heap.cs b/zh-hant/codes/csharp/chapter_heap/heap.cs new file mode 100644 index 000000000..ac8b34d54 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_heap/heap.cs @@ -0,0 +1,64 @@ +/** + * File: heap.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_heap; + +public class heap { + void TestPush(PriorityQueue heap, int val) { + heap.Enqueue(val, val); // 元素入堆積 + Console.WriteLine($"\n元素 {val} 入堆積後\n"); + PrintUtil.PrintHeap(heap); + } + + void TestPop(PriorityQueue heap) { + int val = heap.Dequeue(); // 堆積頂元素出堆積 + Console.WriteLine($"\n堆積頂元素 {val} 出堆積後\n"); + PrintUtil.PrintHeap(heap); + } + + [Test] + public void Test() { + /* 初始化堆積 */ + // 初始化小頂堆積 + PriorityQueue minHeap = new(); + // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y - x)); + Console.WriteLine("以下測試樣例為大頂堆積"); + + /* 元素入堆積 */ + TestPush(maxHeap, 1); + TestPush(maxHeap, 3); + TestPush(maxHeap, 2); + TestPush(maxHeap, 5); + TestPush(maxHeap, 4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.Peek(); + Console.WriteLine($"堆積頂元素為 {peek}"); + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + + /* 獲取堆積大小 */ + int size = maxHeap.Count; + Console.WriteLine($"堆積元素數量為 {size}"); + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.Count == 0; + Console.WriteLine($"堆積是否為空 {isEmpty}"); + + /* 輸入串列並建堆積 */ + var list = new int[] { 1, 3, 2, 5, 4 }; + minHeap = new PriorityQueue(list.Select(x => (x, x))); + Console.WriteLine("輸入串列並建立小頂堆積後"); + PrintUtil.PrintHeap(minHeap); + } +} diff --git a/zh-hant/codes/csharp/chapter_heap/my_heap.cs b/zh-hant/codes/csharp/chapter_heap/my_heap.cs new file mode 100644 index 000000000..ccfb4d1f2 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_heap/my_heap.cs @@ -0,0 +1,160 @@ +/** + * File: my_heap.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_heap; + +/* 大頂堆積 */ +class MaxHeap { + // 使用串列而非陣列,這樣無須考慮擴容問題 + List maxHeap; + + /* 建構子,建立空堆積 */ + public MaxHeap() { + maxHeap = []; + } + + /* 建構子,根據輸入串列建堆積 */ + public MaxHeap(IEnumerable nums) { + // 將串列元素原封不動新增進堆積 + maxHeap = new List(nums); + // 堆積化除葉節點以外的其他所有節點 + var size = Parent(this.Size() - 1); + for (int i = size; i >= 0; i--) { + SiftDown(i); + } + } + + /* 獲取左子節點的索引 */ + int Left(int i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + int Right(int i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + int Parent(int i) { + return (i - 1) / 2; // 向下整除 + } + + /* 訪問堆積頂元素 */ + public int Peek() { + return maxHeap[0]; + } + + /* 元素入堆積 */ + public void Push(int val) { + // 新增節點 + maxHeap.Add(val); + // 從底至頂堆積化 + SiftUp(Size() - 1); + } + + /* 獲取堆積大小 */ + public int Size() { + return maxHeap.Count; + } + + /* 判斷堆積是否為空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 從節點 i 開始,從底至頂堆積化 */ + void SiftUp(int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = Parent(i); + // 若“越過根節點”或“節點無須修復”,則結束堆積化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 交換兩節點 + Swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + public int Pop() { + // 判空處理 + if (IsEmpty()) + throw new IndexOutOfRangeException(); + // 交換根節點與最右葉節點(交換首元素與尾元素) + Swap(0, Size() - 1); + // 刪除節點 + int val = maxHeap.Last(); + maxHeap.RemoveAt(Size() - 1); + // 從頂至底堆積化 + SiftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + void SiftDown(int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = Left(i), r = Right(i), ma = i; + if (l < Size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < Size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 若“節點 i 最大”或“越過葉節點”,則結束堆積化 + if (ma == i) break; + // 交換兩節點 + Swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 交換元素 */ + void Swap(int i, int p) { + (maxHeap[i], maxHeap[p]) = (maxHeap[p], maxHeap[i]); + } + + /* 列印堆積(二元樹) */ + public void Print() { + var queue = new Queue(maxHeap); + PrintUtil.PrintHeap(queue); + } +} + +public class my_heap { + [Test] + public void Test() { + /* 初始化大頂堆積 */ + MaxHeap maxHeap = new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + Console.WriteLine("\n輸入串列並建堆積後"); + maxHeap.Print(); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.Peek(); + Console.WriteLine($"堆積頂元素為 {peek}"); + + /* 元素入堆積 */ + int val = 7; + maxHeap.Push(val); + Console.WriteLine($"元素 {val} 入堆積後"); + maxHeap.Print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.Pop(); + Console.WriteLine($"堆積頂元素 {peek} 出堆積後"); + maxHeap.Print(); + + /* 獲取堆積大小 */ + int size = maxHeap.Size(); + Console.WriteLine($"堆積元素數量為 {size}"); + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.IsEmpty(); + Console.WriteLine($"堆積是否為空 {isEmpty}"); + } +} diff --git a/zh-hant/codes/csharp/chapter_heap/top_k.cs b/zh-hant/codes/csharp/chapter_heap/top_k.cs new file mode 100644 index 000000000..3e0a2e282 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_heap/top_k.cs @@ -0,0 +1,37 @@ +/** +* File: top_k.cs +* Created Time: 2023-06-14 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_heap; + +public class top_k { + /* 基於堆積查詢陣列中最大的 k 個元素 */ + PriorityQueue TopKHeap(int[] nums, int k) { + // 初始化小頂堆積 + PriorityQueue heap = new(); + // 將陣列的前 k 個元素入堆積 + for (int i = 0; i < k; i++) { + heap.Enqueue(nums[i], nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (int i = k; i < nums.Length; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > heap.Peek()) { + heap.Dequeue(); + heap.Enqueue(nums[i], nums[i]); + } + } + return heap; + } + + [Test] + public void Test() { + int[] nums = [1, 7, 6, 3, 2]; + int k = 3; + PriorityQueue res = TopKHeap(nums, k); + Console.WriteLine("最大的 " + k + " 個元素為"); + PrintUtil.PrintHeap(res); + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/binary_search.cs b/zh-hant/codes/csharp/chapter_searching/binary_search.cs new file mode 100644 index 000000000..f330e1fed --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/binary_search.cs @@ -0,0 +1,59 @@ +/** + * File: binary_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class binary_search { + /* 二分搜尋(雙閉區間) */ + int BinarySearch(int[] nums, int target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + int i = 0, j = nums.Length - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + else // 找到目標元素,返回其索引 + return m; + } + // 未找到目標元素,返回 -1 + return -1; + } + + /* 二分搜尋(左閉右開區間) */ + int BinarySearchLCRO(int[] nums, int target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + int i = 0, j = nums.Length; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m) 中 + j = m; + else // 找到目標元素,返回其索引 + return m; + } + // 未找到目標元素,返回 -1 + return -1; + } + + [Test] + public void Test() { + int target = 6; + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + /* 二分搜尋(雙閉區間) */ + int index = BinarySearch(nums, target); + Console.WriteLine("目標元素 6 的索引 = " + index); + + /* 二分搜尋(左閉右開區間) */ + index = BinarySearchLCRO(nums, target); + Console.WriteLine("目標元素 6 的索引 = " + index); + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/binary_search_edge.cs b/zh-hant/codes/csharp/chapter_searching/binary_search_edge.cs new file mode 100644 index 000000000..7dd0e8848 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/binary_search_edge.cs @@ -0,0 +1,50 @@ +/** +* File: binary_search_edge.cs +* Created Time: 2023-08-06 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_searching; + +public class binary_search_edge { + /* 二分搜尋最左一個 target */ + int BinarySearchLeftEdge(int[] nums, int target) { + // 等價於查詢 target 的插入點 + int i = binary_search_insertion.BinarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.Length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; + } + + /* 二分搜尋最右一個 target */ + int BinarySearchRightEdge(int[] nums, int target) { + // 轉化為查詢最左一個 target + 1 + int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; + } + + [Test] + public void Test() { + // 包含重複元素的陣列 + int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + Console.WriteLine("\n陣列 nums = " + nums.PrintList()); + + // 二分搜尋左邊界和右邊界 + foreach (int target in new int[] { 6, 7 }) { + int index = BinarySearchLeftEdge(nums, target); + Console.WriteLine("最左一個元素 " + target + " 的索引為 " + index); + index = BinarySearchRightEdge(nums, target); + Console.WriteLine("最右一個元素 " + target + " 的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/binary_search_insertion.cs b/zh-hant/codes/csharp/chapter_searching/binary_search_insertion.cs new file mode 100644 index 000000000..8b4659ac7 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/binary_search_insertion.cs @@ -0,0 +1,64 @@ +/** +* File: binary_search_insertion.cs +* Created Time: 2023-08-06 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_searching; + +public class binary_search_insertion { + /* 二分搜尋插入點(無重複元素) */ + public static int BinarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.Length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i; + } + + /* 二分搜尋插入點(存在重複元素) */ + public static int BinarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.Length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; + } + + [Test] + public void Test() { + // 無重複元素的陣列 + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + Console.WriteLine("\n陣列 nums = " + nums.PrintList()); + // 二分搜尋插入點 + foreach (int target in new int[] { 6, 9 }) { + int index = BinarySearchInsertionSimple(nums, target); + Console.WriteLine("元素 " + target + " 的插入點的索引為 " + index); + } + + // 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + Console.WriteLine("\n陣列 nums = " + nums.PrintList()); + // 二分搜尋插入點 + foreach (int target in new int[] { 2, 6, 20 }) { + int index = BinarySearchInsertion(nums, target); + Console.WriteLine("元素 " + target + " 的插入點的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/hashing_search.cs b/zh-hant/codes/csharp/chapter_searching/hashing_search.cs new file mode 100644 index 000000000..28dd9af0d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/hashing_search.cs @@ -0,0 +1,50 @@ +/** + * File: hashing_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class hashing_search { + /* 雜湊查詢(陣列) */ + int HashingSearchArray(Dictionary map, int target) { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map.GetValueOrDefault(target, -1); + } + + /* 雜湊查詢(鏈結串列) */ + ListNode? HashingSearchLinkedList(Dictionary map, int target) { + + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map.GetValueOrDefault(target); + } + + [Test] + public void Test() { + int target = 3; + + /* 雜湊查詢(陣列) */ + int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // 初始化雜湊表 + Dictionary map = []; + for (int i = 0; i < nums.Length; i++) { + map[nums[i]] = i; // key: 元素,value: 索引 + } + int index = HashingSearchArray(map, target); + Console.WriteLine("目標元素 3 的索引 = " + index); + + /* 雜湊查詢(鏈結串列) */ + ListNode? head = ListNode.ArrToLinkedList(nums); + // 初始化雜湊表 + Dictionary map1 = []; + while (head != null) { + map1[head.val] = head; // key: 節點值,value: 節點 + head = head.next; + } + ListNode? node = HashingSearchLinkedList(map1, target); + Console.WriteLine("目標節點值 3 的對應節點物件為 " + node); + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/linear_search.cs b/zh-hant/codes/csharp/chapter_searching/linear_search.cs new file mode 100644 index 000000000..dc0ca9e7e --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/linear_search.cs @@ -0,0 +1,49 @@ +/** + * File: linear_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class linear_search { + /* 線性查詢(陣列) */ + int LinearSearchArray(int[] nums, int target) { + // 走訪陣列 + for (int i = 0; i < nums.Length; i++) { + // 找到目標元素,返回其索引 + if (nums[i] == target) + return i; + } + // 未找到目標元素,返回 -1 + return -1; + } + + /* 線性查詢(鏈結串列) */ + ListNode? LinearSearchLinkedList(ListNode? head, int target) { + // 走訪鏈結串列 + while (head != null) { + // 找到目標節點,返回之 + if (head.val == target) + return head; + head = head.next; + } + // 未找到目標節點,返回 null + return null; + } + + [Test] + public void Test() { + int target = 3; + + /* 在陣列中執行線性查詢 */ + int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + int index = LinearSearchArray(nums, target); + Console.WriteLine("目標元素 3 的索引 = " + index); + + /* 在鏈結串列中執行線性查詢 */ + ListNode? head = ListNode.ArrToLinkedList(nums); + ListNode? node = LinearSearchLinkedList(head, target); + Console.WriteLine("目標節點值 3 的對應節點物件為 " + node); + } +} diff --git a/zh-hant/codes/csharp/chapter_searching/two_sum.cs b/zh-hant/codes/csharp/chapter_searching/two_sum.cs new file mode 100644 index 000000000..2c60ce7e4 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_searching/two_sum.cs @@ -0,0 +1,52 @@ +/** + * File: two_sum.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class two_sum { + /* 方法一:暴力列舉 */ + int[] TwoSumBruteForce(int[] nums, int target) { + int size = nums.Length; + // 兩層迴圈,時間複雜度為 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return [i, j]; + } + } + return []; + } + + /* 方法二:輔助雜湊表 */ + int[] TwoSumHashTable(int[] nums, int target) { + int size = nums.Length; + // 輔助雜湊表,空間複雜度為 O(n) + Dictionary dic = []; + // 單層迴圈,時間複雜度為 O(n) + for (int i = 0; i < size; i++) { + if (dic.ContainsKey(target - nums[i])) { + return [dic[target - nums[i]], i]; + } + dic.Add(nums[i], i); + } + return []; + } + + [Test] + public void Test() { + // ======= Test Case ======= + int[] nums = [2, 7, 11, 15]; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + int[] res = TwoSumBruteForce(nums, target); + Console.WriteLine("方法一 res = " + string.Join(",", res)); + // 方法二 + res = TwoSumHashTable(nums, target); + Console.WriteLine("方法二 res = " + string.Join(",", res)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/bubble_sort.cs b/zh-hant/codes/csharp/chapter_sorting/bubble_sort.cs new file mode 100644 index 000000000..511f867d7 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/bubble_sort.cs @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class bubble_sort { + /* 泡沫排序 */ + void BubbleSort(int[] nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + } + } + } + } + + /* 泡沫排序(標誌最佳化)*/ + void BubbleSortWithFlag(int[] nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + bool flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + flag = true; // 記錄交換元素 + } + } + if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + BubbleSort(nums); + Console.WriteLine("泡沫排序完成後 nums = " + string.Join(",", nums)); + + int[] nums1 = [4, 1, 3, 1, 5, 2]; + BubbleSortWithFlag(nums1); + Console.WriteLine("泡沫排序完成後 nums1 = " + string.Join(",", nums1)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/bucket_sort.cs b/zh-hant/codes/csharp/chapter_sorting/bucket_sort.cs new file mode 100644 index 000000000..684c4a3c1 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/bucket_sort.cs @@ -0,0 +1,46 @@ +/** + * File: bucket_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class bucket_sort { + /* 桶排序 */ + void BucketSort(float[] nums) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + int k = nums.Length / 2; + List> buckets = []; + for (int i = 0; i < k; i++) { + buckets.Add([]); + } + // 1. 將陣列元素分配到各個桶中 + foreach (float num in nums) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + int i = (int)(num * k); + // 將 num 新增進桶 i + buckets[i].Add(num); + } + // 2. 對各個桶執行排序 + foreach (List bucket in buckets) { + // 使用內建排序函式,也可以替換成其他排序演算法 + bucket.Sort(); + } + // 3. 走訪桶合併結果 + int j = 0; + foreach (List bucket in buckets) { + foreach (float num in bucket) { + nums[j++] = num; + } + } + } + + [Test] + public void Test() { + // 設輸入資料為浮點數,範圍為 [0, 1) + float[] nums = [0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f]; + BucketSort(nums); + Console.WriteLine("桶排序完成後 nums = " + string.Join(" ", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/counting_sort.cs b/zh-hant/codes/csharp/chapter_sorting/counting_sort.cs new file mode 100644 index 000000000..b86ba72d4 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/counting_sort.cs @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class counting_sort { + /* 計數排序 */ + // 簡單實現,無法用於排序物件 + void CountingSortNaive(int[] nums) { + // 1. 統計陣列最大元素 m + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + } + + /* 計數排序 */ + // 完整實現,可排序物件,並且是穩定排序 + void CountingSort(int[] nums) { + // 1. 統計陣列最大元素 m + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + int n = nums.Length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + [Test] + public void Test() { + int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + CountingSortNaive(nums); + Console.WriteLine("計數排序(無法排序物件)完成後 nums = " + string.Join(" ", nums)); + + int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + CountingSort(nums1); + Console.WriteLine("計數排序完成後 nums1 = " + string.Join(" ", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/heap_sort.cs b/zh-hant/codes/csharp/chapter_sorting/heap_sort.cs new file mode 100644 index 000000000..e365cfaf2 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/heap_sort.cs @@ -0,0 +1,52 @@ +/** +* File: heap_sort.cs +* Created Time: 2023-06-01 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_sorting; + +public class heap_sort { + /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ + void SiftDown(int[] nums, int n, int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) + break; + // 交換兩節點 + (nums[ma], nums[i]) = (nums[i], nums[ma]); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 堆積排序 */ + void HeapSort(int[] nums) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (int i = nums.Length / 2 - 1; i >= 0; i--) { + SiftDown(nums, nums.Length, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (int i = nums.Length - 1; i > 0; i--) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + (nums[i], nums[0]) = (nums[0], nums[i]); + // 以根節點為起點,從頂至底進行堆積化 + SiftDown(nums, i, 0); + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + HeapSort(nums); + Console.WriteLine("堆積排序完成後 nums = " + string.Join(" ", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/insertion_sort.cs b/zh-hant/codes/csharp/chapter_sorting/insertion_sort.cs new file mode 100644 index 000000000..c4953bae5 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/insertion_sort.cs @@ -0,0 +1,30 @@ +/** + * File: insertion_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class insertion_sort { + /* 插入排序 */ + void InsertionSort(int[] nums) { + // 外迴圈:已排序區間為 [0, i-1] + for (int i = 1; i < nums.Length; i++) { + int bas = nums[i], j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > bas) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = bas; // 將 base 賦值到正確位置 + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + InsertionSort(nums); + Console.WriteLine("插入排序完成後 nums = " + string.Join(",", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/merge_sort.cs b/zh-hant/codes/csharp/chapter_sorting/merge_sort.cs new file mode 100644 index 000000000..a22c1f891 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/merge_sort.cs @@ -0,0 +1,56 @@ +/** + * File: merge_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class merge_sort { + /* 合併左子陣列和右子陣列 */ + void Merge(int[] nums, int left, int mid, int right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + int[] tmp = new int[right - left + 1]; + // 初始化左子陣列和右子陣列的起始索引 + int i = left, j = mid + 1, k = 0; + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for (k = 0; k < tmp.Length; ++k) { + nums[left + k] = tmp[k]; + } + } + + /* 合併排序 */ + void MergeSort(int[] nums, int left, int right) { + // 終止條件 + if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + int mid = (left + right) / 2; // 計算中點 + MergeSort(nums, left, mid); // 遞迴左子陣列 + MergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + Merge(nums, left, mid, right); + } + + [Test] + public void Test() { + /* 合併排序 */ + int[] nums = [7, 3, 2, 6, 0, 1, 5, 4]; + MergeSort(nums, 0, nums.Length - 1); + Console.WriteLine("合併排序完成後 nums = " + string.Join(",", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/quick_sort.cs b/zh-hant/codes/csharp/chapter_sorting/quick_sort.cs new file mode 100644 index 000000000..90fa517f9 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/quick_sort.cs @@ -0,0 +1,150 @@ +/** + * File: quick_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +class quickSort { + /* 元素交換 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* 哨兵劃分 */ + static int Partition(int[] nums, int left, int right) { + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + Swap(nums, i, j); // 交換這兩個元素 + } + Swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + public static void QuickSort(int[] nums, int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) + return; + // 哨兵劃分 + int pivot = Partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + QuickSort(nums, left, pivot - 1); + QuickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + /* 元素交換 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* 選取三個候選元素的中位數 */ + static int MedianThree(int[] nums, int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m 在 l 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l 在 m 和 r 之間 + return right; + } + + /* 哨兵劃分(三數取中值) */ + static int Partition(int[] nums, int left, int right) { + // 選取三個候選元素的中位數 + int med = MedianThree(nums, left, (left + right) / 2, right); + // 將中位數交換至陣列最左端 + Swap(nums, left, med); + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + Swap(nums, i, j); // 交換這兩個元素 + } + Swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + public static void QuickSort(int[] nums, int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) + return; + // 哨兵劃分 + int pivot = Partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + QuickSort(nums, left, pivot - 1); + QuickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + /* 元素交換 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* 哨兵劃分 */ + static int Partition(int[] nums, int left, int right) { + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + Swap(nums, i, j); // 交換這兩個元素 + } + Swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + public static void QuickSort(int[] nums, int left, int right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + int pivot = Partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + QuickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + QuickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +public class quick_sort { + [Test] + public void Test() { + /* 快速排序 */ + int[] nums = [2, 4, 1, 0, 3, 5]; + quickSort.QuickSort(nums, 0, nums.Length - 1); + Console.WriteLine("快速排序完成後 nums = " + string.Join(",", nums)); + + /* 快速排序(中位基準數最佳化) */ + int[] nums1 = [2, 4, 1, 0, 3, 5]; + QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1); + Console.WriteLine("快速排序(中位基準數最佳化)完成後 nums1 = " + string.Join(",", nums1)); + + /* 快速排序(尾遞迴最佳化) */ + int[] nums2 = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1); + Console.WriteLine("快速排序(尾遞迴最佳化)完成後 nums2 = " + string.Join(",", nums2)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/radix_sort.cs b/zh-hant/codes/csharp/chapter_sorting/radix_sort.cs new file mode 100644 index 000000000..2cc67ec58 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/radix_sort.cs @@ -0,0 +1,69 @@ +/** + * File: radix_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class radix_sort { + /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ + int Digit(int num, int exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10; + } + + /* 計數排序(根據 nums 第 k 位排序) */ + void CountingSortDigit(int[] nums, int exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + int[] counter = new int[10]; + int n = nums.Length; + // 統計 0~9 各數字的出現次數 + for (int i = 0; i < n; i++) { + int d = Digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int d = Digit(nums[i], exp); + int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d]--; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + /* 基數排序 */ + void RadixSort(int[] nums) { + // 獲取陣列的最大元素,用於判斷最大位數 + int m = int.MinValue; + foreach (int num in nums) { + if (num > m) m = num; + } + // 按照從低位到高位的順序走訪 + for (int exp = 1; exp <= m; exp *= 10) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + CountingSortDigit(nums, exp); + } + } + + [Test] + public void Test() { + // 基數排序 + int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 ]; + RadixSort(nums); + Console.WriteLine("基數排序完成後 nums = " + string.Join(" ", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_sorting/selection_sort.cs b/zh-hant/codes/csharp/chapter_sorting/selection_sort.cs new file mode 100644 index 000000000..81b2b3a13 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_sorting/selection_sort.cs @@ -0,0 +1,32 @@ +/** +* File: selection_sort.cs +* Created Time: 2023-06-01 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_sorting; + +public class selection_sort { + /* 選擇排序 */ + void SelectionSort(int[] nums) { + int n = nums.Length; + // 外迴圈:未排序區間為 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 內迴圈:找到未排序區間內的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 記錄最小元素的索引 + } + // 將該最小元素與未排序區間的首個元素交換 + (nums[k], nums[i]) = (nums[i], nums[k]); + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + SelectionSort(nums); + Console.WriteLine("選擇排序完成後 nums = " + string.Join(" ", nums)); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/array_deque.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/array_deque.cs new file mode 100644 index 000000000..6e8a1657e --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/array_deque.cs @@ -0,0 +1,152 @@ +/** + * File: array_deque.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基於環形陣列實現的雙向佇列 */ +public class ArrayDeque { + int[] nums; // 用於儲存雙向佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 雙向佇列長度 + + /* 建構子 */ + public ArrayDeque(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + int Capacity() { + return nums.Length; + } + + /* 獲取雙向佇列的長度 */ + public int Size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + public bool IsEmpty() { + return queSize == 0; + } + + /* 計算環形陣列索引 */ + int Index(int i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + Capacity()) % Capacity(); + } + + /* 佇列首入列 */ + public void PushFirst(int num) { + if (queSize == Capacity()) { + Console.WriteLine("雙向佇列已滿"); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + front = Index(front - 1); + // 將 num 新增至佇列首 + nums[front] = num; + queSize++; + } + + /* 佇列尾入列 */ + public void PushLast(int num) { + if (queSize == Capacity()) { + Console.WriteLine("雙向佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + int rear = Index(front + queSize); + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 佇列首出列 */ + public int PopFirst() { + int num = PeekFirst(); + // 佇列首指標向後移動一位 + front = Index(front + 1); + queSize--; + return num; + } + + /* 佇列尾出列 */ + public int PopLast() { + int num = PeekLast(); + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int PeekFirst() { + if (IsEmpty()) { + throw new InvalidOperationException(); + } + return nums[front]; + } + + /* 訪問佇列尾元素 */ + public int PeekLast() { + if (IsEmpty()) { + throw new InvalidOperationException(); + } + // 計算尾元素索引 + int last = Index(front + queSize - 1); + return nums[last]; + } + + /* 返回陣列用於列印 */ + public int[] ToArray() { + // 僅轉換有效長度範圍內的串列元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[Index(j)]; + } + return res; + } +} + +public class array_deque { + [Test] + public void Test() { + /* 初始化雙向佇列 */ + ArrayDeque deque = new(10); + deque.PushLast(3); + deque.PushLast(2); + deque.PushLast(5); + Console.WriteLine("雙向佇列 deque = " + string.Join(" ", deque.ToArray())); + + /* 訪問元素 */ + int peekFirst = deque.PeekFirst(); + Console.WriteLine("佇列首元素 peekFirst = " + peekFirst); + int peekLast = deque.PeekLast(); + Console.WriteLine("佇列尾元素 peekLast = " + peekLast); + + /* 元素入列 */ + deque.PushLast(4); + Console.WriteLine("元素 4 佇列尾入列後 deque = " + string.Join(" ", deque.ToArray())); + deque.PushFirst(1); + Console.WriteLine("元素 1 佇列首入列後 deque = " + string.Join(" ", deque.ToArray())); + + /* 元素出列 */ + int popLast = deque.PopLast(); + Console.WriteLine("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + string.Join(" ", deque.ToArray())); + int popFirst = deque.PopFirst(); + Console.WriteLine("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + string.Join(" ", deque.ToArray())); + + /* 獲取雙向佇列的長度 */ + int size = deque.Size(); + Console.WriteLine("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.IsEmpty(); + Console.WriteLine("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/array_queue.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/array_queue.cs new file mode 100644 index 000000000..7e1118820 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/array_queue.cs @@ -0,0 +1,114 @@ +/** + * File: array_queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + int[] nums; // 用於儲存佇列元素的陣列 + int front; // 佇列首指標,指向佇列首元素 + int queSize; // 佇列長度 + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* 獲取佇列的容量 */ + int Capacity() { + return nums.Length; + } + + /* 獲取佇列的長度 */ + public int Size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + public bool IsEmpty() { + return queSize == 0; + } + + /* 入列 */ + public void Push(int num) { + if (queSize == Capacity()) { + Console.WriteLine("佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + int rear = (front + queSize) % Capacity(); + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 出列 */ + public int Pop() { + int num = Peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + front = (front + 1) % Capacity(); + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return nums[front]; + } + + /* 返回陣列 */ + public int[] ToArray() { + // 僅轉換有效長度範圍內的串列元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % this.Capacity()]; + } + return res; + } +} + +public class array_queue { + [Test] + public void Test() { + /* 初始化佇列 */ + int capacity = 10; + ArrayQueue queue = new(capacity); + + /* 元素入列 */ + queue.Push(1); + queue.Push(3); + queue.Push(2); + queue.Push(5); + queue.Push(4); + Console.WriteLine("佇列 queue = " + string.Join(",", queue.ToArray())); + + /* 訪問佇列首元素 */ + int peek = queue.Peek(); + Console.WriteLine("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.Pop(); + Console.WriteLine("出列元素 pop = " + pop + ",出列後 queue = " + string.Join(",", queue.ToArray())); + + /* 獲取佇列的長度 */ + int size = queue.Size(); + Console.WriteLine("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + bool isEmpty = queue.IsEmpty(); + Console.WriteLine("佇列是否為空 = " + isEmpty); + + /* 測試環形陣列 */ + for (int i = 0; i < 10; i++) { + queue.Push(i); + queue.Pop(); + Console.WriteLine("第 " + i + " 輪入列 + 出列後 queue = " + string.Join(",", queue.ToArray())); + } + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/array_stack.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/array_stack.cs new file mode 100644 index 000000000..654311550 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/array_stack.cs @@ -0,0 +1,84 @@ +/** + * File: array_stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + List stack; + public ArrayStack() { + // 初始化串列(動態陣列) + stack = []; + } + + /* 獲取堆疊的長度 */ + public int Size() { + return stack.Count; + } + + /* 判斷堆疊是否為空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入堆疊 */ + public void Push(int num) { + stack.Add(num); + } + + /* 出堆疊 */ + public int Pop() { + if (IsEmpty()) + throw new Exception(); + var val = Peek(); + stack.RemoveAt(Size() - 1); + return val; + } + + /* 訪問堆疊頂元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stack[Size() - 1]; + } + + /* 將 List 轉化為 Array 並返回 */ + public int[] ToArray() { + return [.. stack]; + } +} + +public class array_stack { + [Test] + public void Test() { + /* 初始化堆疊 */ + ArrayStack stack = new(); + + /* 元素入堆疊 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("堆疊 stack = " + string.Join(",", stack.ToArray())); + + /* 訪問堆疊頂元素 */ + int peek = stack.Peek(); + Console.WriteLine("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.Pop(); + Console.WriteLine("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + string.Join(",", stack.ToArray())); + + /* 獲取堆疊的長度 */ + int size = stack.Size(); + Console.WriteLine("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + bool isEmpty = stack.IsEmpty(); + Console.WriteLine("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/deque.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/deque.cs new file mode 100644 index 000000000..b89368461 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/deque.cs @@ -0,0 +1,44 @@ +/** + * File: deque.cs + * Created Time: 2022-12-30 + * Author: moonache (microin1301@outlook.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class deque { + [Test] + public void Test() { + /* 初始化雙向佇列 */ + // 在 C# 中,將鏈結串列 LinkedList 看作雙向佇列來使用 + LinkedList deque = new(); + + /* 元素入列 */ + deque.AddLast(2); // 新增至佇列尾 + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // 新增至佇列首 + deque.AddFirst(1); + Console.WriteLine("雙向佇列 deque = " + string.Join(",", deque)); + + /* 訪問元素 */ + int? peekFirst = deque.First?.Value; // 佇列首元素 + Console.WriteLine("佇列首元素 peekFirst = " + peekFirst); + int? peekLast = deque.Last?.Value; // 佇列尾元素 + Console.WriteLine("佇列尾元素 peekLast = " + peekLast); + + /* 元素出列 */ + deque.RemoveFirst(); // 佇列首元素出列 + Console.WriteLine("佇列首元素出列後 deque = " + string.Join(",", deque)); + deque.RemoveLast(); // 佇列尾元素出列 + Console.WriteLine("佇列尾元素出列後 deque = " + string.Join(",", deque)); + + /* 獲取雙向佇列的長度 */ + int size = deque.Count; + Console.WriteLine("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.Count == 0; + Console.WriteLine("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs new file mode 100644 index 000000000..1379a4182 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs @@ -0,0 +1,177 @@ +/** + * File: linkedlist_deque.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 雙向鏈結串列節點 */ +public class ListNode(int val) { + public int val = val; // 節點值 + public ListNode? next = null; // 後繼節點引用 + public ListNode? prev = null; // 前驅節點引用 +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +public class LinkedListDeque { + ListNode? front, rear; // 頭節點 front, 尾節點 rear + int queSize = 0; // 雙向佇列的長度 + + public LinkedListDeque() { + front = null; + rear = null; + } + + /* 獲取雙向佇列的長度 */ + public int Size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入列操作 */ + void Push(int num, bool isFront) { + ListNode node = new(num); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (IsEmpty()) { + front = node; + rear = node; + } + // 佇列首入列操作 + else if (isFront) { + // 將 node 新增至鏈結串列頭部 + front!.prev = node; + node.next = front; + front = node; // 更新頭節點 + } + // 佇列尾入列操作 + else { + // 將 node 新增至鏈結串列尾部 + rear!.next = node; + node.prev = rear; + rear = node; // 更新尾節點 + } + + queSize++; // 更新佇列長度 + } + + /* 佇列首入列 */ + public void PushFirst(int num) { + Push(num, true); + } + + /* 佇列尾入列 */ + public void PushLast(int num) { + Push(num, false); + } + + /* 出列操作 */ + int? Pop(bool isFront) { + if (IsEmpty()) + throw new Exception(); + int? val; + // 佇列首出列操作 + if (isFront) { + val = front?.val; // 暫存頭節點值 + // 刪除頭節點 + ListNode? fNext = front?.next; + if (fNext != null) { + fNext.prev = null; + front!.next = null; + } + front = fNext; // 更新頭節點 + } + // 佇列尾出列操作 + else { + val = rear?.val; // 暫存尾節點值 + // 刪除尾節點 + ListNode? rPrev = rear?.prev; + if (rPrev != null) { + rPrev.next = null; + rear!.prev = null; + } + rear = rPrev; // 更新尾節點 + } + + queSize--; // 更新佇列長度 + return val; + } + + /* 佇列首出列 */ + public int? PopFirst() { + return Pop(true); + } + + /* 佇列尾出列 */ + public int? PopLast() { + return Pop(false); + } + + /* 訪問佇列首元素 */ + public int? PeekFirst() { + if (IsEmpty()) + throw new Exception(); + return front?.val; + } + + /* 訪問佇列尾元素 */ + public int? PeekLast() { + if (IsEmpty()) + throw new Exception(); + return rear?.val; + } + + /* 返回陣列用於列印 */ + public int?[] ToArray() { + ListNode? node = front; + int?[] res = new int?[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node?.val; + node = node?.next; + } + + return res; + } +} + +public class linkedlist_deque { + [Test] + public void Test() { + /* 初始化雙向佇列 */ + LinkedListDeque deque = new(); + deque.PushLast(3); + deque.PushLast(2); + deque.PushLast(5); + Console.WriteLine("雙向佇列 deque = " + string.Join(" ", deque.ToArray())); + + /* 訪問元素 */ + int? peekFirst = deque.PeekFirst(); + Console.WriteLine("佇列首元素 peekFirst = " + peekFirst); + int? peekLast = deque.PeekLast(); + Console.WriteLine("佇列尾元素 peekLast = " + peekLast); + + /* 元素入列 */ + deque.PushLast(4); + Console.WriteLine("元素 4 佇列尾入列後 deque = " + string.Join(" ", deque.ToArray())); + deque.PushFirst(1); + Console.WriteLine("元素 1 佇列首入列後 deque = " + string.Join(" ", deque.ToArray())); + + /* 元素出列 */ + int? popLast = deque.PopLast(); + Console.WriteLine("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + string.Join(" ", deque.ToArray())); + int? popFirst = deque.PopFirst(); + Console.WriteLine("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + string.Join(" ", deque.ToArray())); + + /* 獲取雙向佇列的長度 */ + int size = deque.Size(); + Console.WriteLine("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.IsEmpty(); + Console.WriteLine("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs new file mode 100644 index 000000000..17515d33e --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs @@ -0,0 +1,106 @@ +/** + * File: linkedlist_queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + ListNode? front, rear; // 頭節點 front ,尾節點 rear + int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* 獲取佇列的長度 */ + public int Size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入列 */ + public void Push(int num) { + // 在尾節點後新增 num + ListNode node = new(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (front == null) { + front = node; + rear = node; + // 如果佇列不為空,則將該節點新增到尾節點後 + } else if (rear != null) { + rear.next = node; + rear = node; + } + queSize++; + } + + /* 出列 */ + public int Pop() { + int num = Peek(); + // 刪除頭節點 + front = front?.next; + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return front!.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + public int[] ToArray() { + if (front == null) + return []; + + ListNode? node = front; + int[] res = new int[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node!.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_queue { + [Test] + public void Test() { + /* 初始化佇列 */ + LinkedListQueue queue = new(); + + /* 元素入列 */ + queue.Push(1); + queue.Push(3); + queue.Push(2); + queue.Push(5); + queue.Push(4); + Console.WriteLine("佇列 queue = " + string.Join(",", queue.ToArray())); + + /* 訪問佇列首元素 */ + int peek = queue.Peek(); + Console.WriteLine("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.Pop(); + Console.WriteLine("出列元素 pop = " + pop + ",出列後 queue = " + string.Join(",", queue.ToArray())); + + /* 獲取佇列的長度 */ + int size = queue.Size(); + Console.WriteLine("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + bool isEmpty = queue.IsEmpty(); + Console.WriteLine("佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs new file mode 100644 index 000000000..0749a21f6 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs @@ -0,0 +1,97 @@ +/** + * File: linkedlist_stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + ListNode? stackPeek; // 將頭節點作為堆疊頂 + int stkSize = 0; // 堆疊的長度 + + public LinkedListStack() { + stackPeek = null; + } + + /* 獲取堆疊的長度 */ + public int Size() { + return stkSize; + } + + /* 判斷堆疊是否為空 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* 入堆疊 */ + public void Push(int num) { + ListNode node = new(num) { + next = stackPeek + }; + stackPeek = node; + stkSize++; + } + + /* 出堆疊 */ + public int Pop() { + int num = Peek(); + stackPeek = stackPeek!.next; + stkSize--; + return num; + } + + /* 訪問堆疊頂元素 */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stackPeek!.val; + } + + /* 將 List 轉化為 Array 並返回 */ + public int[] ToArray() { + if (stackPeek == null) + return []; + + ListNode? node = stackPeek; + int[] res = new int[Size()]; + for (int i = res.Length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_stack { + [Test] + public void Test() { + /* 初始化堆疊 */ + LinkedListStack stack = new(); + + /* 元素入堆疊 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("堆疊 stack = " + string.Join(",", stack.ToArray())); + + /* 訪問堆疊頂元素 */ + int peek = stack.Peek(); + Console.WriteLine("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.Pop(); + Console.WriteLine("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + string.Join(",", stack.ToArray())); + + /* 獲取堆疊的長度 */ + int size = stack.Size(); + Console.WriteLine("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + bool isEmpty = stack.IsEmpty(); + Console.WriteLine("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/queue.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/queue.cs new file mode 100644 index 000000000..bca7cec1d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/queue.cs @@ -0,0 +1,39 @@ +/** + * File: queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class queue { + [Test] + public void Test() { + /* 初始化佇列 */ + Queue queue = new(); + + /* 元素入列 */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + Console.WriteLine("佇列 queue = " + string.Join(",", queue)); + + /* 訪問佇列首元素 */ + int peek = queue.Peek(); + Console.WriteLine("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.Dequeue(); + Console.WriteLine("出列元素 pop = " + pop + ",出列後 queue = " + string.Join(",", queue)); + + /* 獲取佇列的長度 */ + int size = queue.Count; + Console.WriteLine("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + bool isEmpty = queue.Count == 0; + Console.WriteLine("佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_stack_and_queue/stack.cs b/zh-hant/codes/csharp/chapter_stack_and_queue/stack.cs new file mode 100644 index 000000000..f30d4532a --- /dev/null +++ b/zh-hant/codes/csharp/chapter_stack_and_queue/stack.cs @@ -0,0 +1,40 @@ +/** + * File: stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class stack { + [Test] + public void Test() { + /* 初始化堆疊 */ + Stack stack = new(); + + /* 元素入堆疊 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + // 請注意,stack.ToArray() 得到的是倒序序列,即索引 0 為堆疊頂 + Console.WriteLine("堆疊 stack = " + string.Join(",", stack)); + + /* 訪問堆疊頂元素 */ + int peek = stack.Peek(); + Console.WriteLine("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.Pop(); + Console.WriteLine("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + string.Join(",", stack)); + + /* 獲取堆疊的長度 */ + int size = stack.Count; + Console.WriteLine("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + bool isEmpty = stack.Count == 0; + Console.WriteLine("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/csharp/chapter_tree/array_binary_tree.cs b/zh-hant/codes/csharp/chapter_tree/array_binary_tree.cs new file mode 100644 index 000000000..a2e949499 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/array_binary_tree.cs @@ -0,0 +1,129 @@ +/** +* File: array_binary_tree.cs +* Created Time: 2023-07-20 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_tree; + +/* 陣列表示下的二元樹類別 */ +public class ArrayBinaryTree(List arr) { + List tree = new(arr); + + /* 串列容量 */ + public int Size() { + return tree.Count; + } + + /* 獲取索引為 i 節點的值 */ + public int? Val(int i) { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= Size()) + return null; + return tree[i]; + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + public int Left(int i) { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + public int Right(int i) { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + public int Parent(int i) { + return (i - 1) / 2; + } + + /* 層序走訪 */ + public List LevelOrder() { + List res = []; + // 直接走訪陣列 + for (int i = 0; i < Size(); i++) { + if (Val(i).HasValue) + res.Add(Val(i)!.Value); + } + return res; + } + + /* 深度優先走訪 */ + void DFS(int i, string order, List res) { + // 若為空位,則返回 + if (!Val(i).HasValue) + return; + // 前序走訪 + if (order == "pre") + res.Add(Val(i)!.Value); + DFS(Left(i), order, res); + // 中序走訪 + if (order == "in") + res.Add(Val(i)!.Value); + DFS(Right(i), order, res); + // 後序走訪 + if (order == "post") + res.Add(Val(i)!.Value); + } + + /* 前序走訪 */ + public List PreOrder() { + List res = []; + DFS(0, "pre", res); + return res; + } + + /* 中序走訪 */ + public List InOrder() { + List res = []; + DFS(0, "in", res); + return res; + } + + /* 後序走訪 */ + public List PostOrder() { + List res = []; + DFS(0, "post", res); + return res; + } +} + +public class array_binary_tree { + [Test] + public void Test() { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + List arr = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + + TreeNode? root = TreeNode.ListToTree(arr); + Console.WriteLine("\n初始化二元樹\n"); + Console.WriteLine("二元樹的陣列表示:"); + Console.WriteLine(arr.PrintList()); + Console.WriteLine("二元樹的鏈結串列表示:"); + PrintUtil.PrintTree(root); + + // 陣列表示下的二元樹類別 + ArrayBinaryTree abt = new(arr); + + // 訪問節點 + int i = 1; + int l = abt.Left(i); + int r = abt.Right(i); + int p = abt.Parent(i); + Console.WriteLine("\n當前節點的索引為 " + i + " ,值為 " + abt.Val(i)); + Console.WriteLine("其左子節點的索引為 " + l + " ,值為 " + (abt.Val(l).HasValue ? abt.Val(l) : "null")); + Console.WriteLine("其右子節點的索引為 " + r + " ,值為 " + (abt.Val(r).HasValue ? abt.Val(r) : "null")); + Console.WriteLine("其父節點的索引為 " + p + " ,值為 " + (abt.Val(p).HasValue ? abt.Val(p) : "null")); + + // 走訪樹 + List res = abt.LevelOrder(); + Console.WriteLine("\n層序走訪為:" + res.PrintList()); + res = abt.PreOrder(); + Console.WriteLine("前序走訪為:" + res.PrintList()); + res = abt.InOrder(); + Console.WriteLine("中序走訪為:" + res.PrintList()); + res = abt.PostOrder(); + Console.WriteLine("後序走訪為:" + res.PrintList()); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/chapter_tree/avl_tree.cs b/zh-hant/codes/csharp/chapter_tree/avl_tree.cs new file mode 100644 index 000000000..1e1f52dad --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/avl_tree.cs @@ -0,0 +1,216 @@ +/** + * File: avl_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +/* AVL 樹 */ +class AVLTree { + public TreeNode? root; // 根節點 + + /* 獲取節點高度 */ + int Height(TreeNode? node) { + // 空節點高度為 -1 ,葉節點高度為 0 + return node == null ? -1 : node.height; + } + + /* 更新節點高度 */ + void UpdateHeight(TreeNode node) { + // 節點高度等於最高子樹高度 + 1 + node.height = Math.Max(Height(node.left), Height(node.right)) + 1; + } + + /* 獲取平衡因子 */ + public int BalanceFactor(TreeNode? node) { + // 空節點平衡因子為 0 + if (node == null) return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return Height(node.left) - Height(node.right); + } + + /* 右旋操作 */ + TreeNode? RightRotate(TreeNode? node) { + TreeNode? child = node?.left; + TreeNode? grandChild = child?.right; + // 以 child 為原點,將 node 向右旋轉 + child.right = node; + node.left = grandChild; + // 更新節點高度 + UpdateHeight(node); + UpdateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + TreeNode? LeftRotate(TreeNode? node) { + TreeNode? child = node?.right; + TreeNode? grandChild = child?.left; + // 以 child 為原點,將 node 向左旋轉 + child.left = node; + node.right = grandChild; + // 更新節點高度 + UpdateHeight(node); + UpdateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + TreeNode? Rotate(TreeNode? node) { + // 獲取節點 node 的平衡因子 + int balanceFactorInt = BalanceFactor(node); + // 左偏樹 + if (balanceFactorInt > 1) { + if (BalanceFactor(node?.left) >= 0) { + // 右旋 + return RightRotate(node); + } else { + // 先左旋後右旋 + node!.left = LeftRotate(node!.left); + return RightRotate(node); + } + } + // 右偏樹 + if (balanceFactorInt < -1) { + if (BalanceFactor(node?.right) <= 0) { + // 左旋 + return LeftRotate(node); + } else { + // 先右旋後左旋 + node!.right = RightRotate(node!.right); + return LeftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 插入節點 */ + public void Insert(int val) { + root = InsertHelper(root, val); + } + + /* 遞迴插入節點(輔助方法) */ + TreeNode? InsertHelper(TreeNode? node, int val) { + if (node == null) return new TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node.val) + node.left = InsertHelper(node.left, val); + else if (val > node.val) + node.right = InsertHelper(node.right, val); + else + return node; // 重複節點不插入,直接返回 + UpdateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = Rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 刪除節點 */ + public void Remove(int val) { + root = RemoveHelper(root, val); + } + + /* 遞迴刪除節點(輔助方法) */ + TreeNode? RemoveHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. 查詢節點並刪除 */ + if (val < node.val) + node.left = RemoveHelper(node.left, val); + else if (val > node.val) + node.right = RemoveHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == null) + return null; + // 子節點數量 = 1 ,直接刪除 node + else + node = child; + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + TreeNode? temp = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = RemoveHelper(node.right, temp.val!.Value); + node.val = temp.val; + } + } + UpdateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = Rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 查詢節點 */ + public TreeNode? Search(int val) { + TreeNode? cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < val) + cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > val) + cur = cur.left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } +} + +public class avl_tree { + static void TestInsert(AVLTree tree, int val) { + tree.Insert(val); + Console.WriteLine("\n插入節點 " + val + " 後,AVL 樹為"); + PrintUtil.PrintTree(tree.root); + } + + static void TestRemove(AVLTree tree, int val) { + tree.Remove(val); + Console.WriteLine("\n刪除節點 " + val + " 後,AVL 樹為"); + PrintUtil.PrintTree(tree.root); + } + + [Test] + public void Test() { + /* 初始化空 AVL 樹 */ + AVLTree avlTree = new(); + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + TestInsert(avlTree, 1); + TestInsert(avlTree, 2); + TestInsert(avlTree, 3); + TestInsert(avlTree, 4); + TestInsert(avlTree, 5); + TestInsert(avlTree, 8); + TestInsert(avlTree, 7); + TestInsert(avlTree, 9); + TestInsert(avlTree, 10); + TestInsert(avlTree, 6); + + /* 插入重複節點 */ + TestInsert(avlTree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + TestRemove(avlTree, 8); // 刪除度為 0 的節點 + TestRemove(avlTree, 5); // 刪除度為 1 的節點 + TestRemove(avlTree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + TreeNode? node = avlTree.Search(7); + Console.WriteLine("\n查詢到的節點物件為 " + node + ",節點值 = " + node?.val); + } +} diff --git a/zh-hant/codes/csharp/chapter_tree/binary_search_tree.cs b/zh-hant/codes/csharp/chapter_tree/binary_search_tree.cs new file mode 100644 index 000000000..ca0c1ef7f --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/binary_search_tree.cs @@ -0,0 +1,160 @@ +/** + * File: binary_search_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +class BinarySearchTree { + TreeNode? root; + + public BinarySearchTree() { + // 初始化空樹 + root = null; + } + + /* 獲取二元樹根節點 */ + public TreeNode? GetRoot() { + return root; + } + + /* 查詢節點 */ + public TreeNode? Search(int num) { + TreeNode? cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < num) cur = + cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > num) + cur = cur.left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + public void Insert(int num) { + // 若樹為空,則初始化根節點 + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode? cur = root, pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到重複節點,直接返回 + if (cur.val == num) + return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.val < num) + cur = cur.right; + // 插入位置在 cur 的左子樹中 + else + cur = cur.left; + } + + // 插入節點 + TreeNode node = new(num); + if (pre != null) { + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + } + + + /* 刪除節點 */ + public void Remove(int num) { + // 若樹為空,直接提前返回 + if (root == null) + return; + TreeNode? cur = root, pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到待刪除節點,跳出迴圈 + if (cur.val == num) + break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.val < num) + cur = cur.right; + // 待刪除節點在 cur 的左子樹中 + else + cur = cur.left; + } + // 若無待刪除節點,則直接返回 + if (cur == null) + return; + // 子節點數量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + TreeNode? child = cur.left ?? cur.right; + // 刪除節點 cur + if (cur != root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + root = child; + } + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + TreeNode? tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // 遞迴刪除節點 tmp + Remove(tmp.val!.Value); + // 用 tmp 覆蓋 cur + cur.val = tmp.val; + } + } +} + +public class binary_search_tree { + [Test] + public void Test() { + /* 初始化二元搜尋樹 */ + BinarySearchTree bst = new(); + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + foreach (int num in nums) { + bst.Insert(num); + } + + Console.WriteLine("\n初始化的二元樹為\n"); + PrintUtil.PrintTree(bst.GetRoot()); + + /* 查詢節點 */ + TreeNode? node = bst.Search(7); + Console.WriteLine("\n查詢到的節點物件為 " + node + ",節點值 = " + node?.val); + + /* 插入節點 */ + bst.Insert(16); + Console.WriteLine("\n插入節點 16 後,二元樹為\n"); + PrintUtil.PrintTree(bst.GetRoot()); + + /* 刪除節點 */ + bst.Remove(1); + Console.WriteLine("\n刪除節點 1 後,二元樹為\n"); + PrintUtil.PrintTree(bst.GetRoot()); + bst.Remove(2); + Console.WriteLine("\n刪除節點 2 後,二元樹為\n"); + PrintUtil.PrintTree(bst.GetRoot()); + bst.Remove(4); + Console.WriteLine("\n刪除節點 4 後,二元樹為\n"); + PrintUtil.PrintTree(bst.GetRoot()); + } +} diff --git a/zh-hant/codes/csharp/chapter_tree/binary_tree.cs b/zh-hant/codes/csharp/chapter_tree/binary_tree.cs new file mode 100644 index 000000000..8e16c4a50 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/binary_tree.cs @@ -0,0 +1,39 @@ +/** + * File: binary_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree { + [Test] + public void Test() { + /* 初始化二元樹 */ + // 初始化節點 + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + Console.WriteLine("\n初始化二元樹\n"); + PrintUtil.PrintTree(n1); + + /* 插入與刪除節點 */ + TreeNode P = new(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + Console.WriteLine("\n插入節點 P 後\n"); + PrintUtil.PrintTree(n1); + // 刪除節點 P + n1.left = n2; + Console.WriteLine("\n刪除節點 P 後\n"); + PrintUtil.PrintTree(n1); + } +} diff --git a/zh-hant/codes/csharp/chapter_tree/binary_tree_bfs.cs b/zh-hant/codes/csharp/chapter_tree/binary_tree_bfs.cs new file mode 100644 index 000000000..b4e730ff1 --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/binary_tree_bfs.cs @@ -0,0 +1,40 @@ +/** + * File: binary_tree_bfs.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree_bfs { + + /* 層序走訪 */ + List LevelOrder(TreeNode root) { + // 初始化佇列,加入根節點 + Queue queue = new(); + queue.Enqueue(root); + // 初始化一個串列,用於儲存走訪序列 + List list = []; + while (queue.Count != 0) { + TreeNode node = queue.Dequeue(); // 隊列出隊 + list.Add(node.val!.Value); // 儲存節點值 + if (node.left != null) + queue.Enqueue(node.left); // 左子節點入列 + if (node.right != null) + queue.Enqueue(node.right); // 右子節點入列 + } + return list; + } + + [Test] + public void Test() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹\n"); + PrintUtil.PrintTree(root); + + List list = LevelOrder(root!); + Console.WriteLine("\n層序走訪的節點列印序列 = " + string.Join(",", list)); + } +} diff --git a/zh-hant/codes/csharp/chapter_tree/binary_tree_dfs.cs b/zh-hant/codes/csharp/chapter_tree/binary_tree_dfs.cs new file mode 100644 index 000000000..e65d1da8d --- /dev/null +++ b/zh-hant/codes/csharp/chapter_tree/binary_tree_dfs.cs @@ -0,0 +1,59 @@ +/** + * File: binary_tree_dfs.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree_dfs { + List list = []; + + /* 前序走訪 */ + void PreOrder(TreeNode? root) { + if (root == null) return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.Add(root.val!.Value); + PreOrder(root.left); + PreOrder(root.right); + } + + /* 中序走訪 */ + void InOrder(TreeNode? root) { + if (root == null) return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + InOrder(root.left); + list.Add(root.val!.Value); + InOrder(root.right); + } + + /* 後序走訪 */ + void PostOrder(TreeNode? root) { + if (root == null) return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + PostOrder(root.left); + PostOrder(root.right); + list.Add(root.val!.Value); + } + + [Test] + public void Test() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); + Console.WriteLine("\n初始化二元樹\n"); + PrintUtil.PrintTree(root); + + list.Clear(); + PreOrder(root); + Console.WriteLine("\n前序走訪的節點列印序列 = " + string.Join(",", list)); + + list.Clear(); + InOrder(root); + Console.WriteLine("\n中序走訪的節點列印序列 = " + string.Join(",", list)); + + list.Clear(); + PostOrder(root); + Console.WriteLine("\n後序走訪的節點列印序列 = " + string.Join(",", list)); + } +} diff --git a/zh-hant/codes/csharp/csharp.sln b/zh-hant/codes/csharp/csharp.sln new file mode 100644 index 000000000..5df88ec83 --- /dev/null +++ b/zh-hant/codes/csharp/csharp.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE} + EndGlobalSection +EndGlobal diff --git a/zh-hant/codes/csharp/hello-algo.csproj b/zh-hant/codes/csharp/hello-algo.csproj new file mode 100644 index 000000000..43817cc38 --- /dev/null +++ b/zh-hant/codes/csharp/hello-algo.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + hello_algo + enable + enable + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/zh-hant/codes/csharp/utils/ListNode.cs b/zh-hant/codes/csharp/utils/ListNode.cs new file mode 100644 index 000000000..30047c487 --- /dev/null +++ b/zh-hant/codes/csharp/utils/ListNode.cs @@ -0,0 +1,32 @@ +// File: ListNode.cs +// Created Time: 2022-12-16 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.utils; + +/* 鏈結串列節點 */ +public class ListNode(int x) { + public int val = x; + public ListNode? next; + + /* 將陣列反序列化為鏈結串列 */ + public static ListNode? ArrToLinkedList(int[] arr) { + ListNode dum = new(0); + ListNode head = dum; + foreach (int val in arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; + } + + public override string? ToString() { + List list = []; + var head = this; + while (head != null) { + list.Add(head.val.ToString()); + head = head.next; + } + return string.Join("->", list); + } +} diff --git a/zh-hant/codes/csharp/utils/PrintUtil.cs b/zh-hant/codes/csharp/utils/PrintUtil.cs new file mode 100644 index 000000000..538866991 --- /dev/null +++ b/zh-hant/codes/csharp/utils/PrintUtil.cs @@ -0,0 +1,132 @@ +/** +* File: PrintUtil.cs +* Created Time: 2022-12-23 +* Author: haptear (haptear@hotmail.com), krahets (krahets@163.com) +*/ + +namespace hello_algo.utils; + +public class Trunk(Trunk? prev, string str) { + public Trunk? prev = prev; + public string str = str; +}; + +public static class PrintUtil { + /* 列印串列 */ + public static void PrintList(IList list) { + Console.WriteLine("[" + string.Join(", ", list) + "]"); + } + + public static string PrintList(this IEnumerable list) { + return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]"; + } + + /* 列印矩陣 (Array) */ + public static void PrintMatrix(T[][] matrix) { + Console.WriteLine("["); + foreach (T[] row in matrix) { + Console.WriteLine(" " + string.Join(", ", row) + ","); + } + Console.WriteLine("]"); + } + + /* 列印矩陣 (List) */ + public static void PrintMatrix(List> matrix) { + Console.WriteLine("["); + foreach (List row in matrix) { + Console.WriteLine(" " + string.Join(", ", row) + ","); + } + Console.WriteLine("]"); + } + + /* 列印鏈結串列 */ + public static void PrintLinkedList(ListNode? head) { + List list = []; + while (head != null) { + list.Add(head.val.ToString()); + head = head.next; + } + Console.Write(string.Join(" -> ", list)); + } + + /** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ + public static void PrintTree(TreeNode? root) { + PrintTree(root, null, false); + } + + /* 列印二元樹 */ + public static void PrintTree(TreeNode? root, Trunk? prev, bool isRight) { + if (root == null) { + return; + } + + string prev_str = " "; + Trunk trunk = new(prev, prev_str); + + PrintTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.str = prev_str; + } + + ShowTrunks(trunk); + Console.WriteLine(" " + root.val); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = " |"; + + PrintTree(root.left, trunk, false); + } + + public static void ShowTrunks(Trunk? p) { + if (p == null) { + return; + } + + ShowTrunks(p.prev); + Console.Write(p.str); + } + + /* 列印雜湊表 */ + public static void PrintHashMap(Dictionary map) where K : notnull { + foreach (var kv in map.Keys) { + Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); + } + } + + /* 列印堆積 */ + public static void PrintHeap(Queue queue) { + Console.Write("堆積的陣列表示:"); + List list = [.. queue]; + Console.WriteLine(string.Join(',', list)); + Console.WriteLine("堆積的樹狀表示:"); + TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); + PrintTree(tree); + } + + /* 列印優先佇列 */ + public static void PrintHeap(PriorityQueue queue) { + var newQueue = new PriorityQueue(queue.UnorderedItems, queue.Comparer); + Console.Write("堆積的陣列表示:"); + List list = []; + while (newQueue.TryDequeue(out int element, out _)) { + list.Add(element); + } + Console.WriteLine("堆積的樹狀表示:"); + Console.WriteLine(string.Join(',', list.ToList())); + TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); + PrintTree(tree); + } +} \ No newline at end of file diff --git a/zh-hant/codes/csharp/utils/TreeNode.cs b/zh-hant/codes/csharp/utils/TreeNode.cs new file mode 100644 index 000000000..af0f771ec --- /dev/null +++ b/zh-hant/codes/csharp/utils/TreeNode.cs @@ -0,0 +1,67 @@ +/** + * File: TreeNode.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.utils; + +/* 二元樹節點類別 */ +public class TreeNode(int? x) { + public int? val = x; // 節點值 + public int height; // 節點高度 + public TreeNode? left; // 左子節點引用 + public TreeNode? right; // 右子節點引用 + + // 序列化編碼規則請參考: + // 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 + + /* 將串列反序列化為二元樹:遞迴 */ + static TreeNode? ListToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.Count || !arr[i].HasValue) { + return null; + } + TreeNode root = new(arr[i]) { + left = ListToTreeDFS(arr, 2 * i + 1), + right = ListToTreeDFS(arr, 2 * i + 2) + }; + return root; + } + + /* 將串列反序列化為二元樹 */ + public static TreeNode? ListToTree(List arr) { + return ListToTreeDFS(arr, 0); + } + + /* 將二元樹序列化為串列:遞迴 */ + static void TreeToListDFS(TreeNode? root, int i, List res) { + if (root == null) + return; + while (i >= res.Count) { + res.Add(null); + } + res[i] = root.val; + TreeToListDFS(root.left, 2 * i + 1, res); + TreeToListDFS(root.right, 2 * i + 2, res); + } + + /* 將二元樹序列化為串列 */ + public static List TreeToList(TreeNode root) { + List res = []; + TreeToListDFS(root, 0, res); + return res; + } +} diff --git a/zh-hant/codes/csharp/utils/Vertex.cs b/zh-hant/codes/csharp/utils/Vertex.cs new file mode 100644 index 000000000..e1dc7d139 --- /dev/null +++ b/zh-hant/codes/csharp/utils/Vertex.cs @@ -0,0 +1,30 @@ +/** + * File: Vertex.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com) + */ + +namespace hello_algo.utils; + +/* 頂點類別 */ +public class Vertex(int val) { + public int val = val; + + /* 輸入值串列 vals ,返回頂點串列 vets */ + public static Vertex[] ValsToVets(int[] vals) { + Vertex[] vets = new Vertex[vals.Length]; + for (int i = 0; i < vals.Length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + public static List VetsToVals(List vets) { + List vals = []; + foreach (Vertex vet in vets) { + vals.Add(vet.val); + } + return vals; + } +} diff --git a/zh-hant/codes/dart/build.dart b/zh-hant/codes/dart/build.dart new file mode 100644 index 000000000..7bd5b51a1 --- /dev/null +++ b/zh-hant/codes/dart/build.dart @@ -0,0 +1,39 @@ +import 'dart:io'; + +void main() { + Directory foldPath = Directory('codes/dart/'); + List files = foldPath.listSync(); + int totalCount = 0; + int errorCount = 0; + for (var file in files) { + if (file.path.endsWith('build.dart')) continue; + if (file is File && file.path.endsWith('.dart')) { + totalCount++; + try { + Process.runSync('dart', [file.path]); + } catch (e) { + errorCount++; + print('Error: $e'); + print('File: ${file.path}'); + } + } else if (file is Directory) { + List subFiles = file.listSync(); + for (var subFile in subFiles) { + if (subFile is File && subFile.path.endsWith('.dart')) { + totalCount++; + try { + Process.runSync('dart', [subFile.path]); + } catch (e) { + errorCount++; + print('Error: $e'); + print('File: ${file.path}'); + } + } + } + } + } + + print('===== Build Complete ====='); + print('Total: $totalCount'); + print('Error: $errorCount'); +} diff --git a/zh-hant/codes/dart/chapter_array_and_linkedlist/array.dart b/zh-hant/codes/dart/chapter_array_and_linkedlist/array.dart new file mode 100644 index 000000000..4d1c48843 --- /dev/null +++ b/zh-hant/codes/dart/chapter_array_and_linkedlist/array.dart @@ -0,0 +1,105 @@ +/** + * File: array.dart + * Created Time: 2023-01-20 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +import 'dart:math'; + +/* 隨機訪問元素 */ +int randomAccess(List nums) { + // 在區間 [0, nums.length) 中隨機抽取一個數字 + int randomIndex = Random().nextInt(nums.length); + // 獲取並返回隨機元素 + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* 擴展陣列長度 */ +List extend(List nums, int enlarge) { + // 初始化一個擴展長度後的陣列 + List res = List.filled(nums.length + enlarge, 0); + // 將原陣列中的所有元素複製到新陣列 + for (var i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + return res; +} + +/* 在陣列的索引 index 處插入元素 _num */ +void insert(List nums, int _num, int index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (var i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 _num 賦給 index 處元素 + nums[index] = _num; +} + +/* 刪除索引 index 處的元素 */ +void remove(List nums, int index) { + // 把索引 index 之後的所有元素向前移動一位 + for (var i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列元素 */ +void traverse(List nums) { + int count = 0; + // 透過索引走訪陣列 + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 直接走訪陣列元素 + for (int _num in nums) { + count += _num; + } + // 透過 forEach 方法走訪陣列 + nums.forEach((_num) { + count += _num; + }); +} + +/* 在陣列中查詢指定元素 */ +int find(List nums, int target) { + for (var i = 0; i < nums.length; i++) { + if (nums[i] == target) return i; + } + return -1; +} + +/* Driver Code */ +void main() { + /* 初始化陣列 */ + var arr = List.filled(5, 0); + print('陣列 arr = $arr'); + List nums = [1, 3, 2, 5, 4]; + print('陣列 nums = $nums'); + + /* 隨機訪問 */ + int randomNum = randomAccess(nums); + print('在 nums 中獲取隨機元素 $randomNum'); + + /* 長度擴展 */ + nums = 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); + + /* 查詢元素 */ + int index = find(nums, 3); + print("在 nums 中查詢元素 3 ,得到索引 = $index"); +} diff --git a/zh-hant/codes/dart/chapter_array_and_linkedlist/linked_list.dart b/zh-hant/codes/dart/chapter_array_and_linkedlist/linked_list.dart new file mode 100644 index 000000000..415576a32 --- /dev/null +++ b/zh-hant/codes/dart/chapter_array_and_linkedlist/linked_list.dart @@ -0,0 +1,83 @@ +/** + * File: linked_list.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import '../utils/list_node.dart'; +import '../utils/print_util.dart'; + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +void insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +void remove(ListNode n0) { + if (n0.next == null) return; + // n0 -> P -> n1 + ListNode P = n0.next!; + ListNode? n1 = P.next; + n0.next = n1; +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +ListNode? access(ListNode? head, int index) { + for (var i = 0; i < index; i++) { + if (head == null) return null; + head = head.next; + } + return head; +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +int find(ListNode? head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) { + return index; + } + head = head.next; + index++; + } + return -1; +} + +/* Driver Code */ +void main() { + // 初始化鏈結串列 + // 初始化各個節點 + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + + print('初始化的鏈結串列為'); + printLinkedList(n0); + + /* 插入節點 */ + insert(n0, ListNode(0)); + print('插入節點後的鏈結串列為'); + printLinkedList(n0); + + /* 刪除節點 */ + remove(n0); + print('刪除節點後的鏈結串列為'); + printLinkedList(n0); + + /* 訪問節點 */ + ListNode? node = access(n0, 3); + print('鏈結串列中索引 3 處的節點的值 = ${node!.val}'); + + /* 查詢節點 */ + int index = find(n0, 2); + print('鏈結串列中值為 2 的節點的索引 = $index'); +} diff --git a/zh-hant/codes/dart/chapter_array_and_linkedlist/list.dart b/zh-hant/codes/dart/chapter_array_and_linkedlist/list.dart new file mode 100644 index 000000000..9967b4833 --- /dev/null +++ b/zh-hant/codes/dart/chapter_array_and_linkedlist/list.dart @@ -0,0 +1,62 @@ +/** + * File: list.dart + * Created Time: 2023-01-24 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +/* Driver Code */ +void main() { + /* 初始化串列 */ + List nums = [1, 3, 2, 5, 4]; + print('串列 nums = $nums'); + + /* 訪問元素 */ + int _num = nums[1]; + print('訪問索引 1 處的元素,得到 _num = $_num'); + + /* 更新元素 */ + nums[1] = 0; + print('將索引 1 處的元素更新為 0 ,得到 nums = $nums'); + + /* 清空串列 */ + nums.clear(); + print('清空串列後 nums = $nums'); + + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print('新增元素後 nums = $nums'); + + /* 在中間插入元素 */ + nums.insert(3, 6); + print('在索引 3 處插入數字 6 ,得到 nums = $nums'); + + /* 刪除元素 */ + nums.removeAt(3); + print('刪除索引 3 處的元素,得到 nums = $nums'); + + /* 透過索引走訪串列 */ + int count = 0; + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + /* 直接走訪串列元素 */ + count = 0; + for (var x in nums) { + count += x; + } + + /* 拼接兩個串列 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.addAll(nums1); + print('將串列 nums1 拼接到 nums 之後,得到 nums = $nums'); + + /* 排序串列 */ + nums.sort(); + print('排序串列後 nums = $nums'); +} diff --git a/zh-hant/codes/dart/chapter_array_and_linkedlist/my_list.dart b/zh-hant/codes/dart/chapter_array_and_linkedlist/my_list.dart new file mode 100644 index 000000000..b60d9a605 --- /dev/null +++ b/zh-hant/codes/dart/chapter_array_and_linkedlist/my_list.dart @@ -0,0 +1,132 @@ +/** + * File: my_list.dart + * Created Time: 2023-02-05 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 串列類別 */ +class MyList { + late List _arr; // 陣列(儲存串列元素) + int _capacity = 10; // 串列容量 + int _size = 0; // 串列長度(當前元素數量) + int _extendRatio = 2; // 每次串列擴容的倍數 + + /* 建構子 */ + MyList() { + _arr = List.filled(_capacity, 0); + } + + /* 獲取串列長度(當前元素數量)*/ + int size() => _size; + + /* 獲取串列容量 */ + int capacity() => _capacity; + + /* 訪問元素 */ + int get(int index) { + if (index >= _size) throw RangeError('索引越界'); + return _arr[index]; + } + + /* 更新元素 */ + void set(int index, int _num) { + if (index >= _size) throw RangeError('索引越界'); + _arr[index] = _num; + } + + /* 在尾部新增元素 */ + void add(int _num) { + // 元素數量超出容量時,觸發擴容機制 + if (_size == _capacity) extendCapacity(); + _arr[_size] = _num; + // 更新元素數量 + _size++; + } + + /* 在中間插入元素 */ + void insert(int index, int _num) { + if (index >= _size) throw RangeError('索引越界'); + // 元素數量超出容量時,觸發擴容機制 + if (_size == _capacity) extendCapacity(); + // 將索引 index 以及之後的元素都向後移動一位 + for (var j = _size - 1; j >= index; j--) { + _arr[j + 1] = _arr[j]; + } + _arr[index] = _num; + // 更新元素數量 + _size++; + } + + /* 刪除元素 */ + int remove(int index) { + if (index >= _size) throw RangeError('索引越界'); + int _num = _arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for (var j = index; j < _size - 1; j++) { + _arr[j] = _arr[j + 1]; + } + // 更新元素數量 + _size--; + // 返回被刪除的元素 + return _num; + } + + /* 串列擴容 */ + void extendCapacity() { + // 新建一個長度為原陣列 _extendRatio 倍的新陣列 + final _newNums = List.filled(_capacity * _extendRatio, 0); + // 將原陣列複製到新陣列 + List.copyRange(_newNums, 0, _arr); + // 更新 _arr 的引用 + _arr = _newNums; + // 更新串列容量 + _capacity = _arr.length; + } + + /* 將串列轉換為陣列 */ + List toArray() { + List arr = []; + for (var i = 0; i < _size; i++) { + arr.add(get(i)); + } + return arr; + } +} + +/* Driver Code */ +void main() { + /* 初始化串列 */ + MyList nums = MyList(); + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print( + '串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}'); + + /* 在中間插入元素 */ + nums.insert(3, 6); + print('在索引 3 處插入數字 6 ,得到 nums = ${nums.toArray()}'); + + /* 刪除元素 */ + nums.remove(3); + print('刪除索引 3 處的元素,得到 nums = ${nums.toArray()}'); + + /* 訪問元素 */ + int _num = nums.get(1); + print('訪問索引 1 處的元素,得到 _num = $_num'); + + /* 更新元素 */ + nums.set(1, 0); + print('將索引 1 處的元素更新為 0 ,得到 nums = ${nums.toArray()}'); + + /* 測試擴容機制 */ + for (var i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i); + } + print( + '擴容後的串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}'); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/n_queens.dart b/zh-hant/codes/dart/chapter_backtracking/n_queens.dart new file mode 100644 index 000000000..9c2a93fd2 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/n_queens.dart @@ -0,0 +1,75 @@ +/** + * File: n_queens.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:n 皇后 */ +void backtrack( + int row, + int n, + List> state, + List>> res, + List cols, + List diags1, + List diags2, +) { + // 當放置完所有行時,記錄解 + if (row == n) { + List> copyState = []; + for (List sRow in state) { + copyState.add(List.from(sRow)); + } + res.add(copyState); + return; + } + // 走訪所有列 + for (int col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = "Q"; + cols[col] = true; + diags1[diag1] = true; + diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = "#"; + cols[col] = false; + diags1[diag1] = false; + diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +List>> nQueens(int n) { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + List> state = List.generate(n, (index) => List.filled(n, "#")); + List cols = List.filled(n, false); // 記錄列是否有皇后 + List diags1 = List.filled(2 * n - 1, false); // 記錄主對角線上是否有皇后 + List diags2 = List.filled(2 * n - 1, false); // 記錄次對角線上是否有皇后 + List>> res = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +void main() { + int n = 4; + List>> res = nQueens(n); + print("輸入棋盤長寬為 $n"); + print("皇后放置方案共有 ${res.length} 種"); + for (List> state in res) { + print("--------------------"); + for (List row in state) { + print(row); + } + } +} diff --git a/zh-hant/codes/dart/chapter_backtracking/permutations_i.dart b/zh-hant/codes/dart/chapter_backtracking/permutations_i.dart new file mode 100644 index 000000000..56fde7b91 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/permutations_i.dart @@ -0,0 +1,51 @@ +/** + * File: permutations_i.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:全排列 I */ +void backtrack( + List state, + List choices, + List selected, + List> res, +) { + // 當狀態長度等於元素數量時,記錄解 + if (state.length == choices.length) { + res.add(List.from(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.add(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.removeLast(); + } + } +} + +/* 全排列 I */ +List> permutationsI(List nums) { + List> res = []; + backtrack([], nums, List.filled(nums.length, false), res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [1, 2, 3]; + + List> res = permutationsI(nums); + + print("輸入陣列 nums = $nums"); + print("所有排列 res = $res"); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/permutations_ii.dart b/zh-hant/codes/dart/chapter_backtracking/permutations_ii.dart new file mode 100644 index 000000000..1eea3a1f5 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/permutations_ii.dart @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:全排列 II */ +void backtrack( + List state, + List choices, + List selected, + List> res, +) { + // 當狀態長度等於元素數量時,記錄解 + if (state.length == choices.length) { + res.add(List.from(state)); + return; + } + // 走訪所有選擇 + Set duplicated = {}; + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.contains(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.add(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.add(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.removeLast(); + } + } +} + +/* 全排列 II */ +List> permutationsII(List nums) { + List> res = []; + backtrack([], nums, List.filled(nums.length, false), res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [1, 2, 2]; + + List> res = permutationsII(nums); + + print("輸入陣列 nums = $nums"); + print("所有排列 res = $res"); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart new file mode 100644 index 000000000..fe9c1ce78 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart @@ -0,0 +1,35 @@ +/** + * File: preorder_traversal_i_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前序走訪:例題一 */ +void preOrder(TreeNode? root, List res) { + if (root == null) { + return; + } + if (root.val == 7) { + // 記錄解 + res.add(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二元樹"); + printTree(root); + + // 前序走訪 + List res = []; + preOrder(root, res); + + print("\n輸出所有值為 7 的節點"); + print(List.generate(res.length, (i) => res[i].val)); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart new file mode 100644 index 000000000..0cb2ee0ad --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_ii_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前序走訪:例題二 */ +void preOrder( + TreeNode? root, + List path, + List> res, +) { + if (root == null) { + return; + } + + // 嘗試 + path.add(root); + if (root.val == 7) { + // 記錄解 + res.add(List.from(path)); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.removeLast(); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二元樹"); + printTree(root); + + // 前序走訪 + List path = []; + List> res = []; + preOrder(root, path, res); + + print("\n輸出所有根節點到節點 7 的路徑"); + for (List vals in res) { + print(List.generate(vals.length, (i) => vals[i].val)); + } +} diff --git a/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart new file mode 100644 index 000000000..1cdf34c02 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前序走訪:例題三 */ +void preOrder( + TreeNode? root, + List path, + List> res, +) { + if (root == null || root.val == 3) { + return; + } + + // 嘗試 + path.add(root); + if (root.val == 7) { + // 記錄解 + res.add(List.from(path)); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.removeLast(); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二元樹"); + printTree(root); + + // 前序走訪 + List path = []; + List> res = []; + preOrder(root, path, res); + + print("\n輸出所有根節點到節點 7 的路徑"); + for (List vals in res) { + print(List.generate(vals.length, (i) => vals[i].val)); + } +} diff --git a/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart new file mode 100644 index 000000000..e12cd1328 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart @@ -0,0 +1,73 @@ +/** + * File: preorder_traversal_iii_template.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 判斷當前狀態是否為解 */ +bool isSolution(List state) { + return state.isNotEmpty && state.last.val == 7; +} + +/* 記錄解 */ +void recordSolution(List state, List> res) { + res.add(List.from(state)); +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +bool isValid(List state, TreeNode? choice) { + return choice != null && choice.val != 3; +} + +/* 更新狀態 */ +void makeChoice(List state, TreeNode? choice) { + state.add(choice!); +} + +/* 恢復狀態 */ +void undoChoice(List state, TreeNode? choice) { + state.removeLast(); +} + +/* 回溯演算法:例題三 */ +void backtrack( + List state, + List choices, + List> res, +) { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + } + // 走訪所有選擇 + for (TreeNode? choice in choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + // 進行下一輪選擇 + backtrack(state, [choice!.left, choice.right], res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n初始化二元樹"); + printTree(root); + + // 回溯演算法 + List> res = []; + backtrack([], [root!], res); + print("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); + for (List path in res) { + print(List.from(path.map((e) => e.val))); + } +} diff --git a/zh-hant/codes/dart/chapter_backtracking/subset_sum_i.dart b/zh-hant/codes/dart/chapter_backtracking/subset_sum_i.dart new file mode 100644 index 000000000..135e05771 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/subset_sum_i.dart @@ -0,0 +1,56 @@ +/** + * File: subset_sum_i.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +void backtrack( + List state, + int target, + List choices, + int start, + List> res, +) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(List.from(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast(); + } +} + +/* 求解子集和 I */ +List> subsetSumI(List nums, int target) { + List state = []; // 狀態(子集) + nums.sort(); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [3, 4, 5]; + int target = 9; + + List> res = subsetSumI(nums, target); + + print("輸入陣列 nums = $nums, target = $target"); + print("所有和等於 $target 的子集 res = $res"); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/subset_sum_i_naive.dart b/zh-hant/codes/dart/chapter_backtracking/subset_sum_i_naive.dart new file mode 100644 index 000000000..71b2c5e37 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/subset_sum_i_naive.dart @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +void backtrack( + List state, + int target, + int total, + List choices, + List> res, +) { + // 子集和等於 target 時,記錄解 + if (total == target) { + res.add(List.from(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.length; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast(); + } +} + +/* 求解子集和 I(包含重複子集) */ +List> subsetSumINaive(List nums, int target) { + List state = []; // 狀態(子集) + int total = 0; // 元素和 + List> res = []; // 結果串列(子集串列) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [3, 4, 5]; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + print("輸入陣列 nums = $nums, target = $target"); + print("所有和等於 $target 的子集 res = $res"); + print("請注意,該方法輸出的結果包含重複集合"); +} diff --git a/zh-hant/codes/dart/chapter_backtracking/subset_sum_ii.dart b/zh-hant/codes/dart/chapter_backtracking/subset_sum_ii.dart new file mode 100644 index 000000000..51969c1c3 --- /dev/null +++ b/zh-hant/codes/dart/chapter_backtracking/subset_sum_ii.dart @@ -0,0 +1,61 @@ +/** + * File: subset_sum_ii.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯演算法:子集和 II */ +void backtrack( + List state, + int target, + List choices, + int start, + List> res, +) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(List.from(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast(); + } +} + +/* 求解子集和 II */ +List> subsetSumII(List nums, int target) { + List state = []; // 狀態(子集) + nums.sort(); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [4, 4, 5]; + int target = 9; + + List> res = subsetSumII(nums, target); + + print("輸入陣列 nums = $nums, target = $target"); + print("所有和等於 $target 的子集 res = $res"); +} diff --git a/zh-hant/codes/dart/chapter_computational_complexity/iteration.dart b/zh-hant/codes/dart/chapter_computational_complexity/iteration.dart new file mode 100644 index 000000000..1e7812e59 --- /dev/null +++ b/zh-hant/codes/dart/chapter_computational_complexity/iteration.dart @@ -0,0 +1,72 @@ +/** + * File: iteration.dart + * Created Time: 2023-08-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* for 迴圈 */ +int forLoop(int n) { + int res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 迴圈 */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; +} + +/* while 迴圈(兩次更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; +} + +/* 雙層 for 迴圈 */ +String nestedForLoop(int n) { + String res = ""; + // 迴圈 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res += "($i, $j), "; + } + } + return res; +} + +/* Driver Code */ +void main() { + int n = 5; + int res; + + res = forLoop(n); + print("\nfor 迴圈的求和結果 res = $res"); + + res = whileLoop(n); + print("\nwhile 迴圈的求和結果 res = $res"); + + res = whileLoopII(n); + print("\nwhile 迴圈(兩次更新)的求和結果 res = $res"); + + String resStr = nestedForLoop(n); + print("\n雙層 for 迴圈的結果 $resStr"); +} diff --git a/zh-hant/codes/dart/chapter_computational_complexity/recursion.dart b/zh-hant/codes/dart/chapter_computational_complexity/recursion.dart new file mode 100644 index 000000000..6f7df2f15 --- /dev/null +++ b/zh-hant/codes/dart/chapter_computational_complexity/recursion.dart @@ -0,0 +1,70 @@ +/** + * File: recursion.dart + * Created Time: 2023-08-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 遞迴 */ +int recur(int n) { + // 終止條件 + if (n == 1) return 1; + // 遞:遞迴呼叫 + int res = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +/* 使用迭代模擬遞迴 */ +int forLoopRecur(int n) { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + List stack = []; + int res = 0; + // 遞:遞迴呼叫 + for (int i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.add(i); + } + // 迴:返回結果 + while (!stack.isEmpty) { + // 透過“出堆疊操作”模擬“迴” + res += stack.removeLast(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾遞迴 */ +int tailRecur(int n, int res) { + // 終止條件 + if (n == 0) return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +/* 費波那契數列:遞迴 */ +int fib(int n) { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +/* Driver Code */ +void main() { + int n = 5; + int res; + + res = recur(n); + print("\n遞迴函式的求和結果 res = $res"); + + res = tailRecur(n, 0); + print("\n尾遞迴函式的求和結果 res = $res"); + + res = forLoopRecur(n); + print("\n使用迭代模擬遞迴求和結果 res = $res"); + + res = fib(n); + print("\n費波那契數列的第 $n 項為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_computational_complexity/space_complexity.dart b/zh-hant/codes/dart/chapter_computational_complexity/space_complexity.dart new file mode 100644 index 000000000..057b3dc5b --- /dev/null +++ b/zh-hant/codes/dart/chapter_computational_complexity/space_complexity.dart @@ -0,0 +1,106 @@ +/** + * File: space_complexity.dart + * Created Time: 2023-2-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +import 'dart:collection'; +import '../utils/list_node.dart'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 函式 */ +int function() { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +void constant(int n) { + // 常數、變數、物件佔用 O(1) 空間 + final int a = 0; + int b = 0; + List nums = List.filled(10000, 0); + ListNode node = ListNode(0); + // 迴圈中的變數佔用 O(1) 空間 + for (var i = 0; i < n; i++) { + int c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (var i = 0; i < n; i++) { + function(); + } +} + +/* 線性階 */ +void linear(int n) { + // 長度為 n 的陣列佔用 O(n) 空間 + List nums = List.filled(n, 0); + // 長度為 n 的串列佔用 O(n) 空間 + List nodes = []; + for (var i = 0; i < n; i++) { + nodes.add(ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + Map map = HashMap(); + for (var i = 0; i < n; i++) { + map.putIfAbsent(i, () => i.toString()); + } +} + +/* 線性階(遞迴實現) */ +void linearRecur(int n) { + print('遞迴 n = $n'); + if (n == 1) return; + linearRecur(n - 1); +} + +/* 平方階 */ +void quadratic(int n) { + // 矩陣佔用 O(n^2) 空間 + List> numMatrix = List.generate(n, (_) => List.filled(n, 0)); + // 二維串列佔用 O(n^2) 空間 + List> numList = []; + for (var i = 0; i < n; i++) { + List tmp = []; + for (int j = 0; j < n; j++) { + tmp.add(0); + } + numList.add(tmp); + } +} + +/* 平方階(遞迴實現) */ +int quadraticRecur(int n) { + if (n <= 0) return 0; + List nums = List.filled(n, 0); + print('遞迴 n = $n 中的 nums 長度 = ${nums.length}'); + return quadraticRecur(n - 1); +} + +/* 指數階(建立滿二元樹) */ +TreeNode? buildTree(int n) { + if (n == 0) return null; + TreeNode root = TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +void main() { + int n = 5; + // 常數階 + constant(n); + // 線性階 + linear(n); + linearRecur(n); + // 平方階 + quadratic(n); + quadraticRecur(n); + // 指數階 + TreeNode? root = buildTree(n); + printTree(root); +} diff --git a/zh-hant/codes/dart/chapter_computational_complexity/time_complexity.dart b/zh-hant/codes/dart/chapter_computational_complexity/time_complexity.dart new file mode 100644 index 000000000..49b9b2a2b --- /dev/null +++ b/zh-hant/codes/dart/chapter_computational_complexity/time_complexity.dart @@ -0,0 +1,165 @@ +/** + * File: time_complexity.dart + * Created Time: 2023-02-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +/* 常數階 */ +int constant(int n) { + int count = 0; + int size = 100000; + for (var i = 0; i < size; i++) { + count++; + } + return count; +} + +/* 線性階 */ +int linear(int n) { + int count = 0; + for (var i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 線性階(走訪陣列) */ +int arrayTraversal(List nums) { + int count = 0; + // 迴圈次數與陣列長度成正比 + for (var _num in nums) { + count++; + } + return count; +} + +/* 平方階 */ +int quadratic(int n) { + int count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方階(泡沫排序) */ +int bubbleSort(List nums) { + int count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (var i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (var j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +/* 指數階(迴圈實現) */ +int exponential(int n) { + int count = 0, base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (var i = 0; i < n; i++) { + for (var j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指數階(遞迴實現) */ +int expRecur(int n) { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 對數階(迴圈實現) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n ~/ 2; + count++; + } + return count; +} + +/* 對數階(遞迴實現) */ +int logRecur(int n) { + if (n <= 1) return 0; + return logRecur(n ~/ 2) + 1; +} + +/* 線性對數階 */ +int linearLogRecur(int n) { + if (n <= 1) return 1; + int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2); + for (var i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乘階(遞迴實現) */ +int factorialRecur(int n) { + if (n == 0) return 1; + int count = 0; + // 從 1 個分裂出 n 個 + for (var i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +void main() { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + int n = 8; + print('輸入資料大小 n = $n'); + + int count = constant(n); + print('常數階的操作數量 = $count'); + + count = linear(n); + print('線性階的操作數量 = $count'); + + count = arrayTraversal(List.filled(n, 0)); + print('線性階(走訪陣列)的操作數量 = $count'); + + count = quadratic(n); + print('平方階的操作數量 = $count'); + final nums = List.filled(n, 0); + for (int i = 0; i < n; i++) { + nums[i] = n - i; // [n,n-1,...,2,1] + } + count = bubbleSort(nums); + print('平方階(泡沫排序)的操作數量 = $count'); + + count = exponential(n); + print('指數階(迴圈實現)的操作數量 = $count'); + count = expRecur(n); + print('指數階(遞迴實現)的操作數量 = $count'); + + count = logarithmic(n); + print('對數階(迴圈實現)的操作數量 = $count'); + count = logRecur(n); + print('對數階(遞迴實現)的操作數量 = $count'); + + count = linearLogRecur(n); + print('線性對數階(遞迴實現)的操作數量 = $count'); + + count = factorialRecur(n); + print('階乘階(遞迴實現)的操作數量 = $count'); +} diff --git a/zh-hant/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart b/zh-hant/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart new file mode 100644 index 000000000..7e2593f4f --- /dev/null +++ b/zh-hant/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart @@ -0,0 +1,40 @@ +/** + * File: worst_best_time_complexity.dart + * Created Time: 2023-02-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +List randomNumbers(int n) { + final nums = List.filled(n, 0); + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (var i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 隨機打亂陣列元素 + nums.shuffle(); + + return nums; +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +int findOne(List nums) { + for (var i = 0; i < nums.length; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) return i; + } + + return -1; +} + +/* Driver Code */ +void main() { + for (var i = 0; i < 10; i++) { + int n = 100; + final nums = randomNumbers(n); + int index = findOne(nums); + print('\n陣列 [ 1, 2, ..., n ] 被打亂後 = $nums'); + print('數字 1 的索引為 + $index'); + } +} diff --git a/zh-hant/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart b/zh-hant/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart new file mode 100644 index 000000000..4d5b17ab4 --- /dev/null +++ b/zh-hant/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart @@ -0,0 +1,42 @@ +/** + * File: binary_search_recur.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 二分搜尋:問題 f(i, j) */ +int dfs(List nums, int target, int i, int j) { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + int m = (i + j) ~/ 2; + if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } +} + +/* 二分搜尋 */ +int binarySearch(List nums, int target) { + int n = nums.length; + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +void main() { + int target = 6; + List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分搜尋(雙閉區間) + int index = binarySearch(nums, target); + print("目標元素 6 的索引 = $index"); +} diff --git a/zh-hant/codes/dart/chapter_divide_and_conquer/build_tree.dart b/zh-hant/codes/dart/chapter_divide_and_conquer/build_tree.dart new file mode 100644 index 000000000..1ce99f9ca --- /dev/null +++ b/zh-hant/codes/dart/chapter_divide_and_conquer/build_tree.dart @@ -0,0 +1,55 @@ +/** + * File: build_tree.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 構建二元樹:分治 */ +TreeNode? dfs( + List preorder, + Map inorderMap, + int i, + int l, + int r, +) { + // 子樹區間為空時終止 + if (r - l < 0) { + return null; + } + // 初始化根節點 + TreeNode? root = TreeNode(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + int m = inorderMap[preorder[i]]!; + // 子問題:構建左子樹 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; +} + +/* 構建二元樹 */ +TreeNode? buildTree(List preorder, List inorder) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + Map inorderMap = {}; + for (int i = 0; i < inorder.length; i++) { + inorderMap[inorder[i]] = i; + } + TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +void main() { + List preorder = [3, 9, 2, 1, 7]; + List inorder = [9, 3, 1, 2, 7]; + print("前序走訪 = $preorder"); + print("中序走訪 = $inorder"); + + TreeNode? root = buildTree(preorder, inorder); + print("構建的二元樹為:"); + printTree(root!); +} diff --git a/zh-hant/codes/dart/chapter_divide_and_conquer/hanota.dart b/zh-hant/codes/dart/chapter_divide_and_conquer/hanota.dart new file mode 100644 index 000000000..e6d383ef4 --- /dev/null +++ b/zh-hant/codes/dart/chapter_divide_and_conquer/hanota.dart @@ -0,0 +1,54 @@ +/** + * File: hanota.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 移動一個圓盤 */ +void move(List src, List tar) { + // 從 src 頂部拿出一個圓盤 + int pan = src.removeLast(); + // 將圓盤放入 tar 頂部 + tar.add(pan); +} + +/* 求解河內塔問題 f(i) */ +void dfs(int i, List src, List buf, List tar) { + // 若 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); +} + +/* 求解河內塔問題 */ +void solveHanota(List A, List B, List C) { + int n = A.length; + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +void main() { + // 串列尾部是柱子頂部 + List A = [5, 4, 3, 2, 1]; + List B = []; + List C = []; + print("初始狀態下:"); + print("A = $A"); + print("B = $B"); + print("C = $C"); + + solveHanota(A, B, C); + + print("圓盤移動完成後:"); + print("A = $A"); + print("B = $B"); + print("C = $C"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart new file mode 100644 index 000000000..d9216ec8f --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_backtrack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 回溯 */ +void backtrack(List choices, int state, int n, List res) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) { + res[0]++; + } + // 走訪所有選擇 + for (int choice in choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +int climbingStairsBacktrack(int n) { + List choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 + int state = 0; // 從第 0 階開始爬 + List res = []; + res.add(0); // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + print("爬 $n 階樓梯共有 $res 種方案"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart new file mode 100644 index 000000000..84e9990a1 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart @@ -0,0 +1,33 @@ +/** + * File: climbing_stairs_constraint_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 帶約束爬樓梯:動態規劃 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + List> dp = List.generate(n + 1, (index) => List.filled(3, 0)); + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + 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 */ +void main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + print("爬 $n 階樓梯共有 $res 種方案"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart new file mode 100644 index 000000000..32dff0872 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart @@ -0,0 +1,27 @@ +/** + * File: climbing_stairs_dfs.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 搜尋 */ +int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬樓梯:搜尋 */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDFS(n); + print("爬 $n 階樓梯共有 $res 種方案"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart new file mode 100644 index 000000000..3e04a87c4 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart @@ -0,0 +1,33 @@ +/** + * File: climbing_stairs_dfs_mem.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 記憶化搜尋 */ +int dfs(int i, List mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) return i; + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +/* 爬樓梯:記憶化搜尋 */ +int climbingStairsDFSMem(int n) { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + List mem = List.filled(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + print("爬 $n 階樓梯共有 $res 種方案"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart new file mode 100644 index 000000000..c5f7fd0f1 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart @@ -0,0 +1,43 @@ +/** + * File: climbing_stairs_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 爬樓梯:動態規劃 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) return n; + // 初始化 dp 表,用於儲存子問題的解 + List dp = List.filled(n + 1, 0); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDP(n); + print("爬 $n 階樓梯共有 $res 種方案"); + + res = climbingStairsDPComp(n); + print("爬 $n 階樓梯共有 $res 種方案"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/coin_change.dart b/zh-hant/codes/dart/chapter_dynamic_programming/coin_change.dart new file mode 100644 index 000000000..8c975e23b --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/coin_change.dart @@ -0,0 +1,68 @@ +/** + * File: coin_change.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 零錢兌換:動態規劃 */ +int coinChangeDP(List coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); + // 狀態轉移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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] != MAX ? dp[n][amt] : -1; +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +int coinChangeDPComp(List coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + List dp = List.filled(amt + 1, MAX); + dp[0] = 0; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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] != MAX ? dp[amt] : -1; +} + +/* Driver Code */ +void main() { + List coins = [1, 2, 5]; + int amt = 4; + + // 動態規劃 + int res = coinChangeDP(coins, amt); + print("湊到目標金額所需的最少硬幣數量為 $res"); + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt); + print("湊到目標金額所需的最少硬幣數量為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/coin_change_ii.dart b/zh-hant/codes/dart/chapter_dynamic_programming/coin_change_ii.dart new file mode 100644 index 000000000..2bf1dc355 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/coin_change_ii.dart @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 零錢兌換 II:動態規劃 */ +int coinChangeIIDP(List coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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]; +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +int coinChangeIIDPComp(List coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + List dp = List.filled(amt + 1, 0); + dp[0] = 1; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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 */ +void main() { + List coins = [1, 2, 5]; + int amt = 5; + + // 動態規劃 + int res = coinChangeIIDP(coins, amt); + print("湊出目標金額的硬幣組合數量為 $res"); + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins, amt); + print("湊出目標金額的硬幣組合數量為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/edit_distance.dart b/zh-hant/codes/dart/chapter_dynamic_programming/edit_distance.dart new file mode 100644 index 000000000..b8542f5ef --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/edit_distance.dart @@ -0,0 +1,125 @@ +/** + * File: edit_distance.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 編輯距離:暴力搜尋 */ +int editDistanceDFS(String s, String t, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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 editDistanceDFS(s, t, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int delete = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return min(min(insert, delete), replace) + 1; +} + +/* 編輯距離:記憶化搜尋 */ +int editDistanceDFSMem(String s, String t, List> mem, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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 editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int delete = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = min(min(insert, delete), replace) + 1; + return mem[i][j]; +} + +/* 編輯距離:動態規劃 */ +int editDistanceDP(String s, String t) { + int n = s.length, m = t.length; + List> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0)); + // 狀態轉移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +int editDistanceDPComp(String s, String t) { + int n = s.length, m = t.length; + List dp = List.filled(m + 1, 0); + // 狀態轉移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (int i = 1; i <= n; i++) { + // 狀態轉移:首列 + int leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +void main() { + String s = "bag"; + String t = "pack"; + int n = s.length, m = t.length; + + // 暴力搜尋 + int res = editDistanceDFS(s, t, n, m); + print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); + + // 記憶化搜尋 + List> mem = List.generate(n + 1, (_) => List.filled(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); + + // 動態規劃 + res = editDistanceDP(s, t); + print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t); + print("將 " + s + " 更改為 " + t + " 最少需要編輯 $res 步"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/knapsack.dart b/zh-hant/codes/dart/chapter_dynamic_programming/knapsack.dart new file mode 100644 index 000000000..f38ee8cfd --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/knapsack.dart @@ -0,0 +1,116 @@ +/** + * File: knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 0-1 背包:暴力搜尋 */ +int knapsackDFS(List wgt, List val, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return max(no, yes); +} + +/* 0-1 背包:記憶化搜尋 */ +int knapsackDFSMem( + List wgt, + List val, + List> mem, + int i, + int c, +) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:動態規劃 */ +int knapsackDP(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + 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]; +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +int knapsackDPComp(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List dp = List.filled(cap + 1, 0); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + // 倒序走訪 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +void main() { + List wgt = [10, 20, 30, 40, 50]; + List val = [50, 120, 150, 210, 240]; + int cap = 50; + int n = wgt.length; + + // 暴力搜尋 + int res = knapsackDFS(wgt, val, n, cap); + print("不超過背包容量的最大物品價值為 $res"); + + // 記憶化搜尋 + List> mem = + List.generate(n + 1, (index) => List.filled(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + print("不超過背包容量的最大物品價值為 $res"); + + // 動態規劃 + res = knapsackDP(wgt, val, cap); + print("不超過背包容量的最大物品價值為 $res"); + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, val, cap); + print("不超過背包容量的最大物品價值為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart b/zh-hant/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart new file mode 100644 index 000000000..99a436308 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart @@ -0,0 +1,48 @@ +/** + * File: min_cost_climbing_stairs_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 爬樓梯最小代價:動態規劃 */ +int minCostClimbingStairsDP(List cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) return cost[n]; + // 初始化 dp 表,用於儲存子問題的解 + List dp = List.filled(n + 1, 0); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +int minCostClimbingStairsDPComp(List cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +void main() { + List cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + print("輸入樓梯的代價串列為 $cost"); + + int res = minCostClimbingStairsDP(cost); + print("爬完樓梯的最低代價為 $res"); + + res = minCostClimbingStairsDPComp(cost); + print("爬完樓梯的最低代價為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/min_path_sum.dart b/zh-hant/codes/dart/chapter_dynamic_programming/min_path_sum.dart new file mode 100644 index 000000000..4cf15a754 --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/min_path_sum.dart @@ -0,0 +1,120 @@ +/** + * File: min_path_sum.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最小路徑和:暴力搜尋 */ +int minPathSumDFS(List> grid, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + // 在 Dart 中,int 型別是固定範圍的整數,不存在表示“無窮大”的值 + return BigInt.from(2).pow(31).toInt(); + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return min(left, up) + grid[i][j]; +} + +/* 最小路徑和:記憶化搜尋 */ +int minPathSumDFSMem(List> grid, List> mem, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + // 在 Dart 中,int 型別是固定範圍的整數,不存在表示“無窮大”的值 + return BigInt.from(2).pow(31).toInt(); + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路徑和:動態規劃 */ +int minPathSumDP(List> grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + List> dp = List.generate(n, (i) => List.filled(m, 0)); + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +int minPathSumDPComp(List> grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + List dp = List.filled(m, 0); + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (int i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +void main() { + List> grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ]; + int n = grid.length, m = grid[0].length; + +// 暴力搜尋 + int res = minPathSumDFS(grid, n - 1, m - 1); + print("從左上角到右下角的做小路徑和為 $res"); + +// 記憶化搜尋 + List> mem = List.generate(n, (i) => List.filled(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + print("從左上角到右下角的做小路徑和為 $res"); + +// 動態規劃 + res = minPathSumDP(grid); + print("從左上角到右下角的做小路徑和為 $res"); + +// 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid); + print("從左上角到右下角的做小路徑和為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart b/zh-hant/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart new file mode 100644 index 000000000..3b8c7b88d --- /dev/null +++ b/zh-hant/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart @@ -0,0 +1,62 @@ +/** + * File: unbounded_knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 完全背包:動態規劃 */ +int unboundedKnapsackDP(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + 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]; +} + +/* 完全背包:空間最佳化後的動態規劃 */ +int unboundedKnapsackDPComp(List wgt, List val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + List dp = List.filled(cap + 1, 0); + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + 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 */ +void main() { + List wgt = [1, 2, 3]; + List val = [5, 11, 15]; + int cap = 4; + + // 動態規劃 + int res = unboundedKnapsackDP(wgt, val, cap); + print("不超過背包容量的最大物品價值為 $res"); + + // 空間最佳化後的動態規劃 + int resComp = unboundedKnapsackDPComp(wgt, val, cap); + print("不超過背包容量的最大物品價值為 $resComp"); +} diff --git a/zh-hant/codes/dart/chapter_graph/graph_adjacency_list.dart b/zh-hant/codes/dart/chapter_graph/graph_adjacency_list.dart new file mode 100644 index 000000000..01ec2db36 --- /dev/null +++ b/zh-hant/codes/dart/chapter_graph/graph_adjacency_list.dart @@ -0,0 +1,124 @@ +/** + * File: graph_adjacency_list.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/vertex.dart'; + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + Map> adjList = {}; + + /* 建構子 */ + GraphAdjList(List> edges) { + for (List edge in edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + int size() { + return adjList.length; + } + + /* 新增邊 */ + void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // 新增邊 vet1 - vet2 + adjList[vet1]!.add(vet2); + adjList[vet2]!.add(vet1); + } + + /* 刪除邊 */ + void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // 刪除邊 vet1 - vet2 + adjList[vet1]!.remove(vet2); + adjList[vet2]!.remove(vet1); + } + + /* 新增頂點 */ + void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) return; + // 在鄰接表中新增一個新鏈結串列 + adjList[vet] = []; + } + + /* 刪除頂點 */ + void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) { + throw ArgumentError; + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.remove(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + adjList.forEach((key, value) { + value.remove(vet); + }); + } + + /* 列印鄰接表 */ + void printAdjList() { + print("鄰接表 ="); + adjList.forEach((key, value) { + List tmp = []; + for (Vertex vertex in value) { + tmp.add(vertex.val); + } + print("${key.val}: $tmp,"); + }); + } +} + +/* Driver Code */ +void main() { + /* 初始化無向圖 */ + List v = Vertex.valsToVets([1, 3, 2, 5, 4]); + List> 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]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初始化後,圖為"); + graph.printAdjList(); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + print("\n新增邊 1-2 後,圖為"); + graph.printAdjList(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + print("\n刪除邊 1-3 後,圖為"); + graph.printAdjList(); + + /* 新增頂點 */ + Vertex v5 = Vertex(6); + graph.addVertex(v5); + print("\n新增頂點 6 後,圖為"); + graph.printAdjList(); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(v[1]); + print("\n刪除頂點 3 後,圖為"); + graph.printAdjList(); +} diff --git a/zh-hant/codes/dart/chapter_graph/graph_adjacency_matrix.dart b/zh-hant/codes/dart/chapter_graph/graph_adjacency_matrix.dart new file mode 100644 index 000000000..c96c50d3c --- /dev/null +++ b/zh-hant/codes/dart/chapter_graph/graph_adjacency_matrix.dart @@ -0,0 +1,133 @@ +/** + * File: graph_adjacency_matrix.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + List vertices = []; // 頂點元素,元素代表“頂點值”,索引代表“頂點索引” + List> adjMat = []; //鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + GraphAdjMat(List vertices, List> edges) { + this.vertices = []; + this.adjMat = []; + // 新增頂點 + for (int val in vertices) { + addVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (List e in edges) { + addEdge(e[0], e[1]); + } + } + + /* 獲取頂點數量 */ + int size() { + return vertices.length; + } + + /* 新增頂點 */ + void addVertex(int val) { + int n = size(); + // 向頂點串列中新增新頂點的值 + vertices.add(val); + // 在鄰接矩陣中新增一行 + List newRow = List.filled(n, 0, growable: true); + adjMat.add(newRow); + // 在鄰接矩陣中新增一列 + for (List row in adjMat) { + row.add(0); + } + } + + /* 刪除頂點 */ + void removeVertex(int index) { + if (index >= size()) { + throw IndexError; + } + // 在頂點串列中移除索引 index 的頂點 + vertices.removeAt(index); + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.removeAt(index); + // 在鄰接矩陣中刪除索引 index 的列 + for (List row in adjMat) { + row.removeAt(index); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + void addEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + void removeEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + void printAdjMat() { + print("頂點串列 = $vertices"); + print("鄰接矩陣 = "); + printMatrix(adjMat); + } +} + +/* Driver Code */ +void main() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + List vertices = [1, 3, 2, 5, 4]; + List> edges = [ + [0, 1], + [0, 3], + [1, 2], + [2, 3], + [2, 4], + [3, 4], + ]; + GraphAdjMat graph = GraphAdjMat(vertices, edges); + print("\n初始化後,圖為"); + graph.printAdjMat(); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(0, 2); + print("\n新增邊 1-2 後,圖為"); + graph.printAdjMat(); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(0, 1); + print("\n刪除邊 1-3 後,圖為"); + graph.printAdjMat(); + + /* 新增頂點 */ + graph.addVertex(6); + print("\n新增頂點 6 後,圖為"); + graph.printAdjMat(); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(1); + print("\n刪除頂點 3 後,圖為"); + graph.printAdjMat(); +} diff --git a/zh-hant/codes/dart/chapter_graph/graph_bfs.dart b/zh-hant/codes/dart/chapter_graph/graph_bfs.dart new file mode 100644 index 000000000..5ee762152 --- /dev/null +++ b/zh-hant/codes/dart/chapter_graph/graph_bfs.dart @@ -0,0 +1,66 @@ +/** + * File: graph_bfs.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +import '../utils/vertex.dart'; +import 'graph_adjacency_list.dart'; + +/* 廣度優先走訪 */ +List graphBFS(GraphAdjList graph, Vertex startVet) { + // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + // 頂點走訪序列 + List res = []; + // 雜湊表,用於記錄已被訪問過的頂點 + Set visited = {}; + visited.add(startVet); + // 佇列用於實現 BFS + Queue que = Queue(); + que.add(startVet); + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (que.isNotEmpty) { + Vertex vet = que.removeFirst(); // 佇列首頂點出隊 + res.add(vet); // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + que.add(adjVet); // 只入列未訪問的頂點 + visited.add(adjVet); // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res; +} + +/* Dirver Code */ +void main() { + /* 初始化無向圖 */ + List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + List> 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]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初始化後,圖為"); + graph.printAdjList(); + + /* 廣度優先走訪 */ + List res = graphBFS(graph, v[0]); + print("\n廣度優先走訪(BFS)頂點序列為"); + print(Vertex.vetsToVals(res)); +} diff --git a/zh-hant/codes/dart/chapter_graph/graph_dfs.dart b/zh-hant/codes/dart/chapter_graph/graph_dfs.dart new file mode 100644 index 000000000..0da9c94d3 --- /dev/null +++ b/zh-hant/codes/dart/chapter_graph/graph_dfs.dart @@ -0,0 +1,59 @@ +/** + * File: graph_dfs.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/vertex.dart'; +import 'graph_adjacency_list.dart'; + +/* 深度優先走訪輔助函式 */ +void dfs( + GraphAdjList graph, + Set visited, + List res, + Vertex vet, +) { + res.add(vet); // 記錄訪問頂點 + visited.add(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度優先走訪 */ +List graphDFS(GraphAdjList graph, Vertex startVet) { + // 頂點走訪序列 + List res = []; + // 雜湊表,用於記錄已被訪問過的頂點 + Set visited = {}; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +void main() { + /* 初始化無向圖 */ + List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); + List> 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]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初始化後,圖為"); + graph.printAdjList(); + + /* 深度優先走訪 */ + List res = graphDFS(graph, v[0]); + print("\n深度優先走訪(DFS)頂點序列為"); + print(Vertex.vetsToVals(res)); +} diff --git a/zh-hant/codes/dart/chapter_greedy/coin_change_greedy.dart b/zh-hant/codes/dart/chapter_greedy/coin_change_greedy.dart new file mode 100644 index 000000000..96e64010c --- /dev/null +++ b/zh-hant/codes/dart/chapter_greedy/coin_change_greedy.dart @@ -0,0 +1,50 @@ +/** + * File: coin_change_greedy.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 零錢兌換:貪婪 */ +int coinChangeGreedy(List coins, int amt) { + // 假設 coins 串列有序 + int i = coins.length - 1; + int count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (amt > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > amt) { + i--; + } + // 選擇 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,則返回 -1 + return amt == 0 ? count : -1; +} + +/* Driver Code */ +void main() { + // 貪婪:能夠保證找到全域性最優解 + List coins = [1, 5, 10, 20, 50, 100]; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("湊到 $amt 所需的最少硬幣數量為 $res"); + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 20, 50]; + amt = 60; + res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("湊到 $amt 所需的最少硬幣數量為 $res"); + print("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 49, 50]; + amt = 98; + res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("湊到 $amt 所需的最少硬幣數量為 $res"); + print("實際上需要的最少數量為 2 ,即 49 + 49"); +} diff --git a/zh-hant/codes/dart/chapter_greedy/fractional_knapsack.dart b/zh-hant/codes/dart/chapter_greedy/fractional_knapsack.dart new file mode 100644 index 000000000..0e6091ae7 --- /dev/null +++ b/zh-hant/codes/dart/chapter_greedy/fractional_knapsack.dart @@ -0,0 +1,47 @@ +/** + * File: fractional_knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 物品 */ +class Item { + int w; // 物品重量 + int v; // 物品價值 + + Item(this.w, this.v); +} + +/* 分數背包:貪婪 */ +double fractionalKnapsack(List wgt, List val, int cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + List items = List.generate(wgt.length, (i) => Item(wgt[i], val[i])); + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w)); + // 迴圈貪婪選擇 + double res = 0; + for (Item 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 */ +void main() { + List wgt = [10, 20, 30, 40, 50]; + List val = [50, 120, 150, 210, 240]; + int cap = 50; + + // 貪婪演算法 + double res = fractionalKnapsack(wgt, val, cap); + print("不超過背包容量的最大物品價值為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_greedy/max_capacity.dart b/zh-hant/codes/dart/chapter_greedy/max_capacity.dart new file mode 100644 index 000000000..804ee9fb7 --- /dev/null +++ b/zh-hant/codes/dart/chapter_greedy/max_capacity.dart @@ -0,0 +1,37 @@ +/** + * File: max_capacity.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最大容量:貪婪 */ +int maxCapacity(List ht) { + // 初始化 i, j,使其分列陣列兩端 + int i = 0, j = ht.length - 1; + // 初始最大容量為 0 + int res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +void main() { + List ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 貪婪演算法 + int res = maxCapacity(ht); + print("最大容量為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_greedy/max_product_cutting.dart b/zh-hant/codes/dart/chapter_greedy/max_product_cutting.dart new file mode 100644 index 000000000..65b08f63b --- /dev/null +++ b/zh-hant/codes/dart/chapter_greedy/max_product_cutting.dart @@ -0,0 +1,37 @@ +/** + * File: max_product_cutting.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最大切分乘積:貪婪 */ +int maxProductCutting(int n) { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + int a = n ~/ 3; + int b = n % 3; + if (b == 1) { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return (pow(3, a - 1) * 2 * 2).toInt(); + } + if (b == 2) { + // 當餘數為 2 時,不做處理 + return (pow(3, a) * 2).toInt(); + } + // 當餘數為 0 時,不做處理 + return pow(3, a).toInt(); +} + +/* Driver Code */ +void main() { + int n = 58; + + // 貪婪演算法 + int res = maxProductCutting(n); + print("最大切分乘積為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_hashing/array_hash_map.dart b/zh-hant/codes/dart/chapter_hashing/array_hash_map.dart new file mode 100644 index 000000000..9c558247e --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/array_hash_map.dart @@ -0,0 +1,126 @@ +/** + * File: array_hash_map.dart + * Created Time: 2023-03-29 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 鍵值對 */ +class Pair { + int key; + String val; + Pair(this.key, this.val); +} + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + late List _buckets; + + ArrayHashMap() { + // 初始化陣列,包含 100 個桶 + _buckets = List.filled(100, null); + } + + /* 雜湊函式 */ + int _hashFunc(int key) { + final int index = key % 100; + return index; + } + + /* 查詢操作 */ + String? get(int key) { + final int index = _hashFunc(key); + final Pair? pair = _buckets[index]; + if (pair == null) { + return null; + } + return pair.val; + } + + /* 新增操作 */ + void put(int key, String val) { + final Pair pair = Pair(key, val); + final int index = _hashFunc(key); + _buckets[index] = pair; + } + + /* 刪除操作 */ + void remove(int key) { + final int index = _hashFunc(key); + _buckets[index] = null; + } + + /* 獲取所有鍵值對 */ + List pairSet() { + List pairSet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + pairSet.add(pair); + } + } + return pairSet; + } + + /* 獲取所有鍵 */ + List keySet() { + List keySet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + keySet.add(pair.key); + } + } + return keySet; + } + + /* 獲取所有值 */ + List values() { + List valueSet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + valueSet.add(pair.val); + } + } + return valueSet; + } + + /* 列印雜湊表 */ + void printHashMap() { + for (final Pair kv in pairSet()) { + print("${kv.key} -> ${kv.val}"); + } + } +} + +/* Driver Code */ +void main() { + /* 初始化雜湊表 */ + final ArrayHashMap map = ArrayHashMap(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + print("\n新增完成後,雜湊表為\nKey -> Value"); + map.printHashMap(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String? name = map.get(15937); + print("\n輸入學號 15937 ,查詢到姓名 $name"); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + print("\n刪除 10583 後,雜湊表為\nKey -> Value"); + map.printHashMap(); + + /* 走訪雜湊表 */ + print("\n走訪鍵值對 Key->Value"); + map.pairSet().forEach((kv) => print("${kv.key} -> ${kv.val}")); + print("\n單獨走訪鍵 Key"); + map.keySet().forEach((key) => print("$key")); + print("\n單獨走訪值 Value"); + map.values().forEach((val) => print("$val")); +} diff --git a/zh-hant/codes/dart/chapter_hashing/built_in_hash.dart b/zh-hant/codes/dart/chapter_hashing/built_in_hash.dart new file mode 100644 index 000000000..6e5dad53c --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/built_in_hash.dart @@ -0,0 +1,34 @@ +/** + * File: built_in_hash.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../chapter_stack_and_queue/linkedlist_deque.dart'; + +/* Driver Code */ +void main() { + int _num = 3; + int hashNum = _num.hashCode; + print("整數 $_num 的雜湊值為 $hashNum"); + + bool bol = true; + int hashBol = bol.hashCode; + print("布林值 $bol 的雜湊值為 $hashBol"); + + double dec = 3.14159; + int hashDec = dec.hashCode; + print("小數 $dec 的雜湊值為 $hashDec"); + + String str = "Hello 演算法"; + int hashStr = str.hashCode; + print("字串 $str 的雜湊值為 $hashStr"); + + List arr = [12836, "小哈"]; + int hashArr = arr.hashCode; + print("陣列 $arr 的雜湊值為 $hashArr"); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode; + print("節點物件 $obj 的雜湊值為 $hashObj"); +} diff --git a/zh-hant/codes/dart/chapter_hashing/hash_map.dart b/zh-hant/codes/dart/chapter_hashing/hash_map.dart new file mode 100644 index 000000000..9bcec5da5 --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/hash_map.dart @@ -0,0 +1,41 @@ +/** + * File: hash_map.dart + * Created Time: 2023-03-29 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Driver Code */ +void main() { + /* 初始化雜湊表 */ + final Map map = {}; + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈"; + map[15937] = "小囉"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鴨"; + print("\n新增完成後,雜湊表為\nKey -> Value"); + map.forEach((key, value) => print("$key -> $value")); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + final String? name = map[15937]; + print("\n輸入學號 15937 ,查詢到姓名 $name"); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + print("\n刪除 10583 後,雜湊表為\nKey -> Value"); + map.forEach((key, value) => print("$key -> $value")); + + /* 走訪雜湊表 */ + print("\n走訪鍵值對 Key->Value"); + map.forEach((key, value) => print("$key -> $value")); + print("\n單獨走訪鍵 Key"); + map.keys.forEach((key) => print(key)); + print("\n單獨走訪值 Value"); + map.forEach((key, value) => print("$value")); + map.values.forEach((value) => print(value)); +} diff --git a/zh-hant/codes/dart/chapter_hashing/hash_map_chaining.dart b/zh-hant/codes/dart/chapter_hashing/hash_map_chaining.dart new file mode 100644 index 000000000..b59b157c6 --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/hash_map_chaining.dart @@ -0,0 +1,138 @@ +/** + * File: hash_map_chaining.dart + * Created Time: 2023-06-24 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'array_hash_map.dart'; + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + late int size; // 鍵值對數量 + late int capacity; // 雜湊表容量 + late double loadThres; // 觸發擴容的負載因子閾值 + late int extendRatio; // 擴容倍數 + late List> buckets; // 桶陣列 + + /* 建構子 */ + HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = List.generate(capacity, (_) => []); + } + + /* 雜湊函式 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double loadFactor() { + return size / capacity; + } + + /* 查詢操作 */ + String? get(int key) { + int index = hashFunc(key); + List bucket = buckets[index]; + // 走訪桶,若找到 key ,則返回對應 val + for (Pair pair in bucket) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key ,則返回 null + return null; + } + + /* 新增操作 */ + void put(int key, String val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets[index]; + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (Pair pair in bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + Pair pair = Pair(key, val); + bucket.add(pair); + size++; + } + + /* 刪除操作 */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets[index]; + // 走訪桶,從中刪除鍵值對 + for (Pair pair in bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* 擴容雜湊表 */ + void extend() { + // 暫存原雜湊表 + List> bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = List.generate(capacity, (_) => []); + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (List bucket in bucketsTmp) { + for (Pair pair in bucket) { + put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + void printHashMap() { + for (List bucket in buckets) { + List res = []; + for (Pair pair in bucket) { + res.add("${pair.key} -> ${pair.val}"); + } + print(res); + } + } +} + +/* Driver Code */ +void main() { + /* 初始化雜湊表 */ + HashMapChaining map = HashMapChaining(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + print("\n新增完成後,雜湊表為\nKey -> Value"); + map.printHashMap(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String? name = map.get(13276); + print("\n輸入學號 13276 ,查詢到姓名 ${name}"); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(12836); + print("\n刪除 12836 後,雜湊表為\nKey -> Value"); + map.printHashMap(); +} diff --git a/zh-hant/codes/dart/chapter_hashing/hash_map_open_addressing.dart b/zh-hant/codes/dart/chapter_hashing/hash_map_open_addressing.dart new file mode 100644 index 000000000..24e2ed151 --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/hash_map_open_addressing.dart @@ -0,0 +1,157 @@ +/** + * File: hash_map_open_addressing.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'array_hash_map.dart'; + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + late int _size; // 鍵值對數量 + int _capacity = 4; // 雜湊表容量 + double _loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + int _extendRatio = 2; // 擴容倍數 + late List _buckets; // 桶陣列 + Pair _TOMBSTONE = Pair(-1, "-1"); // 刪除標記 + + /* 建構子 */ + HashMapOpenAddressing() { + _size = 0; + _buckets = List.generate(_capacity, (index) => null); + } + + /* 雜湊函式 */ + int hashFunc(int key) { + return key % _capacity; + } + + /* 負載因子 */ + double loadFactor() { + return _size / _capacity; + } + + /* 搜尋 key 對應的桶索引 */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (_buckets[index] != null) { + // 若遇到 key ,返回對應的桶索引 + if (_buckets[index]!.key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + _buckets[firstTombstone] = _buckets[index]; + _buckets[index] = _TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % _capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + String? get(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則返回對應 val + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + return _buckets[index]!.val; + } + // 若鍵值對不存在,則返回 null + return null; + } + + /* 新增操作 */ + void put(int key, String val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > _loadThres) { + extend(); + } + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + _buckets[index]!.val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + _buckets[index] = new Pair(key, val); + _size++; + } + + /* 刪除操作 */ + void remove(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + _buckets[index] = _TOMBSTONE; + _size--; + } + } + + /* 擴容雜湊表 */ + void extend() { + // 暫存原雜湊表 + List bucketsTmp = _buckets; + // 初始化擴容後的新雜湊表 + _capacity *= _extendRatio; + _buckets = List.generate(_capacity, (index) => null); + _size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (Pair? pair in bucketsTmp) { + if (pair != null && pair != _TOMBSTONE) { + put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + void printHashMap() { + for (Pair? pair in _buckets) { + if (pair == null) { + print("null"); + } else if (pair == _TOMBSTONE) { + print("TOMBSTONE"); + } else { + print("${pair.key} -> ${pair.val}"); + } + } + } +} + +/* Driver Code */ +void main() { + /* 初始化雜湊表 */ + HashMapOpenAddressing map = HashMapOpenAddressing(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + print("\n新增完成後,雜湊表為\nKey -> Value"); + map.printHashMap(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String? name = map.get(13276); + print("\n輸入學號 13276 ,查詢到姓名 $name"); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(16750); + print("\n刪除 16750 後,雜湊表為\nKey -> Value"); + map.printHashMap(); +} diff --git a/zh-hant/codes/dart/chapter_hashing/simple_hash.dart b/zh-hant/codes/dart/chapter_hashing/simple_hash.dart new file mode 100644 index 000000000..30e07023e --- /dev/null +++ b/zh-hant/codes/dart/chapter_hashing/simple_hash.dart @@ -0,0 +1,62 @@ +/** + * File: simple_hash.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 加法雜湊 */ +int addHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = (hash + key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* 乘法雜湊 */ +int mulHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = (31 * hash + key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* 互斥或雜湊 */ +int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash ^= key.codeUnitAt(i); + } + return hash & MODULUS; +} + +/* 旋轉雜湊 */ +int rotHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* Dirver Code */ +void main() { + String key = "Hello 演算法"; + + int hash = addHash(key); + print("加法雜湊值為 $hash"); + + hash = mulHash(key); + print("乘法雜湊值為 $hash"); + + hash = xorHash(key); + print("互斥或雜湊值為 $hash"); + + hash = rotHash(key); + print("旋轉雜湊值為 $hash"); +} diff --git a/zh-hant/codes/dart/chapter_heap/my_heap.dart b/zh-hant/codes/dart/chapter_heap/my_heap.dart new file mode 100644 index 000000000..ea3c94274 --- /dev/null +++ b/zh-hant/codes/dart/chapter_heap/my_heap.dart @@ -0,0 +1,151 @@ +/** + * File: my_heap.dart + * Created Time: 2023-04-09 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 大頂堆積 */ +class MaxHeap { + late List _maxHeap; + + /* 建構子,根據輸入串列建堆積 */ + MaxHeap(List nums) { + // 將串列元素原封不動新增進堆積 + _maxHeap = nums; + // 堆積化除葉節點以外的其他所有節點 + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 獲取左子節點的索引 */ + int _left(int i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + int _right(int i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + int _parent(int i) { + return (i - 1) ~/ 2; // 向下整除 + } + + /* 交換元素 */ + void _swap(int i, int j) { + int tmp = _maxHeap[i]; + _maxHeap[i] = _maxHeap[j]; + _maxHeap[j] = tmp; + } + + /* 獲取堆積大小 */ + int size() { + return _maxHeap.length; + } + + /* 判斷堆積是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 訪問堆積頂元素 */ + int peek() { + return _maxHeap[0]; + } + + /* 元素入堆積 */ + void push(int val) { + // 新增節點 + _maxHeap.add(val); + // 從底至頂堆積化 + siftUp(size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + void siftUp(int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = _parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { + break; + } + // 交換兩節點 + _swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + int pop() { + // 判空處理 + if (isEmpty()) throw Exception('堆積為空'); + // 交換根節點與最右葉節點(交換首元素與尾元素) + _swap(0, size() - 1); + // 刪除節點 + int val = _maxHeap.removeLast(); + // 從頂至底堆積化 + siftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + void siftDown(int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = _left(i); + int r = _right(i); + int ma = i; + if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; + if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) break; + // 交換兩節點 + _swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 列印堆積(二元樹) */ + void print() { + printHeap(_maxHeap); + } +} + +/* Driver Code */ +void main() { + /* 初始化大頂堆積 */ + MaxHeap maxHeap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + print("\n輸入串列並建堆積後"); + maxHeap.print(); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.peek(); + print("\n堆積頂元素為 $peek"); + + /* 元素入堆積 */ + int val = 7; + maxHeap.push(val); + print("\n元素 $val 入堆積後"); + maxHeap.print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop(); + print("\n堆積頂元素 $peek 出堆積後"); + maxHeap.print(); + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + print("\n堆積元素數量為 $size"); + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.isEmpty(); + print("\n堆積是否為空 $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_heap/top_k.dart b/zh-hant/codes/dart/chapter_heap/top_k.dart new file mode 100644 index 000000000..f91a8a82c --- /dev/null +++ b/zh-hant/codes/dart/chapter_heap/top_k.dart @@ -0,0 +1,150 @@ +/** + * File: top_k.dart + * Created Time: 2023-08-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +MinHeap topKHeap(List nums, int k) { + // 初始化小頂堆積,將陣列的前 k 個元素入堆積 + MinHeap heap = MinHeap(nums.sublist(0, k)); + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (int i = k; i < nums.length; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > heap.peek()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +/* Driver Code */ +void main() { + List nums = [1, 7, 6, 3, 2]; + int k = 3; + + MinHeap res = topKHeap(nums, k); + print("最大的 $k 個元素為"); + res.print(); +} + +/* 小頂堆積 */ +class MinHeap { + late List _minHeap; + + /* 建構子,根據輸入串列建堆積 */ + MinHeap(List nums) { + // 將串列元素原封不動新增進堆積 + _minHeap = nums; + // 堆積化除葉節點以外的其他所有節點 + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 返回堆積中的元素 */ + List getHeap() { + return _minHeap; + } + + /* 獲取左子節點的索引 */ + int _left(int i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + int _right(int i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + int _parent(int i) { + return (i - 1) ~/ 2; // 向下整除 + } + + /* 交換元素 */ + void _swap(int i, int j) { + int tmp = _minHeap[i]; + _minHeap[i] = _minHeap[j]; + _minHeap[j] = tmp; + } + + /* 獲取堆積大小 */ + int size() { + return _minHeap.length; + } + + /* 判斷堆積是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 訪問堆積頂元素 */ + int peek() { + return _minHeap[0]; + } + + /* 元素入堆積 */ + void push(int val) { + // 新增節點 + _minHeap.add(val); + // 從底至頂堆積化 + siftUp(size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + void siftUp(int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = _parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || _minHeap[i] >= _minHeap[p]) { + break; + } + // 交換兩節點 + _swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + int pop() { + // 判空處理 + if (isEmpty()) throw Exception('堆積為空'); + // 交換根節點與最右葉節點(交換首元素與尾元素) + _swap(0, size() - 1); + // 刪除節點 + int val = _minHeap.removeLast(); + // 從頂至底堆積化 + siftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + void siftDown(int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = _left(i); + int r = _right(i); + int mi = i; + if (l < size() && _minHeap[l] < _minHeap[mi]) mi = l; + if (r < size() && _minHeap[r] < _minHeap[mi]) mi = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (mi == i) break; + // 交換兩節點 + _swap(i, mi); + // 迴圈向下堆積化 + i = mi; + } + } + + /* 列印堆積(二元樹) */ + void print() { + printHeap(_minHeap); + } +} diff --git a/zh-hant/codes/dart/chapter_searching/binary_search.dart b/zh-hant/codes/dart/chapter_searching/binary_search.dart new file mode 100644 index 000000000..449de8acc --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/binary_search.dart @@ -0,0 +1,63 @@ +/** + * File: binary_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 二分搜尋(雙閉區間) */ +int binarySearch(List nums, int target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + int i = 0, j = nums.length - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + int m = i + (j - i) ~/ 2; // 計算中點索引 m + if (nums[m] < target) { + // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + } else if (nums[m] > target) { + // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + } else { + // 找到目標元素,返回其索引 + return m; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 二分搜尋(左閉右開區間) */ +int binarySearchLCRO(List nums, int target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + int i = 0, j = nums.length; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + int m = i + (j - i) ~/ 2; // 計算中點索引 m + if (nums[m] < target) { + // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + } else if (nums[m] > target) { + // 此情況說明 target 在區間 [i, m) 中 + j = m; + } else { + // 找到目標元素,返回其索引 + return m; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* Driver Code*/ +void main() { + int target = 6; + final nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + /* 二分搜尋 (雙閉區間) */ + int index = binarySearch(nums, target); + print('目標元素 6 的索引 = $index'); + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums, target); + print('目標元素 6 的索引 = $index'); +} diff --git a/zh-hant/codes/dart/chapter_searching/binary_search_edge.dart b/zh-hant/codes/dart/chapter_searching/binary_search_edge.dart new file mode 100644 index 000000000..fc20768e9 --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/binary_search_edge.dart @@ -0,0 +1,48 @@ +/** + * File: binary_search_edge.dart + * Created Time: 2023-08-14 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'binary_search_insertion.dart'; + +/* 二分搜尋最左一個 target */ +int binarySearchLeftEdge(List nums, int target) { + // 等價於查詢 target 的插入點 + int i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分搜尋最右一個 target */ +int binarySearchRightEdge(List nums, int target) { + // 轉化為查詢最左一個 target + 1 + int i = binarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +void main() { + // 包含重複元素的陣列 + List nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + print("\n陣列 nums = $nums"); + + // 二分搜尋左邊界和右邊界 + for (int target in [6, 7]) { + int index = binarySearchLeftEdge(nums, target); + print("最左一個元素 $target 的索引為 $index"); + index = binarySearchRightEdge(nums, target); + print("最右一個元素 $target 的索引為 $index"); + } +} diff --git a/zh-hant/codes/dart/chapter_searching/binary_search_insertion.dart b/zh-hant/codes/dart/chapter_searching/binary_search_insertion.dart new file mode 100644 index 000000000..4104243a4 --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/binary_search_insertion.dart @@ -0,0 +1,60 @@ +/** + * File: binary_search_insertion.dart + * Created Time: 2023-08-14 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 二分搜尋插入點(無重複元素) */ +int binarySearchInsertionSimple(List nums, int target) { + int i = 0, j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) ~/ 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i; +} + +/* 二分搜尋插入點(存在重複元素) */ +int binarySearchInsertion(List nums, int target) { + int i = 0, j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) ~/ 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; +} + +/* Driver Code */ +void main() { + // 無重複元素的陣列 + List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + print("\n陣列 nums = $nums"); + // 二分搜尋插入點 + for (int target in [6, 9]) { + int index = binarySearchInsertionSimple(nums, target); + print("元素 $target 的插入點的索引為 $index"); + } + + // 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + print("\n陣列 nums = $nums"); + // 二分搜尋插入點 + for (int target in [2, 6, 20]) { + int index = binarySearchInsertion(nums, target); + print("元素 $target 的插入點的索引為 $index"); + } +} diff --git a/zh-hant/codes/dart/chapter_searching/hashing_search.dart b/zh-hant/codes/dart/chapter_searching/hashing_search.dart new file mode 100644 index 000000000..20a6f0c41 --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/hashing_search.dart @@ -0,0 +1,54 @@ +/** + * File: hashing_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:collection'; +import '../utils/list_node.dart'; + +/* 雜湊查詢(陣列) */ +int hashingSearchArray(Map map, int target) { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + if (!map.containsKey(target)) { + return -1; + } + return map[target]!; +} + +/* 雜湊查詢(鏈結串列) */ +ListNode? hashingSearchLinkedList(Map map, int target) { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + if (!map.containsKey(target)) { + return null; + } + return map[target]!; +} + +/* Driver Code */ +void main() { + int target = 3; + + /* 雜湊查詢(陣列) */ + List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // 初始化雜湊表 + Map map = HashMap(); + for (int i = 0; i < nums.length; i++) { + map.putIfAbsent(nums[i], () => i); // key: 元素,value: 索引 + } + int index = hashingSearchArray(map, target); + print('目標元素 3 的索引 = $index'); + + /* 雜湊查詢(鏈結串列) */ + ListNode? head = listToLinkedList(nums); + // 初始化雜湊表 + Map map1 = HashMap(); + while (head != null) { + map1.putIfAbsent(head.val, () => head!); // key: 節點值,value: 節點 + head = head.next; + } + ListNode? node = hashingSearchLinkedList(map1, target); + print('目標節點值 3 的對應節點物件為 $node'); +} diff --git a/zh-hant/codes/dart/chapter_searching/linear_search.dart b/zh-hant/codes/dart/chapter_searching/linear_search.dart new file mode 100644 index 000000000..46adbb0e3 --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/linear_search.dart @@ -0,0 +1,47 @@ +/** + * File: linear_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 線性查詢(陣列) */ +int linearSearchArray(List nums, int target) { + // 走訪陣列 + for (int i = 0; i < nums.length; i++) { + // 找到目標元素,返回其索引 + if (nums[i] == target) { + return i; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 線性查詢(鏈結串列) */ +ListNode? linearSearchList(ListNode? head, int target) { + // 走訪鏈結串列 + while (head != null) { + // 找到目標節點,返回之 + if (head.val == target) return head; + head = head.next; + } + // 未找到目標元素,返回 null + return null; +} + +/* Driver Code */ +void main() { + int target = 3; + + /* 在陣列中執行線性查詢 */ + List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + int index = linearSearchArray(nums, target); + print('目標元素 3 的索引 = $index'); + + /* 在鏈結串列中執行線性查詢 */ + ListNode? head = listToLinkedList(nums); + ListNode? node = linearSearchList(head, target); + print('目標節點值 3 的對應節點物件為 $node'); +} diff --git a/zh-hant/codes/dart/chapter_searching/two_sum.dart b/zh-hant/codes/dart/chapter_searching/two_sum.dart new file mode 100644 index 000000000..0a1a4ad18 --- /dev/null +++ b/zh-hant/codes/dart/chapter_searching/two_sum.dart @@ -0,0 +1,49 @@ +/** + * File: two_sum.dart + * Created Time: 2023-2-11 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:collection'; + +/* 方法一: 暴力列舉 */ +List twoSumBruteForce(List nums, int target) { + int size = nums.length; + // 兩層迴圈,時間複雜度為 O(n^2) + for (var i = 0; i < size - 1; i++) { + for (var j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) return [i, j]; + } + } + return [0]; +} + +/* 方法二: 輔助雜湊表 */ +List twoSumHashTable(List nums, int target) { + int size = nums.length; + // 輔助雜湊表,空間複雜度為 O(n) + Map dic = HashMap(); + // 單層迴圈,時間複雜度為 O(n) + for (var i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return [dic[target - nums[i]]!, i]; + } + dic.putIfAbsent(nums[i], () => i); + } + return [0]; +} + +/* Driver Code */ +void main() { + // ======= Test Case ======= + List nums = [2, 7, 11, 15]; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + List res = twoSumBruteForce(nums, target); + print('方法一 res = $res'); + // 方法二 + res = twoSumHashTable(nums, target); + print('方法二 res = $res'); +} diff --git a/zh-hant/codes/dart/chapter_sorting/bubble_sort.dart b/zh-hant/codes/dart/chapter_sorting/bubble_sort.dart new file mode 100644 index 000000000..9e3b518e6 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/bubble_sort.dart @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 泡沫排序 */ +void bubbleSort(List nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +void bubbleSortWithFlag(List nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.length - 1; i > 0; i--) { + bool flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 記錄交換元素 + } + } + if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + bubbleSort(nums); + print("泡沫排序完成後 nums = $nums"); + + List nums1 = [4, 1, 3, 1, 5, 2]; + bubbleSortWithFlag(nums1); + print("泡沫排序完成後 nums1 = $nums1"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/bucket_sort.dart b/zh-hant/codes/dart/chapter_sorting/bucket_sort.dart new file mode 100644 index 000000000..87feba9d7 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/bucket_sort.dart @@ -0,0 +1,39 @@ +/** + * File: bucket_sort.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 桶排序 */ +void bucketSort(List nums) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + int k = nums.length ~/ 2; + List> buckets = List.generate(k, (index) => []); + + // 1. 將陣列元素分配到各個桶中 + for (double _num in nums) { + // 輸入資料範圍為 [0, 1),使用 _num * k 對映到索引範圍 [0, k-1] + int i = (_num * k).toInt(); + // 將 _num 新增進桶 bucket_idx + buckets[i].add(_num); + } + // 2. 對各個桶執行排序 + for (List bucket in buckets) { + bucket.sort(); + } + // 3. 走訪桶合併結果 + int i = 0; + for (List bucket in buckets) { + for (double _num in bucket) { + nums[i++] = _num; + } + } +} + +/* Driver Code*/ +void main() { + // 設輸入資料為浮點數,範圍為 [0, 1) + final nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; + bucketSort(nums); + print('桶排序完成後 nums = $nums'); +} diff --git a/zh-hant/codes/dart/chapter_sorting/counting_sort.dart b/zh-hant/codes/dart/chapter_sorting/counting_sort.dart new file mode 100644 index 000000000..09c38bd16 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/counting_sort.dart @@ -0,0 +1,72 @@ +/** + * File: counting_sort.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ +import 'dart:math'; + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +void countingSortNaive(List nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int _num in nums) { + m = max(m, _num); + } + // 2. 統計各數字的出現次數 + // counter[_num] 代表 _num 的出現次數 + List counter = List.filled(m + 1, 0); + for (int _num in nums) { + counter[_num]++; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + int i = 0; + for (int _num = 0; _num < m + 1; _num++) { + for (int j = 0; j < counter[_num]; j++, i++) { + nums[i] = _num; + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +void countingSort(List nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int _num in nums) { + m = max(m, _num); + } + // 2. 統計各數字的出現次數 + // counter[_num] 代表 _num 的出現次數 + List counter = List.filled(m + 1, 0); + for (int _num in nums) { + counter[_num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[_num]-1 是 _num 在 res 中最後一次出現的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + int n = nums.length; + List res = List.filled(n, 0); + for (int i = n - 1; i >= 0; i--) { + int _num = nums[i]; + res[counter[_num] - 1] = _num; // 將 _num 放置到對應索引處 + counter[_num]--; // 令前綴和自減 1 ,得到下次放置 _num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + nums.setAll(0, res); +} + +/* Driver Code*/ +void main() { + final nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + countingSortNaive(nums); + print('計數排序(無法排序物件)完成後 nums = $nums'); + + final nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + countingSort(nums1); + print('計數排序完成後 nums1 = $nums1'); +} diff --git a/zh-hant/codes/dart/chapter_sorting/heap_sort.dart b/zh-hant/codes/dart/chapter_sorting/heap_sort.dart new file mode 100644 index 000000000..67119a403 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/heap_sort.dart @@ -0,0 +1,49 @@ +/** + * File: heap_sort.dart + * Created Time: 2023-06-01 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +void siftDown(List nums, int n, int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) ma = l; + if (r < n && nums[r] > nums[ma]) ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) break; + // 交換兩節點 + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +void heapSort(List nums) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (int i = nums.length ~/ 2 - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (int i = nums.length - 1; i > 0; i--) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + heapSort(nums); + print("堆積排序完成後 nums = $nums"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/insertion_sort.dart b/zh-hant/codes/dart/chapter_sorting/insertion_sort.dart new file mode 100644 index 000000000..68be93791 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/insertion_sort.dart @@ -0,0 +1,26 @@ +/** + * File: insertion_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 插入排序 */ +void insertionSort(List nums) { + // 外迴圈:已排序區間為 [0, i-1] + for (int i = 1; i < nums.length; i++) { + int base = nums[i], j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = base; // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + insertionSort(nums); + print("插入排序完成後 nums = $nums"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/merge_sort.dart b/zh-hant/codes/dart/chapter_sorting/merge_sort.dart new file mode 100644 index 000000000..a492de39b --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/merge_sort.dart @@ -0,0 +1,52 @@ +/** + * File: merge_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 合併左子陣列和右子陣列 */ +void merge(List nums, int left, int mid, int right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + List tmp = List.filled(right - left + 1, 0); + // 初始化左子陣列和右子陣列的起始索引 + int i = left, j = mid + 1, k = 0; + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* 合併排序 */ +void mergeSort(List nums, int left, int right) { + // 終止條件 + if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + int mid = (left + right) ~/ 2; // 計算中點 + mergeSort(nums, left, mid); // 遞迴左子陣列 + mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +void main() { + /* 合併排序 */ + List nums = [7, 3, 2, 6, 0, 1, 5, 4]; + mergeSort(nums, 0, nums.length - 1); + print("合併排序完成後 nums = $nums"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/quick_sort.dart b/zh-hant/codes/dart/chapter_sorting/quick_sort.dart new file mode 100644 index 000000000..369910845 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/quick_sort.dart @@ -0,0 +1,145 @@ +/** + * File: quick_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 快速排序類別 */ +class QuickSort { + /* 元素交換 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + static int _partition(List nums, int left, int right) { + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 + _swap(nums, i, j); // 交換這兩個元素 + } + _swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + static void quickSort(List nums, int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return; + // 哨兵劃分 + int pivot = _partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + /* 元素交換 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 選取三個候選元素的中位數 */ + static int _medianThree(List nums, int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m 在 l 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l 在 m 和 r 之間 + return right; + } + + /* 哨兵劃分(三數取中值) */ + static int _partition(List nums, int left, int right) { + // 選取三個候選元素的中位數 + int med = _medianThree(nums, left, (left + right) ~/ 2, right); + // 將中位數交換至陣列最左端 + _swap(nums, left, med); + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 + _swap(nums, i, j); // 交換這兩個元素 + } + _swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + static void quickSort(List nums, int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return; + // 哨兵劃分 + int pivot = _partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + /* 元素交換 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + static int _partition(List nums, int left, int right) { + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 + _swap(nums, i, j); // 交換這兩個元素 + } + _swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + static void quickSort(List nums, int left, int right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + int pivot = _partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +void main() { + /* 快速排序 */ + List nums = [2, 4, 1, 0, 3, 5]; + QuickSort.quickSort(nums, 0, nums.length - 1); + print("快速排序完成後 nums = $nums"); + + /* 快速排序(中位基準數最佳化) */ + List nums1 = [2, 4, 1, 0, 3, 5]; + QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); + print("快速排序(中位基準數最佳化)完成後 nums1 = $nums1"); + + /* 快速排序(尾遞迴最佳化) */ + List nums2 = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); + print("快速排序(尾遞迴最佳化)完成後 nums2 = $nums2"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/radix_sort.dart b/zh-hant/codes/dart/chapter_sorting/radix_sort.dart new file mode 100644 index 000000000..b479f9f52 --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/radix_sort.dart @@ -0,0 +1,71 @@ +/** + * File: radix_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 獲取元素 _num 的第 k 位,其中 exp = 10^(k-1) */ +int digit(int _num, int exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (_num ~/ exp) % 10; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +void countingSortDigit(List nums, int exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + List counter = List.filled(10, 0); + int n = nums.length; + // 統計 0~9 各數字的出現次數 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + List res = List.filled(n, 0); + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d]--; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for (int i = 0; i < n; i++) nums[i] = res[i]; +} + +/* 基數排序 */ +void radixSort(List nums) { + // 獲取陣列的最大元素,用於判斷最大位數 + // dart 中 int 的長度是 64 位的 + int m = -1 << 63; + for (int _num in nums) if (_num > m) m = _num; + // 按照從低位到高位的順序走訪 + for (int exp = 1; exp <= m; exp *= 10) + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +void main() { + // 基數排序 + List nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996 + ]; + radixSort(nums); + print("基數排序完成後 nums = $nums"); +} diff --git a/zh-hant/codes/dart/chapter_sorting/selection_sort.dart b/zh-hant/codes/dart/chapter_sorting/selection_sort.dart new file mode 100644 index 000000000..6637c040a --- /dev/null +++ b/zh-hant/codes/dart/chapter_sorting/selection_sort.dart @@ -0,0 +1,29 @@ +/** + * File: selection_sort.dart + * Created Time: 2023-06-01 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 選擇排序 */ +void selectionSort(List nums) { + int n = nums.length; + // 外迴圈:未排序區間為 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 內迴圈:找到未排序區間內的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) k = j; // 記錄最小元素的索引 + } + // 將該最小元素與未排序區間的首個元素交換 + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + selectionSort(nums); + print("選擇排序完成後 nums = $nums"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/array_deque.dart b/zh-hant/codes/dart/chapter_stack_and_queue/array_deque.dart new file mode 100644 index 000000000..8c1041b0d --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/array_deque.dart @@ -0,0 +1,146 @@ +/** + * File: array_deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + late List _nums; // 用於儲存雙向佇列元素的陣列 + late int _front; // 佇列首指標,指向佇列首元素 + late int _queSize; // 雙向佇列長度 + + /* 建構子 */ + ArrayDeque(int capacity) { + this._nums = List.filled(capacity, 0); + this._front = this._queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + int capacity() { + return _nums.length; + } + + /* 獲取雙向佇列的長度 */ + int size() { + return _queSize; + } + + /* 判斷雙向佇列是否為空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 計算環形陣列索引 */ + int index(int i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 佇列首入列 */ + void pushFirst(int _num) { + if (_queSize == capacity()) { + throw Exception("雙向佇列已滿"); + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 _front 越過陣列頭部後回到尾部 + _front = index(_front - 1); + // 將 _num 新增至佇列首 + _nums[_front] = _num; + _queSize++; + } + + /* 佇列尾入列 */ + void pushLast(int _num) { + if (_queSize == capacity()) { + throw Exception("雙向佇列已滿"); + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + int rear = index(_front + _queSize); + // 將 _num 新增至佇列尾 + _nums[rear] = _num; + _queSize++; + } + + /* 佇列首出列 */ + int popFirst() { + int _num = peekFirst(); + // 佇列首指標向右移動一位 + _front = index(_front + 1); + _queSize--; + return _num; + } + + /* 佇列尾出列 */ + int popLast() { + int _num = peekLast(); + _queSize--; + return _num; + } + + /* 訪問佇列首元素 */ + int peekFirst() { + if (isEmpty()) { + throw Exception("雙向佇列為空"); + } + return _nums[_front]; + } + + /* 訪問佇列尾元素 */ + int peekLast() { + if (isEmpty()) { + throw Exception("雙向佇列為空"); + } + // 計算尾元素索引 + int last = index(_front + _queSize - 1); + return _nums[last]; + } + + /* 返回陣列用於列印 */ + List toArray() { + // 僅轉換有效長度範圍內的串列元素 + List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[index(j)]; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 初始化雙向佇列 */ + final ArrayDeque deque = ArrayDeque(10); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + print("雙向佇列 deque = ${deque.toArray()}"); + + /* 訪問元素 */ + final int peekFirst = deque.peekFirst(); + print("佇列首元素 peekFirst = $peekFirst"); + final int peekLast = deque.peekLast(); + print("佇列尾元素 peekLast = $peekLast"); + + /* 元素入列 */ + deque.pushLast(4); + print("元素 4 佇列尾入列後 deque = ${deque.toArray()}"); + deque.pushFirst(1); + print("元素 1 佇列首入列後 deque = ${deque.toArray()}"); + + /* 元素出列 */ + final int popLast = deque.popLast(); + print("佇列尾出列元素 = $popLast ,佇列尾出列後 deque = ${deque.toArray()}"); + final int popFirst = deque.popFirst(); + print("佇列首出列元素 = $popFirst ,佇列首出列後 deque = ${deque.toArray()}"); + + /* 獲取雙向佇列的長度 */ + final int size = deque.size(); + print("雙向佇列長度 size = $size"); + + /* 判斷雙向佇列是否為空 */ + final bool isEmpty = deque.isEmpty(); + print("雙向佇列是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/array_queue.dart b/zh-hant/codes/dart/chapter_stack_and_queue/array_queue.dart new file mode 100644 index 000000000..85e5fa690 --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/array_queue.dart @@ -0,0 +1,110 @@ +/** + * File: array_queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + late List _nums; // 用於儲存佇列元素的陣列 + late int _front; // 佇列首指標,指向佇列首元素 + late int _queSize; // 佇列長度 + + ArrayQueue(int capacity) { + _nums = List.filled(capacity, 0); + _front = _queSize = 0; + } + + /* 獲取佇列的容量 */ + int capaCity() { + return _nums.length; + } + + /* 獲取佇列的長度 */ + int size() { + return _queSize; + } + + /* 判斷佇列是否為空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 入列 */ + void push(int _num) { + if (_queSize == capaCity()) { + throw Exception("佇列已滿"); + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + int rear = (_front + _queSize) % capaCity(); + // 將 _num 新增至佇列尾 + _nums[rear] = _num; + _queSize++; + } + + /* 出列 */ + int pop() { + int _num = peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + _front = (_front + 1) % capaCity(); + _queSize--; + return _num; + } + + /* 訪問佇列首元素 */ + int peek() { + if (isEmpty()) { + throw Exception("佇列為空"); + } + return _nums[_front]; + } + + /* 返回 Array */ + List toArray() { + // 僅轉換有效長度範圍內的串列元素 + final List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[j % capaCity()]; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 初始化佇列 */ + final int capacity = 10; + final ArrayQueue queue = ArrayQueue(capacity); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print("佇列 queue = ${queue.toArray()}"); + + /* 訪問佇列首元素 */ + final int peek = queue.peek(); + print("佇列首元素 peek = $peek"); + + /* 元素出列 */ + final int pop = queue.pop(); + print("出列元素 pop = $pop ,出列後 queue = ${queue.toArray()}"); + + /* 獲取佇列長度 */ + final int size = queue.size(); + print("佇列長度 size = $size"); + + /* 判斷佇列是否為空 */ + final bool isEmpty = queue.isEmpty(); + print("佇列是否為空 = $isEmpty"); + + /* 測試環形陣列 */ + for (int i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + print("第 $i 輪入列 + 出列後 queue = ${queue.toArray()}"); + } +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/array_stack.dart b/zh-hant/codes/dart/chapter_stack_and_queue/array_stack.dart new file mode 100644 index 000000000..bd72a070f --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/array_stack.dart @@ -0,0 +1,77 @@ +/** + * File: array_stack.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + late List _stack; + ArrayStack() { + _stack = []; + } + + /* 獲取堆疊的長度 */ + int size() { + return _stack.length; + } + + /* 判斷堆疊是否為空 */ + bool isEmpty() { + return _stack.isEmpty; + } + + /* 入堆疊 */ + void push(int _num) { + _stack.add(_num); + } + + /* 出堆疊 */ + int pop() { + if (isEmpty()) { + throw Exception("堆疊為空"); + } + return _stack.removeLast(); + } + + /* 訪問堆疊頂元素 */ + int peek() { + if (isEmpty()) { + throw Exception("堆疊為空"); + } + return _stack.last; + } + + /* 將堆疊轉化為 Array 並返回 */ + List toArray() => _stack; +} + +/* Driver Code */ +void main() { + /* 初始化堆疊 */ + final ArrayStack stack = ArrayStack(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print("堆疊 stack = ${stack.toArray()}"); + + /* 訪問堆疊頂元素 */ + final int peek = stack.peek(); + print("堆疊頂元素 peek = $peek"); + + /* 元素出堆疊 */ + final int pop = stack.pop(); + print("出堆疊元素 pop = $pop ,出堆疊後 stack = ${stack.toArray()}"); + + /* 獲取堆疊的長度 */ + final int size = stack.size(); + print("堆疊的長度 size = $size"); + + /* 判斷是否為空 */ + final bool isEmpty = stack.isEmpty(); + print("堆疊是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/deque.dart b/zh-hant/codes/dart/chapter_stack_and_queue/deque.dart new file mode 100644 index 000000000..ba14256ba --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/deque.dart @@ -0,0 +1,42 @@ +/** + * File: deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +void main() { + /* 初始化雙向佇列 */ + final Queue deque = Queue(); + deque.addFirst(3); + deque.addLast(2); + deque.addLast(5); + print("雙向佇列 deque = $deque"); + + /* 訪問元素 */ + final int peekFirst = deque.first; + print("佇列首元素 peekFirst = $peekFirst"); + final int peekLast = deque.last; + print("佇列尾元素 peekLast = $peekLast"); + + /* 元素入列 */ + deque.addLast(4); + print("元素 4 佇列尾入列後 deque = $deque"); + deque.addFirst(1); + print("元素 1 佇列首入列後 deque = $deque"); + + /* 元素出列 */ + final int popLast = deque.removeLast(); + print("佇列尾出列元素 = $popLast ,佇列尾出列後 deque = $deque"); + final int popFirst = deque.removeFirst(); + print("佇列首出列元素 = $popFirst ,佇列首出列後 deque = $deque"); + + /* 獲取雙向佇列的長度 */ + final int size = deque.length; + print("雙向佇列長度 size = $size"); + + /* 判斷雙向佇列是否為空 */ + final bool isEmpty = deque.isEmpty; + print("雙向佇列是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart new file mode 100644 index 000000000..547cee251 --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 雙向鏈結串列節點 */ +class ListNode { + int val; // 節點值 + ListNode? next; // 後繼節點引用 + ListNode? prev; // 前驅節點引用 + + ListNode(this.val, {this.next, this.prev}); +} + +/* 基於雙向鏈結串列實現的雙向對列 */ +class LinkedListDeque { + late ListNode? _front; // 頭節點 _front + late ListNode? _rear; // 尾節點 _rear + int _queSize = 0; // 雙向佇列的長度 + + LinkedListDeque() { + this._front = null; + this._rear = null; + } + + /* 獲取雙向佇列長度 */ + int size() { + return this._queSize; + } + + /* 判斷雙向佇列是否為空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入列操作 */ + void push(int _num, bool isFront) { + final ListNode node = ListNode(_num); + if (isEmpty()) { + // 若鏈結串列為空,則令 _front 和 _rear 都指向 node + _front = _rear = node; + } else if (isFront) { + // 佇列首入列操作 + // 將 node 新增至鏈結串列頭部 + _front!.prev = node; + node.next = _front; + _front = node; // 更新頭節點 + } else { + // 佇列尾入列操作 + // 將 node 新增至鏈結串列尾部 + _rear!.next = node; + node.prev = _rear; + _rear = node; // 更新尾節點 + } + _queSize++; // 更新佇列長度 + } + + /* 佇列首入列 */ + void pushFirst(int _num) { + push(_num, true); + } + + /* 佇列尾入列 */ + void pushLast(int _num) { + push(_num, false); + } + + /* 出列操作 */ + int? pop(bool isFront) { + // 若佇列為空,直接返回 null + if (isEmpty()) { + return null; + } + final int val; + if (isFront) { + // 佇列首出列操作 + val = _front!.val; // 暫存頭節點值 + // 刪除頭節點 + ListNode? fNext = _front!.next; + if (fNext != null) { + fNext.prev = null; + _front!.next = null; + } + _front = fNext; // 更新頭節點 + } else { + // 佇列尾出列操作 + val = _rear!.val; // 暫存尾節點值 + // 刪除尾節點 + ListNode? rPrev = _rear!.prev; + if (rPrev != null) { + rPrev.next = null; + _rear!.prev = null; + } + _rear = rPrev; // 更新尾節點 + } + _queSize--; // 更新佇列長度 + return val; + } + + /* 佇列首出列 */ + int? popFirst() { + return pop(true); + } + + /* 佇列尾出列 */ + int? popLast() { + return pop(false); + } + + /* 訪問佇列首元素 */ + int? peekFirst() { + return _front?.val; + } + + /* 訪問佇列尾元素 */ + int? peekLast() { + return _rear?.val; + } + + /* 返回陣列用於列印 */ + List toArray() { + ListNode? node = _front; + final List res = []; + for (int i = 0; i < _queSize; i++) { + res.add(node!.val); + node = node.next; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 初始化雙向佇列 */ + final LinkedListDeque deque = LinkedListDeque(); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + print("雙向佇列 deque = ${deque.toArray()}"); + + /* 訪問元素 */ + int? peekFirst = deque.peekFirst(); + print("佇列首元素 peekFirst = $peekFirst"); + int? peekLast = deque.peekLast(); + print("佇列尾元素 peekLast = $peekLast"); + + /* 元素入列 */ + deque.pushLast(4); + print("元素 4 佇列尾入列後 deque = ${deque.toArray()}"); + deque.pushFirst(1); + print("元素 1 佇列首入列後 deque = ${deque.toArray()}"); + + /* 元素出列 */ + int? popLast = deque.popLast(); + print("佇列尾出列元素 = $popLast ,佇列尾出列後 deque = ${deque.toArray()}"); + int? popFirst = deque.popFirst(); + print("佇列首出列元素 = $popFirst ,佇列首出列後 deque = ${deque.toArray()}"); + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + print("雙向佇列長度 size = $size"); + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.isEmpty(); + print("雙向佇列是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart new file mode 100644 index 000000000..64d8ac742 --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart @@ -0,0 +1,103 @@ +/** + * File: linkedlist_queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + ListNode? _front; // 頭節點 _front + ListNode? _rear; // 尾節點 _rear + int _queSize = 0; // 佇列長度 + + LinkedListQueue() { + _front = null; + _rear = null; + } + + /* 獲取佇列的長度 */ + int size() { + return _queSize; + } + + /* 判斷佇列是否為空 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 入列 */ + void push(int _num) { + // 在尾節點後新增 _num + final node = ListNode(_num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (_front == null) { + _front = node; + _rear = node; + } else { + // 如果佇列不為空,則將該節點新增到尾節點後 + _rear!.next = node; + _rear = node; + } + _queSize++; + } + + /* 出列 */ + int pop() { + final int _num = peek(); + // 刪除頭節點 + _front = _front!.next; + _queSize--; + return _num; + } + + /* 訪問佇列首元素 */ + int peek() { + if (_queSize == 0) { + throw Exception('佇列為空'); + } + return _front!.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + List toArray() { + ListNode? node = _front; + final List queue = []; + while (node != null) { + queue.add(node.val); + node = node.next; + } + return queue; + } +} + +/* Driver Code */ +void main() { + /* 初始化佇列 */ + final queue = LinkedListQueue(); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print("佇列 queue = ${queue.toArray()}"); + + /* 訪問佇列首元素 */ + final int peek = queue.peek(); + print("佇列首元素 peek = $peek"); + + /* 元素出列 */ + final int pop = queue.pop(); + print("出列元素 pop = $pop ,出列後 queue = ${queue.toArray()}"); + + /* 獲取佇列的長度 */ + final int size = queue.size(); + print("佇列長度 size = $size"); + + /* 判斷佇列是否為空 */ + final bool isEmpty = queue.isEmpty(); + print("佇列是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart new file mode 100644 index 000000000..39a458a07 --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart @@ -0,0 +1,93 @@ +/** + * File: linkedlist_stack.dart + * Created Time: 2023-03-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 基於鏈結串列類別實現的堆疊 */ +class LinkedListStack { + ListNode? _stackPeek; // 將頭節點作為堆疊頂 + int _stkSize = 0; // 堆疊的長度 + + LinkedListStack() { + _stackPeek = null; + } + + /* 獲取堆疊的長度 */ + int size() { + return _stkSize; + } + + /* 判斷堆疊是否為空 */ + bool isEmpty() { + return _stkSize == 0; + } + + /* 入堆疊 */ + void push(int _num) { + final ListNode node = ListNode(_num); + node.next = _stackPeek; + _stackPeek = node; + _stkSize++; + } + + /* 出堆疊 */ + int pop() { + final int _num = peek(); + _stackPeek = _stackPeek!.next; + _stkSize--; + return _num; + } + + /* 訪問堆疊頂元素 */ + int peek() { + if (_stackPeek == null) { + throw Exception("堆疊為空"); + } + return _stackPeek!.val; + } + + /* 將鏈結串列轉化為 List 並返回 */ + List toList() { + ListNode? node = _stackPeek; + List list = []; + while (node != null) { + list.add(node.val); + node = node.next; + } + list = list.reversed.toList(); + return list; + } +} + +/* Driver Code */ +void main() { + /* 初始化堆疊 */ + final LinkedListStack stack = LinkedListStack(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print("堆疊 stack = ${stack.toList()}"); + + /* 訪問堆疊頂元素 */ + final int peek = stack.peek(); + print("堆疊頂元素 peek = $peek"); + + /* 元素出堆疊 */ + final int pop = stack.pop(); + print("出堆疊元素 pop = $pop ,出堆疊後 stack = ${stack.toList()}"); + + /* 獲取堆疊的長度 */ + final int size = stack.size(); + print("堆疊的長度 size = $size"); + + /* 判斷是否為空 */ + final bool isEmpty = stack.isEmpty(); + print("堆疊是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/queue.dart b/zh-hant/codes/dart/chapter_stack_and_queue/queue.dart new file mode 100644 index 000000000..269c39066 --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/queue.dart @@ -0,0 +1,37 @@ +/** + * File: queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +void main() { + /* 初始化佇列 */ + // 在 Dart 中,一般將雙向佇列類別 Queue 看作佇列使用 + final Queue queue = Queue(); + + /* 元素入列 */ + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); + print("佇列 queue = $queue"); + + /* 訪問佇列首元素 */ + final int peek = queue.first; + print("佇列首元素 peek = $peek"); + + /* 元素出列 */ + final int pop = queue.removeFirst(); + print("出列元素 pop = $pop ,出列後 queue = $queue"); + + /* 獲取佇列長度 */ + final int size = queue.length; + print("佇列長度 size = $size"); + + /* 判斷佇列是否為空 */ + final bool isEmpty = queue.isEmpty; + print("佇列是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_stack_and_queue/stack.dart b/zh-hant/codes/dart/chapter_stack_and_queue/stack.dart new file mode 100644 index 000000000..eb492f7d1 --- /dev/null +++ b/zh-hant/codes/dart/chapter_stack_and_queue/stack.dart @@ -0,0 +1,35 @@ +/** + * File: stack.dart + * Created Time: 2023-03-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +void main() { + /* 初始化堆疊 */ + // Dart 沒有內建的堆疊類別,可以把 List 當作堆疊來使用 + final List stack = []; + + /* 元素入堆疊 */ + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + print("堆疊 stack = $stack"); + + /* 訪問堆疊頂元素 */ + final int peek = stack.last; + print("堆疊頂元素 peek = $peek"); + + /* 元素出堆疊 */ + final int pop = stack.removeLast(); + print("出堆疊元素 pop = $pop ,出堆疊後 stack = $stack"); + + /* 獲取堆疊的長度 */ + final int size = stack.length; + print("堆疊的長度 size = $size"); + + /* 判斷是否為空 */ + final bool isEmpty = stack.isEmpty; + print("堆疊是否為空 = $isEmpty"); +} diff --git a/zh-hant/codes/dart/chapter_tree/array_binary_tree.dart b/zh-hant/codes/dart/chapter_tree/array_binary_tree.dart new file mode 100644 index 000000000..30bc18b82 --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/array_binary_tree.dart @@ -0,0 +1,152 @@ +/** + * File: array_binary_tree.dart + * Created Time: 2023-08-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + late List _tree; + + /* 建構子 */ + ArrayBinaryTree(this._tree); + + /* 串列容量 */ + int size() { + return _tree.length; + } + + /* 獲取索引為 i 節點的值 */ + int? val(int i) { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= size()) { + return null; + } + return _tree[i]; + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + int? left(int i) { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + int? right(int i) { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + int? parent(int i) { + return (i - 1) ~/ 2; + } + + /* 層序走訪 */ + List levelOrder() { + List res = []; + for (int i = 0; i < size(); i++) { + if (val(i) != null) { + res.add(val(i)!); + } + } + return res; + } + + /* 深度優先走訪 */ + void dfs(int i, String order, List res) { + // 若為空位,則返回 + if (val(i) == null) { + return; + } + // 前序走訪 + if (order == 'pre') { + res.add(val(i)); + } + dfs(left(i)!, order, res); + // 中序走訪 + if (order == 'in') { + res.add(val(i)); + } + dfs(right(i)!, order, res); + // 後序走訪 + if (order == 'post') { + res.add(val(i)); + } + } + + /* 前序走訪 */ + List preOrder() { + List res = []; + dfs(0, 'pre', res); + return res; + } + + /* 中序走訪 */ + List inOrder() { + List res = []; + dfs(0, 'in', res); + return res; + } + + /* 後序走訪 */ + List postOrder() { + List res = []; + dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +void main() { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + List arr = [ + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 + ]; + + TreeNode? root = listToTree(arr); + print("\n初始化二元樹\n"); + print("二元樹的陣列表示:"); + print(arr); + print("二元樹的鏈結串列表示:"); + printTree(root); + + // 陣列表示下的二元樹類別 + ArrayBinaryTree abt = ArrayBinaryTree(arr); + + // 訪問節點 + int i = 1; + int? l = abt.left(i); + int? r = abt.right(i); + int? p = abt.parent(i); + print("\n當前節點的索引為 $i ,值為 ${abt.val(i)}"); + print("其左子節點的索引為 $l ,值為 ${(l == null ? "null" : abt.val(l))}"); + print("其右子節點的索引為 $r ,值為 ${(r == null ? "null" : abt.val(r))}"); + print("其父節點的索引為 $p ,值為 ${(p == null ? "null" : abt.val(p))}"); + + // 走訪樹 + List res = abt.levelOrder(); + print("\n層序走訪為:$res"); + res = abt.preOrder(); + print("前序走訪為 $res"); + res = abt.inOrder(); + print("中序走訪為 $res"); + res = abt.postOrder(); + print("後序走訪為 $res"); +} diff --git a/zh-hant/codes/dart/chapter_tree/avl_tree.dart b/zh-hant/codes/dart/chapter_tree/avl_tree.dart new file mode 100644 index 000000000..c5c6d4485 --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/avl_tree.dart @@ -0,0 +1,218 @@ +/** + * File: avl_tree.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +class AVLTree { + TreeNode? root; + + /* 建構子 */ + AVLTree() { + root = null; + } + + /* 獲取節點高度 */ + int height(TreeNode? node) { + // 空節點高度為 -1 ,葉節點高度為 0 + return node == null ? -1 : node.height; + } + + /* 更新節點高度 */ + void updateHeight(TreeNode? node) { + // 節點高度等於最高子樹高度 + 1 + node!.height = max(height(node.left), height(node.right)) + 1; + } + + /* 獲取平衡因子 */ + int balanceFactor(TreeNode? node) { + // 空節點平衡因子為 0 + if (node == null) return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node.left) - height(node.right); + } + + /* 右旋操作 */ + TreeNode? rightRotate(TreeNode? node) { + TreeNode? child = node!.left; + TreeNode? grandChild = child!.right; + // 以 child 為原點,將 node 向右旋轉 + child.right = node; + node.left = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + TreeNode? leftRotate(TreeNode? node) { + TreeNode? child = node!.right; + TreeNode? grandChild = child!.left; + // 以 child 為原點,將 node 向左旋轉 + child.left = node; + node.right = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + TreeNode? rotate(TreeNode? node) { + // 獲取節點 node 的平衡因子 + int factor = balanceFactor(node); + // 左偏樹 + if (factor > 1) { + if (balanceFactor(node!.left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋後右旋 + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // 右偏樹 + if (factor < -1) { + if (balanceFactor(node!.right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋後左旋 + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 插入節點 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 遞迴插入節點(輔助方法) */ + TreeNode? insertHelper(TreeNode? node, int val) { + if (node == null) return TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // 重複節點不插入,直接返回 + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 刪除節點 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 遞迴刪除節點(輔助方法) */ + TreeNode? removeHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. 查詢節點並刪除 */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == null) + return null; + // 子節點數量 = 1 ,直接刪除 node + else + node = child; + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + TreeNode? temp = node.right; + while (temp!.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 查詢節點 */ + TreeNode? search(int val) { + TreeNode? cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (val < cur.val) + cur = cur.left; + // 目標節點在 cur 的左子樹中 + else if (val > cur.val) + cur = cur.right; + // 目標節點與當前節點相等 + else + break; + } + return cur; + } +} + +void testInsert(AVLTree tree, int val) { + tree.insert(val); + print("\n插入節點 $val 後,AVL 樹為"); + printTree(tree.root); +} + +void testRemove(AVLTree tree, int val) { + tree.remove(val); + print("\n刪除節點 $val 後,AVL 樹為"); + printTree(tree.root); +} + +/* Driver Code */ +void main() { + /* 初始化空 AVL 樹 */ + AVLTree avlTree = AVLTree(); + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 插入重複節點 */ + testInsert(avlTree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(avlTree, 8); // 刪除度為 0 的節點 + testRemove(avlTree, 5); // 刪除度為 1 的節點 + testRemove(avlTree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + TreeNode? node = avlTree.search(7); + print("\n查詢到的節點物件為 $node ,節點值 = ${node!.val}"); +} diff --git a/zh-hant/codes/dart/chapter_tree/binary_search_tree.dart b/zh-hant/codes/dart/chapter_tree/binary_search_tree.dart new file mode 100644 index 000000000..d27a8dae2 --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/binary_search_tree.dart @@ -0,0 +1,153 @@ +/** + * File: binary_search_tree.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 二元搜尋樹 */ +class BinarySearchTree { + late TreeNode? _root; + + /* 建構子 */ + BinarySearchTree() { + // 初始化空樹 + _root = null; + } + + /* 獲取二元樹的根節點 */ + TreeNode? getRoot() { + return _root; + } + + /* 查詢節點 */ + TreeNode? search(int _num) { + TreeNode? cur = _root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < _num) + cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > _num) + cur = cur.left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + void insert(int _num) { + // 若樹為空,則初始化根節點 + if (_root == null) { + _root = TreeNode(_num); + return; + } + TreeNode? cur = _root; + TreeNode? pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到重複節點,直接返回 + if (cur.val == _num) return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.val < _num) + cur = cur.right; + // 插入位置在 cur 的左子樹中 + else + cur = cur.left; + } + // 插入節點 + TreeNode? node = TreeNode(_num); + if (pre!.val < _num) + pre.right = node; + else + pre.left = node; + } + + /* 刪除節點 */ + void remove(int _num) { + // 若樹為空,直接提前返回 + if (_root == null) return; + TreeNode? cur = _root; + TreeNode? pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到待刪除節點,跳出迴圈 + if (cur.val == _num) break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.val < _num) + cur = cur.right; + // 待刪除節點在 cur 的左子樹中 + else + cur = cur.left; + } + // 若無待刪除節點,直接返回 + if (cur == null) return; + // 子節點數量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + TreeNode? child = cur.left ?? cur.right; + // 刪除節點 cur + if (cur != _root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + _root = child; + } + } else { + // 子節點數量 = 2 + // 獲取中序走訪中 cur 的下一個節點 + TreeNode? tmp = cur.right; + while (tmp!.left != null) { + tmp = tmp.left; + } + // 遞迴刪除節點 tmp + remove(tmp.val); + // 用 tmp 覆蓋 cur + cur.val = tmp.val; + } + } +} + +/* Driver Code */ +void main() { + /* 初始化二元搜尋樹 */ + BinarySearchTree bst = BinarySearchTree(); + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + List nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + for (int _num in nums) { + bst.insert(_num); + } + print("\n初始化的二元樹為\n"); + printTree(bst.getRoot()); + + /* 查詢節點 */ + TreeNode? node = bst.search(7); + print("\n查詢到的節點物件為 $node ,節點值 = ${node?.val}"); + + /* 插入節點 */ + bst.insert(16); + print("\n插入節點 16 後,二元樹為\n"); + printTree(bst.getRoot()); + + /* 刪除節點 */ + bst.remove(1); + print("\n刪除節點 1 後,二元樹為\n"); + printTree(bst.getRoot()); + bst.remove(2); + print("\n刪除節點 2 後,二元樹為\n"); + printTree(bst.getRoot()); + bst.remove(4); + print("\n刪除節點 4 後,二元樹為\n"); + printTree(bst.getRoot()); +} diff --git a/zh-hant/codes/dart/chapter_tree/binary_tree.dart b/zh-hant/codes/dart/chapter_tree/binary_tree.dart new file mode 100644 index 000000000..4cdbfaacb --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/binary_tree.dart @@ -0,0 +1,37 @@ +/** + * File: binary_tree.dart + * Created Time: 2023-04-03 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +void main() { + /* 初始化二元樹 */ + // 舒適化節點 + TreeNode n1 = TreeNode(1); + TreeNode n2 = TreeNode(2); + TreeNode n3 = TreeNode(3); + TreeNode n4 = TreeNode(4); + TreeNode n5 = TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + print("\n初始化二元樹\n"); + printTree(n1); + + /* 插入與刪除節點 */ + TreeNode p = TreeNode(0); + // 在 n1 -> n2 中間插入節點 p + n1.left = p; + p.left = n2; + print("\n插入節點 P 後\n"); + printTree(n1); + // 刪除節點 P + n1.left = n2; + print("\n刪除節點 P 後\n"); + printTree(n1); +} diff --git a/zh-hant/codes/dart/chapter_tree/binary_tree_bfs.dart b/zh-hant/codes/dart/chapter_tree/binary_tree_bfs.dart new file mode 100644 index 000000000..69bf549aa --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/binary_tree_bfs.dart @@ -0,0 +1,38 @@ +/** + * File: binary_tree_bfs.dart + * Created Time: 2023-04-03 + * Author: liuyuxin (gvenusleo@gmai.com) + */ + +import 'dart:collection'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 層序走訪 */ +List levelOrder(TreeNode? root) { + // 初始化佇列,加入根節點 + Queue queue = Queue(); + queue.add(root); + // 初始化一個串列,用於儲存走訪序列 + List res = []; + while (queue.isNotEmpty) { + TreeNode? node = queue.removeFirst(); // 隊列出隊 + res.add(node!.val); // 儲存節點值 + if (node.left != null) queue.add(node.left); // 左子節點入列 + if (node.right != null) queue.add(node.right); // 右子節點入列 + } + return res; +} + +/* Driver Code */ +void main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); + print("\n初始化二元樹\n"); + printTree(root); + + // 層序走訪 + List res = levelOrder(root); + print("\n層序走訪的節點列印序列 = $res"); +} diff --git a/zh-hant/codes/dart/chapter_tree/binary_tree_dfs.dart b/zh-hant/codes/dart/chapter_tree/binary_tree_dfs.dart new file mode 100644 index 000000000..27c373235 --- /dev/null +++ b/zh-hant/codes/dart/chapter_tree/binary_tree_dfs.dart @@ -0,0 +1,62 @@ +/** + * File: binary_tree_dfs.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +// 初始化串列,用於儲存走訪序列 +List list = []; + +/* 前序走訪 */ +void preOrder(TreeNode? node) { + if (node == null) return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.add(node.val); + preOrder(node.left); + preOrder(node.right); +} + +/* 中序走訪 */ +void inOrder(TreeNode? node) { + if (node == null) return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(node.left); + list.add(node.val); + inOrder(node.right); +} + +/* 後序走訪 */ +void postOrder(TreeNode? node) { + if (node == null) return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(node.left); + postOrder(node.right); + list.add(node.val); +} + +/* Driver Code */ +void main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); + print("\n初始化二元樹\n"); + printTree(root); + + /* 前序走訪 */ + list.clear(); + preOrder(root); + print("\n前序走訪的節點列印序列 = $list"); + + /* 中序走訪 */ + list.clear(); + inOrder(root); + print("\n中序走訪的節點列印序列 = $list"); + + /* 後序走訪 */ + list.clear(); + postOrder(root); + print("\n後序走訪的節點列印序列 = $list"); +} diff --git a/zh-hant/codes/dart/utils/list_node.dart b/zh-hant/codes/dart/utils/list_node.dart new file mode 100644 index 000000000..b240a0c16 --- /dev/null +++ b/zh-hant/codes/dart/utils/list_node.dart @@ -0,0 +1,24 @@ +/** + * File: list_node.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 鏈結串列節點 */ +class ListNode { + int val; + ListNode? next; + + ListNode(this.val, [this.next]); +} + +/* 將串列反序列化為鏈結串列 */ +ListNode? listToLinkedList(List list) { + ListNode dum = ListNode(0); + ListNode? head = dum; + for (int val in list) { + head?.next = ListNode(val); + head = head?.next; + } + return dum.next; +} diff --git a/zh-hant/codes/dart/utils/print_util.dart b/zh-hant/codes/dart/utils/print_util.dart new file mode 100644 index 000000000..4426bd01e --- /dev/null +++ b/zh-hant/codes/dart/utils/print_util.dart @@ -0,0 +1,90 @@ +/** + * File: print_util.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:io'; + +import 'list_node.dart'; +import 'tree_node.dart'; + +class Trunk { + Trunk? prev; + String str; + + Trunk(this.prev, this.str); +} + +/* 列印矩陣 (Array) */ +void printMatrix(List> matrix) { + print("["); + for (List row in matrix) { + print(" $row,"); + } + print("]"); +} + +/* 列印鏈結串列 */ +void printLinkedList(ListNode? head) { + List list = []; + + while (head != null) { + list.add('${head.val}'); + head = head.next; + } + + print(list.join(' -> ')); +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode? root, [Trunk? prev = null, bool isRight = false]) { + if (root == null) { + return; + } + + String prev_str = ' '; + Trunk trunk = Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + showTrunks(trunk); + print(' ${root.val}'); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTree(root.left, trunk, false); +} + +void showTrunks(Trunk? p) { + if (p == null) { + return; + } + + showTrunks(p.prev); + stdout.write(p.str); +} + +/* 列印堆積 */ +void printHeap(List heap) { + print("堆積的陣列表示:$heap"); + print("堆積的樹狀表示:"); + TreeNode? root = listToTree(heap); + printTree(root); +} diff --git a/zh-hant/codes/dart/utils/tree_node.dart b/zh-hant/codes/dart/utils/tree_node.dart new file mode 100644 index 000000000..704f66d41 --- /dev/null +++ b/zh-hant/codes/dart/utils/tree_node.dart @@ -0,0 +1,50 @@ +/** + * File: tree_node.dart + * Created Time: 2023-2-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 二元樹節點類別 */ +class TreeNode { + int val; // 節點值 + int height; // 節點高度 + TreeNode? left; // 左子節點引用 + TreeNode? right; // 右子節點引用 + + /* 建構子 */ + TreeNode(this.val, [this.height = 0, this.left, this.right]); +} + +/* 將串列反序列化為二元樹:遞迴 */ +TreeNode? listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.length || arr[i] == null) { + return null; + } + TreeNode? root = TreeNode(arr[i]!); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* 將串列反序列化為二元樹 */ +TreeNode? listToTree(List arr) { + return listToTreeDFS(arr, 0); +} + +/* 將二元樹序列化為串列:遞迴 */ +void treeToListDFS(TreeNode? root, int i, List res) { + if (root == null) return; + while (i >= res.length) { + res.add(null); + } + res[i] = root.val; + treeToListDFS(root.left, 2 * i + 1, res); + treeToListDFS(root.right, 2 * i + 2, res); +} + +/* 將二元樹序列化為串列 */ +List treeToList(TreeNode? root) { + List res = []; + treeToListDFS(root, 0, res); + return res; +} diff --git a/zh-hant/codes/dart/utils/vertex.dart b/zh-hant/codes/dart/utils/vertex.dart new file mode 100644 index 000000000..57c2b8ea6 --- /dev/null +++ b/zh-hant/codes/dart/utils/vertex.dart @@ -0,0 +1,29 @@ +/** + * File: Vertex.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 頂點類別 */ +class Vertex { + int val; + Vertex(this.val); + + /* 輸入值串列 vals ,返回頂點串列 vets */ + static List valsToVets(List vals) { + List vets = []; + for (int i in vals) { + vets.add(Vertex(i)); + } + return vets; + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + static List vetsToVals(List vets) { + List vals = []; + for (Vertex vet in vets) { + vals.add(vet.val); + } + return vals; + } +} diff --git a/zh-hant/codes/docker-compose.yml b/zh-hant/codes/docker-compose.yml new file mode 100644 index 000000000..ea5bd9e8d --- /dev/null +++ b/zh-hant/codes/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' +services: + hello-algo-code: + build: + context: . + args: + # 設定需要安裝的語言,使用空格隔開 + # Set the languages to be installed, separated by spaces + LANGS: "python cpp java csharp" + image: hello-algo-code + container_name: hello-algo-code + stdin_open: true + tty: true diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/array.go b/zh-hant/codes/go/chapter_array_and_linkedlist/array.go new file mode 100644 index 000000000..cbe62d0cf --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/array.go @@ -0,0 +1,79 @@ +// File: array.go +// Created Time: 2022-12-29 +// Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "math/rand" +) + +/* 隨機訪問元素 */ +func randomAccess(nums []int) (randomNum int) { + // 在區間 [0, nums.length) 中隨機抽取一個數字 + randomIndex := rand.Intn(len(nums)) + // 獲取並返回隨機元素 + randomNum = nums[randomIndex] + return +} + +/* 擴展陣列長度 */ +func extend(nums []int, enlarge int) []int { + // 初始化一個擴展長度後的陣列 + res := make([]int, len(nums)+enlarge) + // 將原陣列中的所有元素複製到新陣列 + for i, num := range nums { + res[i] = num + } + // 返回擴展後的新陣列 + return res +} + +/* 在陣列的索引 index 處插入元素 num */ +func insert(nums []int, num int, index int) { + // 把索引 index 以及之後的所有元素向後移動一位 + for i := len(nums) - 1; i > index; i-- { + nums[i] = nums[i-1] + } + // 將 num 賦給 index 處的元素 + nums[index] = num +} + +/* 刪除索引 index 處的元素 */ +func remove(nums []int, index int) { + // 把索引 index 之後的所有元素向前移動一位 + for i := index; i < len(nums)-1; i++ { + nums[i] = nums[i+1] + } +} + +/* 走訪陣列 */ +func traverse(nums []int) { + count := 0 + // 透過索引走訪陣列 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + count = 0 + // 直接走訪陣列元素 + for _, num := range nums { + count += num + } + // 同時走訪資料索引和元素 + for i, num := range nums { + count += nums[i] + count += num + } +} + +/* 在陣列中查詢指定元素 */ +func find(nums []int, target int) (index int) { + index = -1 + for i := 0; i < len(nums); i++ { + if nums[i] == target { + index = i + break + } + } + return +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/array_test.go b/zh-hant/codes/go/chapter_array_and_linkedlist/array_test.go new file mode 100644 index 000000000..b2d9910b9 --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/array_test.go @@ -0,0 +1,50 @@ +// File: array_test.go +// Created Time: 2022-12-29 +// Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +/** +我們將 Go 中的 Slice 切片看作 Array 陣列。因為這樣可以 +降低理解成本,利於我們將關注點放在資料結構與演算法上。 +*/ + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestArray(t *testing.T) { + /* 初始化陣列 */ + var arr [5]int + fmt.Println("陣列 arr =", arr) + // 在 Go 中,指定長度時([5]int)為陣列,不指定長度時([]int)為切片 + // 由於 Go 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 + // 為了方便實現擴容 extend() 函式,以下將切片(Slice)看作陣列(Array) + nums := []int{1, 3, 2, 5, 4} + fmt.Println("陣列 nums =", nums) + + /* 隨機訪問 */ + randomNum := randomAccess(nums) + fmt.Println("在 nums 中獲取隨機元素", randomNum) + + /* 長度擴展 */ + nums = extend(nums, 3) + fmt.Println("將陣列長度擴展至 8 ,得到 nums =", nums) + + /* 插入元素 */ + insert(nums, 6, 3) + fmt.Println("在索引 3 處插入數字 6 ,得到 nums =", nums) + + /* 刪除元素 */ + remove(nums, 2) + fmt.Println("刪除索引 2 處的元素,得到 nums =", nums) + + /* 走訪陣列 */ + traverse(nums) + + /* 查詢元素 */ + index := find(nums, 3) + fmt.Println("在 nums 中查詢元素 3 ,得到索引 =", index) +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list.go b/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list.go new file mode 100644 index 000000000..df7e4ce6d --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list.go @@ -0,0 +1,51 @@ +// File: linked_list.go +// Created Time: 2022-12-29 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +func insertNode(n0 *ListNode, P *ListNode) { + n1 := n0.Next + P.Next = n1 + n0.Next = P +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +func removeItem(n0 *ListNode) { + if n0.Next == nil { + return + } + // n0 -> P -> n1 + P := n0.Next + n1 := P.Next + n0.Next = n1 +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +func access(head *ListNode, index int) *ListNode { + for i := 0; i < index; i++ { + if head == nil { + return nil + } + head = head.Next + } + return head +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +func findNode(head *ListNode, target int) int { + index := 0 + for head != nil { + if head.Val == target { + return index + } + head = head.Next + index++ + } + return -1 +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list_test.go b/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list_test.go new file mode 100644 index 000000000..940592d8a --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/linked_list_test.go @@ -0,0 +1,48 @@ +// File: linked_list_test.go +// Created Time: 2022-12-29 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLinkedList(t *testing.T) { + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + n0 := NewListNode(1) + n1 := NewListNode(3) + n2 := NewListNode(2) + n3 := NewListNode(5) + n4 := NewListNode(4) + + // 構建節點之間的引用 + n0.Next = n1 + n1.Next = n2 + n2.Next = n3 + n3.Next = n4 + fmt.Println("初始化的鏈結串列為") + PrintLinkedList(n0) + + /* 插入節點 */ + insertNode(n0, NewListNode(0)) + fmt.Println("插入節點後的鏈結串列為") + PrintLinkedList(n0) + + /* 刪除節點 */ + removeItem(n0) + fmt.Println("刪除節點後的鏈結串列為") + PrintLinkedList(n0) + + /* 訪問節點 */ + node := access(n0, 3) + fmt.Println("鏈結串列中索引 3 處的節點的值 =", node) + + /* 查詢節點 */ + index := findNode(n0, 2) + fmt.Println("鏈結串列中值為 2 的節點的索引 =", index) +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/list_test.go b/zh-hant/codes/go/chapter_array_and_linkedlist/list_test.go new file mode 100644 index 000000000..fdc7cf248 --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/list_test.go @@ -0,0 +1,66 @@ +// File: list_test.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "sort" + "testing" +) + +/* Driver Code */ +func TestList(t *testing.T) { + /* 初始化串列 */ + nums := []int{1, 3, 2, 5, 4} + fmt.Println("串列 nums =", nums) + + /* 訪問元素 */ + num := nums[1] // 訪問索引 1 處的元素 + fmt.Println("訪問索引 1 處的元素,得到 num =", num) + + /* 更新元素 */ + nums[1] = 0 // 將索引 1 處的元素更新為 0 + fmt.Println("將索引 1 處的元素更新為 0 ,得到 nums =", nums) + + /* 清空串列 */ + nums = nil + fmt.Println("清空串列後 nums =", nums) + + /* 在尾部新增元素 */ + nums = append(nums, 1) + nums = append(nums, 3) + nums = append(nums, 2) + nums = append(nums, 5) + nums = append(nums, 4) + fmt.Println("新增元素後 nums =", nums) + + /* 在中間插入元素 */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 處插入數字 6 + fmt.Println("在索引 3 處插入數字 6 ,得到 nums =", nums) + + /* 刪除元素 */ + nums = append(nums[:3], nums[4:]...) // 刪除索引 3 處的元素 + fmt.Println("刪除索引 3 處的元素,得到 nums =", nums) + + /* 透過索引走訪串列 */ + count := 0 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + /* 直接走訪串列元素 */ + count = 0 + for _, x := range nums { + count += x + } + + /* 拼接兩個串列 */ + nums1 := []int{6, 8, 7, 10, 9} + nums = append(nums, nums1...) // 將串列 nums1 拼接到 nums 之後 + fmt.Println("將串列 nums1 拼接到 nums 之後,得到 nums =", nums) + + /* 排序串列 */ + sort.Ints(nums) // 排序後,串列元素從小到大排列 + fmt.Println("排序串列後 nums =", nums) +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/my_list.go b/zh-hant/codes/go/chapter_array_and_linkedlist/my_list.go new file mode 100644 index 000000000..ac06cb620 --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/my_list.go @@ -0,0 +1,109 @@ +// File: my_list.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +/* 串列類別 */ +type myList struct { + arrCapacity int + arr []int + arrSize int + extendRatio int +} + +/* 建構子 */ +func newMyList() *myList { + return &myList{ + arrCapacity: 10, // 串列容量 + arr: make([]int, 10), // 陣列(儲存串列元素) + arrSize: 0, // 串列長度(當前元素數量) + extendRatio: 2, // 每次串列擴容的倍數 + } +} + +/* 獲取串列長度(當前元素數量) */ +func (l *myList) size() int { + return l.arrSize +} + +/* 獲取串列容量 */ +func (l *myList) capacity() int { + return l.arrCapacity +} + +/* 訪問元素 */ +func (l *myList) get(index int) int { + // 索引如果越界,則丟擲異常,下同 + if index < 0 || index >= l.arrSize { + panic("索引越界") + } + return l.arr[index] +} + +/* 更新元素 */ +func (l *myList) set(num, index int) { + if index < 0 || index >= l.arrSize { + panic("索引越界") + } + l.arr[index] = num +} + +/* 在尾部新增元素 */ +func (l *myList) add(num int) { + // 元素數量超出容量時,觸發擴容機制 + if l.arrSize == l.arrCapacity { + l.extendCapacity() + } + l.arr[l.arrSize] = num + // 更新元素數量 + l.arrSize++ +} + +/* 在中間插入元素 */ +func (l *myList) insert(num, index int) { + if index < 0 || index >= l.arrSize { + panic("索引越界") + } + // 元素數量超出容量時,觸發擴容機制 + if l.arrSize == l.arrCapacity { + l.extendCapacity() + } + // 將索引 index 以及之後的元素都向後移動一位 + for j := l.arrSize - 1; j >= index; j-- { + l.arr[j+1] = l.arr[j] + } + l.arr[index] = num + // 更新元素數量 + l.arrSize++ +} + +/* 刪除元素 */ +func (l *myList) remove(index int) int { + if index < 0 || index >= l.arrSize { + panic("索引越界") + } + num := l.arr[index] + // 將索引 index 之後的元素都向前移動一位 + for j := index; j < l.arrSize-1; j++ { + l.arr[j] = l.arr[j+1] + } + // 更新元素數量 + l.arrSize-- + // 返回被刪除的元素 + return num +} + +/* 串列擴容 */ +func (l *myList) extendCapacity() { + // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 + l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...) + // 更新串列容量 + l.arrCapacity = len(l.arr) +} + +/* 返回有效長度的串列 */ +func (l *myList) toArray() []int { + // 僅轉換有效長度範圍內的串列元素 + return l.arr[:l.arrSize] +} diff --git a/zh-hant/codes/go/chapter_array_and_linkedlist/my_list_test.go b/zh-hant/codes/go/chapter_array_and_linkedlist/my_list_test.go new file mode 100644 index 000000000..f094c227a --- /dev/null +++ b/zh-hant/codes/go/chapter_array_and_linkedlist/my_list_test.go @@ -0,0 +1,46 @@ +// File: my_list_test.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestMyList(t *testing.T) { + /* 初始化串列 */ + nums := newMyList() + /* 在尾部新增元素 */ + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + fmt.Printf("串列 nums = %v ,容量 = %v ,長度 = %v\n", nums.toArray(), nums.capacity(), nums.size()) + + /* 在中間插入元素 */ + nums.insert(6, 3) + fmt.Printf("在索引 3 處插入數字 6 ,得到 nums = %v\n", nums.toArray()) + + /* 刪除元素 */ + nums.remove(3) + fmt.Printf("刪除索引 3 處的元素,得到 nums = %v\n", nums.toArray()) + + /* 訪問元素 */ + num := nums.get(1) + fmt.Printf("訪問索引 1 處的元素,得到 num = %v\n", num) + + /* 更新元素 */ + nums.set(0, 1) + fmt.Printf("將索引 1 處的元素更新為 0 ,得到 nums = %v\n", nums.toArray()) + + /* 測試擴容機制 */ + for i := 0; i < 10; i++ { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i) + } + fmt.Printf("擴容後的串列 nums = %v ,容量 = %v ,長度 = %v\n", nums.toArray(), nums.capacity(), nums.size()) +} diff --git a/zh-hant/codes/go/chapter_backtracking/n_queens.go b/zh-hant/codes/go/chapter_backtracking/n_queens.go new file mode 100644 index 000000000..7d8fd5e82 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/n_queens.go @@ -0,0 +1,56 @@ +// File: n_queens.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯演算法:n 皇后 */ +func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { + // 當放置完所有行時,記錄解 + if row == n { + newState := make([][]string, len(*state)) + for i, _ := range newState { + newState[i] = make([]string, len((*state)[0])) + copy(newState[i], (*state)[i]) + + } + *res = append(*res, newState) + } + // 走訪所有列 + for col := 0; col < n; col++ { + // 計算該格子對應的主對角線和次對角線 + diag1 := row - col + n - 1 + diag2 := row + col + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { + // 嘗試:將皇后放置在該格子 + (*state)[row][col] = "Q" + (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true + // 放置下一行 + backtrack(row+1, n, state, res, cols, diags1, diags2) + // 回退:將該格子恢復為空位 + (*state)[row][col] = "#" + (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false + } + } +} + +/* 求解 n 皇后 */ +func nQueens(n int) [][][]string { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + state := make([][]string, n) + for i := 0; i < n; i++ { + row := make([]string, n) + for i := 0; i < n; i++ { + row[i] = "#" + } + state[i] = row + } + // 記錄列是否有皇后 + cols := make([]bool, n) + diags1 := make([]bool, 2*n-1) + diags2 := make([]bool, 2*n-1) + res := make([][][]string, 0) + backtrack(0, n, &state, &res, &cols, &diags1, &diags2) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/n_queens_test.go b/zh-hant/codes/go/chapter_backtracking/n_queens_test.go new file mode 100644 index 000000000..08bd2057b --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/n_queens_test.go @@ -0,0 +1,24 @@ +// File: n_queens_test.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" +) + +func TestNQueens(t *testing.T) { + n := 4 + res := nQueens(n) + + fmt.Println("輸入棋盤長寬為 ", n) + fmt.Println("皇后放置方案共有 ", len(res), " 種") + for _, state := range res { + fmt.Println("--------------------") + for _, row := range state { + fmt.Println(row) + } + } +} diff --git a/zh-hant/codes/go/chapter_backtracking/permutation_test.go b/zh-hant/codes/go/chapter_backtracking/permutation_test.go new file mode 100644 index 000000000..183095eef --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/permutation_test.go @@ -0,0 +1,33 @@ +// File: permutation_test.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPermutationI(t *testing.T) { + /* 全排列 I */ + nums := []int{1, 2, 3} + fmt.Printf("輸入陣列 nums = ") + PrintSlice(nums) + + res := permutationsI(nums) + fmt.Printf("所有排列 res = ") + fmt.Println(res) +} + +func TestPermutationII(t *testing.T) { + nums := []int{1, 2, 2} + fmt.Printf("輸入陣列 nums = ") + PrintSlice(nums) + + res := permutationsII(nums) + fmt.Printf("所有排列 res = ") + fmt.Println(res) +} diff --git a/zh-hant/codes/go/chapter_backtracking/permutations_i.go b/zh-hant/codes/go/chapter_backtracking/permutations_i.go new file mode 100644 index 000000000..a068c12bf --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/permutations_i.go @@ -0,0 +1,38 @@ +// File: permutations_i.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯演算法:全排列 I */ +func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { + // 當狀態長度等於元素數量時,記錄解 + if len(*state) == len(*choices) { + newState := append([]int{}, *state...) + *res = append(*res, newState) + } + // 走訪所有選擇 + for i := 0; i < len(*choices); i++ { + choice := (*choices)[i] + // 剪枝:不允許重複選擇元素 + if !(*selected)[i] { + // 嘗試:做出選擇,更新狀態 + (*selected)[i] = true + *state = append(*state, choice) + // 進行下一輪選擇 + backtrackI(state, choices, selected, res) + // 回退:撤銷選擇,恢復到之前的狀態 + (*selected)[i] = false + *state = (*state)[:len(*state)-1] + } + } +} + +/* 全排列 I */ +func permutationsI(nums []int) [][]int { + res := make([][]int, 0) + state := make([]int, 0) + selected := make([]bool, len(nums)) + backtrackI(&state, &nums, &selected, &res) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/permutations_ii.go b/zh-hant/codes/go/chapter_backtracking/permutations_ii.go new file mode 100644 index 000000000..421824f44 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/permutations_ii.go @@ -0,0 +1,41 @@ +// File: permutations_ii.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯演算法:全排列 II */ +func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { + // 當狀態長度等於元素數量時,記錄解 + if len(*state) == len(*choices) { + newState := append([]int{}, *state...) + *res = append(*res, newState) + } + // 走訪所有選擇 + duplicated := make(map[int]struct{}, 0) + for i := 0; i < len(*choices); i++ { + choice := (*choices)[i] + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if _, ok := duplicated[choice]; !ok && !(*selected)[i] { + // 嘗試:做出選擇,更新狀態 + // 記錄選擇過的元素值 + duplicated[choice] = struct{}{} + (*selected)[i] = true + *state = append(*state, choice) + // 進行下一輪選擇 + backtrackI(state, choices, selected, res) + // 回退:撤銷選擇,恢復到之前的狀態 + (*selected)[i] = false + *state = (*state)[:len(*state)-1] + } + } +} + +/* 全排列 II */ +func permutationsII(nums []int) [][]int { + res := make([][]int, 0) + state := make([]int, 0) + selected := make([]bool, len(nums)) + backtrackII(&state, &nums, &selected, &res) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/preorder_traversal_i_compact.go b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_i_compact.go new file mode 100644 index 000000000..1c84ab866 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_i_compact.go @@ -0,0 +1,22 @@ +// File: preorder_traversal_i_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前序走訪:例題一 */ +func preOrderI(root *TreeNode, res *[]*TreeNode) { + if root == nil { + return + } + if (root.Val).(int) == 7 { + // 記錄解 + *res = append(*res, root) + } + preOrderI(root.Left, res) + preOrderI(root.Right, res) +} diff --git a/zh-hant/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go new file mode 100644 index 000000000..b1b78acaf --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go @@ -0,0 +1,26 @@ +// File: preorder_traversal_ii_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前序走訪:例題二 */ +func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { + if root == nil { + return + } + // 嘗試 + *path = append(*path, root) + if root.Val.(int) == 7 { + // 記錄解 + *res = append(*res, append([]*TreeNode{}, *path...)) + } + preOrderII(root.Left, res, path) + preOrderII(root.Right, res, path) + // 回退 + *path = (*path)[:len(*path)-1] +} diff --git a/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go new file mode 100644 index 000000000..c539925de --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go @@ -0,0 +1,27 @@ +// File: preorder_traversal_iii_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前序走訪:例題三 */ +func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { + // 剪枝 + if root == nil || root.Val == 3 { + return + } + // 嘗試 + *path = append(*path, root) + if root.Val.(int) == 7 { + // 記錄解 + *res = append(*res, append([]*TreeNode{}, *path...)) + } + preOrderIII(root.Left, res, path) + preOrderIII(root.Right, res, path) + // 回退 + *path = (*path)[:len(*path)-1] +} diff --git a/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_template.go b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_template.go new file mode 100644 index 000000000..63c24f196 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_iii_template.go @@ -0,0 +1,57 @@ +// File: preorder_traversal_iii_template.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 判斷當前狀態是否為解 */ +func isSolution(state *[]*TreeNode) bool { + return len(*state) != 0 && (*state)[len(*state)-1].Val == 7 +} + +/* 記錄解 */ +func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) { + *res = append(*res, append([]*TreeNode{}, *state...)) +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +func isValid(state *[]*TreeNode, choice *TreeNode) bool { + return choice != nil && choice.Val != 3 +} + +/* 更新狀態 */ +func makeChoice(state *[]*TreeNode, choice *TreeNode) { + *state = append(*state, choice) +} + +/* 恢復狀態 */ +func undoChoice(state *[]*TreeNode, choice *TreeNode) { + *state = (*state)[:len(*state)-1] +} + +/* 回溯演算法:例題三 */ +func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) { + // 檢查是否為解 + if isSolution(state) { + // 記錄解 + recordSolution(state, res) + } + // 走訪所有選擇 + for _, choice := range *choices { + // 剪枝:檢查選擇是否合法 + if isValid(state, choice) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice) + // 進行下一輪選擇 + temp := make([]*TreeNode, 0) + temp = append(temp, choice.Left, choice.Right) + backtrackIII(state, &temp, res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice) + } + } +} diff --git a/zh-hant/codes/go/chapter_backtracking/preorder_traversal_test.go b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_test.go new file mode 100644 index 000000000..1dfd1fd79 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/preorder_traversal_test.go @@ -0,0 +1,91 @@ +// File: preorder_traversal_i_compact_test.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPreorderTraversalICompact(t *testing.T) { + /* 初始化二元樹 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹") + PrintTree(root) + + // 前序走訪 + res := make([]*TreeNode, 0) + preOrderI(root, &res) + + fmt.Println("\n輸出所有值為 7 的節點") + for _, node := range res { + fmt.Printf("%v ", node.Val) + } + fmt.Println() +} + +func TestPreorderTraversalIICompact(t *testing.T) { + /* 初始化二元樹 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹") + PrintTree(root) + + // 前序走訪 + path := make([]*TreeNode, 0) + res := make([][]*TreeNode, 0) + preOrderII(root, &res, &path) + + fmt.Println("\n輸出所有根節點到節點 7 的路徑") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} + +func TestPreorderTraversalIIICompact(t *testing.T) { + /* 初始化二元樹 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹") + PrintTree(root) + + // 前序走訪 + path := make([]*TreeNode, 0) + res := make([][]*TreeNode, 0) + preOrderIII(root, &res, &path) + + fmt.Println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} + +func TestPreorderTraversalIIITemplate(t *testing.T) { + /* 初始化二元樹 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹") + PrintTree(root) + + // 回溯演算法 + res := make([][]*TreeNode, 0) + state := make([]*TreeNode, 0) + choices := make([]*TreeNode, 0) + choices = append(choices, root) + backtrackIII(&state, &choices, &res) + + fmt.Println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} diff --git a/zh-hant/codes/go/chapter_backtracking/subset_sum_i.go b/zh-hant/codes/go/chapter_backtracking/subset_sum_i.go new file mode 100644 index 000000000..01ff3fbd6 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/subset_sum_i.go @@ -0,0 +1,42 @@ +// File: subset_sum_i.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import "sort" + +/* 回溯演算法:子集和 I */ +func backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) { + // 子集和等於 target 時,記錄解 + if target == 0 { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for i := start; i < len(*choices); i++ { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target-(*choices)[i] < 0 { + break + } + // 嘗試:做出選擇,更新 target, start + *state = append(*state, (*choices)[i]) + // 進行下一輪選擇 + backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + *state = (*state)[:len(*state)-1] + } +} + +/* 求解子集和 I */ +func subsetSumI(nums []int, target int) [][]int { + state := make([]int, 0) // 狀態(子集) + sort.Ints(nums) // 對 nums 進行排序 + start := 0 // 走訪起始點 + res := make([][]int, 0) // 結果串列(子集串列) + backtrackSubsetSumI(start, target, &state, &nums, &res) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/subset_sum_i_naive.go b/zh-hant/codes/go/chapter_backtracking/subset_sum_i_naive.go new file mode 100644 index 000000000..c8ee15699 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/subset_sum_i_naive.go @@ -0,0 +1,37 @@ +// File: subset_sum_i_naive.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* 回溯演算法:子集和 I */ +func backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) { + // 子集和等於 target 時,記錄解 + if target == total { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // 走訪所有選擇 + for i := 0; i < len(*choices); i++ { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if total+(*choices)[i] > target { + continue + } + // 嘗試:做出選擇,更新元素和 total + *state = append(*state, (*choices)[i]) + // 進行下一輪選擇 + backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + *state = (*state)[:len(*state)-1] + } +} + +/* 求解子集和 I(包含重複子集) */ +func subsetSumINaive(nums []int, target int) [][]int { + state := make([]int, 0) // 狀態(子集) + total := 0 // 子集和 + res := make([][]int, 0) // 結果串列(子集串列) + backtrackSubsetSumINaive(total, target, &state, &nums, &res) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/subset_sum_ii.go b/zh-hant/codes/go/chapter_backtracking/subset_sum_ii.go new file mode 100644 index 000000000..3e03790c3 --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/subset_sum_ii.go @@ -0,0 +1,47 @@ +// File: subset_sum_ii.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import "sort" + +/* 回溯演算法:子集和 II */ +func backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) { + // 子集和等於 target 時,記錄解 + if target == 0 { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for i := start; i < len(*choices); i++ { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target-(*choices)[i] < 0 { + break + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if i > start && (*choices)[i] == (*choices)[i-1] { + continue + } + // 嘗試:做出選擇,更新 target, start + *state = append(*state, (*choices)[i]) + // 進行下一輪選擇 + backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + *state = (*state)[:len(*state)-1] + } +} + +/* 求解子集和 II */ +func subsetSumII(nums []int, target int) [][]int { + state := make([]int, 0) // 狀態(子集) + sort.Ints(nums) // 對 nums 進行排序 + start := 0 // 走訪起始點 + res := make([][]int, 0) // 結果串列(子集串列) + backtrackSubsetSumII(start, target, &state, &nums, &res) + return res +} diff --git a/zh-hant/codes/go/chapter_backtracking/subset_sum_test.go b/zh-hant/codes/go/chapter_backtracking/subset_sum_test.go new file mode 100644 index 000000000..5920be25d --- /dev/null +++ b/zh-hant/codes/go/chapter_backtracking/subset_sum_test.go @@ -0,0 +1,56 @@ +// File: subset_sum_test.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestSubsetSumINaive(t *testing.T) { + nums := []int{3, 4, 5} + target := 9 + res := subsetSumINaive(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 輸入陣列 nums = ") + PrintSlice(nums) + + fmt.Println("所有和等於 " + strconv.Itoa(target) + " 的子集 res = ") + for i := range res { + PrintSlice(res[i]) + } + fmt.Println("請注意,該方法輸出的結果包含重複集合") +} + +func TestSubsetSumI(t *testing.T) { + nums := []int{3, 4, 5} + target := 9 + res := subsetSumI(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 輸入陣列 nums = ") + PrintSlice(nums) + + fmt.Println("所有和等於 " + strconv.Itoa(target) + " 的子集 res = ") + for i := range res { + PrintSlice(res[i]) + } +} + +func TestSubsetSumII(t *testing.T) { + nums := []int{4, 4, 5} + target := 9 + res := subsetSumII(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 輸入陣列 nums = ") + PrintSlice(nums) + + fmt.Println("所有和等於 " + strconv.Itoa(target) + " 的子集 res = ") + for i := range res { + PrintSlice(res[i]) + } +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/iteration.go b/zh-hant/codes/go/chapter_computational_complexity/iteration.go new file mode 100644 index 000000000..a65acb804 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/iteration.go @@ -0,0 +1,59 @@ +// File: iteration.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import "fmt" + +/* for 迴圈 */ +func forLoop(n int) int { + res := 0 + // 迴圈求和 1, 2, ..., n-1, n + for i := 1; i <= n; i++ { + res += i + } + return res +} + +/* while 迴圈 */ +func whileLoop(n int) int { + res := 0 + // 初始化條件變數 + i := 1 + // 迴圈求和 1, 2, ..., n-1, n + for i <= n { + res += i + // 更新條件變數 + i++ + } + return res +} + +/* while 迴圈(兩次更新) */ +func whileLoopII(n int) int { + res := 0 + // 初始化條件變數 + i := 1 + // 迴圈求和 1, 4, 10, ... + for i <= n { + res += i + // 更新條件變數 + i++ + i *= 2 + } + return res +} + +/* 雙層 for 迴圈 */ +func nestedForLoop(n int) string { + res := "" + // 迴圈 i = 1, 2, ..., n-1, n + for i := 1; i <= n; i++ { + for j := 1; j <= n; j++ { + // 迴圈 j = 1, 2, ..., n-1, n + res += fmt.Sprintf("(%d, %d), ", i, j) + } + } + return res +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/iteration_test.go b/zh-hant/codes/go/chapter_computational_complexity/iteration_test.go new file mode 100644 index 000000000..d7e893c35 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/iteration_test.go @@ -0,0 +1,26 @@ +// File: iteration_test.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestIteration(t *testing.T) { + n := 5 + res := forLoop(n) + fmt.Println("\nfor 迴圈的求和結果 res = ", res) + + res = whileLoop(n) + fmt.Println("\nwhile 迴圈的求和結果 res = ", res) + + res = whileLoopII(n) + fmt.Println("\nwhile 迴圈(兩次更新)求和結果 res = ", res) + + resStr := nestedForLoop(n) + fmt.Println("\n雙層 for 迴圈的走訪結果 ", resStr) +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/recursion.go b/zh-hant/codes/go/chapter_computational_complexity/recursion.go new file mode 100644 index 000000000..a0c4d6698 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/recursion.go @@ -0,0 +1,61 @@ +// File: recursion.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import "container/list" + +/* 遞迴 */ +func recur(n int) int { + // 終止條件 + if n == 1 { + return 1 + } + // 遞:遞迴呼叫 + res := recur(n - 1) + // 迴:返回結果 + return n + res +} + +/* 使用迭代模擬遞迴 */ +func forLoopRecur(n int) int { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + stack := list.New() + res := 0 + // 遞:遞迴呼叫 + for i := n; i > 0; i-- { + // 透過“入堆疊操作”模擬“遞” + stack.PushBack(i) + } + // 迴:返回結果 + for stack.Len() != 0 { + // 透過“出堆疊操作”模擬“迴” + res += stack.Back().Value.(int) + stack.Remove(stack.Back()) + } + // res = 1+2+3+...+n + return res +} + +/* 尾遞迴 */ +func tailRecur(n int, res int) int { + // 終止條件 + if n == 0 { + return res + } + // 尾遞迴呼叫 + return tailRecur(n-1, res+n) +} + +/* 費波那契數列:遞迴 */ +func fib(n int) int { + // 終止條件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + res := fib(n-1) + fib(n-2) + // 返回結果 f(n) + return res +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/recursion_test.go b/zh-hant/codes/go/chapter_computational_complexity/recursion_test.go new file mode 100644 index 000000000..4798877db --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/recursion_test.go @@ -0,0 +1,26 @@ +// File: recursion_test.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestRecursion(t *testing.T) { + n := 5 + res := recur(n) + fmt.Println("\n遞迴函式的求和結果 res = ", res) + + res = forLoopRecur(n) + fmt.Println("\n使用迭代模擬遞迴求和結果 res = ", res) + + res = tailRecur(n, 0) + fmt.Println("\n尾遞迴函式的求和結果 res = ", res) + + res = fib(n) + fmt.Println("\n費波那契數列的第", n, "項為", res) +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/space_complexity.go b/zh-hant/codes/go/chapter_computational_complexity/space_complexity.go new file mode 100644 index 000000000..8e5f14156 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/space_complexity.go @@ -0,0 +1,106 @@ +// File: space_complexity.go +// Created Time: 2022-12-15 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "strconv" + + . "github.com/krahets/hello-algo/pkg" +) + +/* 結構體 */ +type node struct { + val int + next *node +} + +/* 建立 node 結構體 */ +func newNode(val int) *node { + return &node{val: val} +} + +/* 函式 */ +func function() int { + // 執行某些操作... + return 0 +} + +/* 常數階 */ +func spaceConstant(n int) { + // 常數、變數、物件佔用 O(1) 空間 + const a = 0 + b := 0 + nums := make([]int, 10000) + node := newNode(0) + // 迴圈中的變數佔用 O(1) 空間 + var c int + for i := 0; i < n; i++ { + c = 0 + } + // 迴圈中的函式佔用 O(1) 空間 + for i := 0; i < n; i++ { + function() + } + b += 0 + c += 0 + nums[0] = 0 + node.val = 0 +} + +/* 線性階 */ +func spaceLinear(n int) { + // 長度為 n 的陣列佔用 O(n) 空間 + _ = make([]int, n) + // 長度為 n 的串列佔用 O(n) 空間 + var nodes []*node + for i := 0; i < n; i++ { + nodes = append(nodes, newNode(i)) + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + m := make(map[int]string, n) + for i := 0; i < n; i++ { + m[i] = strconv.Itoa(i) + } +} + +/* 線性階(遞迴實現) */ +func spaceLinearRecur(n int) { + fmt.Println("遞迴 n =", n) + if n == 1 { + return + } + spaceLinearRecur(n - 1) +} + +/* 平方階 */ +func spaceQuadratic(n int) { + // 矩陣佔用 O(n^2) 空間 + numMatrix := make([][]int, n) + for i := 0; i < n; i++ { + numMatrix[i] = make([]int, n) + } +} + +/* 平方階(遞迴實現) */ +func spaceQuadraticRecur(n int) int { + if n <= 0 { + return 0 + } + nums := make([]int, n) + fmt.Printf("遞迴 n = %d 中的 nums 長度 = %d \n", n, len(nums)) + return spaceQuadraticRecur(n - 1) +} + +/* 指數階(建立滿二元樹) */ +func buildTree(n int) *TreeNode { + if n == 0 { + return nil + } + root := NewTreeNode(0) + root.Left = buildTree(n - 1) + root.Right = buildTree(n - 1) + return root +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/space_complexity_test.go b/zh-hant/codes/go/chapter_computational_complexity/space_complexity_test.go new file mode 100644 index 000000000..f40b79622 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/space_complexity_test.go @@ -0,0 +1,26 @@ +// File: space_complexity_test.go +// Created Time: 2022-12-15 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestSpaceComplexity(t *testing.T) { + n := 5 + // 常數階 + spaceConstant(n) + // 線性階 + spaceLinear(n) + spaceLinearRecur(n) + // 平方階 + spaceQuadratic(n) + spaceQuadraticRecur(n) + // 指數階 + root := buildTree(n) + PrintTree(root) +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/time_complexity.go b/zh-hant/codes/go/chapter_computational_complexity/time_complexity.go new file mode 100644 index 000000000..5592eadaf --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/time_complexity.go @@ -0,0 +1,130 @@ +// File: time_complexity.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_computational_complexity + +/* 常數階 */ +func constant(n int) int { + count := 0 + size := 100000 + for i := 0; i < size; i++ { + count++ + } + return count +} + +/* 線性階 */ +func linear(n int) int { + count := 0 + for i := 0; i < n; i++ { + count++ + } + return count +} + +/* 線性階(走訪陣列) */ +func arrayTraversal(nums []int) int { + count := 0 + // 迴圈次數與陣列長度成正比 + for range nums { + count++ + } + return count +} + +/* 平方階 */ +func quadratic(n int) int { + count := 0 + // 迴圈次數與資料大小 n 成平方關係 + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + count++ + } + } + return count +} + +/* 平方階(泡沫排序) */ +func bubbleSort(nums []int) int { + count := 0 // 計數器 + // 外迴圈:未排序區間為 [0, i] + for i := len(nums) - 1; i > 0; i-- { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // 交換 nums[j] 與 nums[j + 1] + tmp := nums[j] + nums[j] = nums[j+1] + nums[j+1] = tmp + count += 3 // 元素交換包含 3 個單元操作 + } + } + } + return count +} + +/* 指數階(迴圈實現)*/ +func exponential(n int) int { + count, base := 0, 1 + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for i := 0; i < n; i++ { + for j := 0; j < base; j++ { + count++ + } + base *= 2 + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count +} + +/* 指數階(遞迴實現)*/ +func expRecur(n int) int { + if n == 1 { + return 1 + } + return expRecur(n-1) + expRecur(n-1) + 1 +} + +/* 對數階(迴圈實現)*/ +func logarithmic(n int) int { + count := 0 + for n > 1 { + n = n / 2 + count++ + } + return count +} + +/* 對數階(遞迴實現)*/ +func logRecur(n int) int { + if n <= 1 { + return 0 + } + return logRecur(n/2) + 1 +} + +/* 線性對數階 */ +func linearLogRecur(n int) int { + if n <= 1 { + return 1 + } + count := linearLogRecur(n/2) + linearLogRecur(n/2) + for i := 0; i < n; i++ { + count++ + } + return count +} + +/* 階乘階(遞迴實現) */ +func factorialRecur(n int) int { + if n == 0 { + return 1 + } + count := 0 + // 從 1 個分裂出 n 個 + for i := 0; i < n; i++ { + count += factorialRecur(n - 1) + } + return count +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/time_complexity_test.go b/zh-hant/codes/go/chapter_computational_complexity/time_complexity_test.go new file mode 100644 index 000000000..86b121d7c --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/time_complexity_test.go @@ -0,0 +1,48 @@ +// File: time_complexity_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +func TestTimeComplexity(t *testing.T) { + n := 8 + fmt.Println("輸入資料大小 n =", n) + + count := constant(n) + fmt.Println("常數階的操作數量 =", count) + + count = linear(n) + fmt.Println("線性階的操作數量 =", count) + count = arrayTraversal(make([]int, n)) + fmt.Println("線性階(走訪陣列)的操作數量 =", count) + + count = quadratic(n) + fmt.Println("平方階的操作數量 =", count) + nums := make([]int, n) + for i := 0; i < n; i++ { + nums[i] = n - i + } + count = bubbleSort(nums) + fmt.Println("平方階(泡沫排序)的操作數量 =", count) + + count = exponential(n) + fmt.Println("指數階(迴圈實現)的操作數量 =", count) + count = expRecur(n) + fmt.Println("指數階(遞迴實現)的操作數量 =", count) + + count = logarithmic(n) + fmt.Println("對數階(迴圈實現)的操作數量 =", count) + count = logRecur(n) + fmt.Println("對數階(遞迴實現)的操作數量 =", count) + + count = linearLogRecur(n) + fmt.Println("線性對數階(遞迴實現)的操作數量 =", count) + + count = factorialRecur(n) + fmt.Println("階乘階(遞迴實現)的操作數量 =", count) +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity.go b/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity.go new file mode 100644 index 000000000..e64bfce53 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity.go @@ -0,0 +1,35 @@ +// File: worst_best_time_complexity.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "math/rand" +) + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +func randomNumbers(n int) []int { + nums := make([]int, n) + // 生成陣列 nums = { 1, 2, 3, ..., n } + for i := 0; i < n; i++ { + nums[i] = i + 1 + } + // 隨機打亂陣列元素 + rand.Shuffle(len(nums), func(i, j int) { + nums[i], nums[j] = nums[j], nums[i] + }) + return nums +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +func findOne(nums []int) int { + for i := 0; i < len(nums); i++ { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if nums[i] == 1 { + return i + } + } + return -1 +} diff --git a/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go b/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go new file mode 100644 index 000000000..af6e04124 --- /dev/null +++ b/zh-hant/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go @@ -0,0 +1,20 @@ +// File: worst_best_time_complexity_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +func TestWorstBestTimeComplexity(t *testing.T) { + for i := 0; i < 10; i++ { + n := 100 + nums := randomNumbers(n) + index := findOne(nums) + fmt.Println("\n陣列 [ 1, 2, ..., n ] 被打亂後 =", nums) + fmt.Println("數字 1 的索引為", index) + } +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur.go b/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur.go new file mode 100644 index 000000000..f24aaf89d --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur.go @@ -0,0 +1,34 @@ +// File: binary_search_recur.go +// Created Time: 2023-07-19 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +/* 二分搜尋:問題 f(i, j) */ +func dfs(nums []int, target, i, j int) int { + // 如果區間為空,代表沒有目標元素,則返回 -1 + if i > j { + return -1 + } + // 計算索引中點 + m := i + ((j - i) >> 1) + //判斷中點與目標元素大小 + if nums[m] < target { + // 小於則遞迴右半陣列 + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m+1, j) + } else if nums[m] > target { + // 小於則遞迴左半陣列 + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m-1) + } else { + // 找到目標元素,返回其索引 + return m + } +} + +/* 二分搜尋 */ +func binarySearch(nums []int, target int) int { + n := len(nums) + return dfs(nums, target, 0, n-1) +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go b/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go new file mode 100644 index 000000000..afd4f105a --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go @@ -0,0 +1,20 @@ +// File: binary_search_recur_test.go +// Created Time: 2023-07-19 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "fmt" + "testing" +) + +func TestBinarySearch(t *testing.T) { + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + target := 6 + noTarget := 99 + targetIndex := binarySearch(nums, target) + fmt.Println("目標元素 6 的索引 = ", targetIndex) + noTargetIndex := binarySearch(nums, noTarget) + fmt.Println("不存在目標元素的索引 = ", noTargetIndex) +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/build_tree.go b/zh-hant/codes/go/chapter_divide_and_conquer/build_tree.go new file mode 100644 index 000000000..1bc127cdf --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/build_tree.go @@ -0,0 +1,37 @@ +// File: build_tree.go +// Created Time: 2023-07-20 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import . "github.com/krahets/hello-algo/pkg" + +/* 構建二元樹:分治 */ +func dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode { + // 子樹區間為空時終止 + if r-l < 0 { + return nil + } + // 初始化根節點 + root := NewTreeNode(preorder[i]) + // 查詢 m ,從而劃分左右子樹 + m := inorderMap[preorder[i]] + // 子問題:構建左子樹 + root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1) + // 子問題:構建右子樹 + root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r) + // 返回根節點 + return root +} + +/* 構建二元樹 */ +func buildTree(preorder, inorder []int) *TreeNode { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + inorderMap := make(map[int]int, len(inorder)) + for i := 0; i < len(inorder); i++ { + inorderMap[inorder[i]] = i + } + + root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1) + return root +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/build_tree_test.go b/zh-hant/codes/go/chapter_divide_and_conquer/build_tree_test.go new file mode 100644 index 000000000..9f3d1c260 --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/build_tree_test.go @@ -0,0 +1,25 @@ +// File: build_tree_test.go +// Created Time: 2023-07-20 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestBuildTree(t *testing.T) { + preorder := []int{3, 9, 2, 1, 7} + inorder := []int{9, 3, 1, 2, 7} + fmt.Print("前序走訪 = ") + PrintSlice(preorder) + fmt.Print("中序走訪 = ") + PrintSlice(inorder) + + root := buildTree(preorder, inorder) + fmt.Println("構建的二元樹為:") + PrintTree(root) +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/hanota.go b/zh-hant/codes/go/chapter_divide_and_conquer/hanota.go new file mode 100644 index 000000000..414b55ba3 --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/hanota.go @@ -0,0 +1,39 @@ +// File: hanota.go +// Created Time: 2023-07-21 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import "container/list" + +/* 移動一個圓盤 */ +func move(src, tar *list.List) { + // 從 src 頂部拿出一個圓盤 + pan := src.Back() + // 將圓盤放入 tar 頂部 + tar.PushBack(pan.Value) + // 移除 src 頂部圓盤 + src.Remove(pan) +} + +/* 求解河內塔問題 f(i) */ +func dfsHanota(i int, src, buf, tar *list.List) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if i == 1 { + move(src, tar) + return + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfsHanota(i-1, src, tar, buf) + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src, tar) + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfsHanota(i-1, buf, src, tar) +} + +/* 求解河內塔問題 */ +func solveHanota(A, B, C *list.List) { + n := A.Len() + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfsHanota(n, A, B, C) +} diff --git a/zh-hant/codes/go/chapter_divide_and_conquer/hanota_test.go b/zh-hant/codes/go/chapter_divide_and_conquer/hanota_test.go new file mode 100644 index 000000000..a7550d347 --- /dev/null +++ b/zh-hant/codes/go/chapter_divide_and_conquer/hanota_test.go @@ -0,0 +1,40 @@ +// File: hanota_test.go +// Created Time: 2023-07-21 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHanota(t *testing.T) { + // 串列尾部是柱子頂部 + A := list.New() + for i := 5; i > 0; i-- { + A.PushBack(i) + } + B := list.New() + C := list.New() + fmt.Println("初始狀態下:") + fmt.Print("A = ") + PrintList(A) + fmt.Print("B = ") + PrintList(B) + fmt.Print("C = ") + PrintList(C) + + solveHanota(A, B, C) + + fmt.Println("圓盤移動完成後:") + fmt.Print("A = ") + PrintList(A) + fmt.Print("B = ") + PrintList(B) + fmt.Print("C = ") + PrintList(C) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go new file mode 100644 index 000000000..e76833fff --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go @@ -0,0 +1,36 @@ +// File: climbing_stairs_backtrack.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 回溯 */ +func backtrack(choices []int, state, n int, res []int) { + // 當爬到第 n 階時,方案數量加 1 + if state == n { + res[0] = res[0] + 1 + } + // 走訪所有選擇 + for _, choice := range choices { + // 剪枝:不允許越過第 n 階 + if state+choice > n { + continue + } + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state+choice, n, res) + // 回退 + } +} + +/* 爬樓梯:回溯 */ +func climbingStairsBacktrack(n int) int { + // 可選擇向上爬 1 階或 2 階 + choices := []int{1, 2} + // 從第 0 階開始爬 + state := 0 + res := make([]int, 1) + // 使用 res[0] 記錄方案數量 + res[0] = 0 + backtrack(choices, state, n, res) + return res[0] +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go new file mode 100644 index 000000000..ec96f8af6 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go @@ -0,0 +1,25 @@ +// File: climbing_stairs_constraint_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 帶約束爬樓梯:動態規劃 */ +func climbingStairsConstraintDP(n int) int { + if n == 1 || n == 2 { + return 1 + } + // 初始化 dp 表,用於儲存子問題的解 + dp := make([][3]int, n+1) + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i := 3; i <= n; i++ { + 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] +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go new file mode 100644 index 000000000..6fb2d6f3f --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go @@ -0,0 +1,21 @@ +// File: climbing_stairs_dfs.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 搜尋 */ +func dfs(i int) int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + count := dfs(i-1) + dfs(i-2) + return count +} + +/* 爬樓梯:搜尋 */ +func climbingStairsDFS(n int) int { + return dfs(n) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go new file mode 100644 index 000000000..549522379 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go @@ -0,0 +1,32 @@ +// File: climbing_stairs_dfs_mem.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 記憶化搜尋 */ +func dfsMem(i int, mem []int) int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // 若存在記錄 dp[i] ,則直接返回之 + if mem[i] != -1 { + return mem[i] + } + // dp[i] = dp[i-1] + dp[i-2] + count := dfsMem(i-1, mem) + dfsMem(i-2, mem) + // 記錄 dp[i] + mem[i] = count + return count +} + +/* 爬樓梯:記憶化搜尋 */ +func climbingStairsDFSMem(n int) int { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + mem := make([]int, n+1) + for i := range mem { + mem[i] = -1 + } + return dfsMem(n, mem) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go new file mode 100644 index 000000000..22af573e3 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go @@ -0,0 +1,35 @@ +// File: climbing_stairs_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 爬樓梯:動態規劃 */ +func climbingStairsDP(n int) int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用於儲存子問題的解 + dp := make([]int, n+1) + // 初始狀態:預設最小子問題的解 + dp[1] = 1 + dp[2] = 2 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i := 3; i <= n; i++ { + dp[i] = dp[i-1] + dp[i-2] + } + return dp[n] +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +func climbingStairsDPComp(n int) int { + if n == 1 || n == 2 { + return n + } + a, b := 1, 2 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i := 3; i <= n; i++ { + a, b = b, a+b + } + return b +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_test.go b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_test.go new file mode 100644 index 000000000..eeaa4c5ad --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/climbing_stairs_test.go @@ -0,0 +1,57 @@ +// File: climbing_stairs_test.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestClimbingStairsBacktrack(t *testing.T) { + n := 9 + res := climbingStairsBacktrack(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestClimbingStairsDFS(t *testing.T) { + n := 9 + res := climbingStairsDFS(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestClimbingStairsDFSMem(t *testing.T) { + n := 9 + res := climbingStairsDFSMem(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestClimbingStairsDP(t *testing.T) { + n := 9 + res := climbingStairsDP(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestClimbingStairsDPComp(t *testing.T) { + n := 9 + res := climbingStairsDPComp(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestClimbingStairsConstraintDP(t *testing.T) { + n := 9 + res := climbingStairsConstraintDP(n) + fmt.Printf("爬 %d 階樓梯共有 %d 種方案\n", n, res) +} + +func TestMinCostClimbingStairsDPComp(t *testing.T) { + cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} + fmt.Printf("輸入樓梯的代價串列為 %v\n", cost) + + res := minCostClimbingStairsDP(cost) + fmt.Printf("爬完樓梯的最低代價為 %d\n", res) + + res = minCostClimbingStairsDPComp(cost) + fmt.Printf("爬完樓梯的最低代價為 %d\n", res) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/coin_change.go b/zh-hant/codes/go/chapter_dynamic_programming/coin_change.go new file mode 100644 index 000000000..e481310de --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/coin_change.go @@ -0,0 +1,66 @@ +// File: coin_change.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 零錢兌換:動態規劃 */ +func coinChangeDP(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // 狀態轉移:首行首列 + for a := 1; a <= amt; a++ { + dp[0][a] = max + } + // 狀態轉移:其餘行和列 + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i-1][a] + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) + } + } + } + if dp[n][amt] != max { + return dp[n][amt] + } + return -1 +} + +/* 零錢兌換:動態規劃 */ +func coinChangeDPComp(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // 初始化 dp 表 + dp := make([]int, amt+1) + for i := 1; i <= amt; i++ { + dp[i] = max + } + // 狀態轉移 + for i := 1; i <= n; i++ { + // 倒序走訪 + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) + } + } + } + if dp[amt] != max { + return dp[amt] + } + return -1 +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/coin_change_ii.go b/zh-hant/codes/go/chapter_dynamic_programming/coin_change_ii.go new file mode 100644 index 000000000..fdd02bc84 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/coin_change_ii.go @@ -0,0 +1,54 @@ +// File: coin_change_ii.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 零錢兌換 II:動態規劃 */ +func coinChangeIIDP(coins []int, amt int) int { + n := len(coins) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // 初始化首列 + for i := 0; i <= n; i++ { + dp[i][0] = 1 + } + // 狀態轉移:其餘行和列 + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + 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] +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +func coinChangeIIDPComp(coins []int, amt int) int { + n := len(coins) + // 初始化 dp 表 + dp := make([]int, amt+1) + dp[0] = 1 + // 狀態轉移 + for i := 1; i <= n; i++ { + // 倒序走訪 + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a-coins[i-1]] + } + } + } + return dp[amt] +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/coin_change_test.go b/zh-hant/codes/go/chapter_dynamic_programming/coin_change_test.go new file mode 100644 index 000000000..97bea53da --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/coin_change_test.go @@ -0,0 +1,23 @@ +// File: coin_change_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestCoinChange(t *testing.T) { + coins := []int{1, 2, 5} + amt := 4 + + // 動態規劃 + res := coinChangeDP(coins, amt) + fmt.Printf("湊到目標金額所需的最少硬幣數量為 %d\n", res) + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt) + fmt.Printf("湊到目標金額所需的最少硬幣數量為 %d\n", res) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/edit_distance.go b/zh-hant/codes/go/chapter_dynamic_programming/edit_distance.go new file mode 100644 index 000000000..90ae241ea --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/edit_distance.go @@ -0,0 +1,129 @@ +// File: edit_distance.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 編輯距離:暴力搜尋 */ +func editDistanceDFS(s string, t string, i int, j int) int { + // 若 s 和 t 都為空,則返回 0 + if i == 0 && 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 editDistanceDFS(s, t, i-1, j-1) + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + insert := editDistanceDFS(s, t, i, j-1) + deleted := editDistanceDFS(s, t, i-1, j) + replace := editDistanceDFS(s, t, i-1, j-1) + // 返回最少編輯步數 + return MinInt(MinInt(insert, deleted), replace) + 1 +} + +/* 編輯距離:記憶化搜尋 */ +func editDistanceDFSMem(s string, t string, mem [][]int, i int, j int) int { + // 若 s 和 t 都為空,則返回 0 + if i == 0 && 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 editDistanceDFSMem(s, t, mem, i-1, j-1) + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + insert := editDistanceDFSMem(s, t, mem, i, j-1) + deleted := editDistanceDFSMem(s, t, mem, i-1, j) + replace := editDistanceDFSMem(s, t, mem, i-1, j-1) + // 記錄並返回最少編輯步數 + mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 + return mem[i][j] +} + +/* 編輯距離:動態規劃 */ +func editDistanceDP(s string, t string) int { + n := len(s) + m := len(t) + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, m+1) + } + // 狀態轉移:首行首列 + for i := 1; i <= n; i++ { + dp[i][0] = i + } + for j := 1; j <= m; j++ { + dp[0][j] = j + } + // 狀態轉移:其餘行和列 + for i := 1; i <= n; i++ { + for j := 1; j <= m; j++ { + if s[i-1] == t[j-1] { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i-1][j-1] + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1 + } + } + } + return dp[n][m] +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +func editDistanceDPComp(s string, t string) int { + n := len(s) + m := len(t) + dp := make([]int, m+1) + // 狀態轉移:首行 + for j := 1; j <= m; j++ { + dp[j] = j + } + // 狀態轉移:其餘行 + for i := 1; i <= n; i++ { + // 狀態轉移:首列 + leftUp := dp[0] // 暫存 dp[i-1, j-1] + dp[0] = i + // 狀態轉移:其餘列 + for j := 1; j <= m; j++ { + temp := dp[j] + if s[i-1] == t[j-1] { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftUp + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 + } + leftUp = temp // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m] +} + +func MinInt(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/edit_distance_test.go b/zh-hant/codes/go/chapter_dynamic_programming/edit_distance_test.go new file mode 100644 index 000000000..3973397ee --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/edit_distance_test.go @@ -0,0 +1,40 @@ +// File: edit_distance_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestEditDistanceDFS(test *testing.T) { + s := "bag" + t := "pack" + n := len(s) + m := len(t) + + // 暴力搜尋 + res := editDistanceDFS(s, t, n, m) + fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) + + // 記憶化搜尋 + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, m+1) + for j := 0; j <= m; j++ { + mem[i][j] = -1 + } + } + res = editDistanceDFSMem(s, t, mem, n, m) + fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) + + // 動態規劃 + res = editDistanceDP(s, t) + fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t) + fmt.Printf("將 %s 更改為 %s 最少需要編輯 %d 步\n", s, t, res) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/knapsack.go b/zh-hant/codes/go/chapter_dynamic_programming/knapsack.go new file mode 100644 index 000000000..029569cf5 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/knapsack.go @@ -0,0 +1,87 @@ +// File: knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 0-1 背包:暴力搜尋 */ +func knapsackDFS(wgt, val []int, i, c int) int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0 + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i-1] > c { + return knapsackDFS(wgt, val, i-1, c) + } + // 計算不放入和放入物品 i 的最大價值 + no := knapsackDFS(wgt, val, i-1, c) + yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] + // 返回兩種方案中價值更大的那一個 + return int(math.Max(float64(no), float64(yes))) +} + +/* 0-1 背包:記憶化搜尋 */ +func knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0 + } + // 若已有記錄,則直接返回 + if mem[i][c] != -1 { + return mem[i][c] + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i-1] > c { + return knapsackDFSMem(wgt, val, mem, i-1, c) + } + // 計算不放入和放入物品 i 的最大價值 + no := knapsackDFSMem(wgt, val, mem, i-1, c) + yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] + // 返回兩種方案中價值更大的那一個 + mem[i][c] = int(math.Max(float64(no), float64(yes))) + return mem[i][c] +} + +/* 0-1 背包:動態規劃 */ +func knapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // 狀態轉移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i-1][c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +func knapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([]int, cap+1) + // 狀態轉移 + for i := 1; i <= n; i++ { + // 倒序走訪 + for c := cap; c >= 1; c-- { + if wgt[i-1] <= c { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/knapsack_test.go b/zh-hant/codes/go/chapter_dynamic_programming/knapsack_test.go new file mode 100644 index 000000000..8816bfb03 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/knapsack_test.go @@ -0,0 +1,54 @@ +// File: knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + c := 50 + n := len(wgt) + + // 暴力搜尋 + res := knapsackDFS(wgt, val, n, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) + + // 記憶化搜尋 + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, c+1) + for j := 0; j <= c; j++ { + mem[i][j] = -1 + } + } + res = knapsackDFSMem(wgt, val, mem, n, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) + + // 動態規劃 + res = knapsackDP(wgt, val, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, val, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) +} + +func TestUnboundedKnapsack(t *testing.T) { + wgt := []int{1, 2, 3} + val := []int{5, 11, 15} + c := 4 + + // 動態規劃 + res := unboundedKnapsackDP(wgt, val, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt, val, c) + fmt.Printf("不超過背包容量的最大物品價值為 %d\n", res) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go b/zh-hant/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go new file mode 100644 index 000000000..1b00880cb --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go @@ -0,0 +1,52 @@ +// File: min_cost_climbing_stairs_dp.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 爬樓梯最小代價:動態規劃 */ +func minCostClimbingStairsDP(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + min := func(a, b int) int { + if a < b { + return a + } + return b + } + // 初始化 dp 表,用於儲存子問題的解 + dp := make([]int, n+1) + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1] + dp[2] = cost[2] + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i := 3; i <= n; i++ { + dp[i] = min(dp[i-1], dp[i-2]) + cost[i] + } + return dp[n] +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +func minCostClimbingStairsDPComp(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + min := func(a, b int) int { + if a < b { + return a + } + return b + } + // 初始狀態:預設最小子問題的解 + a, b := cost[1], cost[2] + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i := 3; i <= n; i++ { + tmp := b + b = min(a, tmp) + cost[i] + a = tmp + } + return b +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum.go b/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum.go new file mode 100644 index 000000000..ef0c0365b --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum.go @@ -0,0 +1,94 @@ +// File: min_path_sum.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 最小路徑和:暴力搜尋 */ +func minPathSumDFS(grid [][]int, i, j int) int { + // 若為左上角單元格,則終止搜尋 + if i == 0 && j == 0 { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return math.MaxInt + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + up := minPathSumDFS(grid, i-1, j) + left := minPathSumDFS(grid, i, j-1) + // 返回從左上角到 (i, j) 的最小路徑代價 + return int(math.Min(float64(left), float64(up))) + grid[i][j] +} + +/* 最小路徑和:記憶化搜尋 */ +func minPathSumDFSMem(grid, mem [][]int, i, j int) int { + // 若為左上角單元格,則終止搜尋 + if i == 0 && j == 0 { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return math.MaxInt + } + // 若已有記錄,則直接返回 + if mem[i][j] != -1 { + return mem[i][j] + } + // 左邊和上邊單元格的最小路徑代價 + up := minPathSumDFSMem(grid, mem, i-1, j) + left := minPathSumDFSMem(grid, mem, i, j-1) + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] + return mem[i][j] +} + +/* 最小路徑和:動態規劃 */ +func minPathSumDP(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // 初始化 dp 表 + dp := make([][]int, n) + for i := 0; i < n; i++ { + dp[i] = make([]int, m) + } + dp[0][0] = grid[0][0] + // 狀態轉移:首行 + for j := 1; j < m; j++ { + dp[0][j] = dp[0][j-1] + grid[0][j] + } + // 狀態轉移:首列 + for i := 1; i < n; i++ { + dp[i][0] = dp[i-1][0] + grid[i][0] + } + // 狀態轉移:其餘行和列 + for i := 1; i < n; i++ { + for j := 1; j < m; j++ { + dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] + } + } + return dp[n-1][m-1] +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +func minPathSumDPComp(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // 初始化 dp 表 + dp := make([]int, m) + // 狀態轉移:首行 + dp[0] = grid[0][0] + for j := 1; j < m; j++ { + dp[j] = dp[j-1] + grid[0][j] + } + // 狀態轉移:其餘行和列 + for i := 1; i < n; i++ { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0] + // 狀態轉移:其餘列 + for j := 1; j < m; j++ { + dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] + } + } + return dp[m-1] +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum_test.go b/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum_test.go new file mode 100644 index 000000000..9a57c6c31 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/min_path_sum_test.go @@ -0,0 +1,43 @@ +// File: min_path_sum_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestMinPathSum(t *testing.T) { + grid := [][]int{ + {1, 3, 1, 5}, + {2, 2, 4, 2}, + {5, 3, 2, 1}, + {4, 3, 5, 2}, + } + n, m := len(grid), len(grid[0]) + + // 暴力搜尋 + res := minPathSumDFS(grid, n-1, m-1) + fmt.Printf("從左上角到右下角的做小路徑和為 %d\n", res) + + // 記憶化搜尋 + mem := make([][]int, n) + for i := 0; i < n; i++ { + mem[i] = make([]int, m) + for j := 0; j < m; j++ { + mem[i][j] = -1 + } + } + res = minPathSumDFSMem(grid, mem, n-1, m-1) + fmt.Printf("從左上角到右下角的做小路徑和為 %d\n", res) + + // 動態規劃 + res = minPathSumDP(grid) + fmt.Printf("從左上角到右下角的做小路徑和為 %d\n", res) + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid) + fmt.Printf("從左上角到右下角的做小路徑和為 %d\n", res) +} diff --git a/zh-hant/codes/go/chapter_dynamic_programming/unbounded_knapsack.go b/zh-hant/codes/go/chapter_dynamic_programming/unbounded_knapsack.go new file mode 100644 index 000000000..4868aeb00 --- /dev/null +++ b/zh-hant/codes/go/chapter_dynamic_programming/unbounded_knapsack.go @@ -0,0 +1,50 @@ +// File: unbounded_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 完全背包:動態規劃 */ +func unboundedKnapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // 狀態轉移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i-1][c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* 完全背包:空間最佳化後的動態規劃 */ +func unboundedKnapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // 初始化 dp 表 + dp := make([]int, cap+1) + // 狀態轉移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/zh-hant/codes/go/chapter_graph/graph_adjacency_list.go b/zh-hant/codes/go/chapter_graph/graph_adjacency_list.go new file mode 100644 index 000000000..839d555f9 --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_adjacency_list.go @@ -0,0 +1,100 @@ +// File: graph_adjacency_list.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "strconv" + "strings" + + . "github.com/krahets/hello-algo/pkg" +) + +/* 基於鄰接表實現的無向圖類別 */ +type graphAdjList struct { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + adjList map[Vertex][]Vertex +} + +/* 建構子 */ +func newGraphAdjList(edges [][]Vertex) *graphAdjList { + g := &graphAdjList{ + adjList: make(map[Vertex][]Vertex), + } + // 新增所有頂點和邊 + for _, edge := range edges { + g.addVertex(edge[0]) + g.addVertex(edge[1]) + g.addEdge(edge[0], edge[1]) + } + return g +} + +/* 獲取頂點數量 */ +func (g *graphAdjList) size() int { + return len(g.adjList) +} + +/* 新增邊 */ +func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // 新增邊 vet1 - vet2, 新增匿名 struct{}, + g.adjList[vet1] = append(g.adjList[vet1], vet2) + g.adjList[vet2] = append(g.adjList[vet2], vet1) +} + +/* 刪除邊 */ +func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // 刪除邊 vet1 - vet2 + g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) + g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) +} + +/* 新增頂點 */ +func (g *graphAdjList) addVertex(vet Vertex) { + _, ok := g.adjList[vet] + if ok { + return + } + // 在鄰接表中新增一個新鏈結串列 + g.adjList[vet] = make([]Vertex, 0) +} + +/* 刪除頂點 */ +func (g *graphAdjList) removeVertex(vet Vertex) { + _, ok := g.adjList[vet] + if !ok { + panic("error") + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + delete(g.adjList, vet) + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for v, list := range g.adjList { + g.adjList[v] = DeleteSliceElms(list, vet) + } +} + +/* 列印鄰接表 */ +func (g *graphAdjList) print() { + var builder strings.Builder + fmt.Printf("鄰接表 = \n") + for k, v := range g.adjList { + builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") + for _, vet := range v { + builder.WriteString(strconv.Itoa(vet.Val) + " ") + } + fmt.Println(builder.String()) + builder.Reset() + } +} diff --git a/zh-hant/codes/go/chapter_graph/graph_adjacency_list_test.go b/zh-hant/codes/go/chapter_graph/graph_adjacency_list_test.go new file mode 100644 index 000000000..59e254b3f --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_adjacency_list_test.go @@ -0,0 +1,45 @@ +// File: graph_adjacency_list_test.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphAdjList(t *testing.T) { + /* 初始化無向圖 */ + v := ValsToVets([]int{1, 3, 2, 5, 4}) + edges := [][]Vertex{{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 := newGraphAdjList(edges) + fmt.Println("初始化後,圖為:") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]) + fmt.Println("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]) + fmt.Println("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + v5 := NewVertex(6) + graph.addVertex(v5) + fmt.Println("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(v[1]) + fmt.Println("\n刪除頂點 3 後,圖為") + graph.print() +} diff --git a/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix.go b/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix.go new file mode 100644 index 000000000..54e6c108b --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix.go @@ -0,0 +1,102 @@ +// File: graph_adjacency_matrix.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import "fmt" + +/* 基於鄰接矩陣實現的無向圖類別 */ +type graphAdjMat struct { + // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + vertices []int + // 鄰接矩陣,行列索引對應“頂點索引” + adjMat [][]int +} + +/* 建構子 */ +func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { + // 新增頂點 + n := len(vertices) + adjMat := make([][]int, n) + for i := range adjMat { + adjMat[i] = make([]int, n) + } + // 初始化圖 + g := &graphAdjMat{ + vertices: vertices, + adjMat: adjMat, + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for i := range edges { + g.addEdge(edges[i][0], edges[i][1]) + } + return g +} + +/* 獲取頂點數量 */ +func (g *graphAdjMat) size() int { + return len(g.vertices) +} + +/* 新增頂點 */ +func (g *graphAdjMat) addVertex(val int) { + n := g.size() + // 向頂點串列中新增新頂點的值 + g.vertices = append(g.vertices, val) + // 在鄰接矩陣中新增一行 + newRow := make([]int, n) + g.adjMat = append(g.adjMat, newRow) + // 在鄰接矩陣中新增一列 + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i], 0) + } +} + +/* 刪除頂點 */ +func (g *graphAdjMat) removeVertex(index int) { + if index >= g.size() { + return + } + // 在頂點串列中移除索引 index 的頂點 + g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) + // 在鄰接矩陣中刪除索引 index 的行 + g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) + // 在鄰接矩陣中刪除索引 index 的列 + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) + } +} + +/* 新增邊 */ +// 參數 i, j 對應 vertices 元素索引 +func (g *graphAdjMat) addEdge(i, j int) { + // 索引越界與相等處理 + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + g.adjMat[i][j] = 1 + g.adjMat[j][i] = 1 +} + +/* 刪除邊 */ +// 參數 i, j 對應 vertices 元素索引 +func (g *graphAdjMat) removeEdge(i, j int) { + // 索引越界與相等處理 + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + g.adjMat[i][j] = 0 + g.adjMat[j][i] = 0 +} + +/* 列印鄰接矩陣 */ +func (g *graphAdjMat) print() { + fmt.Printf("\t頂點串列 = %v\n", g.vertices) + fmt.Printf("\t鄰接矩陣 = \n") + for i := range g.adjMat { + fmt.Printf("\t\t\t%v\n", g.adjMat[i]) + } +} diff --git a/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix_test.go b/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix_test.go new file mode 100644 index 000000000..f332eb036 --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_adjacency_matrix_test.go @@ -0,0 +1,43 @@ +// File: graph_adjacency_matrix_test.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" +) + +func TestGraphAdjMat(t *testing.T) { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + vertices := []int{1, 3, 2, 5, 4} + edges := [][]int{{0, 1}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}} + graph := newGraphAdjMat(vertices, edges) + fmt.Println("初始化後,圖為:") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(0, 2) + fmt.Println("新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(0, 1) + fmt.Println("刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + graph.addVertex(6) + fmt.Println("新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(1) + fmt.Println("刪除頂點 3 後,圖為") + graph.print() +} diff --git a/zh-hant/codes/go/chapter_graph/graph_bfs.go b/zh-hant/codes/go/chapter_graph/graph_bfs.go new file mode 100644 index 000000000..7928604b2 --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_bfs.go @@ -0,0 +1,41 @@ +// File: graph_bfs.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { + // 頂點走訪序列 + res := make([]Vertex, 0) + // 雜湊表,用於記錄已被訪問過的頂點 + visited := make(map[Vertex]struct{}) + visited[startVet] = struct{}{} + // 佇列用於實現 BFS, 使用切片模擬佇列 + queue := make([]Vertex, 0) + queue = append(queue, startVet) + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + for len(queue) > 0 { + // 佇列首頂點出隊 + vet := queue[0] + queue = queue[1:] + // 記錄訪問頂點 + res = append(res, vet) + // 走訪該頂點的所有鄰接頂點 + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // 只入列未訪問的頂點 + if !isExist { + queue = append(queue, adjVet) + visited[adjVet] = struct{}{} + } + } + } + // 返回頂點走訪序列 + return res +} diff --git a/zh-hant/codes/go/chapter_graph/graph_bfs_test.go b/zh-hant/codes/go/chapter_graph/graph_bfs_test.go new file mode 100644 index 000000000..c1023d766 --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_bfs_test.go @@ -0,0 +1,29 @@ +// File: graph_bfs_test.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphBFS(t *testing.T) { + /* 初始化無向圖 */ + vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) + edges := [][]Vertex{ + {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[1], vets[4]}, + {vets[2], vets[5]}, {vets[3], vets[4]}, {vets[3], vets[6]}, {vets[4], vets[5]}, + {vets[4], vets[7]}, {vets[5], vets[8]}, {vets[6], vets[7]}, {vets[7], vets[8]}} + graph := newGraphAdjList(edges) + fmt.Println("初始化後,圖為:") + graph.print() + + /* 廣度優先走訪 */ + res := graphBFS(graph, vets[0]) + fmt.Println("廣度優先走訪(BFS)頂點序列為:") + PrintSlice(VetsToVals(res)) +} diff --git a/zh-hant/codes/go/chapter_graph/graph_dfs.go b/zh-hant/codes/go/chapter_graph/graph_dfs.go new file mode 100644 index 000000000..a1561932a --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_dfs.go @@ -0,0 +1,36 @@ +// File: graph_dfs.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 深度優先走訪輔助函式 */ +func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { + // append 操作會返回新的的引用,必須讓原引用重新賦值為新slice的引用 + *res = append(*res, vet) + visited[vet] = struct{}{} + // 走訪該頂點的所有鄰接頂點 + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // 遞迴訪問鄰接頂點 + if !isExist { + dfs(g, visited, res, adjVet) + } + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { + // 頂點走訪序列 + res := make([]Vertex, 0) + // 雜湊表,用於記錄已被訪問過的頂點 + visited := make(map[Vertex]struct{}) + dfs(g, visited, &res, startVet) + // 返回頂點走訪序列 + return res +} diff --git a/zh-hant/codes/go/chapter_graph/graph_dfs_test.go b/zh-hant/codes/go/chapter_graph/graph_dfs_test.go new file mode 100644 index 000000000..0778baabb --- /dev/null +++ b/zh-hant/codes/go/chapter_graph/graph_dfs_test.go @@ -0,0 +1,28 @@ +// File: graph_dfs_test.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphDFS(t *testing.T) { + /* 初始化無向圖 */ + vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6}) + edges := [][]Vertex{ + {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, + {vets[2], vets[5]}, {vets[4], vets[5]}, {vets[5], vets[6]}} + graph := newGraphAdjList(edges) + fmt.Println("初始化後,圖為:") + graph.print() + + /* 深度優先走訪 */ + res := graphDFS(graph, vets[0]) + fmt.Println("深度優先走訪(DFS)頂點序列為:") + PrintSlice(VetsToVals(res)) +} diff --git a/zh-hant/codes/go/chapter_greedy/coin_change_greedy.go b/zh-hant/codes/go/chapter_greedy/coin_change_greedy.go new file mode 100644 index 000000000..ea068b652 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/coin_change_greedy.go @@ -0,0 +1,27 @@ +// File: coin_change_greedy.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +/* 零錢兌換:貪婪 */ +func coinChangeGreedy(coins []int, amt int) int { + // 假設 coins 串列有序 + i := len(coins) - 1 + count := 0 + // 迴圈進行貪婪選擇,直到無剩餘金額 + for amt > 0 { + // 找到小於且最接近剩餘金額的硬幣 + for i > 0 && coins[i] > amt { + i-- + } + // 選擇 coins[i] + amt -= coins[i] + count++ + } + // 若未找到可行方案,則返回 -1 + if amt != 0 { + return -1 + } + return count +} diff --git a/zh-hant/codes/go/chapter_greedy/coin_change_greedy_test.go b/zh-hant/codes/go/chapter_greedy/coin_change_greedy_test.go new file mode 100644 index 000000000..15cc38675 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/coin_change_greedy_test.go @@ -0,0 +1,35 @@ +// File: coin_change_greedy_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestCoinChangeGreedy(t *testing.T) { + // 貪婪:能夠保證找到全域性最優解 + coins := []int{1, 5, 10, 20, 50, 100} + amt := 186 + res := coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res) + + // 貪婪:無法保證找到全域性最優解 + coins = []int{1, 20, 50} + amt = 60 + res = coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res) + fmt.Println("實際上需要的最少數量為 3 ,即 20 + 20 + 20") + + // 貪婪:無法保證找到全域性最優解 + coins = []int{1, 49, 50} + amt = 98 + res = coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("湊到 %d 所需的最少硬幣數量為 %d\n", amt, res) + fmt.Println("實際上需要的最少數量為 2 ,即 49 + 49") +} diff --git a/zh-hant/codes/go/chapter_greedy/fractional_knapsack.go b/zh-hant/codes/go/chapter_greedy/fractional_knapsack.go new file mode 100644 index 000000000..bfccb9d95 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/fractional_knapsack.go @@ -0,0 +1,41 @@ +// File: fractional_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "sort" + +/* 物品 */ +type Item struct { + w int // 物品重量 + v int // 物品價值 +} + +/* 分數背包:貪婪 */ +func fractionalKnapsack(wgt []int, val []int, cap int) float64 { + // 建立物品串列,包含兩個屬性:重量、價值 + items := make([]Item, len(wgt)) + for i := 0; i < len(wgt); i++ { + items[i] = Item{wgt[i], val[i]} + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + sort.Slice(items, func(i, j int) bool { + return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w) + }) + // 迴圈貪婪選擇 + res := 0.0 + for _, item := range items { + if item.w <= cap { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += float64(item.v) + cap -= item.w + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += float64(item.v) / float64(item.w) * float64(cap) + // 已無剩餘容量,因此跳出迴圈 + break + } + } + return res +} diff --git a/zh-hant/codes/go/chapter_greedy/fractional_knapsack_test.go b/zh-hant/codes/go/chapter_greedy/fractional_knapsack_test.go new file mode 100644 index 000000000..9de0a9384 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/fractional_knapsack_test.go @@ -0,0 +1,20 @@ +// File: fractional_knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestFractionalKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + capacity := 50 + + // 貪婪演算法 + res := fractionalKnapsack(wgt, val, capacity) + fmt.Println("不超過背包容量的最大物品價值為", res) +} diff --git a/zh-hant/codes/go/chapter_greedy/max_capacity.go b/zh-hant/codes/go/chapter_greedy/max_capacity.go new file mode 100644 index 000000000..ca768df4c --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/max_capacity.go @@ -0,0 +1,28 @@ +// File: max_capacity.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "math" + +/* 最大容量:貪婪 */ +func maxCapacity(ht []int) int { + // 初始化 i, j,使其分列陣列兩端 + i, j := 0, len(ht)-1 + // 初始最大容量為 0 + res := 0 + // 迴圈貪婪選擇,直至兩板相遇 + for i < j { + // 更新最大容量 + capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i) + res = int(math.Max(float64(res), float64(capacity))) + // 向內移動短板 + if ht[i] < ht[j] { + i++ + } else { + j-- + } + } + return res +} diff --git a/zh-hant/codes/go/chapter_greedy/max_capacity_test.go b/zh-hant/codes/go/chapter_greedy/max_capacity_test.go new file mode 100644 index 000000000..fe1724c78 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/max_capacity_test.go @@ -0,0 +1,18 @@ +// File: max_capacity_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestMaxCapacity(t *testing.T) { + ht := []int{3, 8, 5, 2, 7, 7, 3, 4} + + // 貪婪演算法 + res := maxCapacity(ht) + fmt.Println("最大容量為", res) +} diff --git a/zh-hant/codes/go/chapter_greedy/max_product_cutting.go b/zh-hant/codes/go/chapter_greedy/max_product_cutting.go new file mode 100644 index 000000000..650969615 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/max_product_cutting.go @@ -0,0 +1,28 @@ +// File: max_product_cutting.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "math" + +/* 最大切分乘積:貪婪 */ +func maxProductCutting(n int) int { + // 當 n <= 3 時,必須切分出一個 1 + if n <= 3 { + return 1 * (n - 1) + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + a := n / 3 + b := n % 3 + if b == 1 { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return int(math.Pow(3, float64(a-1))) * 2 * 2 + } + if b == 2 { + // 當餘數為 2 時,不做處理 + return int(math.Pow(3, float64(a))) * 2 + } + // 當餘數為 0 時,不做處理 + return int(math.Pow(3, float64(a))) +} diff --git a/zh-hant/codes/go/chapter_greedy/max_product_cutting_test.go b/zh-hant/codes/go/chapter_greedy/max_product_cutting_test.go new file mode 100644 index 000000000..0f246bdb9 --- /dev/null +++ b/zh-hant/codes/go/chapter_greedy/max_product_cutting_test.go @@ -0,0 +1,17 @@ +// File: max_product_cutting_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestMaxProductCutting(t *testing.T) { + n := 58 + // 貪婪演算法 + res := maxProductCutting(n) + fmt.Println("最大切分乘積為", res) +} diff --git a/zh-hant/codes/go/chapter_hashing/array_hash_map.go b/zh-hant/codes/go/chapter_hashing/array_hash_map.go new file mode 100644 index 000000000..d1fac0714 --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/array_hash_map.go @@ -0,0 +1,97 @@ +// File: array_hash_map.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import "fmt" + +/* 鍵值對 */ +type pair struct { + key int + val string +} + +/* 基於陣列實現的雜湊表 */ +type arrayHashMap struct { + buckets []*pair +} + +/* 初始化雜湊表 */ +func newArrayHashMap() *arrayHashMap { + // 初始化陣列,包含 100 個桶 + buckets := make([]*pair, 100) + return &arrayHashMap{buckets: buckets} +} + +/* 雜湊函式 */ +func (a *arrayHashMap) hashFunc(key int) int { + index := key % 100 + return index +} + +/* 查詢操作 */ +func (a *arrayHashMap) get(key int) string { + index := a.hashFunc(key) + pair := a.buckets[index] + if pair == nil { + return "Not Found" + } + return pair.val +} + +/* 新增操作 */ +func (a *arrayHashMap) put(key int, val string) { + pair := &pair{key: key, val: val} + index := a.hashFunc(key) + a.buckets[index] = pair +} + +/* 刪除操作 */ +func (a *arrayHashMap) remove(key int) { + index := a.hashFunc(key) + // 置為 nil ,代表刪除 + a.buckets[index] = nil +} + +/* 獲取所有鍵對 */ +func (a *arrayHashMap) pairSet() []*pair { + var pairs []*pair + for _, pair := range a.buckets { + if pair != nil { + pairs = append(pairs, pair) + } + } + return pairs +} + +/* 獲取所有鍵 */ +func (a *arrayHashMap) keySet() []int { + var keys []int + for _, pair := range a.buckets { + if pair != nil { + keys = append(keys, pair.key) + } + } + return keys +} + +/* 獲取所有值 */ +func (a *arrayHashMap) valueSet() []string { + var values []string + for _, pair := range a.buckets { + if pair != nil { + values = append(values, pair.val) + } + } + return values +} + +/* 列印雜湊表 */ +func (a *arrayHashMap) print() { + for _, pair := range a.buckets { + if pair != nil { + fmt.Println(pair.key, "->", pair.val) + } + } +} diff --git a/zh-hant/codes/go/chapter_hashing/array_hash_map_test.go b/zh-hant/codes/go/chapter_hashing/array_hash_map_test.go new file mode 100644 index 000000000..e2e62db8d --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/array_hash_map_test.go @@ -0,0 +1,52 @@ +// File: array_hash_map_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "testing" +) + +func TestArrayHashMap(t *testing.T) { + /* 初始化雜湊表 */ + hmap := newArrayHashMap() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小囉") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鴨") + fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") + hmap.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + name := hmap.get(15937) + fmt.Println("\n輸入學號 15937 ,查詢到姓名 " + name) + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + hmap.remove(10583) + fmt.Println("\n刪除 10583 後,雜湊表為\nKey -> Value") + hmap.print() + + /* 走訪雜湊表 */ + fmt.Println("\n走訪鍵值對 Key->Value") + for _, kv := range hmap.pairSet() { + fmt.Println(kv.key, " -> ", kv.val) + } + + fmt.Println("\n單獨走訪鍵 Key") + for _, key := range hmap.keySet() { + fmt.Println(key) + } + + fmt.Println("\n單獨走訪值 Value") + for _, val := range hmap.valueSet() { + fmt.Println(val) + } +} diff --git a/zh-hant/codes/go/chapter_hashing/hash_collision_test.go b/zh-hant/codes/go/chapter_hashing/hash_collision_test.go new file mode 100644 index 000000000..67184ad29 --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/hash_collision_test.go @@ -0,0 +1,62 @@ +// File: hash_collision_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "testing" +) + +func TestHashMapChaining(t *testing.T) { + /* 初始化雜湊表 */ + hmap := newHashMapChaining() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小囉") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鴨") + fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") + hmap.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + name := hmap.get(15937) + fmt.Println("\n輸入學號 15937 ,查詢到姓名", name) + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + hmap.remove(12836) + fmt.Println("\n刪除 12836 後,雜湊表為\nKey -> Value") + hmap.print() +} + +func TestHashMapOpenAddressing(t *testing.T) { + /* 初始化雜湊表 */ + hmap := newHashMapOpenAddressing() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hmap.put(12836, "小哈") + hmap.put(15937, "小囉") + hmap.put(16750, "小算") + hmap.put(13276, "小法") + hmap.put(10583, "小鴨") + fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") + hmap.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + name := hmap.get(13276) + fmt.Println("\n輸入學號 13276 ,查詢到姓名 ", name) + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + hmap.remove(16750) + fmt.Println("\n刪除 16750 後,雜湊表為\nKey -> Value") + hmap.print() +} diff --git a/zh-hant/codes/go/chapter_hashing/hash_map_chaining.go b/zh-hant/codes/go/chapter_hashing/hash_map_chaining.go new file mode 100644 index 000000000..acdb8805e --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/hash_map_chaining.go @@ -0,0 +1,134 @@ +// File: hash_map_chaining.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import ( + "fmt" + "strconv" + "strings" +) + +/* 鏈式位址雜湊表 */ +type hashMapChaining struct { + size int // 鍵值對數量 + capacity int // 雜湊表容量 + loadThres float64 // 觸發擴容的負載因子閾值 + extendRatio int // 擴容倍數 + buckets [][]pair // 桶陣列 +} + +/* 建構子 */ +func newHashMapChaining() *hashMapChaining { + buckets := make([][]pair, 4) + for i := 0; i < 4; i++ { + buckets[i] = make([]pair, 0) + } + return &hashMapChaining{ + size: 0, + capacity: 4, + loadThres: 2.0 / 3.0, + extendRatio: 2, + buckets: buckets, + } +} + +/* 雜湊函式 */ +func (m *hashMapChaining) hashFunc(key int) int { + return key % m.capacity +} + +/* 負載因子 */ +func (m *hashMapChaining) loadFactor() float64 { + return float64(m.size) / float64(m.capacity) +} + +/* 查詢操作 */ +func (m *hashMapChaining) get(key int) string { + idx := m.hashFunc(key) + bucket := m.buckets[idx] + // 走訪桶,若找到 key ,則返回對應 val + for _, p := range bucket { + if p.key == key { + return p.val + } + } + // 若未找到 key ,則返回空字串 + return "" +} + +/* 新增操作 */ +func (m *hashMapChaining) put(key int, val string) { + // 當負載因子超過閾值時,執行擴容 + if m.loadFactor() > m.loadThres { + m.extend() + } + idx := m.hashFunc(key) + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for i := range m.buckets[idx] { + if m.buckets[idx][i].key == key { + m.buckets[idx][i].val = val + return + } + } + // 若無該 key ,則將鍵值對新增至尾部 + p := pair{ + key: key, + val: val, + } + m.buckets[idx] = append(m.buckets[idx], p) + m.size += 1 +} + +/* 刪除操作 */ +func (m *hashMapChaining) remove(key int) { + idx := m.hashFunc(key) + // 走訪桶,從中刪除鍵值對 + for i, p := range m.buckets[idx] { + if p.key == key { + // 切片刪除 + m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) + m.size -= 1 + break + } + } +} + +/* 擴容雜湊表 */ +func (m *hashMapChaining) extend() { + // 暫存原雜湊表 + tmpBuckets := make([][]pair, len(m.buckets)) + for i := 0; i < len(m.buckets); i++ { + tmpBuckets[i] = make([]pair, len(m.buckets[i])) + copy(tmpBuckets[i], m.buckets[i]) + } + // 初始化擴容後的新雜湊表 + m.capacity *= m.extendRatio + m.buckets = make([][]pair, m.capacity) + for i := 0; i < m.capacity; i++ { + m.buckets[i] = make([]pair, 0) + } + m.size = 0 + // 將鍵值對從原雜湊表搬運至新雜湊表 + for _, bucket := range tmpBuckets { + for _, p := range bucket { + m.put(p.key, p.val) + } + } +} + +/* 列印雜湊表 */ +func (m *hashMapChaining) print() { + var builder strings.Builder + + for _, bucket := range m.buckets { + builder.WriteString("[") + for _, p := range bucket { + builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") + } + builder.WriteString("]") + fmt.Println(builder.String()) + builder.Reset() + } +} diff --git a/zh-hant/codes/go/chapter_hashing/hash_map_open_addressing.go b/zh-hant/codes/go/chapter_hashing/hash_map_open_addressing.go new file mode 100644 index 000000000..943f21691 --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/hash_map_open_addressing.go @@ -0,0 +1,126 @@ +// File: hash_map_open_addressing.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import ( + "fmt" +) + +/* 開放定址雜湊表 */ +type hashMapOpenAddressing struct { + size int // 鍵值對數量 + capacity int // 雜湊表容量 + loadThres float64 // 觸發擴容的負載因子閾值 + extendRatio int // 擴容倍數 + buckets []*pair // 桶陣列 + TOMBSTONE *pair // 刪除標記 +} + +/* 建構子 */ +func newHashMapOpenAddressing() *hashMapOpenAddressing { + return &hashMapOpenAddressing{ + size: 0, + capacity: 4, + loadThres: 2.0 / 3.0, + extendRatio: 2, + buckets: make([]*pair, 4), + TOMBSTONE: &pair{-1, "-1"}, + } +} + +/* 雜湊函式 */ +func (h *hashMapOpenAddressing) hashFunc(key int) int { + return key % h.capacity // 根據鍵計算雜湊值 +} + +/* 負載因子 */ +func (h *hashMapOpenAddressing) loadFactor() float64 { + return float64(h.size) / float64(h.capacity) // 計算當前負載因子 +} + +/* 搜尋 key 對應的桶索引 */ +func (h *hashMapOpenAddressing) findBucket(key int) int { + index := h.hashFunc(key) // 獲取初始索引 + firstTombstone := -1 // 記錄遇到的第一個TOMBSTONE的位置 + for h.buckets[index] != nil { + if h.buckets[index].key == key { + if firstTombstone != -1 { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + h.buckets[firstTombstone] = h.buckets[index] + h.buckets[index] = h.TOMBSTONE + return firstTombstone // 返回移動後的桶索引 + } + return index // 返回找到的索引 + } + if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { + firstTombstone = index // 記錄遇到的首個刪除標記的位置 + } + index = (index + 1) % h.capacity // 線性探查,越過尾部則返回頭部 + } + // 若 key 不存在,則返回新增點的索引 + if firstTombstone != -1 { + return firstTombstone + } + return index +} + +/* 查詢操作 */ +func (h *hashMapOpenAddressing) get(key int) string { + index := h.findBucket(key) // 搜尋 key 對應的桶索引 + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + return h.buckets[index].val // 若找到鍵值對,則返回對應 val + } + return "" // 若鍵值對不存在,則返回 "" +} + +/* 新增操作 */ +func (h *hashMapOpenAddressing) put(key int, val string) { + if h.loadFactor() > h.loadThres { + h.extend() // 當負載因子超過閾值時,執行擴容 + } + index := h.findBucket(key) // 搜尋 key 對應的桶索引 + if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { + h.buckets[index] = &pair{key, val} // 若鍵值對不存在,則新增該鍵值對 + h.size++ + } else { + h.buckets[index].val = val // 若找到鍵值對,則覆蓋 val + } +} + +/* 刪除操作 */ +func (h *hashMapOpenAddressing) remove(key int) { + index := h.findBucket(key) // 搜尋 key 對應的桶索引 + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + h.buckets[index] = h.TOMBSTONE // 若找到鍵值對,則用刪除標記覆蓋它 + h.size-- + } +} + +/* 擴容雜湊表 */ +func (h *hashMapOpenAddressing) extend() { + oldBuckets := h.buckets // 暫存原雜湊表 + h.capacity *= h.extendRatio // 更新容量 + h.buckets = make([]*pair, h.capacity) // 初始化擴容後的新雜湊表 + h.size = 0 // 重置大小 + // 將鍵值對從原雜湊表搬運至新雜湊表 + for _, pair := range oldBuckets { + if pair != nil && pair != h.TOMBSTONE { + h.put(pair.key, pair.val) + } + } +} + +/* 列印雜湊表 */ +func (h *hashMapOpenAddressing) print() { + for _, pair := range h.buckets { + if pair == nil { + fmt.Println("nil") + } else if pair == h.TOMBSTONE { + fmt.Println("TOMBSTONE") + } else { + fmt.Printf("%d -> %s\n", pair.key, pair.val) + } + } +} diff --git a/zh-hant/codes/go/chapter_hashing/hash_map_test.go b/zh-hant/codes/go/chapter_hashing/hash_map_test.go new file mode 100644 index 000000000..bcf64ee91 --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/hash_map_test.go @@ -0,0 +1,74 @@ +// File: hash_map_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHashMap(t *testing.T) { + /* 初始化雜湊表 */ + hmap := make(map[int]string) + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小囉" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鴨" + fmt.Println("\n新增完成後,雜湊表為\nKey -> Value") + PrintMap(hmap) + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + name := hmap[15937] + fmt.Println("\n輸入學號 15937 ,查詢到姓名 ", name) + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + delete(hmap, 10583) + fmt.Println("\n刪除 10583 後,雜湊表為\nKey -> Value") + PrintMap(hmap) + + /* 走訪雜湊表 */ + // 走訪鍵值對 key->value + fmt.Println("\n走訪鍵值對 Key->Value") + for key, value := range hmap { + fmt.Println(key, "->", value) + } + // 單獨走訪鍵 key + fmt.Println("\n單獨走訪鍵 Key") + for key := range hmap { + fmt.Println(key) + } + // 單獨走訪值 value + fmt.Println("\n單獨走訪值 Value") + for _, value := range hmap { + fmt.Println(value) + } +} + +func TestSimpleHash(t *testing.T) { + var hash int + + key := "Hello 演算法" + + hash = addHash(key) + fmt.Println("加法雜湊值為 " + strconv.Itoa(hash)) + + hash = mulHash(key) + fmt.Println("乘法雜湊值為 " + strconv.Itoa(hash)) + + hash = xorHash(key) + fmt.Println("互斥或雜湊值為 " + strconv.Itoa(hash)) + + hash = rotHash(key) + fmt.Println("旋轉雜湊值為 " + strconv.Itoa(hash)) +} diff --git a/zh-hant/codes/go/chapter_hashing/simple_hash.go b/zh-hant/codes/go/chapter_hashing/simple_hash.go new file mode 100644 index 000000000..db2e4c5bd --- /dev/null +++ b/zh-hant/codes/go/chapter_hashing/simple_hash.go @@ -0,0 +1,55 @@ +// File: simple_hash.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import "fmt" + +/* 加法雜湊 */ +func addHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = (hash + int64(b)) % modulus + } + return int(hash) +} + +/* 乘法雜湊 */ +func mulHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = (31*hash + int64(b)) % modulus + } + return int(hash) +} + +/* 互斥或雜湊 */ +func xorHash(key string) int { + hash := 0 + modulus := 1000000007 + for _, b := range []byte(key) { + fmt.Println(int(b)) + hash ^= int(b) + hash = (31*hash + int(b)) % modulus + } + return hash & modulus +} + +/* 旋轉雜湊 */ +func rotHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus + } + return int(hash) +} diff --git a/zh-hant/codes/go/chapter_heap/heap.go b/zh-hant/codes/go/chapter_heap/heap.go new file mode 100644 index 000000000..765556a49 --- /dev/null +++ b/zh-hant/codes/go/chapter_heap/heap.go @@ -0,0 +1,45 @@ +// File: heap.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +// Go 語言中可以透過實現 heap.Interface 來構建整數大頂堆積 +// 實現 heap.Interface 需要同時實現 sort.Interface +type intHeap []any + +// Push heap.Interface 的函式,實現推入元素到堆積 +func (h *intHeap) Push(x any) { + // Push 和 Pop 使用 pointer receiver 作為參數 + // 因為它們不僅會對切片的內容進行調整,還會修改切片的長度。 + *h = append(*h, x.(int)) +} + +// Pop heap.Interface 的函式,實現彈出堆積頂元素 +func (h *intHeap) Pop() any { + // 待出堆積元素存放在最後 + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last +} + +// Len sort.Interface 的函式 +func (h *intHeap) Len() int { + return len(*h) +} + +// Less sort.Interface 的函式 +func (h *intHeap) Less(i, j int) bool { + // 如果實現小頂堆積,則需要調整為小於號 + return (*h)[i].(int) > (*h)[j].(int) +} + +// Swap sort.Interface 的函式 +func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] +} + +// Top 獲取堆積頂元素 +func (h *intHeap) Top() any { + return (*h)[0] +} diff --git a/zh-hant/codes/go/chapter_heap/heap_test.go b/zh-hant/codes/go/chapter_heap/heap_test.go new file mode 100644 index 000000000..3fb6e1ae4 --- /dev/null +++ b/zh-hant/codes/go/chapter_heap/heap_test.go @@ -0,0 +1,101 @@ +// File: heap_test.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import ( + "container/heap" + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func testPush(h *intHeap, val int) { + // 呼叫 heap.Interface 的函式,來新增元素 + heap.Push(h, val) + fmt.Printf("\n元素 %d 入堆積後 \n", val) + PrintHeap(*h) +} + +func testPop(h *intHeap) { + // 呼叫 heap.Interface 的函式,來移除元素 + val := heap.Pop(h) + fmt.Printf("\n堆積頂元素 %d 出堆積後 \n", val) + PrintHeap(*h) +} + +func TestHeap(t *testing.T) { + /* 初始化堆積 */ + // 初始化大頂堆積 + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* 元素入堆積 */ + testPush(maxHeap, 1) + testPush(maxHeap, 3) + testPush(maxHeap, 2) + testPush(maxHeap, 5) + testPush(maxHeap, 4) + + /* 獲取堆積頂元素 */ + top := maxHeap.Top() + fmt.Printf("堆積頂元素為 %d\n", top) + + /* 堆積頂元素出堆積 */ + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + + /* 獲取堆積大小 */ + size := len(*maxHeap) + fmt.Printf("堆積元素數量為 %d\n", size) + + /* 判斷堆積是否為空 */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("堆積是否為空 %t\n", isEmpty) +} + +func TestMyHeap(t *testing.T) { + /* 初始化堆積 */ + // 初始化大頂堆積 + maxHeap := newMaxHeap([]any{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}) + fmt.Printf("輸入陣列並建堆積後\n") + maxHeap.print() + + /* 獲取堆積頂元素 */ + peek := maxHeap.peek() + fmt.Printf("\n堆積頂元素為 %d\n", peek) + + /* 元素入堆積 */ + val := 7 + maxHeap.push(val) + fmt.Printf("\n元素 %d 入堆積後\n", val) + maxHeap.print() + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop() + fmt.Printf("\n堆積頂元素 %d 出堆積後\n", peek) + maxHeap.print() + + /* 獲取堆積大小 */ + size := maxHeap.size() + fmt.Printf("\n堆積元素數量為 %d\n", size) + + /* 判斷堆積是否為空 */ + isEmpty := maxHeap.isEmpty() + fmt.Printf("\n堆積是否為空 %t\n", isEmpty) +} + +func TestTopKHeap(t *testing.T) { + /* 初始化堆積 */ + // 初始化大頂堆積 + nums := []int{1, 7, 6, 3, 2} + k := 3 + res := topKHeap(nums, k) + fmt.Printf("最大的 " + strconv.Itoa(k) + " 個元素為") + PrintHeap(*res) +} diff --git a/zh-hant/codes/go/chapter_heap/my_heap.go b/zh-hant/codes/go/chapter_heap/my_heap.go new file mode 100644 index 000000000..e25189f21 --- /dev/null +++ b/zh-hant/codes/go/chapter_heap/my_heap.go @@ -0,0 +1,140 @@ +// File: my_heap.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import ( + "fmt" + + . "github.com/krahets/hello-algo/pkg" +) + +type maxHeap struct { + // 使用切片而非陣列,這樣無須考慮擴容問題 + data []any +} + +/* 建構子,建立空堆積 */ +func newHeap() *maxHeap { + return &maxHeap{ + data: make([]any, 0), + } +} + +/* 建構子,根據切片建堆積 */ +func newMaxHeap(nums []any) *maxHeap { + // 將串列元素原封不動新增進堆積 + h := &maxHeap{data: nums} + for i := h.parent(len(h.data) - 1); i >= 0; i-- { + // 堆積化除葉節點以外的其他所有節點 + h.siftDown(i) + } + return h +} + +/* 獲取左子節點的索引 */ +func (h *maxHeap) left(i int) int { + return 2*i + 1 +} + +/* 獲取右子節點的索引 */ +func (h *maxHeap) right(i int) int { + return 2*i + 2 +} + +/* 獲取父節點的索引 */ +func (h *maxHeap) parent(i int) int { + // 向下整除 + return (i - 1) / 2 +} + +/* 交換元素 */ +func (h *maxHeap) swap(i, j int) { + h.data[i], h.data[j] = h.data[j], h.data[i] +} + +/* 獲取堆積大小 */ +func (h *maxHeap) size() int { + return len(h.data) +} + +/* 判斷堆積是否為空 */ +func (h *maxHeap) isEmpty() bool { + return len(h.data) == 0 +} + +/* 訪問堆積頂元素 */ +func (h *maxHeap) peek() any { + return h.data[0] +} + +/* 元素入堆積 */ +func (h *maxHeap) push(val any) { + // 新增節點 + h.data = append(h.data, val) + // 從底至頂堆積化 + h.siftUp(len(h.data) - 1) +} + +/* 從節點 i 開始,從底至頂堆積化 */ +func (h *maxHeap) siftUp(i int) { + for true { + // 獲取節點 i 的父節點 + p := h.parent(i) + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if p < 0 || h.data[i].(int) <= h.data[p].(int) { + break + } + // 交換兩節點 + h.swap(i, p) + // 迴圈向上堆積化 + i = p + } +} + +/* 元素出堆積 */ +func (h *maxHeap) pop() any { + // 判空處理 + if h.isEmpty() { + fmt.Println("error") + return nil + } + // 交換根節點與最右葉節點(交換首元素與尾元素) + h.swap(0, h.size()-1) + // 刪除節點 + val := h.data[len(h.data)-1] + h.data = h.data[:len(h.data)-1] + // 從頂至底堆積化 + h.siftDown(0) + + // 返回堆積頂元素 + return val +} + +/* 從節點 i 開始,從頂至底堆積化 */ +func (h *maxHeap) siftDown(i int) { + for true { + // 判斷節點 i, l, r 中值最大的節點,記為 max + l, r, max := h.left(i), h.right(i), i + if l < h.size() && h.data[l].(int) > h.data[max].(int) { + max = l + } + if r < h.size() && h.data[r].(int) > h.data[max].(int) { + max = r + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if max == i { + break + } + // 交換兩節點 + h.swap(i, max) + // 迴圈向下堆積化 + i = max + } +} + +/* 列印堆積(二元樹) */ +func (h *maxHeap) print() { + PrintHeap(h.data) +} diff --git a/zh-hant/codes/go/chapter_heap/top_k.go b/zh-hant/codes/go/chapter_heap/top_k.go new file mode 100644 index 000000000..21a4bb7ac --- /dev/null +++ b/zh-hant/codes/go/chapter_heap/top_k.go @@ -0,0 +1,51 @@ +// File: top_k.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import "container/heap" + +type minHeap []any + +func (h *minHeap) Len() int { return len(*h) } +func (h *minHeap) Less(i, j int) bool { return (*h)[i].(int) < (*h)[j].(int) } +func (h *minHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } + +// Push heap.Interface 的方法,實現推入元素到堆積 +func (h *minHeap) Push(x any) { + *h = append(*h, x.(int)) +} + +// Pop heap.Interface 的方法,實現彈出堆積頂元素 +func (h *minHeap) Pop() any { + // 待出堆積元素存放在最後 + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last +} + +// Top 獲取堆積頂元素 +func (h *minHeap) Top() any { + return (*h)[0] +} + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +func topKHeap(nums []int, k int) *minHeap { + // 初始化小頂堆積 + h := &minHeap{} + heap.Init(h) + // 將陣列的前 k 個元素入堆積 + for i := 0; i < k; i++ { + heap.Push(h, nums[i]) + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for i := k; i < len(nums); i++ { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if nums[i] > h.Top().(int) { + heap.Pop(h) + heap.Push(h, nums[i]) + } + } + return h +} diff --git a/zh-hant/codes/go/chapter_searching/binary_search.go b/zh-hant/codes/go/chapter_searching/binary_search.go new file mode 100644 index 000000000..b78f615d6 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/binary_search.go @@ -0,0 +1,43 @@ +// File: binary_search.go +// Created Time: 2022-12-05 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +/* 二分搜尋(雙閉區間) */ +func binarySearch(nums []int, target int) int { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + i, j := 0, len(nums)-1 + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + for i <= j { + m := i + (j-i)/2 // 計算中點索引 m + if nums[m] < target { // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1 + } else if nums[m] > target { // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1 + } else { // 找到目標元素,返回其索引 + return m + } + } + // 未找到目標元素,返回 -1 + return -1 +} + +/* 二分搜尋(左閉右開區間) */ +func binarySearchLCRO(nums []int, target int) int { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + i, j := 0, len(nums) + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + for i < j { + m := i + (j-i)/2 // 計算中點索引 m + if nums[m] < target { // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1 + } else if nums[m] > target { // 此情況說明 target 在區間 [i, m) 中 + j = m + } else { // 找到目標元素,返回其索引 + return m + } + } + // 未找到目標元素,返回 -1 + return -1 +} diff --git a/zh-hant/codes/go/chapter_searching/binary_search_edge.go b/zh-hant/codes/go/chapter_searching/binary_search_edge.go new file mode 100644 index 000000000..017744d82 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/binary_search_edge.go @@ -0,0 +1,31 @@ +// File: binary_search_edge.go +// Created Time: 2023-08-23 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +/* 二分搜尋最左一個 target */ +func binarySearchLeftEdge(nums []int, target int) int { + // 等價於查詢 target 的插入點 + i := binarySearchInsertion(nums, target) + // 未找到 target ,返回 -1 + if i == len(nums) || nums[i] != target { + return -1 + } + // 找到 target ,返回索引 i + return i +} + +/* 二分搜尋最右一個 target */ +func binarySearchRightEdge(nums []int, target int) int { + // 轉化為查詢最左一個 target + 1 + i := binarySearchInsertion(nums, target+1) + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + j := i - 1 + // 未找到 target ,返回 -1 + if j == -1 || nums[j] != target { + return -1 + } + // 找到 target ,返回索引 j + return j +} diff --git a/zh-hant/codes/go/chapter_searching/binary_search_insertion.go b/zh-hant/codes/go/chapter_searching/binary_search_insertion.go new file mode 100644 index 000000000..1aef06084 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/binary_search_insertion.go @@ -0,0 +1,49 @@ +// File: binary_search_insertion.go +// Created Time: 2023-08-23 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +/* 二分搜尋插入點(無重複元素) */ +func binarySearchInsertionSimple(nums []int, target int) int { + // 初始化雙閉區間 [0, n-1] + i, j := 0, len(nums)-1 + for i <= j { + // 計算中點索引 m + m := i + (j-i)/2 + if nums[m] < target { + // target 在區間 [m+1, j] 中 + i = m + 1 + } else if nums[m] > target { + // target 在區間 [i, m-1] 中 + j = m - 1 + } else { + // 找到 target ,返回插入點 m + return m + } + } + // 未找到 target ,返回插入點 i + return i +} + +/* 二分搜尋插入點(存在重複元素) */ +func binarySearchInsertion(nums []int, target int) int { + // 初始化雙閉區間 [0, n-1] + i, j := 0, len(nums)-1 + for i <= j { + // 計算中點索引 m + m := i + (j-i)/2 + if nums[m] < target { + // target 在區間 [m+1, j] 中 + i = m + 1 + } else if nums[m] > target { + // target 在區間 [i, m-1] 中 + j = m - 1 + } else { + // 首個小於 target 的元素在區間 [i, m-1] 中 + j = m - 1 + } + } + // 返回插入點 i + return i +} diff --git a/zh-hant/codes/go/chapter_searching/binary_search_test.go b/zh-hant/codes/go/chapter_searching/binary_search_test.go new file mode 100644 index 000000000..ea650b20d --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/binary_search_test.go @@ -0,0 +1,61 @@ +// File: binary_search_test.go +// Created Time: 2022-12-05 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" +) + +func TestBinarySearch(t *testing.T) { + var ( + target = 6 + nums = []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + expected = 2 + ) + // 在陣列中執行二分搜尋 + actual := binarySearch(nums, target) + fmt.Println("目標元素 6 的索引 =", actual) + if actual != expected { + t.Errorf("目標元素 6 的索引 = %d, 應該為 %d", actual, expected) + } +} + +func TestBinarySearchEdge(t *testing.T) { + // 包含重複元素的陣列 + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + fmt.Println("\n陣列 nums = ", nums) + + // 二分搜尋左邊界和右邊界 + for _, target := range []int{6, 7} { + index := binarySearchLeftEdge(nums, target) + fmt.Println("最左一個元素", target, "的索引為", index) + + index = binarySearchRightEdge(nums, target) + fmt.Println("最右一個元素", target, "的索引為", index) + } +} + +func TestBinarySearchInsertion(t *testing.T) { + // 無重複元素的陣列 + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + fmt.Println("陣列 nums =", nums) + + // 二分搜尋插入點 + for _, target := range []int{6, 9} { + index := binarySearchInsertionSimple(nums, target) + fmt.Println("元素", target, "的插入點的索引為", index) + } + + // 包含重複元素的陣列 + nums = []int{1, 3, 6, 6, 6, 6, 6, 10, 12, 15} + fmt.Println("\n陣列 nums =", nums) + + // 二分搜尋插入點 + for _, target := range []int{2, 6, 20} { + index := binarySearchInsertion(nums, target) + fmt.Println("元素", target, "的插入點的索引為", index) + } +} diff --git a/zh-hant/codes/go/chapter_searching/hashing_search.go b/zh-hant/codes/go/chapter_searching/hashing_search.go new file mode 100644 index 000000000..46852e1c9 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/hashing_search.go @@ -0,0 +1,29 @@ +// File: hashing_search.go +// Created Time: 2022-12-12 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import . "github.com/krahets/hello-algo/pkg" + +/* 雜湊查詢(陣列) */ +func hashingSearchArray(m map[int]int, target int) int { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + if index, ok := m[target]; ok { + return index + } else { + return -1 + } +} + +/* 雜湊查詢(鏈結串列) */ +func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 nil + if node, ok := m[target]; ok { + return node + } else { + return nil + } +} diff --git a/zh-hant/codes/go/chapter_searching/hashing_search_test.go b/zh-hant/codes/go/chapter_searching/hashing_search_test.go new file mode 100644 index 000000000..52488fb80 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/hashing_search_test.go @@ -0,0 +1,36 @@ +// File: hashing_search_test.go +// Created Time: 2022-12-12 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHashingSearch(t *testing.T) { + target := 3 + /* 雜湊查詢(陣列) */ + nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} + // 初始化雜湊表 + m := make(map[int]int) + for i := 0; i < len(nums); i++ { + m[nums[i]] = i + } + index := hashingSearchArray(m, target) + fmt.Println("目標元素 3 的索引 = ", index) + + /* 雜湊查詢(鏈結串列) */ + head := ArrayToLinkedList(nums) + // 初始化雜湊表 + m1 := make(map[int]*ListNode) + for head != nil { + m1[head.Val] = head + head = head.Next + } + node := hashingSearchLinkedList(m1, target) + fmt.Println("目標節點值 3 的對應節點物件為 ", node) +} diff --git a/zh-hant/codes/go/chapter_searching/linear_search.go b/zh-hant/codes/go/chapter_searching/linear_search.go new file mode 100644 index 000000000..68a87ee44 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/linear_search.go @@ -0,0 +1,36 @@ +// File: linear_search.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 線性查詢(陣列) */ +func linearSearchArray(nums []int, target int) int { + // 走訪陣列 + for i := 0; i < len(nums); i++ { + // 找到目標元素,返回其索引 + if nums[i] == target { + return i + } + } + // 未找到目標元素,返回 -1 + return -1 +} + +/* 線性查詢(鏈結串列) */ +func linearSearchLinkedList(node *ListNode, target int) *ListNode { + // 走訪鏈結串列 + for node != nil { + // 找到目標節點,返回之 + if node.Val == target { + return node + } + node = node.Next + } + // 未找到目標元素,返回 nil + return nil +} diff --git a/zh-hant/codes/go/chapter_searching/linear_search_test.go b/zh-hant/codes/go/chapter_searching/linear_search_test.go new file mode 100644 index 000000000..263e2e8a6 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/linear_search_test.go @@ -0,0 +1,26 @@ +// File: linear_search_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLinearSearch(t *testing.T) { + target := 3 + nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} + + // 在陣列中執行線性查詢 + index := linearSearchArray(nums, target) + fmt.Println("目標元素 3 的索引 =", index) + + // 在鏈結串列中執行線性查詢 + head := ArrayToLinkedList(nums) + node := linearSearchLinkedList(head, target) + fmt.Println("目標節點值 3 的對應節點物件為", node) +} diff --git a/zh-hant/codes/go/chapter_searching/two_sum.go b/zh-hant/codes/go/chapter_searching/two_sum.go new file mode 100644 index 000000000..65a243c7e --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/two_sum.go @@ -0,0 +1,33 @@ +// File: two_sum.go +// Created Time: 2022-11-25 +// Author: reanon (793584285@qq.com) + +package chapter_searching + +/* 方法一:暴力列舉 */ +func twoSumBruteForce(nums []int, target int) []int { + size := len(nums) + // 兩層迴圈,時間複雜度為 O(n^2) + for i := 0; i < size-1; i++ { + for j := i + 1; i < size; j++ { + if nums[i]+nums[j] == target { + return []int{i, j} + } + } + } + return nil +} + +/* 方法二:輔助雜湊表 */ +func twoSumHashTable(nums []int, target int) []int { + // 輔助雜湊表,空間複雜度為 O(n) + hashTable := map[int]int{} + // 單層迴圈,時間複雜度為 O(n) + for idx, val := range nums { + if preIdx, ok := hashTable[target-val]; ok { + return []int{preIdx, idx} + } + hashTable[val] = idx + } + return nil +} diff --git a/zh-hant/codes/go/chapter_searching/two_sum_test.go b/zh-hant/codes/go/chapter_searching/two_sum_test.go new file mode 100644 index 000000000..b577d3c09 --- /dev/null +++ b/zh-hant/codes/go/chapter_searching/two_sum_test.go @@ -0,0 +1,24 @@ +// File: two_sum_test.go +// Created Time: 2022-11-25 +// Author: reanon (793584285@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" +) + +func TestTwoSum(t *testing.T) { + // ======= Test Case ======= + nums := []int{2, 7, 11, 15} + target := 13 + + // ====== Driver Code ====== + // 方法一:暴力解法 + res := twoSumBruteForce(nums, target) + fmt.Println("方法一 res =", res) + // 方法二:雜湊表 + res = twoSumHashTable(nums, target) + fmt.Println("方法二 res =", res) +} diff --git a/zh-hant/codes/go/chapter_sorting/bubble_sort.go b/zh-hant/codes/go/chapter_sorting/bubble_sort.go new file mode 100644 index 000000000..7c5b65285 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/bubble_sort.go @@ -0,0 +1,38 @@ +// File: bubble_sort.go +// Created Time: 2022-12-06 +// Author: Slone123c (274325721@qq.com) + +package chapter_sorting + +/* 泡沫排序 */ +func bubbleSort(nums []int) { + // 外迴圈:未排序區間為 [0, i] + for i := len(nums) - 1; i > 0; i-- { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // 交換 nums[j] 與 nums[j + 1] + nums[j], nums[j+1] = nums[j+1], nums[j] + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +func bubbleSortWithFlag(nums []int) { + // 外迴圈:未排序區間為 [0, i] + for i := len(nums) - 1; i > 0; i-- { + flag := false // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // 交換 nums[j] 與 nums[j + 1] + nums[j], nums[j+1] = nums[j+1], nums[j] + flag = true // 記錄交換元素 + } + } + if flag == false { // 此輪“冒泡”未交換任何元素,直接跳出 + break + } + } +} diff --git a/zh-hant/codes/go/chapter_sorting/bubble_sort_test.go b/zh-hant/codes/go/chapter_sorting/bubble_sort_test.go new file mode 100644 index 000000000..af9895eec --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/bubble_sort_test.go @@ -0,0 +1,20 @@ +// File: bubble_sort_test.go +// Created Time: 2022-12-06 +// Author: Slone123c (274325721@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestBubbleSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + bubbleSort(nums) + fmt.Println("泡沫排序完成後 nums = ", nums) + + nums1 := []int{4, 1, 3, 1, 5, 2} + bubbleSortWithFlag(nums1) + fmt.Println("泡沫排序完成後 nums1 = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/bucket_sort.go b/zh-hant/codes/go/chapter_sorting/bucket_sort.go new file mode 100644 index 000000000..e06363d03 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/bucket_sort.go @@ -0,0 +1,37 @@ +// File: bucket_sort.go +// Created Time: 2023-03-27 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import "sort" + +/* 桶排序 */ +func bucketSort(nums []float64) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + k := len(nums) / 2 + buckets := make([][]float64, k) + for i := 0; i < k; i++ { + buckets[i] = make([]float64, 0) + } + // 1. 將陣列元素分配到各個桶中 + for _, num := range nums { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + i := int(num * float64(k)) + // 將 num 新增進桶 i + buckets[i] = append(buckets[i], num) + } + // 2. 對各個桶執行排序 + for i := 0; i < k; i++ { + // 使用內建切片排序函式,也可以替換成其他排序演算法 + sort.Float64s(buckets[i]) + } + // 3. 走訪桶合併結果 + i := 0 + for _, bucket := range buckets { + for _, num := range bucket { + nums[i] = num + i++ + } + } +} diff --git a/zh-hant/codes/go/chapter_sorting/bucket_sort_test.go b/zh-hant/codes/go/chapter_sorting/bucket_sort_test.go new file mode 100644 index 000000000..c6690b600 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/bucket_sort_test.go @@ -0,0 +1,17 @@ +// File: bucket_sort_test.go +// Created Time: 2023-03-27 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestBucketSort(t *testing.T) { + // 設輸入資料為浮點數,範圍為 [0, 1) + nums := []float64{0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37} + bucketSort(nums) + fmt.Println("桶排序完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/counting_sort.go b/zh-hant/codes/go/chapter_sorting/counting_sort.go new file mode 100644 index 000000000..6f6dd8d42 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/counting_sort.go @@ -0,0 +1,68 @@ +// File: counting_sort.go +// Created Time: 2023-03-20 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +type CountingSort struct{} + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +func countingSortNaive(nums []int) { + // 1. 統計陣列最大元素 m + m := 0 + for _, num := range nums { + if num > m { + m = num + } + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + counter := make([]int, m+1) + for _, num := range nums { + counter[num]++ + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + for i, num := 0, 0; num < m+1; num++ { + for j := 0; j < counter[num]; j++ { + nums[i] = num + i++ + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +func countingSort(nums []int) { + // 1. 統計陣列最大元素 m + m := 0 + for _, num := range nums { + if num > m { + m = num + } + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + counter := make([]int, m+1) + for _, num := range nums { + counter[num]++ + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for i := 0; i < m; i++ { + counter[i+1] += counter[i] + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + n := len(nums) + res := make([]int, n) + for i := n - 1; i >= 0; i-- { + num := nums[i] + // 將 num 放置到對應索引處 + res[counter[num]-1] = num + // 令前綴和自減 1 ,得到下次放置 num 的索引 + counter[num]-- + } + // 使用結果陣列 res 覆蓋原陣列 nums + copy(nums, res) +} diff --git a/zh-hant/codes/go/chapter_sorting/counting_sort_test.go b/zh-hant/codes/go/chapter_sorting/counting_sort_test.go new file mode 100644 index 000000000..d04920eb6 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/counting_sort_test.go @@ -0,0 +1,20 @@ +// File: counting_sort_test.go +// Created Time: 2023-03-20 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestCountingSort(t *testing.T) { + nums := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} + countingSortNaive(nums) + fmt.Println("計數排序(無法排序物件)完成後 nums = ", nums) + + nums1 := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} + countingSort(nums1) + fmt.Println("計數排序完成後 nums1 = ", nums1) +} diff --git a/zh-hant/codes/go/chapter_sorting/heap_sort.go b/zh-hant/codes/go/chapter_sorting/heap_sort.go new file mode 100644 index 000000000..9502a70fd --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/heap_sort.go @@ -0,0 +1,44 @@ +// File: heap_sort.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +func siftDown(nums *[]int, n, i int) { + for true { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + l := 2*i + 1 + r := 2*i + 2 + ma := i + if l < n && (*nums)[l] > (*nums)[ma] { + ma = l + } + if r < n && (*nums)[r] > (*nums)[ma] { + ma = r + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if ma == i { + break + } + // 交換兩節點 + (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i] + // 迴圈向下堆積化 + i = ma + } +} + +/* 堆積排序 */ +func heapSort(nums *[]int) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for i := len(*nums)/2 - 1; i >= 0; i-- { + siftDown(nums, len(*nums), i) + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for i := len(*nums) - 1; i > 0; i-- { + // 交換根節點與最右葉節點(交換首元素與尾元素) + (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0] + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0) + } +} diff --git a/zh-hant/codes/go/chapter_sorting/heap_sort_test.go b/zh-hant/codes/go/chapter_sorting/heap_sort_test.go new file mode 100644 index 000000000..de4aa8b25 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/heap_sort_test.go @@ -0,0 +1,16 @@ +// File: heap_sort_test.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestHeapSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + heapSort(&nums) + fmt.Println("堆積排序完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/insertion_sort.go b/zh-hant/codes/go/chapter_sorting/insertion_sort.go new file mode 100644 index 000000000..16ba2cdc1 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/insertion_sort.go @@ -0,0 +1,20 @@ +// File: insertion_sort.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +/* 插入排序 */ +func insertionSort(nums []int) { + // 外迴圈:已排序區間為 [0, i-1] + for i := 1; i < len(nums); i++ { + base := nums[i] + j := i - 1 + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + for j >= 0 && nums[j] > base { + nums[j+1] = nums[j] // 將 nums[j] 向右移動一位 + j-- + } + nums[j+1] = base // 將 base 賦值到正確位置 + } +} diff --git a/zh-hant/codes/go/chapter_sorting/insertion_sort_test.go b/zh-hant/codes/go/chapter_sorting/insertion_sort_test.go new file mode 100644 index 000000000..d98337970 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/insertion_sort_test.go @@ -0,0 +1,16 @@ +// File: insertion_sort_test.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestInsertionSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + insertionSort(nums) + fmt.Println("插入排序完成後 nums =", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/merge_sort.go b/zh-hant/codes/go/chapter_sorting/merge_sort.go new file mode 100644 index 000000000..e5daa81b9 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/merge_sort.go @@ -0,0 +1,54 @@ +// File: merge_sort.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +/* 合併左子陣列和右子陣列 */ +func merge(nums []int, left, mid, right int) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + tmp := make([]int, right-left+1) + // 初始化左子陣列和右子陣列的起始索引 + i, j, k := left, mid+1, 0 + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + for i <= mid && j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i] + i++ + } else { + tmp[k] = nums[j] + j++ + } + k++ + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + for i <= mid { + tmp[k] = nums[i] + i++ + k++ + } + for j <= right { + tmp[k] = nums[j] + j++ + k++ + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for k := 0; k < len(tmp); k++ { + nums[left+k] = tmp[k] + } +} + +/* 合併排序 */ +func mergeSort(nums []int, left, right int) { + // 終止條件 + if left >= right { + return + } + // 劃分階段 + mid := (left + right) / 2 + mergeSort(nums, left, mid) + mergeSort(nums, mid+1, right) + // 合併階段 + merge(nums, left, mid, right) +} diff --git a/zh-hant/codes/go/chapter_sorting/merge_sort_test.go b/zh-hant/codes/go/chapter_sorting/merge_sort_test.go new file mode 100644 index 000000000..c0ff39683 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/merge_sort_test.go @@ -0,0 +1,16 @@ +// File: merge_sort_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestMergeSort(t *testing.T) { + nums := []int{7, 3, 2, 6, 0, 1, 5, 4} + mergeSort(nums, 0, len(nums)-1) + fmt.Println("合併排序完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/quick_sort.go b/zh-hant/codes/go/chapter_sorting/quick_sort.go new file mode 100644 index 000000000..e8b94da2f --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/quick_sort.go @@ -0,0 +1,130 @@ +// File: quick_sort.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +// 快速排序 +type quickSort struct{} + +// 快速排序(中位基準數最佳化) +type quickSortMedian struct{} + +// 快速排序(尾遞迴最佳化) +type quickSortTailCall struct{} + +/* 哨兵劃分 */ +func (q *quickSort) partition(nums []int, left, right int) int { + // 以 nums[left] 為基準數 + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- // 從右向左找首個小於基準數的元素 + } + for i < j && nums[i] <= nums[left] { + i++ // 從左向右找首個大於基準數的元素 + } + // 元素交換 + nums[i], nums[j] = nums[j], nums[i] + } + // 將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + return i // 返回基準數的索引 +} + +/* 快速排序 */ +func (q *quickSort) quickSort(nums []int, left, right int) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return + } + // 哨兵劃分 + pivot := q.partition(nums, left, right) + // 遞迴左子陣列、右子陣列 + q.quickSort(nums, left, pivot-1) + q.quickSort(nums, pivot+1, right) +} + +/* 選取三個候選元素的中位數 */ +func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { + l, m, r := nums[left], nums[mid], nums[right] + if (l <= m && m <= r) || (r <= m && m <= l) { + return mid // m 在 l 和 r 之間 + } + if (m <= l && l <= r) || (r <= l && l <= m) { + return left // l 在 m 和 r 之間 + } + return right +} + +/* 哨兵劃分(三數取中值)*/ +func (q *quickSortMedian) partition(nums []int, left, right int) int { + // 以 nums[left] 為基準數 + med := q.medianThree(nums, left, (left+right)/2, right) + // 將中位數交換至陣列最左端 + nums[left], nums[med] = nums[med], nums[left] + // 以 nums[left] 為基準數 + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- //從右向左找首個小於基準數的元素 + } + for i < j && nums[i] <= nums[left] { + i++ //從左向右找首個大於基準數的元素 + } + //元素交換 + nums[i], nums[j] = nums[j], nums[i] + } + //將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + return i //返回基準數的索引 +} + +/* 快速排序 */ +func (q *quickSortMedian) quickSort(nums []int, left, right int) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return + } + // 哨兵劃分 + pivot := q.partition(nums, left, right) + // 遞迴左子陣列、右子陣列 + q.quickSort(nums, left, pivot-1) + q.quickSort(nums, pivot+1, right) +} + +/* 哨兵劃分 */ +func (q *quickSortTailCall) partition(nums []int, left, right int) int { + // 以 nums[left] 為基準數 + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- // 從右向左找首個小於基準數的元素 + } + for i < j && nums[i] <= nums[left] { + i++ // 從左向右找首個大於基準數的元素 + } + // 元素交換 + nums[i], nums[j] = nums[j], nums[i] + } + // 將基準數交換至兩子陣列的分界線 + nums[i], nums[left] = nums[left], nums[i] + return i // 返回基準數的索引 +} + +/* 快速排序(尾遞迴最佳化)*/ +func (q *quickSortTailCall) quickSort(nums []int, left, right int) { + // 子陣列長度為 1 時終止 + for left < right { + // 哨兵劃分操作 + pivot := q.partition(nums, left, right) + // 對兩個子陣列中較短的那個執行快速排序 + if pivot-left < right-pivot { + q.quickSort(nums, left, pivot-1) // 遞迴排序左子陣列 + left = pivot + 1 // 剩餘未排序區間為 [pivot + 1, right] + } else { + q.quickSort(nums, pivot+1, right) // 遞迴排序右子陣列 + right = pivot - 1 // 剩餘未排序區間為 [left, pivot - 1] + } + } +} diff --git a/zh-hant/codes/go/chapter_sorting/quick_sort_test.go b/zh-hant/codes/go/chapter_sorting/quick_sort_test.go new file mode 100644 index 000000000..503cb8b9f --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/quick_sort_test.go @@ -0,0 +1,34 @@ +// File: quick_sort_test.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +// 快速排序 +func TestQuickSort(t *testing.T) { + q := quickSort{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("快速排序完成後 nums = ", nums) +} + +// 快速排序(中位基準數最佳化) +func TestQuickSortMedian(t *testing.T) { + q := quickSortMedian{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("快速排序(中位基準數最佳化)完成後 nums = ", nums) +} + +// 快速排序(尾遞迴最佳化) +func TestQuickSortTailCall(t *testing.T) { + q := quickSortTailCall{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("快速排序(尾遞迴最佳化)完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/radix_sort.go b/zh-hant/codes/go/chapter_sorting/radix_sort.go new file mode 100644 index 000000000..5edd9596b --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/radix_sort.go @@ -0,0 +1,60 @@ +// File: radix_sort.go +// Created Time: 2023-01-18 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import "math" + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +func digit(num, exp int) int { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10 +} + +/* 計數排序(根據 nums 第 k 位排序) */ +func countingSortDigit(nums []int, exp int) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + counter := make([]int, 10) + n := len(nums) + // 統計 0~9 各數字的出現次數 + for i := 0; i < n; i++ { + d := digit(nums[i], exp) // 獲取 nums[i] 第 k 位,記為 d + counter[d]++ // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for i := 1; i < 10; i++ { + counter[i] += counter[i-1] + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + res := make([]int, n) + for i := n - 1; i >= 0; i-- { + d := digit(nums[i], exp) + j := counter[d] - 1 // 獲取 d 在陣列中的索引 j + res[j] = nums[i] // 將當前元素填入索引 j + counter[d]-- // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for i := 0; i < n; i++ { + nums[i] = res[i] + } +} + +/* 基數排序 */ +func radixSort(nums []int) { + // 獲取陣列的最大元素,用於判斷最大位數 + max := math.MinInt + for _, num := range nums { + if num > max { + max = num + } + } + // 按照從低位到高位的順序走訪 + for exp := 1; max >= exp; exp *= 10 { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp) + } +} diff --git a/zh-hant/codes/go/chapter_sorting/radix_sort_test.go b/zh-hant/codes/go/chapter_sorting/radix_sort_test.go new file mode 100644 index 000000000..c20bb94eb --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/radix_sort_test.go @@ -0,0 +1,18 @@ +// File: radix_sort_test.go +// Created Time: 2023-01-18 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestRadixSort(t *testing.T) { + /* 基數排序 */ + nums := []int{10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996} + radixSort(nums) + fmt.Println("基數排序完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_sorting/selection_sort.go b/zh-hant/codes/go/chapter_sorting/selection_sort.go new file mode 100644 index 000000000..7fcb57d70 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/selection_sort.go @@ -0,0 +1,24 @@ +// File: selection_sort.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +/* 選擇排序 */ +func selectionSort(nums []int) { + n := len(nums) + // 外迴圈:未排序區間為 [i, n-1] + for i := 0; i < n-1; i++ { + // 內迴圈:找到未排序區間內的最小元素 + k := i + for j := i + 1; j < n; j++ { + if nums[j] < nums[k] { + // 記錄最小元素的索引 + k = j + } + } + // 將該最小元素與未排序區間的首個元素交換 + nums[i], nums[k] = nums[k], nums[i] + + } +} diff --git a/zh-hant/codes/go/chapter_sorting/selection_sort_test.go b/zh-hant/codes/go/chapter_sorting/selection_sort_test.go new file mode 100644 index 000000000..509ec3672 --- /dev/null +++ b/zh-hant/codes/go/chapter_sorting/selection_sort_test.go @@ -0,0 +1,16 @@ +// File: selection_sort_test.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestSelectionSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + selectionSort(nums) + fmt.Println("選擇排序完成後 nums = ", nums) +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/array_deque.go b/zh-hant/codes/go/chapter_stack_and_queue/array_deque.go new file mode 100644 index 000000000..89be939b6 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/array_deque.go @@ -0,0 +1,115 @@ +// File: array_deque.go +// Created Time: 2023-03-13 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import "fmt" + +/* 基於環形陣列實現的雙向佇列 */ +type arrayDeque struct { + nums []int // 用於儲存雙向佇列元素的陣列 + front int // 佇列首指標,指向佇列首元素 + queSize int // 雙向佇列長度 + queCapacity int // 佇列容量(即最大容納元素數量) +} + +/* 初始化佇列 */ +func newArrayDeque(queCapacity int) *arrayDeque { + return &arrayDeque{ + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, + } +} + +/* 獲取雙向佇列的長度 */ +func (q *arrayDeque) size() int { + return q.queSize +} + +/* 判斷雙向佇列是否為空 */ +func (q *arrayDeque) isEmpty() bool { + return q.queSize == 0 +} + +/* 計算環形陣列索引 */ +func (q *arrayDeque) index(i int) int { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + q.queCapacity) % q.queCapacity +} + +/* 佇列首入列 */ +func (q *arrayDeque) pushFirst(num int) { + if q.queSize == q.queCapacity { + fmt.Println("雙向佇列已滿") + return + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + q.front = q.index(q.front - 1) + // 將 num 新增至佇列首 + q.nums[q.front] = num + q.queSize++ +} + +/* 佇列尾入列 */ +func (q *arrayDeque) pushLast(num int) { + if q.queSize == q.queCapacity { + fmt.Println("雙向佇列已滿") + return + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + rear := q.index(q.front + q.queSize) + // 將 num 新增至佇列尾 + q.nums[rear] = num + q.queSize++ +} + +/* 佇列首出列 */ +func (q *arrayDeque) popFirst() any { + num := q.peekFirst() + // 佇列首指標向後移動一位 + q.front = q.index(q.front + 1) + q.queSize-- + return num +} + +/* 佇列尾出列 */ +func (q *arrayDeque) popLast() any { + num := q.peekLast() + q.queSize-- + return num +} + +/* 訪問佇列首元素 */ +func (q *arrayDeque) peekFirst() any { + if q.isEmpty() { + return nil + } + return q.nums[q.front] +} + +/* 訪問佇列尾元素 */ +func (q *arrayDeque) peekLast() any { + if q.isEmpty() { + return nil + } + // 計算尾元素索引 + last := q.index(q.front + q.queSize - 1) + return q.nums[last] +} + +/* 獲取 Slice 用於列印 */ +func (q *arrayDeque) toSlice() []int { + // 僅轉換有效長度範圍內的串列元素 + res := make([]int, q.queSize) + for i, j := 0, q.front; i < q.queSize; i++ { + res[i] = q.nums[q.index(j)] + j++ + } + return res +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/array_queue.go b/zh-hant/codes/go/chapter_stack_and_queue/array_queue.go new file mode 100644 index 000000000..a719e5513 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/array_queue.go @@ -0,0 +1,74 @@ +// File: array_queue.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +/* 基於環形陣列實現的佇列 */ +type arrayQueue struct { + nums []int // 用於儲存佇列元素的陣列 + front int // 佇列首指標,指向佇列首元素 + queSize int // 佇列長度 + queCapacity int // 佇列容量(即最大容納元素數量) +} + +/* 初始化佇列 */ +func newArrayQueue(queCapacity int) *arrayQueue { + return &arrayQueue{ + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, + } +} + +/* 獲取佇列的長度 */ +func (q *arrayQueue) size() int { + return q.queSize +} + +/* 判斷佇列是否為空 */ +func (q *arrayQueue) isEmpty() bool { + return q.queSize == 0 +} + +/* 入列 */ +func (q *arrayQueue) push(num int) { + // 當 rear == queCapacity 表示佇列已滿 + if q.queSize == q.queCapacity { + return + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + rear := (q.front + q.queSize) % q.queCapacity + // 將 num 新增至佇列尾 + q.nums[rear] = num + q.queSize++ +} + +/* 出列 */ +func (q *arrayQueue) pop() any { + num := q.peek() + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + q.front = (q.front + 1) % q.queCapacity + q.queSize-- + return num +} + +/* 訪問佇列首元素 */ +func (q *arrayQueue) peek() any { + if q.isEmpty() { + return nil + } + return q.nums[q.front] +} + +/* 獲取 Slice 用於列印 */ +func (q *arrayQueue) toSlice() []int { + rear := (q.front + q.queSize) + if rear >= q.queCapacity { + rear %= q.queCapacity + return append(q.nums[q.front:], q.nums[:rear]...) + } + return q.nums[q.front:rear] +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/array_stack.go b/zh-hant/codes/go/chapter_stack_and_queue/array_stack.go new file mode 100644 index 000000000..cf882bf04 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/array_stack.go @@ -0,0 +1,55 @@ +// File: array_stack.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +/* 基於陣列實現的堆疊 */ +type arrayStack struct { + data []int // 資料 +} + +/* 初始化堆疊 */ +func newArrayStack() *arrayStack { + return &arrayStack{ + // 設定堆疊的長度為 0,容量為 16 + data: make([]int, 0, 16), + } +} + +/* 堆疊的長度 */ +func (s *arrayStack) size() int { + return len(s.data) +} + +/* 堆疊是否為空 */ +func (s *arrayStack) isEmpty() bool { + return s.size() == 0 +} + +/* 入堆疊 */ +func (s *arrayStack) push(v int) { + // 切片會自動擴容 + s.data = append(s.data, v) +} + +/* 出堆疊 */ +func (s *arrayStack) pop() any { + val := s.peek() + s.data = s.data[:len(s.data)-1] + return val +} + +/* 獲取堆疊頂元素 */ +func (s *arrayStack) peek() any { + if s.isEmpty() { + return nil + } + val := s.data[len(s.data)-1] + return val +} + +/* 獲取 Slice 用於列印 */ +func (s *arrayStack) toSlice() []int { + return s.data +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/deque_test.go b/zh-hant/codes/go/chapter_stack_and_queue/deque_test.go new file mode 100644 index 000000000..37e60e80e --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/deque_test.go @@ -0,0 +1,141 @@ +// File: deque_test.go +// Created Time: 2022-11-29 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestDeque(t *testing.T) { + /* 初始化雙向佇列 */ + // 在 Go 中,將 list 作為雙向佇列使用 + deque := list.New() + + /* 元素入列 */ + deque.PushBack(2) + deque.PushBack(5) + deque.PushBack(4) + deque.PushFront(3) + deque.PushFront(1) + fmt.Print("雙向佇列 deque = ") + PrintList(deque) + + /* 訪問元素 */ + front := deque.Front() + fmt.Println("佇列首元素 front =", front.Value) + rear := deque.Back() + fmt.Println("佇列尾元素 rear =", rear.Value) + + /* 元素出列 */ + deque.Remove(front) + fmt.Print("佇列首出列元素 front = ", front.Value, ",佇列首出列後 deque = ") + PrintList(deque) + deque.Remove(rear) + fmt.Print("佇列尾出列元素 rear = ", rear.Value, ",佇列尾出列後 deque = ") + PrintList(deque) + + /* 獲取雙向佇列的長度 */ + size := deque.Len() + fmt.Println("雙向佇列長度 size =", size) + + /* 判斷雙向佇列是否為空 */ + isEmpty := deque.Len() == 0 + fmt.Println("雙向佇列是否為空 =", isEmpty) +} + +func TestArrayDeque(t *testing.T) { + /* 初始化雙向佇列 */ + // 在 Go 中,將 list 作為雙向佇列使用 + deque := newArrayDeque(16) + + /* 元素入列 */ + deque.pushLast(3) + deque.pushLast(2) + deque.pushLast(5) + fmt.Print("雙向佇列 deque = ") + PrintSlice(deque.toSlice()) + + /* 訪問元素 */ + peekFirst := deque.peekFirst() + fmt.Println("佇列首元素 peekFirst =", peekFirst) + peekLast := deque.peekLast() + fmt.Println("佇列尾元素 peekLast =", peekLast) + + /* 元素入列 */ + deque.pushLast(4) + fmt.Print("元素 4 佇列尾入列後 deque = ") + PrintSlice(deque.toSlice()) + deque.pushFirst(1) + fmt.Print("元素 1 佇列首入列後 deque = ") + PrintSlice(deque.toSlice()) + + /* 元素出列 */ + popFirst := deque.popFirst() + fmt.Print("佇列首出列元素 popFirst = ", popFirst, ",佇列首出列後 deque = ") + PrintSlice(deque.toSlice()) + popLast := deque.popLast() + fmt.Print("佇列尾出列元素 popLast = ", popLast, ",佇列尾出列後 deque = ") + PrintSlice(deque.toSlice()) + + /* 獲取雙向佇列的長度 */ + size := deque.size() + fmt.Println("雙向佇列長度 size =", size) + + /* 判斷雙向佇列是否為空 */ + isEmpty := deque.isEmpty() + fmt.Println("雙向佇列是否為空 =", isEmpty) +} + +func TestLinkedListDeque(t *testing.T) { + // 初始化佇列 + deque := newLinkedListDeque() + + // 元素入列 + deque.pushLast(2) + deque.pushLast(5) + deque.pushLast(4) + deque.pushFirst(3) + deque.pushFirst(1) + fmt.Print("佇列 deque = ") + PrintList(deque.toList()) + + // 訪問佇列首元素 + front := deque.peekFirst() + fmt.Println("佇列首元素 front =", front) + rear := deque.peekLast() + fmt.Println("佇列尾元素 rear =", rear) + + // 元素出列 + popFirst := deque.popFirst() + fmt.Print("佇列首出列元素 popFirst = ", popFirst, ",佇列首出列後 deque = ") + PrintList(deque.toList()) + popLast := deque.popLast() + fmt.Print("佇列尾出列元素 popLast = ", popLast, ",佇列尾出列後 deque = ") + PrintList(deque.toList()) + + // 獲取隊的長度 + size := deque.size() + fmt.Println("隊的長度 size =", size) + + // 判斷是否為空 + isEmpty := deque.isEmpty() + fmt.Println("隊是否為空 =", isEmpty) +} + +// BenchmarkLinkedListDeque 67.92 ns/op in Mac M1 Pro +func BenchmarkLinkedListDeque(b *testing.B) { + deque := newLinkedListDeque() + // use b.N for looping + for i := 0; i < b.N; i++ { + deque.pushLast(777) + } + for i := 0; i < b.N; i++ { + deque.popFirst() + } +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_deque.go b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_deque.go new file mode 100644 index 000000000..e47dc5459 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_deque.go @@ -0,0 +1,85 @@ +// File: linkedlist_deque.go +// Created Time: 2022-11-29 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* 基於雙向鏈結串列實現的雙向佇列 */ +type linkedListDeque struct { + // 使用內建包 list + data *list.List +} + +/* 初始化雙端佇列 */ +func newLinkedListDeque() *linkedListDeque { + return &linkedListDeque{ + data: list.New(), + } +} + +/* 佇列首元素入列 */ +func (s *linkedListDeque) pushFirst(value any) { + s.data.PushFront(value) +} + +/* 佇列尾元素入列 */ +func (s *linkedListDeque) pushLast(value any) { + s.data.PushBack(value) +} + +/* 佇列首元素出列 */ +func (s *linkedListDeque) popFirst() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + s.data.Remove(e) + return e.Value +} + +/* 佇列尾元素出列 */ +func (s *linkedListDeque) popLast() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + s.data.Remove(e) + return e.Value +} + +/* 訪問佇列首元素 */ +func (s *linkedListDeque) peekFirst() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + return e.Value +} + +/* 訪問佇列尾元素 */ +func (s *linkedListDeque) peekLast() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + return e.Value +} + +/* 獲取佇列的長度 */ +func (s *linkedListDeque) size() int { + return s.data.Len() +} + +/* 判斷佇列是否為空 */ +func (s *linkedListDeque) isEmpty() bool { + return s.data.Len() == 0 +} + +/* 獲取 List 用於列印 */ +func (s *linkedListDeque) toList() *list.List { + return s.data +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_queue.go b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_queue.go new file mode 100644 index 000000000..fca7332f6 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_queue.go @@ -0,0 +1,61 @@ +// File: linkedlist_queue.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* 基於鏈結串列實現的佇列 */ +type linkedListQueue struct { + // 使用內建包 list 來實現佇列 + data *list.List +} + +/* 初始化佇列 */ +func newLinkedListQueue() *linkedListQueue { + return &linkedListQueue{ + data: list.New(), + } +} + +/* 入列 */ +func (s *linkedListQueue) push(value any) { + s.data.PushBack(value) +} + +/* 出列 */ +func (s *linkedListQueue) pop() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + s.data.Remove(e) + return e.Value +} + +/* 訪問佇列首元素 */ +func (s *linkedListQueue) peek() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + return e.Value +} + +/* 獲取佇列的長度 */ +func (s *linkedListQueue) size() int { + return s.data.Len() +} + +/* 判斷佇列是否為空 */ +func (s *linkedListQueue) isEmpty() bool { + return s.data.Len() == 0 +} + +/* 獲取 List 用於列印 */ +func (s *linkedListQueue) toList() *list.List { + return s.data +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_stack.go b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_stack.go new file mode 100644 index 000000000..4bcac1cfa --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/linkedlist_stack.go @@ -0,0 +1,61 @@ +// File: linkedlist_stack.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* 基於鏈結串列實現的堆疊 */ +type linkedListStack struct { + // 使用內建包 list 來實現堆疊 + data *list.List +} + +/* 初始化堆疊 */ +func newLinkedListStack() *linkedListStack { + return &linkedListStack{ + data: list.New(), + } +} + +/* 入堆疊 */ +func (s *linkedListStack) push(value int) { + s.data.PushBack(value) +} + +/* 出堆疊 */ +func (s *linkedListStack) pop() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + s.data.Remove(e) + return e.Value +} + +/* 訪問堆疊頂元素 */ +func (s *linkedListStack) peek() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + return e.Value +} + +/* 獲取堆疊的長度 */ +func (s *linkedListStack) size() int { + return s.data.Len() +} + +/* 判斷堆疊是否為空 */ +func (s *linkedListStack) isEmpty() bool { + return s.data.Len() == 0 +} + +/* 獲取 List 用於列印 */ +func (s *linkedListStack) toList() *list.List { + return s.data +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/queue_test.go b/zh-hant/codes/go/chapter_stack_and_queue/queue_test.go new file mode 100644 index 000000000..ec216f33b --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/queue_test.go @@ -0,0 +1,142 @@ +// File: queue_test.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestQueue(t *testing.T) { + /* 初始化佇列 */ + // 在 Go 中,將 list 作為佇列來使用 + queue := list.New() + + /* 元素入列 */ + queue.PushBack(1) + queue.PushBack(3) + queue.PushBack(2) + queue.PushBack(5) + queue.PushBack(4) + fmt.Print("佇列 queue = ") + PrintList(queue) + + /* 訪問佇列首元素 */ + peek := queue.Front() + fmt.Println("佇列首元素 peek =", peek.Value) + + /* 元素出列 */ + pop := queue.Front() + queue.Remove(pop) + fmt.Print("出列元素 pop = ", pop.Value, ",出列後 queue = ") + PrintList(queue) + + /* 獲取佇列的長度 */ + size := queue.Len() + fmt.Println("佇列長度 size =", size) + + /* 判斷佇列是否為空 */ + isEmpty := queue.Len() == 0 + fmt.Println("佇列是否為空 =", isEmpty) +} + +func TestArrayQueue(t *testing.T) { + // 初始化佇列,使用佇列的通用介面 + capacity := 10 + queue := newArrayQueue(capacity) + + // 元素入列 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + fmt.Print("佇列 queue = ") + PrintSlice(queue.toSlice()) + + // 訪問佇列首元素 + peek := queue.peek() + fmt.Println("佇列首元素 peek =", peek) + + // 元素出列 + pop := queue.pop() + fmt.Print("出列元素 pop = ", pop, ", 出列後 queue = ") + PrintSlice(queue.toSlice()) + + // 獲取隊的長度 + size := queue.size() + fmt.Println("隊的長度 size =", size) + + // 判斷是否為空 + isEmpty := queue.isEmpty() + fmt.Println("隊是否為空 =", isEmpty) + + /* 測試環形陣列 */ + for i := 0; i < 10; i++ { + queue.push(i) + queue.pop() + fmt.Print("第", i, "輪入列 + 出列後 queue =") + PrintSlice(queue.toSlice()) + } +} + +func TestLinkedListQueue(t *testing.T) { + // 初始化隊 + queue := newLinkedListQueue() + + // 元素入列 + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + fmt.Print("佇列 queue = ") + PrintList(queue.toList()) + + // 訪問佇列首元素 + peek := queue.peek() + fmt.Println("佇列首元素 peek =", peek) + + // 元素出列 + pop := queue.pop() + fmt.Print("出列元素 pop = ", pop, ", 出列後 queue = ") + PrintList(queue.toList()) + + // 獲取隊的長度 + size := queue.size() + fmt.Println("隊的長度 size =", size) + + // 判斷是否為空 + isEmpty := queue.isEmpty() + fmt.Println("隊是否為空 =", isEmpty) +} + +// BenchmarkArrayQueue 8 ns/op in Mac M1 Pro +func BenchmarkArrayQueue(b *testing.B) { + capacity := 1000 + queue := newArrayQueue(capacity) + // use b.N for looping + for i := 0; i < b.N; i++ { + queue.push(777) + } + for i := 0; i < b.N; i++ { + queue.pop() + } +} + +// BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro +func BenchmarkLinkedQueue(b *testing.B) { + queue := newLinkedListQueue() + // use b.N for looping + for i := 0; i < b.N; i++ { + queue.push(777) + } + for i := 0; i < b.N; i++ { + queue.pop() + } +} diff --git a/zh-hant/codes/go/chapter_stack_and_queue/stack_test.go b/zh-hant/codes/go/chapter_stack_and_queue/stack_test.go new file mode 100644 index 000000000..3a5a9e0b0 --- /dev/null +++ b/zh-hant/codes/go/chapter_stack_and_queue/stack_test.go @@ -0,0 +1,130 @@ +// File: stack_test.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestStack(t *testing.T) { + /* 初始化堆疊 */ + // 在 Go 中,推薦將 Slice 當作堆疊來使用 + var stack []int + + /* 元素入堆疊 */ + stack = append(stack, 1) + stack = append(stack, 3) + stack = append(stack, 2) + stack = append(stack, 5) + stack = append(stack, 4) + fmt.Print("堆疊 stack = ") + PrintSlice(stack) + + /* 訪問堆疊頂元素 */ + peek := stack[len(stack)-1] + fmt.Println("堆疊頂元素 peek =", peek) + + /* 元素出堆疊 */ + pop := stack[len(stack)-1] + stack = stack[:len(stack)-1] + fmt.Print("出堆疊元素 pop = ", pop, ",出堆疊後 stack = ") + PrintSlice(stack) + + /* 獲取堆疊的長度 */ + size := len(stack) + fmt.Println("堆疊的長度 size =", size) + + /* 判斷是否為空 */ + isEmpty := len(stack) == 0 + fmt.Println("堆疊是否為空 =", isEmpty) +} + +func TestArrayStack(t *testing.T) { + // 初始化堆疊, 使用介面承接 + stack := newArrayStack() + + // 元素入堆疊 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + fmt.Print("堆疊 stack = ") + PrintSlice(stack.toSlice()) + + // 訪問堆疊頂元素 + peek := stack.peek() + fmt.Println("堆疊頂元素 peek =", peek) + + // 元素出堆疊 + pop := stack.pop() + fmt.Print("出堆疊元素 pop = ", pop, ", 出堆疊後 stack = ") + PrintSlice(stack.toSlice()) + + // 獲取堆疊的長度 + size := stack.size() + fmt.Println("堆疊的長度 size =", size) + + // 判斷是否為空 + isEmpty := stack.isEmpty() + fmt.Println("堆疊是否為空 =", isEmpty) +} + +func TestLinkedListStack(t *testing.T) { + // 初始化堆疊 + stack := newLinkedListStack() + // 元素入堆疊 + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + fmt.Print("堆疊 stack = ") + PrintList(stack.toList()) + + // 訪問堆疊頂元素 + peek := stack.peek() + fmt.Println("堆疊頂元素 peek =", peek) + + // 元素出堆疊 + pop := stack.pop() + fmt.Print("出堆疊元素 pop = ", pop, ", 出堆疊後 stack = ") + PrintList(stack.toList()) + + // 獲取堆疊的長度 + size := stack.size() + fmt.Println("堆疊的長度 size =", size) + + // 判斷是否為空 + isEmpty := stack.isEmpty() + fmt.Println("堆疊是否為空 =", isEmpty) +} + +// BenchmarkArrayStack 8 ns/op in Mac M1 Pro +func BenchmarkArrayStack(b *testing.B) { + stack := newArrayStack() + // use b.N for looping + for i := 0; i < b.N; i++ { + stack.push(777) + } + for i := 0; i < b.N; i++ { + stack.pop() + } +} + +// BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro +func BenchmarkLinkedListStack(b *testing.B) { + stack := newLinkedListStack() + // use b.N for looping + for i := 0; i < b.N; i++ { + stack.push(777) + } + for i := 0; i < b.N; i++ { + stack.pop() + } +} diff --git a/zh-hant/codes/go/chapter_tree/array_binary_tree.go b/zh-hant/codes/go/chapter_tree/array_binary_tree.go new file mode 100644 index 000000000..6f5802c87 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/array_binary_tree.go @@ -0,0 +1,101 @@ +// File: array_binary_tree.go +// Created Time: 2023-07-24 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +/* 陣列表示下的二元樹類別 */ +type arrayBinaryTree struct { + tree []any +} + +/* 建構子 */ +func newArrayBinaryTree(arr []any) *arrayBinaryTree { + return &arrayBinaryTree{ + tree: arr, + } +} + +/* 串列容量 */ +func (abt *arrayBinaryTree) size() int { + return len(abt.tree) +} + +/* 獲取索引為 i 節點的值 */ +func (abt *arrayBinaryTree) val(i int) any { + // 若索引越界,則返回 null ,代表空位 + if i < 0 || i >= abt.size() { + return nil + } + return abt.tree[i] +} + +/* 獲取索引為 i 節點的左子節點的索引 */ +func (abt *arrayBinaryTree) left(i int) int { + return 2*i + 1 +} + +/* 獲取索引為 i 節點的右子節點的索引 */ +func (abt *arrayBinaryTree) right(i int) int { + return 2*i + 2 +} + +/* 獲取索引為 i 節點的父節點的索引 */ +func (abt *arrayBinaryTree) parent(i int) int { + return (i - 1) / 2 +} + +/* 層序走訪 */ +func (abt *arrayBinaryTree) levelOrder() []any { + var res []any + // 直接走訪陣列 + for i := 0; i < abt.size(); i++ { + if abt.val(i) != nil { + res = append(res, abt.val(i)) + } + } + return res +} + +/* 深度優先走訪 */ +func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { + // 若為空位,則返回 + if abt.val(i) == nil { + return + } + // 前序走訪 + if order == "pre" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.left(i), order, res) + // 中序走訪 + if order == "in" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.right(i), order, res) + // 後序走訪 + if order == "post" { + *res = append(*res, abt.val(i)) + } +} + +/* 前序走訪 */ +func (abt *arrayBinaryTree) preOrder() []any { + var res []any + abt.dfs(0, "pre", &res) + return res +} + +/* 中序走訪 */ +func (abt *arrayBinaryTree) inOrder() []any { + var res []any + abt.dfs(0, "in", &res) + return res +} + +/* 後序走訪 */ +func (abt *arrayBinaryTree) postOrder() []any { + var res []any + abt.dfs(0, "post", &res) + return res +} diff --git a/zh-hant/codes/go/chapter_tree/array_binary_tree_test.go b/zh-hant/codes/go/chapter_tree/array_binary_tree_test.go new file mode 100644 index 000000000..ea64c553b --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/array_binary_tree_test.go @@ -0,0 +1,47 @@ +// File: array_binary_tree_test.go +// Created Time: 2023-07-24 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestArrayBinaryTree(t *testing.T) { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + root := SliceToTree(arr) + fmt.Println("\n初始化二元樹") + fmt.Println("二元樹的陣列表示:") + fmt.Println(arr) + fmt.Println("二元樹的鏈結串列表示:") + PrintTree(root) + + // 陣列表示下的二元樹類別 + abt := newArrayBinaryTree(arr) + + // 訪問節點 + i := 1 + l := abt.left(i) + r := abt.right(i) + p := abt.parent(i) + fmt.Println("\n當前節點的索引為", i, ",值為", abt.val(i)) + fmt.Println("其左子節點的索引為", l, ",值為", abt.val(l)) + fmt.Println("其右子節點的索引為", r, ",值為", abt.val(r)) + fmt.Println("其父節點的索引為", p, ",值為", abt.val(p)) + + // 走訪樹 + res := abt.levelOrder() + fmt.Println("\n層序走訪為:", res) + res = abt.preOrder() + fmt.Println("前序走訪為:", res) + res = abt.inOrder() + fmt.Println("中序走訪為:", res) + res = abt.postOrder() + fmt.Println("後序走訪為:", res) +} diff --git a/zh-hant/codes/go/chapter_tree/avl_tree.go b/zh-hant/codes/go/chapter_tree/avl_tree.go new file mode 100644 index 000000000..0ab038ac5 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/avl_tree.go @@ -0,0 +1,200 @@ +// File: avl_tree.go +// Created Time: 2023-01-08 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import . "github.com/krahets/hello-algo/pkg" + +/* AVL 樹 */ +type aVLTree struct { + // 根節點 + root *TreeNode +} + +func newAVLTree() *aVLTree { + return &aVLTree{root: nil} +} + +/* 獲取節點高度 */ +func (t *aVLTree) height(node *TreeNode) int { + // 空節點高度為 -1 ,葉節點高度為 0 + if node != nil { + return node.Height + } + return -1 +} + +/* 更新節點高度 */ +func (t *aVLTree) updateHeight(node *TreeNode) { + lh := t.height(node.Left) + rh := t.height(node.Right) + // 節點高度等於最高子樹高度 + 1 + if lh > rh { + node.Height = lh + 1 + } else { + node.Height = rh + 1 + } +} + +/* 獲取平衡因子 */ +func (t *aVLTree) balanceFactor(node *TreeNode) int { + // 空節點平衡因子為 0 + if node == nil { + return 0 + } + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return t.height(node.Left) - t.height(node.Right) +} + +/* 右旋操作 */ +func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { + child := node.Left + grandChild := child.Right + // 以 child 為原點,將 node 向右旋轉 + child.Right = node + node.Left = grandChild + // 更新節點高度 + t.updateHeight(node) + t.updateHeight(child) + // 返回旋轉後子樹的根節點 + return child +} + +/* 左旋操作 */ +func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { + child := node.Right + grandChild := child.Left + // 以 child 為原點,將 node 向左旋轉 + child.Left = node + node.Right = grandChild + // 更新節點高度 + t.updateHeight(node) + t.updateHeight(child) + // 返回旋轉後子樹的根節點 + return child +} + +/* 執行旋轉操作,使該子樹重新恢復平衡 */ +func (t *aVLTree) rotate(node *TreeNode) *TreeNode { + // 獲取節點 node 的平衡因子 + // Go 推薦短變數,這裡 bf 指代 t.balanceFactor + bf := t.balanceFactor(node) + // 左偏樹 + if bf > 1 { + if t.balanceFactor(node.Left) >= 0 { + // 右旋 + return t.rightRotate(node) + } else { + // 先左旋後右旋 + node.Left = t.leftRotate(node.Left) + return t.rightRotate(node) + } + } + // 右偏樹 + if bf < -1 { + if t.balanceFactor(node.Right) <= 0 { + // 左旋 + return t.leftRotate(node) + } else { + // 先右旋後左旋 + node.Right = t.rightRotate(node.Right) + return t.leftRotate(node) + } + } + // 平衡樹,無須旋轉,直接返回 + return node +} + +/* 插入節點 */ +func (t *aVLTree) insert(val int) { + t.root = t.insertHelper(t.root, val) +} + +/* 遞迴插入節點(輔助函式) */ +func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return NewTreeNode(val) + } + /* 1. 查詢插入位置並插入節點 */ + if val < node.Val.(int) { + node.Left = t.insertHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.insertHelper(node.Right, val) + } else { + // 重複節點不插入,直接返回 + return node + } + // 更新節點高度 + t.updateHeight(node) + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = t.rotate(node) + // 返回子樹的根節點 + return node +} + +/* 刪除節點 */ +func (t *aVLTree) remove(val int) { + t.root = t.removeHelper(t.root, val) +} + +/* 遞迴刪除節點(輔助函式) */ +func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return nil + } + /* 1. 查詢節點並刪除 */ + if val < node.Val.(int) { + node.Left = t.removeHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.removeHelper(node.Right, val) + } else { + if node.Left == nil || node.Right == nil { + child := node.Left + if node.Right != nil { + child = node.Right + } + if child == nil { + // 子節點數量 = 0 ,直接刪除 node 並返回 + return nil + } else { + // 子節點數量 = 1 ,直接刪除 node + node = child + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + temp := node.Right + for temp.Left != nil { + temp = temp.Left + } + node.Right = t.removeHelper(node.Right, temp.Val.(int)) + node.Val = temp.Val + } + } + // 更新節點高度 + t.updateHeight(node) + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = t.rotate(node) + // 返回子樹的根節點 + return node +} + +/* 查詢節點 */ +func (t *aVLTree) search(val int) *TreeNode { + cur := t.root + // 迴圈查詢,越過葉節點後跳出 + for cur != nil { + if cur.Val.(int) < val { + // 目標節點在 cur 的右子樹中 + cur = cur.Right + } else if cur.Val.(int) > val { + // 目標節點在 cur 的左子樹中 + cur = cur.Left + } else { + // 找到目標節點,跳出迴圈 + break + } + } + // 返回目標節點 + return cur +} diff --git a/zh-hant/codes/go/chapter_tree/avl_tree_test.go b/zh-hant/codes/go/chapter_tree/avl_tree_test.go new file mode 100644 index 000000000..184f23507 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/avl_tree_test.go @@ -0,0 +1,54 @@ +// File: avl_tree_test.go +// Created Time: 2023-01-08 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestAVLTree(t *testing.T) { + /* 初始化空 AVL 樹 */ + tree := newAVLTree() + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(tree, 1) + testInsert(tree, 2) + testInsert(tree, 3) + testInsert(tree, 4) + testInsert(tree, 5) + testInsert(tree, 8) + testInsert(tree, 7) + testInsert(tree, 9) + testInsert(tree, 10) + testInsert(tree, 6) + + /* 插入重複節點 */ + testInsert(tree, 7) + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(tree, 8) // 刪除度為 0 的節點 + testRemove(tree, 5) // 刪除度為 1 的節點 + testRemove(tree, 4) // 刪除度為 2 的節點 + + /* 查詢節點 */ + node := tree.search(7) + fmt.Printf("\n查詢到的節點物件為 %#v ,節點值 = %d \n", node, node.Val) +} + +func testInsert(tree *aVLTree, val int) { + tree.insert(val) + fmt.Printf("\n插入節點 %d 後,AVL 樹為 \n", val) + PrintTree(tree.root) +} + +func testRemove(tree *aVLTree, val int) { + tree.remove(val) + fmt.Printf("\n刪除節點 %d 後,AVL 樹為 \n", val) + PrintTree(tree.root) +} diff --git a/zh-hant/codes/go/chapter_tree/binary_search_tree.go b/zh-hant/codes/go/chapter_tree/binary_search_tree.go new file mode 100644 index 000000000..f6f6e899a --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_search_tree.go @@ -0,0 +1,142 @@ +// File: binary_search_tree.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +type binarySearchTree struct { + root *TreeNode +} + +func newBinarySearchTree() *binarySearchTree { + bst := &binarySearchTree{} + // 初始化空樹 + bst.root = nil + return bst +} + +/* 獲取根節點 */ +func (bst *binarySearchTree) getRoot() *TreeNode { + return bst.root +} + +/* 查詢節點 */ +func (bst *binarySearchTree) search(num int) *TreeNode { + node := bst.root + // 迴圈查詢,越過葉節點後跳出 + for node != nil { + if node.Val.(int) < num { + // 目標節點在 cur 的右子樹中 + node = node.Right + } else if node.Val.(int) > num { + // 目標節點在 cur 的左子樹中 + node = node.Left + } else { + // 找到目標節點,跳出迴圈 + break + } + } + // 返回目標節點 + return node +} + +/* 插入節點 */ +func (bst *binarySearchTree) insert(num int) { + cur := bst.root + // 若樹為空,則初始化根節點 + if cur == nil { + bst.root = NewTreeNode(num) + return + } + // 待插入節點之前的節點位置 + var pre *TreeNode = nil + // 迴圈查詢,越過葉節點後跳出 + for cur != nil { + if cur.Val == num { + return + } + pre = cur + if cur.Val.(int) < num { + cur = cur.Right + } else { + cur = cur.Left + } + } + // 插入節點 + node := NewTreeNode(num) + if pre.Val.(int) < num { + pre.Right = node + } else { + pre.Left = node + } +} + +/* 刪除節點 */ +func (bst *binarySearchTree) remove(num int) { + cur := bst.root + // 若樹為空,直接提前返回 + if cur == nil { + return + } + // 待刪除節點之前的節點位置 + var pre *TreeNode = nil + // 迴圈查詢,越過葉節點後跳出 + for cur != nil { + if cur.Val == num { + break + } + pre = cur + if cur.Val.(int) < num { + // 待刪除節點在右子樹中 + cur = cur.Right + } else { + // 待刪除節點在左子樹中 + cur = cur.Left + } + } + // 若無待刪除節點,則直接返回 + if cur == nil { + return + } + // 子節點數為 0 或 1 + if cur.Left == nil || cur.Right == nil { + var child *TreeNode = nil + // 取出待刪除節點的子節點 + if cur.Left != nil { + child = cur.Left + } else { + child = cur.Right + } + // 刪除節點 cur + if cur != bst.root { + if pre.Left == cur { + pre.Left = child + } else { + pre.Right = child + } + } else { + // 若刪除節點為根節點,則重新指定根節點 + bst.root = child + } + // 子節點數為 2 + } else { + // 獲取中序走訪中待刪除節點 cur 的下一個節點 + tmp := cur.Right + for tmp.Left != nil { + tmp = tmp.Left + } + // 遞迴刪除節點 tmp + bst.remove(tmp.Val.(int)) + // 用 tmp 覆蓋 cur + cur.Val = tmp.Val + } +} + +/* 列印二元搜尋樹 */ +func (bst *binarySearchTree) print() { + PrintTree(bst.root) +} diff --git a/zh-hant/codes/go/chapter_tree/binary_search_tree_test.go b/zh-hant/codes/go/chapter_tree/binary_search_tree_test.go new file mode 100644 index 000000000..2b3f45f31 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_search_tree_test.go @@ -0,0 +1,45 @@ +// File: binary_search_tree_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" +) + +func TestBinarySearchTree(t *testing.T) { + bst := newBinarySearchTree() + nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15} + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + for _, num := range nums { + bst.insert(num) + } + fmt.Println("\n初始化的二元樹為:") + bst.print() + + // 獲取根節點 + node := bst.getRoot() + fmt.Println("\n二元樹的根節點為:", node.Val) + + // 查詢節點 + node = bst.search(7) + fmt.Println("查詢到的節點物件為", node, ",節點值 =", node.Val) + + // 插入節點 + bst.insert(16) + fmt.Println("\n插入節點後 16 的二元樹為:") + bst.print() + + // 刪除節點 + bst.remove(1) + fmt.Println("\n刪除節點 1 後的二元樹為:") + bst.print() + bst.remove(2) + fmt.Println("\n刪除節點 2 後的二元樹為:") + bst.print() + bst.remove(4) + fmt.Println("\n刪除節點 4 後的二元樹為:") + bst.print() +} diff --git a/zh-hant/codes/go/chapter_tree/binary_tree_bfs.go b/zh-hant/codes/go/chapter_tree/binary_tree_bfs.go new file mode 100644 index 000000000..821c08d29 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_tree_bfs.go @@ -0,0 +1,35 @@ +// File: binary_tree_bfs.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "container/list" + + . "github.com/krahets/hello-algo/pkg" +) + +/* 層序走訪 */ +func levelOrder(root *TreeNode) []any { + // 初始化佇列,加入根節點 + queue := list.New() + queue.PushBack(root) + // 初始化一個切片,用於儲存走訪序列 + nums := make([]any, 0) + for queue.Len() > 0 { + // 隊列出隊 + node := queue.Remove(queue.Front()).(*TreeNode) + // 儲存節點值 + nums = append(nums, node.Val) + if node.Left != nil { + // 左子節點入列 + queue.PushBack(node.Left) + } + if node.Right != nil { + // 右子節點入列 + queue.PushBack(node.Right) + } + } + return nums +} diff --git a/zh-hant/codes/go/chapter_tree/binary_tree_bfs_test.go b/zh-hant/codes/go/chapter_tree/binary_tree_bfs_test.go new file mode 100644 index 000000000..fd33ce5ae --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_tree_bfs_test.go @@ -0,0 +1,24 @@ +// File: binary_tree_bfs_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLevelOrder(t *testing.T) { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹: ") + PrintTree(root) + + // 層序走訪 + nums := levelOrder(root) + fmt.Println("\n層序走訪的節點列印序列 =", nums) +} diff --git a/zh-hant/codes/go/chapter_tree/binary_tree_dfs.go b/zh-hant/codes/go/chapter_tree/binary_tree_dfs.go new file mode 100644 index 000000000..874bc93ec --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_tree_dfs.go @@ -0,0 +1,44 @@ +// File: binary_tree_dfs.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +var nums []any + +/* 前序走訪 */ +func preOrder(node *TreeNode) { + if node == nil { + return + } + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + nums = append(nums, node.Val) + preOrder(node.Left) + preOrder(node.Right) +} + +/* 中序走訪 */ +func inOrder(node *TreeNode) { + if node == nil { + return + } + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(node.Left) + nums = append(nums, node.Val) + inOrder(node.Right) +} + +/* 後序走訪 */ +func postOrder(node *TreeNode) { + if node == nil { + return + } + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(node.Left) + postOrder(node.Right) + nums = append(nums, node.Val) +} diff --git a/zh-hant/codes/go/chapter_tree/binary_tree_dfs_test.go b/zh-hant/codes/go/chapter_tree/binary_tree_dfs_test.go new file mode 100644 index 000000000..08c635af9 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_tree_dfs_test.go @@ -0,0 +1,35 @@ +// File: binary_tree_dfs_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPreInPostOrderTraversal(t *testing.T) { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二元樹: ") + PrintTree(root) + + // 前序走訪 + nums = nil + preOrder(root) + fmt.Println("\n前序走訪的節點列印序列 =", nums) + + // 中序走訪 + nums = nil + inOrder(root) + fmt.Println("\n中序走訪的節點列印序列 =", nums) + + // 後序走訪 + nums = nil + postOrder(root) + fmt.Println("\n後序走訪的節點列印序列 =", nums) +} diff --git a/zh-hant/codes/go/chapter_tree/binary_tree_test.go b/zh-hant/codes/go/chapter_tree/binary_tree_test.go new file mode 100644 index 000000000..f54b5db95 --- /dev/null +++ b/zh-hant/codes/go/chapter_tree/binary_tree_test.go @@ -0,0 +1,41 @@ +// File: binary_tree_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestBinaryTree(t *testing.T) { + /* 初始化二元樹 */ + // 初始化節點 + n1 := NewTreeNode(1) + n2 := NewTreeNode(2) + n3 := NewTreeNode(3) + n4 := NewTreeNode(4) + n5 := NewTreeNode(5) + // 構建節點之間的引用(指標) + n1.Left = n2 + n1.Right = n3 + n2.Left = n4 + n2.Right = n5 + fmt.Println("初始化二元樹") + PrintTree(n1) + + /* 插入與刪除節點 */ + // 插入節點 + p := NewTreeNode(0) + n1.Left = p + p.Left = n2 + fmt.Println("插入節點 P 後") + PrintTree(n1) + // 刪除節點 + n1.Left = n2 + fmt.Println("刪除節點 P 後") + PrintTree(n1) +} diff --git a/zh-hant/codes/go/go.mod b/zh-hant/codes/go/go.mod new file mode 100644 index 000000000..34f5dac20 --- /dev/null +++ b/zh-hant/codes/go/go.mod @@ -0,0 +1,3 @@ +module github.com/krahets/hello-algo + +go 1.19 diff --git a/zh-hant/codes/go/pkg/list_node.go b/zh-hant/codes/go/pkg/list_node.go new file mode 100644 index 000000000..c88bbf493 --- /dev/null +++ b/zh-hant/codes/go/pkg/list_node.go @@ -0,0 +1,31 @@ +// File: list_node.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +// ListNode 鏈結串列節點 +type ListNode struct { + Next *ListNode + Val int +} + +// NewListNode 鏈結串列節點建構子 +func NewListNode(v int) *ListNode { + return &ListNode{ + Next: nil, + Val: v, + } +} + +// ArrayToLinkedList 將陣列反序列化為鏈結串列 +func ArrayToLinkedList(arr []int) *ListNode { + // dummy header of linked list + dummy := NewListNode(0) + node := dummy + for _, val := range arr { + node.Next = NewListNode(val) + node = node.Next + } + return dummy.Next +} diff --git a/zh-hant/codes/go/pkg/list_node_test.go b/zh-hant/codes/go/pkg/list_node_test.go new file mode 100644 index 000000000..e61d8d5bf --- /dev/null +++ b/zh-hant/codes/go/pkg/list_node_test.go @@ -0,0 +1,16 @@ +// File: list_node_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +import ( + "testing" +) + +func TestListNode(t *testing.T) { + arr := []int{2, 3, 5, 6, 7} + head := ArrayToLinkedList(arr) + + PrintLinkedList(head) +} diff --git a/zh-hant/codes/go/pkg/print_utils.go b/zh-hant/codes/go/pkg/print_utils.go new file mode 100644 index 000000000..e443d318c --- /dev/null +++ b/zh-hant/codes/go/pkg/print_utils.go @@ -0,0 +1,118 @@ +// File: print_utils.go +// Created Time: 2022-12-03 +// Author: Reanon (793584285@qq.com), krahets (krahets@163.com), msk397 (machangxinq@gmail.com) + +package pkg + +import ( + "container/list" + "fmt" + "strconv" + "strings" +) + +// PrintSlice 列印切片 +func PrintSlice[T any](nums []T) { + fmt.Printf("%v", nums) + fmt.Println() +} + +// PrintList 列印串列 +func PrintList(list *list.List) { + if list.Len() == 0 { + fmt.Print("[]\n") + return + } + e := list.Front() + // 強轉為 string, 會影響效率 + fmt.Print("[") + for e.Next() != nil { + fmt.Print(e.Value, " ") + e = e.Next() + } + fmt.Print(e.Value, "]\n") +} + +// PrintMap 列印雜湊表 +func PrintMap[K comparable, V any](m map[K]V) { + for key, value := range m { + fmt.Println(key, "->", value) + } +} + +// PrintHeap 列印堆積 +func PrintHeap(h []any) { + fmt.Printf("堆積的陣列表示:") + fmt.Printf("%v", h) + fmt.Printf("\n堆積的樹狀表示:\n") + root := SliceToTree(h) + PrintTree(root) +} + +// PrintLinkedList 列印鏈結串列 +func PrintLinkedList(node *ListNode) { + if node == nil { + return + } + var builder strings.Builder + for node.Next != nil { + builder.WriteString(strconv.Itoa(node.Val) + " -> ") + node = node.Next + } + builder.WriteString(strconv.Itoa(node.Val)) + fmt.Println(builder.String()) +} + +// PrintTree 列印二元樹 +func PrintTree(root *TreeNode) { + printTreeHelper(root, nil, false) +} + +// printTreeHelper 列印二元樹 +// This tree printer is borrowed from TECHIE DELIGHT +// https://www.techiedelight.com/c-program-print-binary-tree/ +func printTreeHelper(root *TreeNode, prev *trunk, isRight bool) { + if root == nil { + return + } + prevStr := " " + trunk := newTrunk(prev, prevStr) + printTreeHelper(root.Right, trunk, true) + if prev == nil { + trunk.str = "———" + } else if isRight { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev.str = prevStr + } + showTrunk(trunk) + fmt.Println(root.Val) + if prev != nil { + prev.str = prevStr + } + trunk.str = " |" + printTreeHelper(root.Left, trunk, false) +} + +type trunk struct { + prev *trunk + str string +} + +func newTrunk(prev *trunk, str string) *trunk { + return &trunk{ + prev: prev, + str: str, + } +} + +func showTrunk(t *trunk) { + if t == nil { + return + } + + showTrunk(t.prev) + fmt.Print(t.str) +} diff --git a/zh-hant/codes/go/pkg/tree_node.go b/zh-hant/codes/go/pkg/tree_node.go new file mode 100644 index 000000000..1943b44c9 --- /dev/null +++ b/zh-hant/codes/go/pkg/tree_node.go @@ -0,0 +1,78 @@ +// File: tree_node.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +// TreeNode 二元樹節點 +type TreeNode struct { + Val any // 節點值 + Height int // 節點高度 + Left *TreeNode // 左子節點引用 + Right *TreeNode // 右子節點引用 +} + +// NewTreeNode 二元樹節點建構子 +func NewTreeNode(v any) *TreeNode { + return &TreeNode{ + Val: v, + Height: 0, + Left: nil, + Right: nil, + } +} + +// 序列化編碼規則請參考: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二元樹的陣列表示: +// [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] +// 二元樹的鏈結串列表示: +// +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// +// ——— 1 +// +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +// SliceToTreeDFS 將串列反序列化為二元樹:遞迴 +func SliceToTreeDFS(arr []any, i int) *TreeNode { + if i < 0 || i >= len(arr) || arr[i] == nil { + return nil + } + root := NewTreeNode(arr[i]) + root.Left = SliceToTreeDFS(arr, 2*i+1) + root.Right = SliceToTreeDFS(arr, 2*i+2) + return root +} + +// SliceToTree 將切片反序列化為二元樹 +func SliceToTree(arr []any) *TreeNode { + return SliceToTreeDFS(arr, 0) +} + +// TreeToSliceDFS 將二元樹序列化為切片:遞迴 +func TreeToSliceDFS(root *TreeNode, i int, res *[]any) { + if root == nil { + return + } + for i >= len(*res) { + *res = append(*res, nil) + } + (*res)[i] = root.Val + TreeToSliceDFS(root.Left, 2*i+1, res) + TreeToSliceDFS(root.Right, 2*i+2, res) +} + +// TreeToSlice 將二元樹序列化為切片 +func TreeToSlice(root *TreeNode) []any { + var res []any + TreeToSliceDFS(root, 0, &res) + return res +} diff --git a/zh-hant/codes/go/pkg/tree_node_test.go b/zh-hant/codes/go/pkg/tree_node_test.go new file mode 100644 index 000000000..043aab099 --- /dev/null +++ b/zh-hant/codes/go/pkg/tree_node_test.go @@ -0,0 +1,21 @@ +// File: tree_node_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +import ( + "fmt" + "testing" +) + +func TestTreeNode(t *testing.T) { + arr := []any{1, 2, 3, nil, 5, 6, nil} + node := SliceToTree(arr) + + // print tree + PrintTree(node) + + // tree to arr + fmt.Println(TreeToSlice(node)) +} diff --git a/zh-hant/codes/go/pkg/vertex.go b/zh-hant/codes/go/pkg/vertex.go new file mode 100644 index 000000000..303118706 --- /dev/null +++ b/zh-hant/codes/go/pkg/vertex.go @@ -0,0 +1,55 @@ +// File: vertex.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package pkg + +// Vertex 頂點類別 +type Vertex struct { + Val int +} + +// NewVertex 頂點建構子 +func NewVertex(val int) Vertex { + return Vertex{ + Val: val, + } +} + +// ValsToVets 將值串列反序列化為頂點串列 +func ValsToVets(vals []int) []Vertex { + vets := make([]Vertex, len(vals)) + for i := 0; i < len(vals); i++ { + vets[i] = NewVertex(vals[i]) + } + return vets +} + +// VetsToVals 將頂點串列序列化為值串列 +func VetsToVals(vets []Vertex) []int { + vals := make([]int, len(vets)) + for i := range vets { + vals[i] = vets[i].Val + } + return vals +} + +// DeleteSliceElms 刪除切片指定元素 +func DeleteSliceElms[T any](a []T, elms ...T) []T { + if len(a) == 0 || len(elms) == 0 { + return a + } + // 先將元素轉為 set + m := make(map[any]struct{}) + for _, v := range elms { + m[v] = struct{}{} + } + // 過濾掉指定元素 + res := make([]T, 0, len(a)) + for _, v := range a { + if _, ok := m[v]; !ok { + res = append(res, v) + } + } + return res +} diff --git a/zh-hant/codes/java/.gitignore b/zh-hant/codes/java/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/zh-hant/codes/java/.gitignore @@ -0,0 +1 @@ +build diff --git a/zh-hant/codes/java/chapter_array_and_linkedlist/array.java b/zh-hant/codes/java/chapter_array_and_linkedlist/array.java new file mode 100644 index 000000000..bdc6321f0 --- /dev/null +++ b/zh-hant/codes/java/chapter_array_and_linkedlist/array.java @@ -0,0 +1,105 @@ +/** + * File: array.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class array { + /* 隨機訪問元素 */ + static int randomAccess(int[] nums) { + // 在區間 [0, nums.length) 中隨機抽取一個數字 + int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length); + // 獲取並返回隨機元素 + int randomNum = nums[randomIndex]; + return randomNum; + } + + /* 擴展陣列長度 */ + static int[] extend(int[] nums, int enlarge) { + // 初始化一個擴展長度後的陣列 + int[] res = new int[nums.length + enlarge]; + // 將原陣列中的所有元素複製到新陣列 + for (int i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + return res; + } + + /* 在陣列的索引 index 處插入元素 num */ + static void insert(int[] nums, int num, int index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (int i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; + } + + /* 刪除索引 index 處的元素 */ + static void remove(int[] nums, int index) { + // 把索引 index 之後的所有元素向前移動一位 + for (int i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + + /* 走訪陣列 */ + static void traverse(int[] nums) { + int count = 0; + // 透過索引走訪陣列 + for (int i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 直接走訪陣列元素 + for (int num : nums) { + count += num; + } + } + + /* 在陣列中查詢指定元素 */ + static int find(int[] nums, int target) { + for (int i = 0; i < nums.length; i++) { + if (nums[i] == target) + return i; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + /* 初始化陣列 */ + int[] arr = new int[5]; + System.out.println("陣列 arr = " + Arrays.toString(arr)); + int[] nums = { 1, 3, 2, 5, 4 }; + System.out.println("陣列 nums = " + Arrays.toString(nums)); + + /* 隨機訪問 */ + int randomNum = randomAccess(nums); + System.out.println("在 nums 中獲取隨機元素 " + randomNum); + + /* 長度擴展 */ + nums = extend(nums, 3); + System.out.println("將陣列長度擴展至 8 ,得到 nums = " + Arrays.toString(nums)); + + /* 插入元素 */ + insert(nums, 6, 3); + System.out.println("在索引 3 處插入數字 6 ,得到 nums = " + Arrays.toString(nums)); + + /* 刪除元素 */ + remove(nums, 2); + System.out.println("刪除索引 2 處的元素,得到 nums = " + Arrays.toString(nums)); + + /* 走訪陣列 */ + traverse(nums); + + /* 查詢元素 */ + int index = find(nums, 3); + System.out.println("在 nums 中查詢元素 3 ,得到索引 = " + index); + } +} diff --git a/zh-hant/codes/java/chapter_array_and_linkedlist/linked_list.java b/zh-hant/codes/java/chapter_array_and_linkedlist/linked_list.java new file mode 100644 index 000000000..b3ab5a941 --- /dev/null +++ b/zh-hant/codes/java/chapter_array_and_linkedlist/linked_list.java @@ -0,0 +1,86 @@ +/** + * File: linked_list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import utils.*; + +public class linked_list { + /* 在鏈結串列的節點 n0 之後插入節點 P */ + static void insert(ListNode n0, ListNode P) { + ListNode n1 = n0.next; + P.next = n1; + n0.next = P; + } + + /* 刪除鏈結串列的節點 n0 之後的首個節點 */ + static void remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode n1 = P.next; + n0.next = n1; + } + + /* 訪問鏈結串列中索引為 index 的節點 */ + static ListNode access(ListNode head, int index) { + for (int i = 0; i < index; i++) { + if (head == null) + return null; + head = head.next; + } + return head; + } + + /* 在鏈結串列中查詢值為 target 的首個節點 */ + static int find(ListNode head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) + return index; + head = head.next; + index++; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + /* 初始化鏈結串列 */ + // 初始化各個節點 + ListNode n0 = new ListNode(1); + ListNode n1 = new ListNode(3); + ListNode n2 = new ListNode(2); + ListNode n3 = new ListNode(5); + ListNode n4 = new ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + System.out.println("初始化的鏈結串列為"); + PrintUtil.printLinkedList(n0); + + /* 插入節點 */ + insert(n0, new ListNode(0)); + System.out.println("插入節點後的鏈結串列為"); + PrintUtil.printLinkedList(n0); + + /* 刪除節點 */ + remove(n0); + System.out.println("刪除節點後的鏈結串列為"); + PrintUtil.printLinkedList(n0); + + /* 訪問節點 */ + ListNode node = access(n0, 3); + System.out.println("鏈結串列中索引 3 處的節點的值 = " + node.val); + + /* 查詢節點 */ + int index = find(n0, 2); + System.out.println("鏈結串列中值為 2 的節點的索引 = " + index); + } +} diff --git a/zh-hant/codes/java/chapter_array_and_linkedlist/list.java b/zh-hant/codes/java/chapter_array_and_linkedlist/list.java new file mode 100644 index 000000000..13862233b --- /dev/null +++ b/zh-hant/codes/java/chapter_array_and_linkedlist/list.java @@ -0,0 +1,66 @@ +/** + * File: list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; + +public class list { + public static void main(String[] args) { + /* 初始化串列 */ + // 注意陣列的元素型別是 int[] 的包裝類別 Integer[] + Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; + List nums = new ArrayList<>(Arrays.asList(numbers)); + System.out.println("串列 nums = " + nums); + + /* 訪問元素 */ + int num = nums.get(1); + System.out.println("訪問索引 1 處的元素,得到 num = " + num); + + /* 更新元素 */ + nums.set(1, 0); + System.out.println("將索引 1 處的元素更新為 0 ,得到 nums = " + nums); + + /* 清空串列 */ + nums.clear(); + System.out.println("清空串列後 nums = " + nums); + + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("新增元素後 nums = " + nums); + + /* 在中間插入元素 */ + nums.add(3, 6); + System.out.println("在索引 3 處插入數字 6 ,得到 nums = " + nums); + + /* 刪除元素 */ + nums.remove(3); + System.out.println("刪除索引 3 處的元素,得到 nums = " + nums); + + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums.get(i); + } + /* 直接走訪串列元素 */ + for (int x : nums) { + count += x; + } + + /* 拼接兩個串列 */ + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); + System.out.println("將串列 nums1 拼接到 nums 之後,得到 nums = " + nums); + + /* 排序串列 */ + Collections.sort(nums); + System.out.println("排序串列後 nums = " + nums); + } +} diff --git a/zh-hant/codes/java/chapter_array_and_linkedlist/my_list.java b/zh-hant/codes/java/chapter_array_and_linkedlist/my_list.java new file mode 100644 index 000000000..64265e31c --- /dev/null +++ b/zh-hant/codes/java/chapter_array_and_linkedlist/my_list.java @@ -0,0 +1,147 @@ +/** + * File: my_list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; + +/* 串列類別 */ +class MyList { + private int[] arr; // 陣列(儲存串列元素) + private int capacity = 10; // 串列容量 + private int size = 0; // 串列長度(當前元素數量) + private int extendRatio = 2; // 每次串列擴容的倍數 + + /* 建構子 */ + public MyList() { + arr = new int[capacity]; + } + + /* 獲取串列長度(當前元素數量) */ + public int size() { + return size; + } + + /* 獲取串列容量 */ + public int capacity() { + return capacity; + } + + /* 訪問元素 */ + public int get(int index) { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("索引越界"); + return arr[index]; + } + + /* 更新元素 */ + public void set(int index, int num) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("索引越界"); + arr[index] = num; + } + + /* 在尾部新增元素 */ + public void add(int num) { + // 元素數量超出容量時,觸發擴容機制 + if (size == capacity()) + extendCapacity(); + arr[size] = num; + // 更新元素數量 + size++; + } + + /* 在中間插入元素 */ + public void insert(int index, int num) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("索引越界"); + // 元素數量超出容量時,觸發擴容機制 + if (size == capacity()) + extendCapacity(); + // 將索引 index 以及之後的元素都向後移動一位 + for (int j = size - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // 更新元素數量 + size++; + } + + /* 刪除元素 */ + public int remove(int index) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("索引越界"); + int num = arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for (int j = index; j < size - 1; j++) { + arr[j] = arr[j + 1]; + } + // 更新元素數量 + size--; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + public void extendCapacity() { + // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 + arr = Arrays.copyOf(arr, capacity() * extendRatio); + // 更新串列容量 + capacity = arr.length; + } + + /* 將串列轉換為陣列 */ + public int[] toArray() { + int size = size(); + // 僅轉換有效長度範圍內的串列元素 + int[] arr = new int[size]; + for (int i = 0; i < size; i++) { + arr[i] = get(i); + } + return arr; + } +} + +public class my_list { + /* Driver Code */ + public static void main(String[] args) { + /* 初始化串列 */ + MyList nums = new MyList(); + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("串列 nums = " + Arrays.toString(nums.toArray()) + + " ,容量 = " + nums.capacity() + " ,長度 = " + nums.size()); + + /* 在中間插入元素 */ + nums.insert(3, 6); + System.out.println("在索引 3 處插入數字 6 ,得到 nums = " + Arrays.toString(nums.toArray())); + + /* 刪除元素 */ + nums.remove(3); + System.out.println("刪除索引 3 處的元素,得到 nums = " + Arrays.toString(nums.toArray())); + + /* 訪問元素 */ + int num = nums.get(1); + System.out.println("訪問索引 1 處的元素,得到 num = " + num); + + /* 更新元素 */ + nums.set(1, 0); + System.out.println("將索引 1 處的元素更新為 0 ,得到 nums = " + Arrays.toString(nums.toArray())); + + /* 測試擴容機制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i); + } + System.out.println("擴容後的串列 nums = " + Arrays.toString(nums.toArray()) + + " ,容量 = " + nums.capacity() + " ,長度 = " + nums.size()); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/n_queens.java b/zh-hant/codes/java/chapter_backtracking/n_queens.java new file mode 100644 index 000000000..e726a5ad8 --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/n_queens.java @@ -0,0 +1,77 @@ +/** + * File: n_queens.java + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class n_queens { + /* 回溯演算法:n 皇后 */ + public static void backtrack(int row, int n, List> state, List>> res, + boolean[] cols, boolean[] diags1, boolean[] diags2) { + // 當放置完所有行時,記錄解 + if (row == n) { + List> copyState = new ArrayList<>(); + for (List sRow : state) { + copyState.add(new ArrayList<>(sRow)); + } + res.add(copyState); + return; + } + // 走訪所有列 + for (int col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state.get(row).set(col, "Q"); + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state.get(row).set(col, "#"); + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } + } + + /* 求解 n 皇后 */ + public static List>> nQueens(int n) { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + List> state = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List row = new ArrayList<>(); + for (int j = 0; j < n; j++) { + row.add("#"); + } + state.add(row); + } + boolean[] cols = new boolean[n]; // 記錄列是否有皇后 + boolean[] diags1 = new boolean[2 * n - 1]; // 記錄主對角線上是否有皇后 + boolean[] diags2 = new boolean[2 * n - 1]; // 記錄次對角線上是否有皇后 + List>> res = new ArrayList<>(); + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + + public static void main(String[] args) { + int n = 4; + List>> res = nQueens(n); + + System.out.println("輸入棋盤長寬為 " + n); + System.out.println("皇后放置方案共有 " + res.size() + " 種"); + for (List> state : res) { + System.out.println("--------------------"); + for (List row : state) { + System.out.println(row); + } + } + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/permutations_i.java b/zh-hant/codes/java/chapter_backtracking/permutations_i.java new file mode 100644 index 000000000..e7beb496a --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/permutations_i.java @@ -0,0 +1,51 @@ +/** + * File: permutations_i.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_i { + /* 回溯演算法:全排列 I */ + public static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.add(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* 全排列 I */ + static List> permutationsI(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 3 }; + + List> res = permutationsI(nums); + + System.out.println("輸入陣列 nums = " + Arrays.toString(nums)); + System.out.println("所有排列 res = " + res); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/permutations_ii.java b/zh-hant/codes/java/chapter_backtracking/permutations_ii.java new file mode 100644 index 000000000..58c1227e6 --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/permutations_ii.java @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_ii { + /* 回溯演算法:全排列 II */ + static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // 走訪所有選擇 + Set duplicated = new HashSet(); + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.contains(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.add(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.add(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* 全排列 II */ + static List> permutationsII(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 2 }; + + List> res = permutationsII(nums); + + System.out.println("輸入陣列 nums = " + Arrays.toString(nums)); + System.out.println("所有排列 res = " + res); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/preorder_traversal_i_compact.java b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_i_compact.java new file mode 100644 index 000000000..e1e47695f --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_i_compact.java @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_i_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_i_compact { + static List res; + + /* 前序走訪:例題一 */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 記錄解 + res.add(root); + } + preOrder(root.left); + preOrder(root.right); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹"); + PrintUtil.printTree(root); + + // 前序走訪 + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\n輸出所有值為 7 的節點"); + List vals = new ArrayList<>(); + for (TreeNode node : res) { + vals.add(node.val); + } + System.out.println(vals); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java new file mode 100644 index 000000000..472aa4799 --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_ii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_ii_compact { + static List path; + static List> res; + + /* 前序走訪:例題二 */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + // 嘗試 + path.add(root); + if (root.val == 7) { + // 記錄解 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹"); + PrintUtil.printTree(root); + + // 前序走訪 + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\n輸出所有根節點到節點 7 的路徑"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java new file mode 100644 index 000000000..36a4e1a1f --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java @@ -0,0 +1,53 @@ +/** + * File: preorder_traversal_iii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_compact { + static List path; + static List> res; + + /* 前序走訪:例題三 */ + static void preOrder(TreeNode root) { + // 剪枝 + if (root == null || root.val == 3) { + return; + } + // 嘗試 + path.add(root); + if (root.val == 7) { + // 記錄解 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹"); + PrintUtil.printTree(root); + + // 前序走訪 + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_template.java b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_template.java new file mode 100644 index 000000000..4a5088acf --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/preorder_traversal_iii_template.java @@ -0,0 +1,77 @@ +/** + * File: preorder_traversal_iii_template.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_template { + /* 判斷當前狀態是否為解 */ + static boolean isSolution(List state) { + return !state.isEmpty() && state.get(state.size() - 1).val == 7; + } + + /* 記錄解 */ + static void recordSolution(List state, List> res) { + res.add(new ArrayList<>(state)); + } + + /* 判斷在當前狀態下,該選擇是否合法 */ + static boolean isValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* 更新狀態 */ + static void makeChoice(List state, TreeNode choice) { + state.add(choice); + } + + /* 恢復狀態 */ + static void undoChoice(List state, TreeNode choice) { + state.remove(state.size() - 1); + } + + /* 回溯演算法:例題三 */ + static void backtrack(List state, List choices, List> res) { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + } + // 走訪所有選擇 + for (TreeNode choice : choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + // 進行下一輪選擇 + backtrack(state, Arrays.asList(choice.left, choice.right), res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹"); + PrintUtil.printTree(root); + + // 回溯演算法 + List> res = new ArrayList<>(); + backtrack(new ArrayList<>(), Arrays.asList(root), res); + + System.out.println("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + System.out.println(vals); + } + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/subset_sum_i.java b/zh-hant/codes/java/chapter_backtracking/subset_sum_i.java new file mode 100644 index 000000000..03aca353d --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/subset_sum_i.java @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i { + /* 回溯演算法:子集和 I */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.remove(state.size() - 1); + } + } + + /* 求解子集和 I */ + static List> subsetSumI(int[] nums, int target) { + List state = new ArrayList<>(); // 狀態(子集) + Arrays.sort(nums); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = new ArrayList<>(); // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumI(nums, target); + + System.out.println("輸入陣列 nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("所有和等於 " + target + " 的子集 res = " + res); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/subset_sum_i_naive.java b/zh-hant/codes/java/chapter_backtracking/subset_sum_i_naive.java new file mode 100644 index 000000000..be2147b1e --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/subset_sum_i_naive.java @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i_naive.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i_naive { + /* 回溯演算法:子集和 I */ + static void backtrack(List state, int target, int total, int[] choices, List> res) { + // 子集和等於 target 時,記錄解 + if (total == target) { + res.add(new ArrayList<>(state)); + return; + } + // 走訪所有選擇 + for (int i = 0; i < choices.length; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.remove(state.size() - 1); + } + } + + /* 求解子集和 I(包含重複子集) */ + static List> subsetSumINaive(int[] nums, int target) { + List state = new ArrayList<>(); // 狀態(子集) + int total = 0; // 子集和 + List> res = new ArrayList<>(); // 結果串列(子集串列) + backtrack(state, target, total, nums, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + System.out.println("輸入陣列 nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("所有和等於 " + target + " 的子集 res = " + res); + System.out.println("請注意,該方法輸出的結果包含重複集合"); + } +} diff --git a/zh-hant/codes/java/chapter_backtracking/subset_sum_ii.java b/zh-hant/codes/java/chapter_backtracking/subset_sum_ii.java new file mode 100644 index 000000000..38dbf4c2c --- /dev/null +++ b/zh-hant/codes/java/chapter_backtracking/subset_sum_ii.java @@ -0,0 +1,60 @@ +/** + * File: subset_sum_ii.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_ii { + /* 回溯演算法:子集和 II */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (int i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.add(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.remove(state.size() - 1); + } + } + + /* 求解子集和 II */ + static List> subsetSumII(int[] nums, int target) { + List state = new ArrayList<>(); // 狀態(子集) + Arrays.sort(nums); // 對 nums 進行排序 + int start = 0; // 走訪起始點 + List> res = new ArrayList<>(); // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 4, 4, 5 }; + int target = 9; + + List> res = subsetSumII(nums, target); + + System.out.println("輸入陣列 nums = " + Arrays.toString(nums) + ", target = " + target); + System.out.println("所有和等於 " + target + " 的子集 res = " + res); + } +} diff --git a/zh-hant/codes/java/chapter_computational_complexity/iteration.java b/zh-hant/codes/java/chapter_computational_complexity/iteration.java new file mode 100644 index 000000000..2da250a07 --- /dev/null +++ b/zh-hant/codes/java/chapter_computational_complexity/iteration.java @@ -0,0 +1,76 @@ +/** + * File: iteration.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class iteration { + /* for 迴圈 */ + static int forLoop(int n) { + int res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } + + /* while 迴圈 */ + static int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; + } + + /* while 迴圈(兩次更新) */ + static int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; + } + + /* 雙層 for 迴圈 */ + static String nestedForLoop(int n) { + StringBuilder res = new StringBuilder(); + // 迴圈 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; j++) { + res.append("(" + i + ", " + j + "), "); + } + } + return res.toString(); + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + int res; + + res = forLoop(n); + System.out.println("\nfor 迴圈的求和結果 res = " + res); + + res = whileLoop(n); + System.out.println("\nwhile 迴圈的求和結果 res = " + res); + + res = whileLoopII(n); + System.out.println("\nwhile 迴圈(兩次更新)求和結果 res = " + res); + + String resStr = nestedForLoop(n); + System.out.println("\n雙層 for 迴圈的走訪結果 " + resStr); + } +} diff --git a/zh-hant/codes/java/chapter_computational_complexity/recursion.java b/zh-hant/codes/java/chapter_computational_complexity/recursion.java new file mode 100644 index 000000000..e72854e69 --- /dev/null +++ b/zh-hant/codes/java/chapter_computational_complexity/recursion.java @@ -0,0 +1,79 @@ +/** + * File: recursion.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.Stack; + +public class recursion { + /* 遞迴 */ + static int recur(int n) { + // 終止條件 + if (n == 1) + return 1; + // 遞:遞迴呼叫 + int res = recur(n - 1); + // 迴:返回結果 + return n + res; + } + + /* 使用迭代模擬遞迴 */ + static int forLoopRecur(int n) { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + Stack stack = new Stack<>(); + int res = 0; + // 遞:遞迴呼叫 + for (int i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.push(i); + } + // 迴:返回結果 + while (!stack.isEmpty()) { + // 透過“出堆疊操作”模擬“迴” + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; + } + + /* 尾遞迴 */ + static int tailRecur(int n, int res) { + // 終止條件 + if (n == 0) + return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); + } + + /* 費波那契數列:遞迴 */ + static int fib(int n) { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + int res; + + res = recur(n); + System.out.println("\n遞迴函式的求和結果 res = " + res); + + res = forLoopRecur(n); + System.out.println("\n使用迭代模擬遞迴求和結果 res = " + res); + + res = tailRecur(n, 0); + System.out.println("\n尾遞迴函式的求和結果 res = " + res); + + res = fib(n); + System.out.println("\n費波那契數列的第 " + n + " 項為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_computational_complexity/space_complexity.java b/zh-hant/codes/java/chapter_computational_complexity/space_complexity.java new file mode 100644 index 000000000..4aa7c3b65 --- /dev/null +++ b/zh-hant/codes/java/chapter_computational_complexity/space_complexity.java @@ -0,0 +1,110 @@ +/** + * File: space_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import utils.*; +import java.util.*; + +public class space_complexity { + /* 函式 */ + static int function() { + // 執行某些操作 + return 0; + } + + /* 常數階 */ + static void constant(int n) { + // 常數、變數、物件佔用 O(1) 空間 + final int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new ListNode(0); + // 迴圈中的變數佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (int i = 0; i < n; i++) { + function(); + } + } + + /* 線性階 */ + static void linear(int n) { + // 長度為 n 的陣列佔用 O(n) 空間 + int[] nums = new int[n]; + // 長度為 n 的串列佔用 O(n) 空間 + List nodes = new ArrayList<>(); + for (int i = 0; i < n; i++) { + nodes.add(new ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + Map map = new HashMap<>(); + for (int i = 0; i < n; i++) { + map.put(i, String.valueOf(i)); + } + } + + /* 線性階(遞迴實現) */ + static void linearRecur(int n) { + System.out.println("遞迴 n = " + n); + if (n == 1) + return; + linearRecur(n - 1); + } + + /* 平方階 */ + static void quadratic(int n) { + // 矩陣佔用 O(n^2) 空間 + int[][] numMatrix = new int[n][n]; + // 二維串列佔用 O(n^2) 空間 + List> numList = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List tmp = new ArrayList<>(); + for (int j = 0; j < n; j++) { + tmp.add(0); + } + numList.add(tmp); + } + } + + /* 平方階(遞迴實現) */ + static int quadraticRecur(int n) { + if (n <= 0) + return 0; + // 陣列 nums 長度為 n, n-1, ..., 2, 1 + int[] nums = new int[n]; + System.out.println("遞迴 n = " + n + " 中的 nums 長度 = " + nums.length); + return quadraticRecur(n - 1); + } + + /* 指數階(建立滿二元樹) */ + static TreeNode buildTree(int n) { + if (n == 0) + return null; + TreeNode root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; + } + + /* Driver Code */ + public static void main(String[] args) { + int n = 5; + // 常數階 + constant(n); + // 線性階 + linear(n); + linearRecur(n); + // 平方階 + quadratic(n); + quadraticRecur(n); + // 指數階 + TreeNode root = buildTree(n); + PrintUtil.printTree(root); + } +} diff --git a/zh-hant/codes/java/chapter_computational_complexity/time_complexity.java b/zh-hant/codes/java/chapter_computational_complexity/time_complexity.java new file mode 100644 index 000000000..1fedefa05 --- /dev/null +++ b/zh-hant/codes/java/chapter_computational_complexity/time_complexity.java @@ -0,0 +1,167 @@ +/** + * File: time_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class time_complexity { + /* 常數階 */ + static int constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; + } + + /* 線性階 */ + static int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } + + /* 線性階(走訪陣列) */ + static int arrayTraversal(int[] nums) { + int count = 0; + // 迴圈次數與陣列長度成正比 + for (int num : nums) { + count++; + } + return count; + } + + /* 平方階 */ + static int quadratic(int n) { + int count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; + } + + /* 平方階(泡沫排序) */ + static int bubbleSort(int[] nums) { + int count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; + } + + /* 指數階(迴圈實現) */ + static int exponential(int n) { + int count = 0, base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; + } + + /* 指數階(遞迴實現) */ + static int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; + } + + /* 對數階(迴圈實現) */ + static int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; + } + + /* 對數階(遞迴實現) */ + static int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; + } + + /* 線性對數階 */ + static int linearLogRecur(int n) { + if (n <= 1) + return 1; + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; + } + + /* 階乘階(遞迴實現) */ + static int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + // 從 1 個分裂出 n 個 + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; + } + + /* Driver Code */ + public static void main(String[] args) { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + int n = 8; + System.out.println("輸入資料大小 n = " + n); + + int count = constant(n); + System.out.println("常數階的操作數量 = " + count); + + count = linear(n); + System.out.println("線性階的操作數量 = " + count); + count = arrayTraversal(new int[n]); + System.out.println("線性階(走訪陣列)的操作數量 = " + count); + + count = quadratic(n); + System.out.println("平方階的操作數量 = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + System.out.println("平方階(泡沫排序)的操作數量 = " + count); + + count = exponential(n); + System.out.println("指數階(迴圈實現)的操作數量 = " + count); + count = expRecur(n); + System.out.println("指數階(遞迴實現)的操作數量 = " + count); + + count = logarithmic(n); + System.out.println("對數階(迴圈實現)的操作數量 = " + count); + count = logRecur(n); + System.out.println("對數階(遞迴實現)的操作數量 = " + count); + + count = linearLogRecur(n); + System.out.println("線性對數階(遞迴實現)的操作數量 = " + count); + + count = factorialRecur(n); + System.out.println("階乘階(遞迴實現)的操作數量 = " + count); + } +} diff --git a/zh-hant/codes/java/chapter_computational_complexity/worst_best_time_complexity.java b/zh-hant/codes/java/chapter_computational_complexity/worst_best_time_complexity.java new file mode 100644 index 000000000..b098c74f3 --- /dev/null +++ b/zh-hant/codes/java/chapter_computational_complexity/worst_best_time_complexity.java @@ -0,0 +1,50 @@ +/** + * File: worst_best_time_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.*; + +public class worst_best_time_complexity { + /* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ + static int[] randomNumbers(int n) { + Integer[] nums = new Integer[n]; + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 隨機打亂陣列元素 + Collections.shuffle(Arrays.asList(nums)); + // Integer[] -> int[] + int[] res = new int[n]; + for (int i = 0; i < n; i++) { + res[i] = nums[i]; + } + return res; + } + + /* 查詢陣列 nums 中數字 1 所在索引 */ + static int findOne(int[] nums) { + for (int i = 0; i < nums.length; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) + return i; + } + return -1; + } + + /* Driver Code */ + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { + int n = 100; + int[] nums = randomNumbers(n); + int index = findOne(nums); + System.out.println("\n陣列 [ 1, 2, ..., n ] 被打亂後 = " + Arrays.toString(nums)); + System.out.println("數字 1 的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/java/chapter_divide_and_conquer/binary_search_recur.java b/zh-hant/codes/java/chapter_divide_and_conquer/binary_search_recur.java new file mode 100644 index 000000000..7c3b65990 --- /dev/null +++ b/zh-hant/codes/java/chapter_divide_and_conquer/binary_search_recur.java @@ -0,0 +1,45 @@ +/** + * File: binary_search_recur.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +public class binary_search_recur { + /* 二分搜尋:問題 f(i, j) */ + static int dfs(int[] nums, int target, int i, int j) { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } + } + + /* 二分搜尋 */ + static int binarySearch(int[] nums, int target) { + int n = nums.length; + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); + } + + public static void main(String[] args) { + int target = 6; + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + + // 二分搜尋(雙閉區間) + int index = binarySearch(nums, target); + System.out.println("目標元素 6 的索引 = " + index); + } +} diff --git a/zh-hant/codes/java/chapter_divide_and_conquer/build_tree.java b/zh-hant/codes/java/chapter_divide_and_conquer/build_tree.java new file mode 100644 index 000000000..77895cfb9 --- /dev/null +++ b/zh-hant/codes/java/chapter_divide_and_conquer/build_tree.java @@ -0,0 +1,51 @@ +/** + * File: build_tree.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import utils.*; +import java.util.*; + +public class build_tree { + /* 構建二元樹:分治 */ + static TreeNode dfs(int[] preorder, Map inorderMap, int i, int l, int r) { + // 子樹區間為空時終止 + if (r - l < 0) + return null; + // 初始化根節點 + TreeNode root = new TreeNode(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + int m = inorderMap.get(preorder[i]); + // 子問題:構建左子樹 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; + } + + /* 構建二元樹 */ + static TreeNode buildTree(int[] preorder, int[] inorder) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + Map inorderMap = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { + inorderMap.put(inorder[i], i); + } + TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; + } + + public static void main(String[] args) { + int[] preorder = { 3, 9, 2, 1, 7 }; + int[] inorder = { 9, 3, 1, 2, 7 }; + System.out.println("前序走訪 = " + Arrays.toString(preorder)); + System.out.println("中序走訪 = " + Arrays.toString(inorder)); + + TreeNode root = buildTree(preorder, inorder); + System.out.println("構建的二元樹為:"); + PrintUtil.printTree(root); + } +} diff --git a/zh-hant/codes/java/chapter_divide_and_conquer/hanota.java b/zh-hant/codes/java/chapter_divide_and_conquer/hanota.java new file mode 100644 index 000000000..e9bb96df3 --- /dev/null +++ b/zh-hant/codes/java/chapter_divide_and_conquer/hanota.java @@ -0,0 +1,59 @@ +/** + * File: hanota.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import java.util.*; + +public class hanota { + /* 移動一個圓盤 */ + static void move(List src, List tar) { + // 從 src 頂部拿出一個圓盤 + Integer pan = src.remove(src.size() - 1); + // 將圓盤放入 tar 頂部 + tar.add(pan); + } + + /* 求解河內塔問題 f(i) */ + static void dfs(int i, List src, List buf, List tar) { + // 若 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); + } + + /* 求解河內塔問題 */ + static void solveHanota(List A, List B, List C) { + int n = A.size(); + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); + } + + public static void main(String[] args) { + // 串列尾部是柱子頂部 + List A = new ArrayList<>(Arrays.asList(5, 4, 3, 2, 1)); + List B = new ArrayList<>(); + List C = new ArrayList<>(); + System.out.println("初始狀態下:"); + System.out.println("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + + solveHanota(A, B, C); + + System.out.println("圓盤移動完成後:"); + System.out.println("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java new file mode 100644 index 000000000..94b6959cc --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.*; + +public class climbing_stairs_backtrack { + /* 回溯 */ + public static void backtrack(List choices, int state, int n, List res) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) + res.set(0, res.get(0) + 1); + // 走訪所有選擇 + for (Integer choice : choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) + continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } + } + + /* 爬樓梯:回溯 */ + public static int climbingStairsBacktrack(int n) { + List choices = Arrays.asList(1, 2); // 可選擇向上爬 1 階或 2 階 + int state = 0; // 從第 0 階開始爬 + List res = new ArrayList<>(); + res.add(0); // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res); + return res.get(0); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsBacktrack(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java new file mode 100644 index 000000000..7552c7a0f --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.java + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* 帶約束爬樓梯:動態規劃 */ + static int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + int[][] dp = new int[n + 1][3]; + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + 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]; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsConstraintDP(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java new file mode 100644 index 000000000..f51c8b3c2 --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java @@ -0,0 +1,31 @@ +/** + * File: climbing_stairs_dfs.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* 搜尋 */ + public static int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; + } + + /* 爬樓梯:搜尋 */ + public static int climbingStairsDFS(int n) { + return dfs(n); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFS(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java new file mode 100644 index 000000000..9966606bc --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_dfs_mem.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class climbing_stairs_dfs_mem { + /* 記憶化搜尋 */ + public static int dfs(int i, int[] mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; + } + + /* 爬樓梯:記憶化搜尋 */ + public static int climbingStairsDFSMem(int n) { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + int[] mem = new int[n + 1]; + Arrays.fill(mem, -1); + return dfs(n, mem); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFSMem(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + } +} \ No newline at end of file diff --git a/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java new file mode 100644 index 000000000..a74fc02ad --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java @@ -0,0 +1,48 @@ +/** + * File: climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* 爬樓梯:動態規劃 */ + public static int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用於儲存子問題的解 + int[] dp = new int[n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + /* 爬樓梯:空間最佳化後的動態規劃 */ + public static int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDP(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + + res = climbingStairsDPComp(n); + System.out.println(String.format("爬 %d 階樓梯共有 %d 種方案", n, res)); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/coin_change.java b/zh-hant/codes/java/chapter_dynamic_programming/coin_change.java new file mode 100644 index 000000000..bab37eb44 --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/coin_change.java @@ -0,0 +1,72 @@ +/** + * File: coin_change.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class coin_change { + /* 零錢兌換:動態規劃 */ + static int coinChangeDP(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + int[][] dp = new int[n + 1][amt + 1]; + // 狀態轉移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; + } + + /* 零錢兌換:空間最佳化後的動態規劃 */ + static int coinChangeDPComp(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + Arrays.fill(dp, MAX); + dp[0] = 0; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 4; + + // 動態規劃 + int res = coinChangeDP(coins, amt); + System.out.println("湊到目標金額所需的最少硬幣數量為 " + res); + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt); + System.out.println("湊到目標金額所需的最少硬幣數量為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/coin_change_ii.java b/zh-hant/codes/java/chapter_dynamic_programming/coin_change_ii.java new file mode 100644 index 000000000..13e675fd5 --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/coin_change_ii.java @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class coin_change_ii { + /* 零錢兌換 II:動態規劃 */ + static int coinChangeIIDP(int[] coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][amt + 1]; + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + 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]; + } + + /* 零錢兌換 II:空間最佳化後的動態規劃 */ + static int coinChangeIIDPComp(int[] coins, int amt) { + int n = coins.length; + // 初始化 dp 表 + int[] dp = new int[amt + 1]; + dp[0] = 1; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 5; + + // 動態規劃 + int res = coinChangeIIDP(coins, amt); + System.out.println("湊出目標金額的硬幣組合數量為 " + res); + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins, amt); + System.out.println("湊出目標金額的硬幣組合數量為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/edit_distance.java b/zh-hant/codes/java/chapter_dynamic_programming/edit_distance.java new file mode 100644 index 000000000..3f50239a4 --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/edit_distance.java @@ -0,0 +1,139 @@ +/** + * File: edit_distance.java + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class edit_distance { + /* 編輯距離:暴力搜尋 */ + static int editDistanceDFS(String s, String t, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 為空,則返回 t 長度 + if (i == 0) + return j; + // 若 t 為空,則返回 s 長度 + if (j == 0) + return i; + // 若兩字元相等,則直接跳過此兩字元 + if (s.charAt(i - 1) == t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int delete = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return Math.min(Math.min(insert, delete), replace) + 1; + } + + /* 編輯距離:記憶化搜尋 */ + static int editDistanceDFSMem(String s, String t, int[][] mem, int i, int j) { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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.charAt(i - 1) == t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int delete = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = Math.min(Math.min(insert, delete), replace) + 1; + return mem[i][j]; + } + + /* 編輯距離:動態規劃 */ + static int editDistanceDP(String s, String t) { + int n = s.length(), m = t.length(); + int[][] dp = new int[n + 1][m + 1]; + // 狀態轉移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s.charAt(i - 1) == t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; + } + + /* 編輯距離:空間最佳化後的動態規劃 */ + static int editDistanceDPComp(String s, String t) { + int n = s.length(), m = t.length(); + int[] dp = new int[m + 1]; + // 狀態轉移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (int i = 1; i <= n; i++) { + // 狀態轉移:首列 + int leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s.charAt(i - 1) == t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; + } + + public static void main(String[] args) { + String s = "bag"; + String t = "pack"; + int n = s.length(), m = t.length(); + + // 暴力搜尋 + int res = editDistanceDFS(s, t, n, m); + System.out.println("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 記憶化搜尋 + int[][] mem = new int[n + 1][m + 1]; + for (int[] row : mem) + Arrays.fill(row, -1); + res = editDistanceDFSMem(s, t, mem, n, m); + System.out.println("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 動態規劃 + res = editDistanceDP(s, t); + System.out.println("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t); + System.out.println("將 " + s + " 更改為 " + t + " 最少需要編輯 " + res + " 步"); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/knapsack.java b/zh-hant/codes/java/chapter_dynamic_programming/knapsack.java new file mode 100644 index 000000000..a8500450d --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/knapsack.java @@ -0,0 +1,116 @@ +/** + * File: knapsack.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class knapsack { + + /* 0-1 背包:暴力搜尋 */ + static int knapsackDFS(int[] wgt, int[] val, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return Math.max(no, yes); + } + + /* 0-1 背包:記憶化搜尋 */ + static int knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; + } + + /* 0-1 背包:動態規劃 */ + static int knapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* 0-1 背包:空間最佳化後的動態規劃 */ + static int knapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + // 倒序走訪 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + int n = wgt.length; + + // 暴力搜尋 + int res = knapsackDFS(wgt, val, n, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + + // 記憶化搜尋 + int[][] mem = new int[n + 1][cap + 1]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = knapsackDFSMem(wgt, val, mem, n, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + + // 動態規劃 + res = knapsackDP(wgt, val, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, val, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java b/zh-hant/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java new file mode 100644 index 000000000..ffd91ed01 --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class min_cost_climbing_stairs_dp { + /* 爬樓梯最小代價:動態規劃 */ + public static int minCostClimbingStairsDP(int[] cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用於儲存子問題的解 + int[] dp = new int[n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (int i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + + /* 爬樓梯最小代價:空間最佳化後的動態規劃 */ + public static int minCostClimbingStairsDPComp(int[] cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + + public static void main(String[] args) { + int[] cost = { 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; + System.out.println(String.format("輸入樓梯的代價串列為 %s", Arrays.toString(cost))); + + int res = minCostClimbingStairsDP(cost); + System.out.println(String.format("爬完樓梯的最低代價為 %d", res)); + + res = minCostClimbingStairsDPComp(cost); + System.out.println(String.format("爬完樓梯的最低代價為 %d", res)); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/min_path_sum.java b/zh-hant/codes/java/chapter_dynamic_programming/min_path_sum.java new file mode 100644 index 000000000..eaecdd77e --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/min_path_sum.java @@ -0,0 +1,125 @@ +/** + * File: min_path_sum.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class min_path_sum { + /* 最小路徑和:暴力搜尋 */ + static int minPathSumDFS(int[][] grid, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return Math.min(left, up) + grid[i][j]; + } + + /* 最小路徑和:記憶化搜尋 */ + static int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Integer.MAX_VALUE; + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; + } + + /* 最小路徑和:動態規劃 */ + static int minPathSumDP(int[][] grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + int[][] dp = new int[n][m]; + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; + } + + /* 最小路徑和:空間最佳化後的動態規劃 */ + static int minPathSumDPComp(int[][] grid) { + int n = grid.length, m = grid[0].length; + // 初始化 dp 表 + int[] dp = new int[m]; + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (int i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (int j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + + public static void main(String[] args) { + int[][] grid = { + { 1, 3, 1, 5 }, + { 2, 2, 4, 2 }, + { 5, 3, 2, 1 }, + { 4, 3, 5, 2 } + }; + int n = grid.length, m = grid[0].length; + + // 暴力搜尋 + int res = minPathSumDFS(grid, n - 1, m - 1); + System.out.println("從左上角到右下角的最小路徑和為 " + res); + + // 記憶化搜尋 + int[][] mem = new int[n][m]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + System.out.println("從左上角到右下角的最小路徑和為 " + res); + + // 動態規劃 + res = minPathSumDP(grid); + System.out.println("從左上角到右下角的最小路徑和為 " + res); + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid); + System.out.println("從左上角到右下角的最小路徑和為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_dynamic_programming/unbounded_knapsack.java b/zh-hant/codes/java/chapter_dynamic_programming/unbounded_knapsack.java new file mode 100644 index 000000000..944b371ef --- /dev/null +++ b/zh-hant/codes/java/chapter_dynamic_programming/unbounded_knapsack.java @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class unbounded_knapsack { + /* 完全背包:動態規劃 */ + static int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[][] dp = new int[n + 1][cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* 完全背包:空間最佳化後的動態規劃 */ + static int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.length; + // 初始化 dp 表 + int[] dp = new int[cap + 1]; + // 狀態轉移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + public static void main(String[] args) { + int[] wgt = { 1, 2, 3 }; + int[] val = { 5, 11, 15 }; + int cap = 4; + + // 動態規劃 + int res = unboundedKnapsackDP(wgt, val, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt, val, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_graph/graph_adjacency_list.java b/zh-hant/codes/java/chapter_graph/graph_adjacency_list.java new file mode 100644 index 000000000..8e2115811 --- /dev/null +++ b/zh-hant/codes/java/chapter_graph/graph_adjacency_list.java @@ -0,0 +1,117 @@ +/** + * File: graph_adjacency_list.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + Map> adjList; + + /* 建構子 */ + public GraphAdjList(Vertex[][] edges) { + this.adjList = new HashMap<>(); + // 新增所有頂點和邊 + for (Vertex[] edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + public int size() { + return adjList.size(); + } + + /* 新增邊 */ + public void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 新增邊 vet1 - vet2 + adjList.get(vet1).add(vet2); + adjList.get(vet2).add(vet1); + } + + /* 刪除邊 */ + public void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 刪除邊 vet1 - vet2 + adjList.get(vet1).remove(vet2); + adjList.get(vet2).remove(vet1); + } + + /* 新增頂點 */ + public void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) + return; + // 在鄰接表中新增一個新鏈結串列 + adjList.put(vet, new ArrayList<>()); + } + + /* 刪除頂點 */ + public void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) + throw new IllegalArgumentException(); + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.remove(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (List list : adjList.values()) { + list.remove(vet); + } + } + + /* 列印鄰接表 */ + public void print() { + System.out.println("鄰接表 ="); + for (Map.Entry> pair : adjList.entrySet()) { + List tmp = new ArrayList<>(); + for (Vertex vertex : pair.getValue()) + tmp.add(vertex.val); + System.out.println(pair.getKey().val + ": " + tmp + ","); + } + } +} + +public class graph_adjacency_list { + public static void main(String[] args) { + /* 初始化無向圖 */ + Vertex[] v = Vertex.valsToVets(new int[] { 1, 3, 2, 5, 4 }); + Vertex[][] 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] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\n初始化後,圖為"); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + System.out.println("\n新增邊 1-2 後,圖為"); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + System.out.println("\n刪除邊 1-3 後,圖為"); + graph.print(); + + /* 新增頂點 */ + Vertex v5 = new Vertex(6); + graph.addVertex(v5); + System.out.println("\n新增頂點 6 後,圖為"); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(v[1]); + System.out.println("\n刪除頂點 3 後,圖為"); + graph.print(); + } +} diff --git a/zh-hant/codes/java/chapter_graph/graph_adjacency_matrix.java b/zh-hant/codes/java/chapter_graph/graph_adjacency_matrix.java new file mode 100644 index 000000000..2ca0ca625 --- /dev/null +++ b/zh-hant/codes/java/chapter_graph/graph_adjacency_matrix.java @@ -0,0 +1,131 @@ +/** + * File: graph_adjacency_matrix.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import utils.*; +import java.util.*; + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + List vertices; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + List> adjMat; // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = new ArrayList<>(); + this.adjMat = new ArrayList<>(); + // 新增頂點 + for (int val : vertices) { + addVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (int[] e : edges) { + addEdge(e[0], e[1]); + } + } + + /* 獲取頂點數量 */ + public int size() { + return vertices.size(); + } + + /* 新增頂點 */ + public void addVertex(int val) { + int n = size(); + // 向頂點串列中新增新頂點的值 + vertices.add(val); + // 在鄰接矩陣中新增一行 + List newRow = new ArrayList<>(n); + for (int j = 0; j < n; j++) { + newRow.add(0); + } + adjMat.add(newRow); + // 在鄰接矩陣中新增一列 + for (List row : adjMat) { + row.add(0); + } + } + + /* 刪除頂點 */ + public void removeVertex(int index) { + if (index >= size()) + throw new IndexOutOfBoundsException(); + // 在頂點串列中移除索引 index 的頂點 + vertices.remove(index); + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.remove(index); + // 在鄰接矩陣中刪除索引 index 的列 + for (List row : adjMat) { + row.remove(index); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + public void addEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat.get(i).set(j, 1); + adjMat.get(j).set(i, 1); + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + public void removeEdge(int i, int j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw new IndexOutOfBoundsException(); + adjMat.get(i).set(j, 0); + adjMat.get(j).set(i, 0); + } + + /* 列印鄰接矩陣 */ + public void print() { + System.out.print("頂點串列 = "); + System.out.println(vertices); + System.out.println("鄰接矩陣 ="); + PrintUtil.printMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + public static void main(String[] args) { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + int[] vertices = { 1, 3, 2, 5, 4 }; + int[][] edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; + GraphAdjMat graph = new GraphAdjMat(vertices, edges); + System.out.println("\n初始化後,圖為"); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(0, 2); + System.out.println("\n新增邊 1-2 後,圖為"); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(0, 1); + System.out.println("\n刪除邊 1-3 後,圖為"); + graph.print(); + + /* 新增頂點 */ + graph.addVertex(6); + System.out.println("\n新增頂點 6 後,圖為"); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(1); + System.out.println("\n刪除頂點 3 後,圖為"); + graph.print(); + } +} diff --git a/zh-hant/codes/java/chapter_graph/graph_bfs.java b/zh-hant/codes/java/chapter_graph/graph_bfs.java new file mode 100644 index 000000000..a1f077d0a --- /dev/null +++ b/zh-hant/codes/java/chapter_graph/graph_bfs.java @@ -0,0 +1,55 @@ +/** + * File: graph_bfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_bfs { + /* 廣度優先走訪 */ + // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + static List graphBFS(GraphAdjList graph, Vertex startVet) { + // 頂點走訪序列 + List res = new ArrayList<>(); + // 雜湊表,用於記錄已被訪問過的頂點 + Set visited = new HashSet<>(); + visited.add(startVet); + // 佇列用於實現 BFS + Queue que = new LinkedList<>(); + que.offer(startVet); + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (!que.isEmpty()) { + Vertex vet = que.poll(); // 佇列首頂點出隊 + res.add(vet); // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // 跳過已被訪問的頂點 + que.offer(adjVet); // 只入列未訪問的頂點 + visited.add(adjVet); // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res; + } + + public static void main(String[] args) { + /* 初始化無向圖 */ + Vertex[] v = Vertex.valsToVets(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + Vertex[][] 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] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\n初始化後,圖為"); + graph.print(); + + /* 廣度優先走訪 */ + List res = graphBFS(graph, v[0]); + System.out.println("\n廣度優先走訪(BFS)頂點序列為"); + System.out.println(Vertex.vetsToVals(res)); + } +} diff --git a/zh-hant/codes/java/chapter_graph/graph_dfs.java b/zh-hant/codes/java/chapter_graph/graph_dfs.java new file mode 100644 index 000000000..68eff464c --- /dev/null +++ b/zh-hant/codes/java/chapter_graph/graph_dfs.java @@ -0,0 +1,51 @@ +/** + * File: graph_dfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_dfs { + /* 深度優先走訪輔助函式 */ + static void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { + res.add(vet); // 記錄訪問頂點 + visited.add(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + continue; // 跳過已被訪問的頂點 + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet); + } + } + + /* 深度優先走訪 */ + // 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 + static List graphDFS(GraphAdjList graph, Vertex startVet) { + // 頂點走訪序列 + List res = new ArrayList<>(); + // 雜湊表,用於記錄已被訪問過的頂點 + Set visited = new HashSet<>(); + dfs(graph, visited, res, startVet); + return res; + } + + public static void main(String[] args) { + /* 初始化無向圖 */ + Vertex[] v = Vertex.valsToVets(new int[] { 0, 1, 2, 3, 4, 5, 6 }); + Vertex[][] 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] } }; + GraphAdjList graph = new GraphAdjList(edges); + System.out.println("\n初始化後,圖為"); + graph.print(); + + /* 深度優先走訪 */ + List res = graphDFS(graph, v[0]); + System.out.println("\n深度優先走訪(DFS)頂點序列為"); + System.out.println(Vertex.vetsToVals(res)); + } +} diff --git a/zh-hant/codes/java/chapter_greedy/coin_change_greedy.java b/zh-hant/codes/java/chapter_greedy/coin_change_greedy.java new file mode 100644 index 000000000..671ff95b8 --- /dev/null +++ b/zh-hant/codes/java/chapter_greedy/coin_change_greedy.java @@ -0,0 +1,55 @@ +/** + * File: coin_change_greedy.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; + +public class coin_change_greedy { + /* 零錢兌換:貪婪 */ + static int coinChangeGreedy(int[] coins, int amt) { + // 假設 coins 串列有序 + int i = coins.length - 1; + int count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (amt > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > amt) { + i--; + } + // 選擇 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,則返回 -1 + return amt == 0 ? count : -1; + } + + public static void main(String[] args) { + // 貪婪:能夠保證找到全域性最優解 + int[] coins = { 1, 5, 10, 20, 50, 100 }; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + + // 貪婪:無法保證找到全域性最優解 + coins = new int[] { 1, 20, 50 }; + amt = 60; + res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + System.out.println("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); + + // 貪婪:無法保證找到全域性最優解 + coins = new int[] { 1, 49, 50 }; + amt = 98; + res = coinChangeGreedy(coins, amt); + System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); + System.out.println("湊到 " + amt + " 所需的最少硬幣數量為 " + res); + System.out.println("實際上需要的最少數量為 2 ,即 49 + 49"); + } +} diff --git a/zh-hant/codes/java/chapter_greedy/fractional_knapsack.java b/zh-hant/codes/java/chapter_greedy/fractional_knapsack.java new file mode 100644 index 000000000..054f1fd7b --- /dev/null +++ b/zh-hant/codes/java/chapter_greedy/fractional_knapsack.java @@ -0,0 +1,59 @@ +/** + * File: fractional_knapsack.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; +import java.util.Comparator; + +/* 物品 */ +class Item { + int w; // 物品重量 + int v; // 物品價值 + + public Item(int w, int v) { + this.w = w; + this.v = v; + } +} + +public class fractional_knapsack { + /* 分數背包:貪婪 */ + static double fractionalKnapsack(int[] wgt, int[] val, int cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + Item[] items = new Item[wgt.length]; + for (int i = 0; i < wgt.length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w))); + // 迴圈貪婪選擇 + double res = 0; + for (Item item : items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (double) item.v / item.w * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + + // 貪婪演算法 + double res = fractionalKnapsack(wgt, val, cap); + System.out.println("不超過背包容量的最大物品價值為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_greedy/max_capacity.java b/zh-hant/codes/java/chapter_greedy/max_capacity.java new file mode 100644 index 000000000..1d7625946 --- /dev/null +++ b/zh-hant/codes/java/chapter_greedy/max_capacity.java @@ -0,0 +1,38 @@ +/** + * File: max_capacity.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +public class max_capacity { + /* 最大容量:貪婪 */ + static int maxCapacity(int[] ht) { + // 初始化 i, j,使其分列陣列兩端 + int i = 0, j = ht.length - 1; + // 初始最大容量為 0 + int res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + int cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + + public static void main(String[] args) { + int[] ht = { 3, 8, 5, 2, 7, 7, 3, 4 }; + + // 貪婪演算法 + int res = maxCapacity(ht); + System.out.println("最大容量為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_greedy/max_product_cutting.java b/zh-hant/codes/java/chapter_greedy/max_product_cutting.java new file mode 100644 index 000000000..6bc9d8c91 --- /dev/null +++ b/zh-hant/codes/java/chapter_greedy/max_product_cutting.java @@ -0,0 +1,40 @@ +/** + * File: max_product_cutting.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.lang.Math; + +public class max_product_cutting { + /* 最大切分乘積:貪婪 */ + public static int maxProductCutting(int n) { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + int a = n / 3; + int b = 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); + } + + public static void main(String[] args) { + int n = 58; + + // 貪婪演算法 + int res = maxProductCutting(n); + System.out.println("最大切分乘積為 " + res); + } +} diff --git a/zh-hant/codes/java/chapter_hashing/array_hash_map.java b/zh-hant/codes/java/chapter_hashing/array_hash_map.java new file mode 100644 index 000000000..61c509fe6 --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/array_hash_map.java @@ -0,0 +1,141 @@ +/** + * File: array_hash_map.java + * Created Time: 2022-12-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.*; + +/* 鍵值對 */ +class Pair { + public int key; + public String val; + + public Pair(int key, String val) { + this.key = key; + this.val = val; + } +} + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + private List buckets; + + public ArrayHashMap() { + // 初始化陣列,包含 100 個桶 + buckets = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + buckets.add(null); + } + } + + /* 雜湊函式 */ + private int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* 查詢操作 */ + public String get(int key) { + int index = hashFunc(key); + Pair pair = buckets.get(index); + if (pair == null) + return null; + return pair.val; + } + + /* 新增操作 */ + public void put(int key, String val) { + Pair pair = new Pair(key, val); + int index = hashFunc(key); + buckets.set(index, pair); + } + + /* 刪除操作 */ + public void remove(int key) { + int index = hashFunc(key); + // 置為 null ,代表刪除 + buckets.set(index, null); + } + + /* 獲取所有鍵值對 */ + public List pairSet() { + List pairSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + pairSet.add(pair); + } + return pairSet; + } + + /* 獲取所有鍵 */ + public List keySet() { + List keySet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + keySet.add(pair.key); + } + return keySet; + } + + /* 獲取所有值 */ + public List valueSet() { + List valueSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + valueSet.add(pair.val); + } + return valueSet; + } + + /* 列印雜湊表 */ + public void print() { + for (Pair kv : pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + } +} + +public class array_hash_map { + public static void main(String[] args) { + /* 初始化雜湊表 */ + ArrayHashMap map = new ArrayHashMap(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String name = map.get(15937); + System.out.println("\n輸入學號 15937 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + System.out.println("\n刪除 10583 後,雜湊表為\nKey -> Value"); + map.print(); + + /* 走訪雜湊表 */ + System.out.println("\n走訪鍵值對 Key->Value"); + for (Pair kv : map.pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + System.out.println("\n單獨走訪鍵 Key"); + for (int key : map.keySet()) { + System.out.println(key); + } + System.out.println("\n單獨走訪值 Value"); + for (String val : map.valueSet()) { + System.out.println(val); + } + } +} diff --git a/zh-hant/codes/java/chapter_hashing/built_in_hash.java b/zh-hant/codes/java/chapter_hashing/built_in_hash.java new file mode 100644 index 000000000..e2238d068 --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/built_in_hash.java @@ -0,0 +1,38 @@ +/** + * File: built_in_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import utils.*; +import java.util.*; + +public class built_in_hash { + public static void main(String[] args) { + int num = 3; + int hashNum = Integer.hashCode(num); + System.out.println("整數 " + num + " 的雜湊值為 " + hashNum); + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + System.out.println("布林量 " + bol + " 的雜湊值為 " + hashBol); + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + System.out.println("小數 " + dec + " 的雜湊值為 " + hashDec); + + String str = "Hello 演算法"; + int hashStr = str.hashCode(); + System.out.println("字串 " + str + " 的雜湊值為 " + hashStr); + + Object[] arr = { 12836, "小哈" }; + int hashTup = Arrays.hashCode(arr); + System.out.println("陣列 " + Arrays.toString(arr) + " 的雜湊值為 " + hashTup); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + System.out.println("節點物件 " + obj + " 的雜湊值為 " + hashObj); + } +} diff --git a/zh-hant/codes/java/chapter_hashing/hash_map.java b/zh-hant/codes/java/chapter_hashing/hash_map.java new file mode 100644 index 000000000..3ba08f191 --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/hash_map.java @@ -0,0 +1,52 @@ +/** + * File: hash_map.java + * Created Time: 2022-12-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.*; +import utils.*; + +public class hash_map { + public static void main(String[] args) { + /* 初始化雜湊表 */ + Map map = new HashMap<>(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); + PrintUtil.printHashMap(map); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String name = map.get(15937); + System.out.println("\n輸入學號 15937 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + System.out.println("\n刪除 10583 後,雜湊表為\nKey -> Value"); + PrintUtil.printHashMap(map); + + /* 走訪雜湊表 */ + System.out.println("\n走訪鍵值對 Key->Value"); + for (Map.Entry kv : map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + System.out.println("\n單獨走訪鍵 Key"); + for (int key : map.keySet()) { + System.out.println(key); + } + System.out.println("\n單獨走訪值 Value"); + for (String val : map.values()) { + System.out.println(val); + } + } +} diff --git a/zh-hant/codes/java/chapter_hashing/hash_map_chaining.java b/zh-hant/codes/java/chapter_hashing/hash_map_chaining.java new file mode 100644 index 000000000..5c5d17d54 --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/hash_map_chaining.java @@ -0,0 +1,148 @@ +/** + * File: hash_map_chaining.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.ArrayList; +import java.util.List; + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + int size; // 鍵值對數量 + int capacity; // 雜湊表容量 + double loadThres; // 觸發擴容的負載因子閾值 + int extendRatio; // 擴容倍數 + List> buckets; // 桶陣列 + + /* 建構子 */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + } + + /* 雜湊函式 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + double loadFactor() { + return (double) size / capacity; + } + + /* 查詢操作 */ + String get(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // 走訪桶,若找到 key ,則返回對應 val + for (Pair pair : bucket) { + if (pair.key == key) { + return pair.val; + } + } + // 若未找到 key ,則返回 null + return null; + } + + /* 新增操作 */ + void put(int key, String val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets.get(index); + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (Pair pair : bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + Pair pair = new Pair(key, val); + bucket.add(pair); + size++; + } + + /* 刪除操作 */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // 走訪桶,從中刪除鍵值對 + for (Pair pair : bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* 擴容雜湊表 */ + void extend() { + // 暫存原雜湊表 + List> bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = new ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (List bucket : bucketsTmp) { + for (Pair pair : bucket) { + put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + void print() { + for (List bucket : buckets) { + List res = new ArrayList<>(); + for (Pair pair : bucket) { + res.add(pair.key + " -> " + pair.val); + } + System.out.println(res); + } + } +} + +public class hash_map_chaining { + public static void main(String[] args) { + /* 初始化雜湊表 */ + HashMapChaining map = new HashMapChaining(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String name = map.get(13276); + System.out.println("\n輸入學號 13276 ,查詢到姓名 " + name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(12836); + System.out.println("\n刪除 12836 後,雜湊表為\nKey -> Value"); + map.print(); + } +} diff --git a/zh-hant/codes/java/chapter_hashing/hash_map_open_addressing.java b/zh-hant/codes/java/chapter_hashing/hash_map_open_addressing.java new file mode 100644 index 000000000..f398a513e --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/hash_map_open_addressing.java @@ -0,0 +1,158 @@ +/** + * File: hash_map_open_addressing.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + private int size; // 鍵值對數量 + private int capacity = 4; // 雜湊表容量 + private final double loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + private final int extendRatio = 2; // 擴容倍數 + private Pair[] buckets; // 桶陣列 + private final Pair TOMBSTONE = new Pair(-1, "-1"); // 刪除標記 + + /* 建構子 */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* 雜湊函式 */ + private int hashFunc(int key) { + return key % capacity; + } + + /* 負載因子 */ + private double loadFactor() { + return (double) size / capacity; + } + + /* 搜尋 key 對應的桶索引 */ + private int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (buckets[index] != null) { + // 若遇到 key ,返回對應的桶索引 + if (buckets[index].key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + public String get(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則返回對應 val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index].val; + } + // 若鍵值對不存在,則返回 null + return null; + } + + /* 新增操作 */ + public void put(int key, String val) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend(); + } + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index].val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + buckets[index] = new Pair(key, val); + size++; + } + + /* 刪除操作 */ + public void remove(int key) { + // 搜尋 key 對應的桶索引 + int index = findBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE; + size--; + } + } + + /* 擴容雜湊表 */ + private void extend() { + // 暫存原雜湊表 + Pair[] bucketsTmp = buckets; + // 初始化擴容後的新雜湊表 + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (Pair pair : bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + public void print() { + for (Pair pair : buckets) { + if (pair == null) { + System.out.println("null"); + } else if (pair == TOMBSTONE) { + System.out.println("TOMBSTONE"); + } else { + System.out.println(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + public static void main(String[] args) { + // 初始化雜湊表 + HashMapOpenAddressing hashmap = new HashMapOpenAddressing(); + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, val) + hashmap.put(12836, "小哈"); + hashmap.put(15937, "小囉"); + hashmap.put(16750, "小算"); + hashmap.put(13276, "小法"); + hashmap.put(10583, "小鴨"); + System.out.println("\n新增完成後,雜湊表為\nKey -> Value"); + hashmap.print(); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 val + String name = hashmap.get(13276); + System.out.println("\n輸入學號 13276 ,查詢到姓名 " + name); + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, val) + hashmap.remove(16750); + System.out.println("\n刪除 16750 後,雜湊表為\nKey -> Value"); + hashmap.print(); + } +} diff --git a/zh-hant/codes/java/chapter_hashing/simple_hash.java b/zh-hant/codes/java/chapter_hashing/simple_hash.java new file mode 100644 index 000000000..b9acc79ee --- /dev/null +++ b/zh-hant/codes/java/chapter_hashing/simple_hash.java @@ -0,0 +1,65 @@ +/** + * File: simple_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +public class simple_hash { + /* 加法雜湊 */ + static int addHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* 乘法雜湊 */ + static int mulHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (31 * hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* 互斥或雜湊 */ + static int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash ^= (int) c; + } + return hash & MODULUS; + } + + /* 旋轉雜湊 */ + static int rotHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS; + } + return (int) hash; + } + + public static void main(String[] args) { + String key = "Hello 演算法"; + + int hash = addHash(key); + System.out.println("加法雜湊值為 " + hash); + + hash = mulHash(key); + System.out.println("乘法雜湊值為 " + hash); + + hash = xorHash(key); + System.out.println("互斥或雜湊值為 " + hash); + + hash = rotHash(key); + System.out.println("旋轉雜湊值為 " + hash); + } +} diff --git a/zh-hant/codes/java/chapter_heap/heap.java b/zh-hant/codes/java/chapter_heap/heap.java new file mode 100644 index 000000000..407c82678 --- /dev/null +++ b/zh-hant/codes/java/chapter_heap/heap.java @@ -0,0 +1,66 @@ +/** + * File: heap.java + * Created Time: 2023-01-07 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class heap { + public static void testPush(Queue heap, int val) { + heap.offer(val); // 元素入堆積 + System.out.format("\n元素 %d 入堆積後\n", val); + PrintUtil.printHeap(heap); + } + + public static void testPop(Queue heap) { + int val = heap.poll(); // 堆積頂元素出堆積 + System.out.format("\n堆積頂元素 %d 出堆積後\n", val); + PrintUtil.printHeap(heap); + } + + public static void main(String[] args) { + /* 初始化堆積 */ + // 初始化小頂堆積 + Queue minHeap = new PriorityQueue<>(); + // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + System.out.println("\n以下測試樣例為大頂堆積"); + + /* 元素入堆積 */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.peek(); + System.out.format("\n堆積頂元素為 %d\n", peek); + + /* 堆積頂元素出堆積 */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + System.out.format("\n堆積元素數量為 %d\n", size); + + /* 判斷堆積是否為空 */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\n堆積是否為空 %b\n", isEmpty); + + /* 輸入串列並建堆積 */ + // 時間複雜度為 O(n) ,而非 O(nlogn) + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + System.out.println("\n輸入串列並建立小頂堆積後"); + PrintUtil.printHeap(minHeap); + } +} diff --git a/zh-hant/codes/java/chapter_heap/my_heap.java b/zh-hant/codes/java/chapter_heap/my_heap.java new file mode 100644 index 000000000..a6d822504 --- /dev/null +++ b/zh-hant/codes/java/chapter_heap/my_heap.java @@ -0,0 +1,159 @@ +/** + * File: my_heap.java + * Created Time: 2023-01-07 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +/* 大頂堆積 */ +class MaxHeap { + // 使用串列而非陣列,這樣無須考慮擴容問題 + private List maxHeap; + + /* 建構子,根據輸入串列建堆積 */ + public MaxHeap(List nums) { + // 將串列元素原封不動新增進堆積 + maxHeap = new ArrayList<>(nums); + // 堆積化除葉節點以外的其他所有節點 + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 獲取左子節點的索引 */ + private int left(int i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + private int right(int i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + private int parent(int i) { + return (i - 1) / 2; // 向下整除 + } + + /* 交換元素 */ + private void swap(int i, int j) { + int tmp = maxHeap.get(i); + maxHeap.set(i, maxHeap.get(j)); + maxHeap.set(j, tmp); + } + + /* 獲取堆積大小 */ + public int size() { + return maxHeap.size(); + } + + /* 判斷堆積是否為空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 訪問堆積頂元素 */ + public int peek() { + return maxHeap.get(0); + } + + /* 元素入堆積 */ + public void push(int val) { + // 新增節點 + maxHeap.add(val); + // 從底至頂堆積化 + siftUp(size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + private void siftUp(int i) { + while (true) { + // 獲取節點 i 的父節點 + int p = parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) + break; + // 交換兩節點 + swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + public int pop() { + // 判空處理 + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(0, size() - 1); + // 刪除節點 + int val = maxHeap.remove(size() - 1); + // 從頂至底堆積化 + siftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + private void siftDown(int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap.get(l) > maxHeap.get(ma)) + ma = l; + if (r < size() && maxHeap.get(r) > maxHeap.get(ma)) + ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) + break; + // 交換兩節點 + swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 列印堆積(二元樹) */ + public void print() { + Queue queue = new PriorityQueue<>((a, b) -> { return b - a; }); + queue.addAll(maxHeap); + PrintUtil.printHeap(queue); + } +} + +public class my_heap { + public static void main(String[] args) { + /* 初始化大頂堆積 */ + MaxHeap maxHeap = new MaxHeap(Arrays.asList(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)); + System.out.println("\n輸入串列並建堆積後"); + maxHeap.print(); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.peek(); + System.out.format("\n堆積頂元素為 %d\n", peek); + + /* 元素入堆積 */ + int val = 7; + maxHeap.push(val); + System.out.format("\n元素 %d 入堆積後\n", val); + maxHeap.print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop(); + System.out.format("\n堆積頂元素 %d 出堆積後\n", peek); + maxHeap.print(); + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + System.out.format("\n堆積元素數量為 %d\n", size); + + /* 判斷堆積是否為空 */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\n堆積是否為空 %b\n", isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_heap/top_k.java b/zh-hant/codes/java/chapter_heap/top_k.java new file mode 100644 index 000000000..96f76927a --- /dev/null +++ b/zh-hant/codes/java/chapter_heap/top_k.java @@ -0,0 +1,40 @@ +/** + * File: top_k.java + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class top_k { + /* 基於堆積查詢陣列中最大的 k 個元素 */ + static Queue topKHeap(int[] nums, int k) { + // 初始化小頂堆積 + Queue heap = new PriorityQueue(); + // 將陣列的前 k 個元素入堆積 + for (int i = 0; i < k; i++) { + heap.offer(nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (int i = k; i < nums.length; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > heap.peek()) { + heap.poll(); + heap.offer(nums[i]); + } + } + return heap; + } + + public static void main(String[] args) { + int[] nums = { 1, 7, 6, 3, 2 }; + int k = 3; + + Queue res = topKHeap(nums, k); + System.out.println("最大的 " + k + " 個元素為"); + PrintUtil.printHeap(res); + } +} diff --git a/zh-hant/codes/java/chapter_searching/binary_search.java b/zh-hant/codes/java/chapter_searching/binary_search.java new file mode 100644 index 000000000..06b5c257c --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/binary_search.java @@ -0,0 +1,58 @@ +/** + * File: binary_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search { + /* 二分搜尋(雙閉區間) */ + static int binarySearch(int[] nums, int target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + int i = 0, j = nums.length - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + else // 找到目標元素,返回其索引 + return m; + } + // 未找到目標元素,返回 -1 + return -1; + } + + /* 二分搜尋(左閉右開區間) */ + static int binarySearchLCRO(int[] nums, int target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + int i = 0, j = nums.length; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m) 中 + j = m; + else // 找到目標元素,返回其索引 + return m; + } + // 未找到目標元素,返回 -1 + return -1; + } + + public static void main(String[] args) { + int target = 6; + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + + /* 二分搜尋(雙閉區間) */ + int index = binarySearch(nums, target); + System.out.println("目標元素 6 的索引 = " + index); + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums, target); + System.out.println("目標元素 6 的索引 = " + index); + } +} diff --git a/zh-hant/codes/java/chapter_searching/binary_search_edge.java b/zh-hant/codes/java/chapter_searching/binary_search_edge.java new file mode 100644 index 000000000..124e563f5 --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/binary_search_edge.java @@ -0,0 +1,49 @@ +/** + * File: binary_search_edge.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search_edge { + /* 二分搜尋最左一個 target */ + static int binarySearchLeftEdge(int[] nums, int target) { + // 等價於查詢 target 的插入點 + int i = binary_search_insertion.binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.length || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; + } + + /* 二分搜尋最右一個 target */ + static int binarySearchRightEdge(int[] nums, int target) { + // 轉化為查詢最左一個 target + 1 + int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; + } + + public static void main(String[] args) { + // 包含重複元素的陣列 + int[] nums = { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; + System.out.println("\n陣列 nums = " + java.util.Arrays.toString(nums)); + + // 二分搜尋左邊界和右邊界 + for (int target : new int[] { 6, 7 }) { + int index = binarySearchLeftEdge(nums, target); + System.out.println("最左一個元素 " + target + " 的索引為 " + index); + index = binarySearchRightEdge(nums, target); + System.out.println("最右一個元素 " + target + " 的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/java/chapter_searching/binary_search_insertion.java b/zh-hant/codes/java/chapter_searching/binary_search_insertion.java new file mode 100644 index 000000000..445ae13ce --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/binary_search_insertion.java @@ -0,0 +1,63 @@ +/** + * File: binary_search_insertion.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +class binary_search_insertion { + /* 二分搜尋插入點(無重複元素) */ + static int binarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i; + } + + /* 二分搜尋插入點(存在重複元素) */ + static int binarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 計算中點索引 m + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; + } + + public static void main(String[] args) { + // 無重複元素的陣列 + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; + System.out.println("\n陣列 nums = " + java.util.Arrays.toString(nums)); + // 二分搜尋插入點 + for (int target : new int[] { 6, 9 }) { + int index = binarySearchInsertionSimple(nums, target); + System.out.println("元素 " + target + " 的插入點的索引為 " + index); + } + + // 包含重複元素的陣列 + nums = new int[] { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; + System.out.println("\n陣列 nums = " + java.util.Arrays.toString(nums)); + // 二分搜尋插入點 + for (int target : new int[] { 2, 6, 20 }) { + int index = binarySearchInsertion(nums, target); + System.out.println("元素 " + target + " 的插入點的索引為 " + index); + } + } +} diff --git a/zh-hant/codes/java/chapter_searching/hashing_search.java b/zh-hant/codes/java/chapter_searching/hashing_search.java new file mode 100644 index 000000000..6ead475c3 --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/hashing_search.java @@ -0,0 +1,51 @@ +/** + * File: hashing_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import utils.*; +import java.util.*; + +public class hashing_search { + /* 雜湊查詢(陣列) */ + static int hashingSearchArray(Map map, int target) { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map.getOrDefault(target, -1); + } + + /* 雜湊查詢(鏈結串列) */ + static ListNode hashingSearchLinkedList(Map map, int target) { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map.getOrDefault(target, null); + } + + public static void main(String[] args) { + int target = 3; + + /* 雜湊查詢(陣列) */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // 初始化雜湊表 + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + map.put(nums[i], i); // key: 元素,value: 索引 + } + int index = hashingSearchArray(map, target); + System.out.println("目標元素 3 的索引 = " + index); + + /* 雜湊查詢(鏈結串列) */ + ListNode head = ListNode.arrToLinkedList(nums); + // 初始化雜湊表 + Map map1 = new HashMap<>(); + while (head != null) { + map1.put(head.val, head); // key: 節點值,value: 節點 + head = head.next; + } + ListNode node = hashingSearchLinkedList(map1, target); + System.out.println("目標節點值 3 的對應節點物件為 " + node); + } +} diff --git a/zh-hant/codes/java/chapter_searching/linear_search.java b/zh-hant/codes/java/chapter_searching/linear_search.java new file mode 100644 index 000000000..963c5aa8a --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/linear_search.java @@ -0,0 +1,50 @@ +/** + * File: linear_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import utils.*; + +public class linear_search { + /* 線性查詢(陣列) */ + static int linearSearchArray(int[] nums, int target) { + // 走訪陣列 + for (int i = 0; i < nums.length; i++) { + // 找到目標元素,返回其索引 + if (nums[i] == target) + return i; + } + // 未找到目標元素,返回 -1 + return -1; + } + + /* 線性查詢(鏈結串列) */ + static ListNode linearSearchLinkedList(ListNode head, int target) { + // 走訪鏈結串列 + while (head != null) { + // 找到目標節點,返回之 + if (head.val == target) + return head; + head = head.next; + } + // 未找到目標節點,返回 null + return null; + } + + public static void main(String[] args) { + int target = 3; + + /* 在陣列中執行線性查詢 */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + int index = linearSearchArray(nums, target); + System.out.println("目標元素 3 的索引 = " + index); + + /* 在鏈結串列中執行線性查詢 */ + ListNode head = ListNode.arrToLinkedList(nums); + ListNode node = linearSearchLinkedList(head, target); + System.out.println("目標節點值 3 的對應節點物件為 " + node); + } +} diff --git a/zh-hant/codes/java/chapter_searching/two_sum.java b/zh-hant/codes/java/chapter_searching/two_sum.java new file mode 100644 index 000000000..78860ccc5 --- /dev/null +++ b/zh-hant/codes/java/chapter_searching/two_sum.java @@ -0,0 +1,53 @@ +/** + * File: two_sum.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import java.util.*; + +public class two_sum { + /* 方法一:暴力列舉 */ + static int[] twoSumBruteForce(int[] nums, int target) { + int size = nums.length; + // 兩層迴圈,時間複雜度為 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return new int[] { i, j }; + } + } + return new int[0]; + } + + /* 方法二:輔助雜湊表 */ + static int[] twoSumHashTable(int[] nums, int target) { + int size = nums.length; + // 輔助雜湊表,空間複雜度為 O(n) + Map dic = new HashMap<>(); + // 單層迴圈,時間複雜度為 O(n) + for (int i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return new int[] { dic.get(target - nums[i]), i }; + } + dic.put(nums[i], i); + } + return new int[0]; + } + + public static void main(String[] args) { + // ======= Test Case ======= + int[] nums = { 2, 7, 11, 15 }; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + int[] res = twoSumBruteForce(nums, target); + System.out.println("方法一 res = " + Arrays.toString(res)); + // 方法二 + res = twoSumHashTable(nums, target); + System.out.println("方法二 res = " + Arrays.toString(res)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/bubble_sort.java b/zh-hant/codes/java/chapter_sorting/bubble_sort.java new file mode 100644 index 000000000..10d8990e4 --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/bubble_sort.java @@ -0,0 +1,57 @@ +/** + * File: bubble_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bubble_sort { + /* 泡沫排序 */ + static void bubbleSort(int[] nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } + } + + /* 泡沫排序(標誌最佳化) */ + static void bubbleSortWithFlag(int[] nums) { + // 外迴圈:未排序區間為 [0, i] + for (int i = nums.length - 1; i > 0; i--) { + boolean flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 記錄交換元素 + } + } + if (!flag) + break; // 此輪“冒泡”未交換任何元素,直接跳出 + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + bubbleSort(nums); + System.out.println("泡沫排序完成後 nums = " + Arrays.toString(nums)); + + int[] nums1 = { 4, 1, 3, 1, 5, 2 }; + bubbleSortWithFlag(nums1); + System.out.println("泡沫排序完成後 nums1 = " + Arrays.toString(nums1)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/bucket_sort.java b/zh-hant/codes/java/chapter_sorting/bucket_sort.java new file mode 100644 index 000000000..1e2c98fff --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/bucket_sort.java @@ -0,0 +1,47 @@ +/** + * File: bucket_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bucket_sort { + /* 桶排序 */ + static void bucketSort(float[] nums) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + int k = nums.length / 2; + List> buckets = new ArrayList<>(); + for (int i = 0; i < k; i++) { + buckets.add(new ArrayList<>()); + } + // 1. 將陣列元素分配到各個桶中 + for (float num : nums) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + int i = (int) (num * k); + // 將 num 新增進桶 i + buckets.get(i).add(num); + } + // 2. 對各個桶執行排序 + for (List bucket : buckets) { + // 使用內建排序函式,也可以替換成其他排序演算法 + Collections.sort(bucket); + } + // 3. 走訪桶合併結果 + int i = 0; + for (List bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } + } + + public static void main(String[] args) { + // 設輸入資料為浮點數,範圍為 [0, 1) + float[] nums = { 0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f }; + bucketSort(nums); + System.out.println("桶排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/counting_sort.java b/zh-hant/codes/java/chapter_sorting/counting_sort.java new file mode 100644 index 000000000..f4267328b --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/counting_sort.java @@ -0,0 +1,78 @@ +/** + * File: counting_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class counting_sort { + /* 計數排序 */ + // 簡單實現,無法用於排序物件 + static void countingSortNaive(int[] nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + } + + /* 計數排序 */ + // 完整實現,可排序物件,並且是穩定排序 + static void countingSort(int[] nums) { + // 1. 統計陣列最大元素 m + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + int[] counter = new int[m + 1]; + for (int num : nums) { + counter[num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + int n = nums.length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + public static void main(String[] args) { + int[] nums = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSortNaive(nums); + System.out.println("計數排序(無法排序物件)完成後 nums = " + Arrays.toString(nums)); + + int[] nums1 = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSort(nums1); + System.out.println("計數排序完成後 nums1 = " + Arrays.toString(nums1)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/heap_sort.java b/zh-hant/codes/java/chapter_sorting/heap_sort.java new file mode 100644 index 000000000..c3af943df --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/heap_sort.java @@ -0,0 +1,57 @@ +/** + * File: heap_sort.java + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class heap_sort { + /* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ + public static void siftDown(int[] nums, int n, int i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) + break; + // 交換兩節點 + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // 迴圈向下堆積化 + i = ma; + } + } + + /* 堆積排序 */ + public static void heapSort(int[] nums) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (int i = nums.length / 2 - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (int i = nums.length - 1; i > 0; i--) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + heapSort(nums); + System.out.println("堆積排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/insertion_sort.java b/zh-hant/codes/java/chapter_sorting/insertion_sort.java new file mode 100644 index 000000000..b107215e8 --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/insertion_sort.java @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class insertion_sort { + /* 插入排序 */ + static void insertionSort(int[] nums) { + // 外迴圈:已排序區間為 [0, i-1] + for (int i = 1; i < nums.length; i++) { + int base = nums[i], j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = base; // 將 base 賦值到正確位置 + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + insertionSort(nums); + System.out.println("插入排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/merge_sort.java b/zh-hant/codes/java/chapter_sorting/merge_sort.java new file mode 100644 index 000000000..8ba74edf0 --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/merge_sort.java @@ -0,0 +1,58 @@ +/** + * File: merge_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class merge_sort { + /* 合併左子陣列和右子陣列 */ + static void merge(int[] nums, int left, int mid, int right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + int[] tmp = new int[right - left + 1]; + // 初始化左子陣列和右子陣列的起始索引 + int i = left, j = mid + 1, k = 0; + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } + } + + /* 合併排序 */ + static void mergeSort(int[] nums, int left, int right) { + // 終止條件 + if (left >= right) + return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + int mid = (left + right) / 2; // 計算中點 + mergeSort(nums, left, mid); // 遞迴左子陣列 + mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right); + } + + public static void main(String[] args) { + /* 合併排序 */ + int[] nums = { 7, 3, 2, 6, 0, 1, 5, 4 }; + mergeSort(nums, 0, nums.length - 1); + System.out.println("合併排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/quick_sort.java b/zh-hant/codes/java/chapter_sorting/quick_sort.java new file mode 100644 index 000000000..c4d68a950 --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/quick_sort.java @@ -0,0 +1,158 @@ +/** + * File: quick_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +/* 快速排序類別 */ +class QuickSort { + /* 元素交換 */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + static int partition(int[] nums, int left, int right) { + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + public static void quickSort(int[] nums, int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) + return; + // 哨兵劃分 + int pivot = partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + /* 元素交換 */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 選取三個候選元素的中位數 */ + static int medianThree(int[] nums, int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m 在 l 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l 在 m 和 r 之間 + return right; + } + + /* 哨兵劃分(三數取中值) */ + static int partition(int[] nums, int left, int right) { + // 選取三個候選元素的中位數 + int med = medianThree(nums, left, (left + right) / 2, right); + // 將中位數交換至陣列最左端 + swap(nums, left, med); + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + public static void quickSort(int[] nums, int left, int right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) + return; + // 哨兵劃分 + int pivot = partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + /* 元素交換 */ + static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + static int partition(int[] nums, int left, int right) { + // 以 nums[left] 為基準數 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + public static void quickSort(int[] nums, int left, int right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + int pivot = partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +public class quick_sort { + public static void main(String[] args) { + /* 快速排序 */ + int[] nums = { 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(nums, 0, nums.length - 1); + System.out.println("快速排序完成後 nums = " + Arrays.toString(nums)); + + /* 快速排序(中位基準數最佳化) */ + int[] nums1 = { 2, 4, 1, 0, 3, 5 }; + QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); + System.out.println("快速排序(中位基準數最佳化)完成後 nums1 = " + Arrays.toString(nums1)); + + /* 快速排序(尾遞迴最佳化) */ + int[] nums2 = { 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); + System.out.println("快速排序(尾遞迴最佳化)完成後 nums2 = " + Arrays.toString(nums2)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/radix_sort.java b/zh-hant/codes/java/chapter_sorting/radix_sort.java new file mode 100644 index 000000000..905ed667c --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/radix_sort.java @@ -0,0 +1,68 @@ +/** + * File: radix_sort.java + * Created Time: 2023-01-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class radix_sort { + /* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ + static int digit(int num, int exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10; + } + + /* 計數排序(根據 nums 第 k 位排序) */ + static void countingSortDigit(int[] nums, int exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + int[] counter = new int[10]; + int n = nums.length; + // 統計 0~9 各數字的出現次數 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d]--; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for (int i = 0; i < n; i++) + nums[i] = res[i]; + } + + /* 基數排序 */ + static void radixSort(int[] nums) { + // 獲取陣列的最大元素,用於判斷最大位數 + int m = Integer.MIN_VALUE; + for (int num : nums) + if (num > m) + m = num; + // 按照從低位到高位的順序走訪 + for (int exp = 1; exp <= m; exp *= 10) + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); + } + + public static void main(String[] args) { + // 基數排序 + int[] nums = { 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 }; + radixSort(nums); + System.out.println("基數排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_sorting/selection_sort.java b/zh-hant/codes/java/chapter_sorting/selection_sort.java new file mode 100644 index 000000000..47c14391b --- /dev/null +++ b/zh-hant/codes/java/chapter_sorting/selection_sort.java @@ -0,0 +1,35 @@ +/** + * File: selection_sort.java + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class selection_sort { + /* 選擇排序 */ + public static void selectionSort(int[] nums) { + int n = nums.length; + // 外迴圈:未排序區間為 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 內迴圈:找到未排序區間內的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 記錄最小元素的索引 + } + // 將該最小元素與未排序區間的首個元素交換 + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + selectionSort(nums); + System.out.println("選擇排序完成後 nums = " + Arrays.toString(nums)); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/array_deque.java b/zh-hant/codes/java/chapter_stack_and_queue/array_deque.java new file mode 100644 index 000000000..cf8c4de95 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/array_deque.java @@ -0,0 +1,151 @@ +/** + * File: array_deque.java + * Created Time: 2023-02-16 + * Author: krahets (krahets@163.com), FangYuan33 (374072213@qq.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + private int[] nums; // 用於儲存雙向佇列元素的陣列 + private int front; // 佇列首指標,指向佇列首元素 + private int queSize; // 雙向佇列長度 + + /* 建構子 */ + public ArrayDeque(int capacity) { + this.nums = new int[capacity]; + front = queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + public int capacity() { + return nums.length; + } + + /* 獲取雙向佇列的長度 */ + public int size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + public boolean isEmpty() { + return queSize == 0; + } + + /* 計算環形陣列索引 */ + private int index(int i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 佇列首入列 */ + public void pushFirst(int num) { + if (queSize == capacity()) { + System.out.println("雙向佇列已滿"); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + front = index(front - 1); + // 將 num 新增至佇列首 + nums[front] = num; + queSize++; + } + + /* 佇列尾入列 */ + public void pushLast(int num) { + if (queSize == capacity()) { + System.out.println("雙向佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + int rear = index(front + queSize); + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 佇列首出列 */ + public int popFirst() { + int num = peekFirst(); + // 佇列首指標向後移動一位 + front = index(front + 1); + queSize--; + return num; + } + + /* 佇列尾出列 */ + public int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } + + /* 訪問佇列尾元素 */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + // 計算尾元素索引 + int last = index(front + queSize - 1); + return nums[last]; + } + + /* 返回陣列用於列印 */ + public int[] toArray() { + // 僅轉換有效長度範圍內的串列元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +} + +public class array_deque { + public static void main(String[] args) { + /* 初始化雙向佇列 */ + ArrayDeque deque = new ArrayDeque(10); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + System.out.println("雙向佇列 deque = " + Arrays.toString(deque.toArray())); + + /* 訪問元素 */ + int peekFirst = deque.peekFirst(); + System.out.println("佇列首元素 peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("佇列尾元素 peekLast = " + peekLast); + + /* 元素入列 */ + deque.pushLast(4); + System.out.println("元素 4 佇列尾入列後 deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("元素 1 佇列首入列後 deque = " + Arrays.toString(deque.toArray())); + + /* 元素出列 */ + int popLast = deque.popLast(); + System.out.println("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + Arrays.toString(deque.toArray())); + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + System.out.println("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + boolean isEmpty = deque.isEmpty(); + System.out.println("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/array_queue.java b/zh-hant/codes/java/chapter_stack_and_queue/array_queue.java new file mode 100644 index 000000000..3fc6e4483 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/array_queue.java @@ -0,0 +1,115 @@ +/** + * File: array_queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + private int[] nums; // 用於儲存佇列元素的陣列 + private int front; // 佇列首指標,指向佇列首元素 + private int queSize; // 佇列長度 + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* 獲取佇列的容量 */ + public int capacity() { + return nums.length; + } + + /* 獲取佇列的長度 */ + public int size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + public boolean isEmpty() { + return queSize == 0; + } + + /* 入列 */ + public void push(int num) { + if (queSize == capacity()) { + System.out.println("佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + int rear = (front + queSize) % capacity(); + // 將 num 新增至佇列尾 + nums[rear] = num; + queSize++; + } + + /* 出列 */ + public int pop() { + int num = peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + front = (front + 1) % capacity(); + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return nums[front]; + } + + /* 返回陣列 */ + public int[] toArray() { + // 僅轉換有效長度範圍內的串列元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % capacity()]; + } + return res; + } +} + +public class array_queue { + public static void main(String[] args) { + /* 初始化佇列 */ + int capacity = 10; + ArrayQueue queue = new ArrayQueue(capacity); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + System.out.println("佇列 queue = " + Arrays.toString(queue.toArray())); + + /* 訪問佇列首元素 */ + int peek = queue.peek(); + System.out.println("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.pop(); + System.out.println("出列元素 pop = " + pop + ",出列後 queue = " + Arrays.toString(queue.toArray())); + + /* 獲取佇列的長度 */ + int size = queue.size(); + System.out.println("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + boolean isEmpty = queue.isEmpty(); + System.out.println("佇列是否為空 = " + isEmpty); + + /* 測試環形陣列 */ + for (int i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + System.out.println("第 " + i + " 輪入列 + 出列後 queue = " + Arrays.toString(queue.toArray())); + } + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/array_stack.java b/zh-hant/codes/java/chapter_stack_and_queue/array_stack.java new file mode 100644 index 000000000..44e80f597 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/array_stack.java @@ -0,0 +1,84 @@ +/** + * File: array_stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + private ArrayList stack; + + public ArrayStack() { + // 初始化串列(動態陣列) + stack = new ArrayList<>(); + } + + /* 獲取堆疊的長度 */ + public int size() { + return stack.size(); + } + + /* 判斷堆疊是否為空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入堆疊 */ + public void push(int num) { + stack.add(num); + } + + /* 出堆疊 */ + public int pop() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.remove(size() - 1); + } + + /* 訪問堆疊頂元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.get(size() - 1); + } + + /* 將 List 轉化為 Array 並返回 */ + public Object[] toArray() { + return stack.toArray(); + } +} + +public class array_stack { + public static void main(String[] args) { + /* 初始化堆疊 */ + ArrayStack stack = new ArrayStack(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("堆疊 stack = " + Arrays.toString(stack.toArray())); + + /* 訪問堆疊頂元素 */ + int peek = stack.peek(); + System.out.println("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.pop(); + System.out.println("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + Arrays.toString(stack.toArray())); + + /* 獲取堆疊的長度 */ + int size = stack.size(); + System.out.println("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + boolean isEmpty = stack.isEmpty(); + System.out.println("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/deque.java b/zh-hant/codes/java/chapter_stack_and_queue/deque.java new file mode 100644 index 000000000..23b4b35c4 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/deque.java @@ -0,0 +1,46 @@ +/** + * File: deque.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class deque { + public static void main(String[] args) { + /* 初始化雙向佇列 */ + Deque deque = new LinkedList<>(); + deque.offerLast(3); + deque.offerLast(2); + deque.offerLast(5); + System.out.println("雙向佇列 deque = " + deque); + + /* 訪問元素 */ + int peekFirst = deque.peekFirst(); + System.out.println("佇列首元素 peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("佇列尾元素 peekLast = " + peekLast); + + /* 元素入列 */ + deque.offerLast(4); + System.out.println("元素 4 佇列尾入列後 deque = " + deque); + deque.offerFirst(1); + System.out.println("元素 1 佇列首入列後 deque = " + deque); + + /* 元素出列 */ + int popLast = deque.pollLast(); + System.out.println("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + deque); + int popFirst = deque.pollFirst(); + System.out.println("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + deque); + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + System.out.println("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + boolean isEmpty = deque.isEmpty(); + System.out.println("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_deque.java b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_deque.java new file mode 100644 index 000000000..c3168ae33 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_deque.java @@ -0,0 +1,175 @@ +/** + * File: linkedlist_deque.java + * Created Time: 2023-01-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 雙向鏈結串列節點 */ +class ListNode { + int val; // 節點值 + ListNode next; // 後繼節點引用 + ListNode prev; // 前驅節點引用 + + ListNode(int val) { + this.val = val; + prev = next = null; + } +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + private ListNode front, rear; // 頭節點 front ,尾節點 rear + private int queSize = 0; // 雙向佇列的長度 + + public LinkedListDeque() { + front = rear = null; + } + + /* 獲取雙向佇列的長度 */ + public int size() { + return queSize; + } + + /* 判斷雙向佇列是否為空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入列操作 */ + private void push(int num, boolean isFront) { + ListNode node = new ListNode(num); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (isEmpty()) + front = rear = node; + // 佇列首入列操作 + else if (isFront) { + // 將 node 新增至鏈結串列頭部 + front.prev = node; + node.next = front; + front = node; // 更新頭節點 + // 佇列尾入列操作 + } else { + // 將 node 新增至鏈結串列尾部 + rear.next = node; + node.prev = rear; + rear = node; // 更新尾節點 + } + queSize++; // 更新佇列長度 + } + + /* 佇列首入列 */ + public void pushFirst(int num) { + push(num, true); + } + + /* 佇列尾入列 */ + public void pushLast(int num) { + push(num, false); + } + + /* 出列操作 */ + private int pop(boolean isFront) { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + int val; + // 佇列首出列操作 + if (isFront) { + val = front.val; // 暫存頭節點值 + // 刪除頭節點 + ListNode fNext = front.next; + if (fNext != null) { + fNext.prev = null; + front.next = null; + } + front = fNext; // 更新頭節點 + // 佇列尾出列操作 + } else { + val = rear.val; // 暫存尾節點值 + // 刪除尾節點 + ListNode rPrev = rear.prev; + if (rPrev != null) { + rPrev.next = null; + rear.prev = null; + } + rear = rPrev; // 更新尾節點 + } + queSize--; // 更新佇列長度 + return val; + } + + /* 佇列首出列 */ + public int popFirst() { + return pop(true); + } + + /* 佇列尾出列 */ + public int popLast() { + return pop(false); + } + + /* 訪問佇列首元素 */ + public int peekFirst() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* 訪問佇列尾元素 */ + public int peekLast() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return rear.val; + } + + /* 返回陣列用於列印 */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_deque { + public static void main(String[] args) { + /* 初始化雙向佇列 */ + LinkedListDeque deque = new LinkedListDeque(); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + System.out.println("雙向佇列 deque = " + Arrays.toString(deque.toArray())); + + /* 訪問元素 */ + int peekFirst = deque.peekFirst(); + System.out.println("佇列首元素 peekFirst = " + peekFirst); + int peekLast = deque.peekLast(); + System.out.println("佇列尾元素 peekLast = " + peekLast); + + /* 元素入列 */ + deque.pushLast(4); + System.out.println("元素 4 佇列尾入列後 deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("元素 1 佇列首入列後 deque = " + Arrays.toString(deque.toArray())); + + /* 元素出列 */ + int popLast = deque.popLast(); + System.out.println("佇列尾出列元素 = " + popLast + ",佇列尾出列後 deque = " + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("佇列首出列元素 = " + popFirst + ",佇列首出列後 deque = " + Arrays.toString(deque.toArray())); + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + System.out.println("雙向佇列長度 size = " + size); + + /* 判斷雙向佇列是否為空 */ + boolean isEmpty = deque.isEmpty(); + System.out.println("雙向佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_queue.java b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_queue.java new file mode 100644 index 000000000..994d9446e --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_queue.java @@ -0,0 +1,104 @@ +/** + * File: linkedlist_queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + private ListNode front, rear; // 頭節點 front ,尾節點 rear + private int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* 獲取佇列的長度 */ + public int size() { + return queSize; + } + + /* 判斷佇列是否為空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入列 */ + public void push(int num) { + // 在尾節點後新增 num + ListNode node = new ListNode(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (front == null) { + front = node; + rear = node; + // 如果佇列不為空,則將該節點新增到尾節點後 + } else { + rear.next = node; + rear = node; + } + queSize++; + } + + /* 出列 */ + public int pop() { + int num = peek(); + // 刪除頭節點 + front = front.next; + queSize--; + return num; + } + + /* 訪問佇列首元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return front.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_queue { + public static void main(String[] args) { + /* 初始化佇列 */ + LinkedListQueue queue = new LinkedListQueue(); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + System.out.println("佇列 queue = " + Arrays.toString(queue.toArray())); + + /* 訪問佇列首元素 */ + int peek = queue.peek(); + System.out.println("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.pop(); + System.out.println("出列元素 pop = " + pop + ",出列後 queue = " + Arrays.toString(queue.toArray())); + + /* 獲取佇列的長度 */ + int size = queue.size(); + System.out.println("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + boolean isEmpty = queue.isEmpty(); + System.out.println("佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_stack.java b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_stack.java new file mode 100644 index 000000000..0730e606c --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/linkedlist_stack.java @@ -0,0 +1,95 @@ +/** + * File: linkedlist_stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; +import utils.*; + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + private ListNode stackPeek; // 將頭節點作為堆疊頂 + private int stkSize = 0; // 堆疊的長度 + + public LinkedListStack() { + stackPeek = null; + } + + /* 獲取堆疊的長度 */ + public int size() { + return stkSize; + } + + /* 判斷堆疊是否為空 */ + public boolean isEmpty() { + return size() == 0; + } + + /* 入堆疊 */ + public void push(int num) { + ListNode node = new ListNode(num); + node.next = stackPeek; + stackPeek = node; + stkSize++; + } + + /* 出堆疊 */ + public int pop() { + int num = peek(); + stackPeek = stackPeek.next; + stkSize--; + return num; + } + + /* 訪問堆疊頂元素 */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stackPeek.val; + } + + /* 將 List 轉化為 Array 並返回 */ + public int[] toArray() { + ListNode node = stackPeek; + int[] res = new int[size()]; + for (int i = res.length - 1; i >= 0; i--) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_stack { + public static void main(String[] args) { + /* 初始化堆疊 */ + LinkedListStack stack = new LinkedListStack(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("堆疊 stack = " + Arrays.toString(stack.toArray())); + + /* 訪問堆疊頂元素 */ + int peek = stack.peek(); + System.out.println("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.pop(); + System.out.println("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + Arrays.toString(stack.toArray())); + + /* 獲取堆疊的長度 */ + int size = stack.size(); + System.out.println("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + boolean isEmpty = stack.isEmpty(); + System.out.println("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/queue.java b/zh-hant/codes/java/chapter_stack_and_queue/queue.java new file mode 100644 index 000000000..27670cbc1 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/queue.java @@ -0,0 +1,40 @@ +/** + * File: queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class queue { + public static void main(String[] args) { + /* 初始化佇列 */ + Queue queue = new LinkedList<>(); + + /* 元素入列 */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + queue.offer(4); + System.out.println("佇列 queue = " + queue); + + /* 訪問佇列首元素 */ + int peek = queue.peek(); + System.out.println("佇列首元素 peek = " + peek); + + /* 元素出列 */ + int pop = queue.poll(); + System.out.println("出列元素 pop = " + pop + ",出列後 queue = " + queue); + + /* 獲取佇列的長度 */ + int size = queue.size(); + System.out.println("佇列長度 size = " + size); + + /* 判斷佇列是否為空 */ + boolean isEmpty = queue.isEmpty(); + System.out.println("佇列是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_stack_and_queue/stack.java b/zh-hant/codes/java/chapter_stack_and_queue/stack.java new file mode 100644 index 000000000..212a51b85 --- /dev/null +++ b/zh-hant/codes/java/chapter_stack_and_queue/stack.java @@ -0,0 +1,40 @@ +/** + * File: stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class stack { + public static void main(String[] args) { + /* 初始化堆疊 */ + Stack stack = new Stack<>(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + System.out.println("堆疊 stack = " + stack); + + /* 訪問堆疊頂元素 */ + int peek = stack.peek(); + System.out.println("堆疊頂元素 peek = " + peek); + + /* 元素出堆疊 */ + int pop = stack.pop(); + System.out.println("出堆疊元素 pop = " + pop + ",出堆疊後 stack = " + stack); + + /* 獲取堆疊的長度 */ + int size = stack.size(); + System.out.println("堆疊的長度 size = " + size); + + /* 判斷是否為空 */ + boolean isEmpty = stack.isEmpty(); + System.out.println("堆疊是否為空 = " + isEmpty); + } +} diff --git a/zh-hant/codes/java/chapter_tree/array_binary_tree.java b/zh-hant/codes/java/chapter_tree/array_binary_tree.java new file mode 100644 index 000000000..34278bfbc --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/array_binary_tree.java @@ -0,0 +1,136 @@ +/** + * File: array_binary_tree.java + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + private List tree; + + /* 建構子 */ + public ArrayBinaryTree(List arr) { + tree = new ArrayList<>(arr); + } + + /* 串列容量 */ + public int size() { + return tree.size(); + } + + /* 獲取索引為 i 節點的值 */ + public Integer val(int i) { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= size()) + return null; + return tree.get(i); + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + public Integer left(int i) { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + public Integer right(int i) { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + public Integer parent(int i) { + return (i - 1) / 2; + } + + /* 層序走訪 */ + public List levelOrder() { + List res = new ArrayList<>(); + // 直接走訪陣列 + for (int i = 0; i < size(); i++) { + if (val(i) != null) + res.add(val(i)); + } + return res; + } + + /* 深度優先走訪 */ + private void dfs(Integer i, String order, List res) { + // 若為空位,則返回 + if (val(i) == null) + return; + // 前序走訪 + if ("pre".equals(order)) + res.add(val(i)); + dfs(left(i), order, res); + // 中序走訪 + if ("in".equals(order)) + res.add(val(i)); + dfs(right(i), order, res); + // 後序走訪 + if ("post".equals(order)) + res.add(val(i)); + } + + /* 前序走訪 */ + public List preOrder() { + List res = new ArrayList<>(); + dfs(0, "pre", res); + return res; + } + + /* 中序走訪 */ + public List inOrder() { + List res = new ArrayList<>(); + dfs(0, "in", res); + return res; + } + + /* 後序走訪 */ + public List postOrder() { + List res = new ArrayList<>(); + dfs(0, "post", res); + return res; + } +} + +public class array_binary_tree { + public static void main(String[] args) { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + List arr = Arrays.asList(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15); + + TreeNode root = TreeNode.listToTree(arr); + System.out.println("\n初始化二元樹\n"); + System.out.println("二元樹的陣列表示:"); + System.out.println(arr); + System.out.println("二元樹的鏈結串列表示:"); + PrintUtil.printTree(root); + + // 陣列表示下的二元樹類別 + ArrayBinaryTree abt = new ArrayBinaryTree(arr); + + // 訪問節點 + int i = 1; + Integer l = abt.left(i); + Integer r = abt.right(i); + Integer p = abt.parent(i); + System.out.println("\n當前節點的索引為 " + i + " ,值為 " + abt.val(i)); + System.out.println("其左子節點的索引為 " + l + " ,值為 " + (l == null ? "null" : abt.val(l))); + System.out.println("其右子節點的索引為 " + r + " ,值為 " + (r == null ? "null" : abt.val(r))); + System.out.println("其父節點的索引為 " + p + " ,值為 " + (p == null ? "null" : abt.val(p))); + + // 走訪樹 + List res = abt.levelOrder(); + System.out.println("\n層序走訪為:" + res); + res = abt.preOrder(); + System.out.println("前序走訪為:" + res); + res = abt.inOrder(); + System.out.println("中序走訪為:" + res); + res = abt.postOrder(); + System.out.println("後序走訪為:" + res); + } +} diff --git a/zh-hant/codes/java/chapter_tree/avl_tree.java b/zh-hant/codes/java/chapter_tree/avl_tree.java new file mode 100644 index 000000000..f6124530c --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/avl_tree.java @@ -0,0 +1,220 @@ +/** + * File: avl_tree.java + * Created Time: 2022-12-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +/* AVL 樹 */ +class AVLTree { + TreeNode root; // 根節點 + + /* 獲取節點高度 */ + public int height(TreeNode node) { + // 空節點高度為 -1 ,葉節點高度為 0 + return node == null ? -1 : node.height; + } + + /* 更新節點高度 */ + private void updateHeight(TreeNode node) { + // 節點高度等於最高子樹高度 + 1 + node.height = Math.max(height(node.left), height(node.right)) + 1; + } + + /* 獲取平衡因子 */ + public int balanceFactor(TreeNode node) { + // 空節點平衡因子為 0 + if (node == null) + return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node.left) - height(node.right); + } + + /* 右旋操作 */ + private TreeNode rightRotate(TreeNode node) { + TreeNode child = node.left; + TreeNode grandChild = child.right; + // 以 child 為原點,將 node 向右旋轉 + child.right = node; + node.left = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + private TreeNode leftRotate(TreeNode node) { + TreeNode child = node.right; + TreeNode grandChild = child.left; + // 以 child 為原點,將 node 向左旋轉 + child.left = node; + node.right = grandChild; + // 更新節點高度 + updateHeight(node); + updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + private TreeNode rotate(TreeNode node) { + // 獲取節點 node 的平衡因子 + int balanceFactor = balanceFactor(node); + // 左偏樹 + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋後右旋 + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // 右偏樹 + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋後左旋 + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 插入節點 */ + public void insert(int val) { + root = insertHelper(root, val); + } + + /* 遞迴插入節點(輔助方法) */ + private TreeNode insertHelper(TreeNode node, int val) { + if (node == null) + return new TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // 重複節點不插入,直接返回 + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 刪除節點 */ + public void remove(int val) { + root = removeHelper(root, val); + } + + /* 遞迴刪除節點(輔助方法) */ + private TreeNode removeHelper(TreeNode node, int val) { + if (node == null) + return null; + /* 1. 查詢節點並刪除 */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode child = node.left != null ? node.left : node.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == null) + return null; + // 子節點數量 = 1 ,直接刪除 node + else + node = child; + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + TreeNode temp = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 查詢節點 */ + public TreeNode search(int val) { + TreeNode cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < val) + cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > val) + cur = cur.left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } +} + +public class avl_tree { + static void testInsert(AVLTree tree, int val) { + tree.insert(val); + System.out.println("\n插入節點 " + val + " 後,AVL 樹為"); + PrintUtil.printTree(tree.root); + } + + static void testRemove(AVLTree tree, int val) { + tree.remove(val); + System.out.println("\n刪除節點 " + val + " 後,AVL 樹為"); + PrintUtil.printTree(tree.root); + } + + public static void main(String[] args) { + /* 初始化空 AVL 樹 */ + AVLTree avlTree = new AVLTree(); + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 插入重複節點 */ + testInsert(avlTree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(avlTree, 8); // 刪除度為 0 的節點 + testRemove(avlTree, 5); // 刪除度為 1 的節點 + testRemove(avlTree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + TreeNode node = avlTree.search(7); + System.out.println("\n查詢到的節點物件為 " + node + ",節點值 = " + node.val); + } +} diff --git a/zh-hant/codes/java/chapter_tree/binary_search_tree.java b/zh-hant/codes/java/chapter_tree/binary_search_tree.java new file mode 100644 index 000000000..00a906540 --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/binary_search_tree.java @@ -0,0 +1,158 @@ +/** + * File: binary_search_tree.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +/* 二元搜尋樹 */ +class BinarySearchTree { + private TreeNode root; + + /* 建構子 */ + public BinarySearchTree() { + // 初始化空樹 + root = null; + } + + /* 獲取二元樹根節點 */ + public TreeNode getRoot() { + return root; + } + + /* 查詢節點 */ + public TreeNode search(int num) { + TreeNode cur = root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < num) + cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > num) + cur = cur.left; + // 找到目標節點,跳出迴圈 + else + break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + public void insert(int num) { + // 若樹為空,則初始化根節點 + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode cur = root, pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到重複節點,直接返回 + if (cur.val == num) + return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.val < num) + cur = cur.right; + // 插入位置在 cur 的左子樹中 + else + cur = cur.left; + } + // 插入節點 + TreeNode node = new TreeNode(num); + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + + /* 刪除節點 */ + public void remove(int num) { + // 若樹為空,直接提前返回 + if (root == null) + return; + TreeNode cur = root, pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到待刪除節點,跳出迴圈 + if (cur.val == num) + break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.val < num) + cur = cur.right; + // 待刪除節點在 cur 的左子樹中 + else + cur = cur.left; + } + // 若無待刪除節點,則直接返回 + if (cur == null) + return; + // 子節點數量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + TreeNode child = cur.left != null ? cur.left : cur.right; + // 刪除節點 cur + if (cur != root) { + if (pre.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + root = child; + } + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + TreeNode tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // 遞迴刪除節點 tmp + remove(tmp.val); + // 用 tmp 覆蓋 cur + cur.val = tmp.val; + } + } +} + +public class binary_search_tree { + public static void main(String[] args) { + /* 初始化二元搜尋樹 */ + BinarySearchTree bst = new BinarySearchTree(); + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + int[] nums = { 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 }; + for (int num : nums) { + bst.insert(num); + } + System.out.println("\n初始化的二元樹為\n"); + PrintUtil.printTree(bst.getRoot()); + + /* 查詢節點 */ + TreeNode node = bst.search(7); + System.out.println("\n查詢到的節點物件為 " + node + ",節點值 = " + node.val); + + /* 插入節點 */ + bst.insert(16); + System.out.println("\n插入節點 16 後,二元樹為\n"); + PrintUtil.printTree(bst.getRoot()); + + /* 刪除節點 */ + bst.remove(1); + System.out.println("\n刪除節點 1 後,二元樹為\n"); + PrintUtil.printTree(bst.getRoot()); + bst.remove(2); + System.out.println("\n刪除節點 2 後,二元樹為\n"); + PrintUtil.printTree(bst.getRoot()); + bst.remove(4); + System.out.println("\n刪除節點 4 後,二元樹為\n"); + PrintUtil.printTree(bst.getRoot()); + } +} diff --git a/zh-hant/codes/java/chapter_tree/binary_tree.java b/zh-hant/codes/java/chapter_tree/binary_tree.java new file mode 100644 index 000000000..81b8946dc --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/binary_tree.java @@ -0,0 +1,40 @@ +/** + * File: binary_tree.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +public class binary_tree { + public static void main(String[] args) { + /* 初始化二元樹 */ + // 初始化節點 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + System.out.println("\n初始化二元樹\n"); + PrintUtil.printTree(n1); + + /* 插入與刪除節點 */ + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + System.out.println("\n插入節點 P 後\n"); + PrintUtil.printTree(n1); + // 刪除節點 P + n1.left = n2; + System.out.println("\n刪除節點 P 後\n"); + PrintUtil.printTree(n1); + } +} diff --git a/zh-hant/codes/java/chapter_tree/binary_tree_bfs.java b/zh-hant/codes/java/chapter_tree/binary_tree_bfs.java new file mode 100644 index 000000000..1c2711f57 --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/binary_tree_bfs.java @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +public class binary_tree_bfs { + /* 層序走訪 */ + static List levelOrder(TreeNode root) { + // 初始化佇列,加入根節點 + Queue queue = new LinkedList<>(); + queue.add(root); + // 初始化一個串列,用於儲存走訪序列 + List list = new ArrayList<>(); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); // 隊列出隊 + list.add(node.val); // 儲存節點值 + if (node.left != null) + queue.offer(node.left); // 左子節點入列 + if (node.right != null) + queue.offer(node.right); // 右子節點入列 + } + return list; + } + + public static void main(String[] args) { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹\n"); + PrintUtil.printTree(root); + + /* 層序走訪 */ + List list = levelOrder(root); + System.out.println("\n層序走訪的節點列印序列 = " + list); + } +} diff --git a/zh-hant/codes/java/chapter_tree/binary_tree_dfs.java b/zh-hant/codes/java/chapter_tree/binary_tree_dfs.java new file mode 100644 index 000000000..e4dfdc3f5 --- /dev/null +++ b/zh-hant/codes/java/chapter_tree/binary_tree_dfs.java @@ -0,0 +1,68 @@ +/** + * File: binary_tree_dfs.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +public class binary_tree_dfs { + // 初始化串列,用於儲存走訪序列 + static ArrayList list = new ArrayList<>(); + + /* 前序走訪 */ + static void preOrder(TreeNode root) { + if (root == null) + return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.add(root.val); + preOrder(root.left); + preOrder(root.right); + } + + /* 中序走訪 */ + static void inOrder(TreeNode root) { + if (root == null) + return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root.left); + list.add(root.val); + inOrder(root.right); + } + + /* 後序走訪 */ + static void postOrder(TreeNode root) { + if (root == null) + return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root.left); + postOrder(root.right); + list.add(root.val); + } + + public static void main(String[] args) { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); + System.out.println("\n初始化二元樹\n"); + PrintUtil.printTree(root); + + /* 前序走訪 */ + list.clear(); + preOrder(root); + System.out.println("\n前序走訪的節點列印序列 = " + list); + + /* 中序走訪 */ + list.clear(); + inOrder(root); + System.out.println("\n中序走訪的節點列印序列 = " + list); + + /* 後序走訪 */ + list.clear(); + postOrder(root); + System.out.println("\n後序走訪的節點列印序列 = " + list); + } +} diff --git a/zh-hant/codes/java/utils/ListNode.java b/zh-hant/codes/java/utils/ListNode.java new file mode 100755 index 000000000..e1ce13631 --- /dev/null +++ b/zh-hant/codes/java/utils/ListNode.java @@ -0,0 +1,28 @@ +/** + * File: ListNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +/* 鏈結串列節點 */ +public class ListNode { + public int val; + public ListNode next; + + public ListNode(int x) { + val = x; + } + + /* 將串列反序列化為鏈結串列 */ + public static ListNode arrToLinkedList(int[] arr) { + ListNode dum = new ListNode(0); + ListNode head = dum; + for (int val : arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; + } +} diff --git a/zh-hant/codes/java/utils/PrintUtil.java b/zh-hant/codes/java/utils/PrintUtil.java new file mode 100755 index 000000000..490cd3c16 --- /dev/null +++ b/zh-hant/codes/java/utils/PrintUtil.java @@ -0,0 +1,116 @@ +/** + * File: PrintUtil.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +class Trunk { + Trunk prev; + String str; + + Trunk(Trunk prev, String str) { + this.prev = prev; + this.str = str; + } +}; + +public class PrintUtil { + /* 列印矩陣(Array) */ + public static void printMatrix(T[][] matrix) { + System.out.println("["); + for (T[] row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* 列印矩陣(List) */ + public static void printMatrix(List> matrix) { + System.out.println("["); + for (List row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* 列印鏈結串列 */ + public static void printLinkedList(ListNode head) { + List list = new ArrayList<>(); + while (head != null) { + list.add(String.valueOf(head.val)); + head = head.next; + } + System.out.println(String.join(" -> ", list)); + } + + /* 列印二元樹 */ + public static void printTree(TreeNode root) { + printTree(root, null, false); + } + + /** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ + public static void printTree(TreeNode root, Trunk prev, boolean isRight) { + if (root == null) { + return; + } + + String prev_str = " "; + Trunk trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.str = prev_str; + } + + showTrunks(trunk); + System.out.println(" " + root.val); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = " |"; + + printTree(root.left, trunk, false); + } + + public static void showTrunks(Trunk p) { + if (p == null) { + return; + } + + showTrunks(p.prev); + System.out.print(p.str); + } + + /* 列印雜湊表 */ + public static void printHashMap(Map map) { + for (Map.Entry kv : map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + } + + /* 列印堆積(優先佇列) */ + public static void printHeap(Queue queue) { + List list = new ArrayList<>(queue); + System.out.print("堆積的陣列表示:"); + System.out.println(list); + System.out.println("堆積的樹狀表示:"); + TreeNode root = TreeNode.listToTree(list); + printTree(root); + } +} diff --git a/zh-hant/codes/java/utils/TreeNode.java b/zh-hant/codes/java/utils/TreeNode.java new file mode 100644 index 000000000..b3495d457 --- /dev/null +++ b/zh-hant/codes/java/utils/TreeNode.java @@ -0,0 +1,73 @@ +/** + * File: TreeNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* 二元樹節點類別 */ +public class TreeNode { + public int val; // 節點值 + public int height; // 節點高度 + public TreeNode left; // 左子節點引用 + public TreeNode right; // 右子節點引用 + + /* 建構子 */ + public TreeNode(int x) { + val = x; + } + + // 序列化編碼規則請參考: + // 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 + + /* 將串列反序列化為二元樹:遞迴 */ + private static TreeNode listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.size() || arr.get(i) == null) { + return null; + } + TreeNode root = new TreeNode(arr.get(i)); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + return root; + } + + /* 將串列反序列化為二元樹 */ + public static TreeNode listToTree(List arr) { + return listToTreeDFS(arr, 0); + } + + /* 將二元樹序列化為串列:遞迴 */ + private static void treeToListDFS(TreeNode root, int i, List res) { + if (root == null) + return; + while (i >= res.size()) { + res.add(null); + } + res.set(i, root.val); + treeToListDFS(root.left, 2 * i + 1, res); + treeToListDFS(root.right, 2 * i + 2, res); + } + + /* 將二元樹序列化為串列 */ + public static List treeToList(TreeNode root) { + List res = new ArrayList<>(); + treeToListDFS(root, 0, res); + return res; + } +} diff --git a/zh-hant/codes/java/utils/Vertex.java b/zh-hant/codes/java/utils/Vertex.java new file mode 100644 index 000000000..5de8bf3c8 --- /dev/null +++ b/zh-hant/codes/java/utils/Vertex.java @@ -0,0 +1,36 @@ +/** + * File: Vertex.java + * Created Time: 2023-02-15 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* 頂點類別 */ +public class Vertex { + public int val; + + public Vertex(int val) { + this.val = val; + } + + /* 輸入值串列 vals ,返回頂點串列 vets */ + public static Vertex[] valsToVets(int[] vals) { + Vertex[] vets = new Vertex[vals.length]; + for (int i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + public static List vetsToVals(List vets) { + List vals = new ArrayList<>(); + for (Vertex vet : vets) { + vals.add(vet.val); + } + return vals; + } +} diff --git a/zh-hant/codes/javascript/.prettierrc b/zh-hant/codes/javascript/.prettierrc new file mode 100644 index 000000000..3f4aa8cb6 --- /dev/null +++ b/zh-hant/codes/javascript/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true +} diff --git a/zh-hant/codes/javascript/chapter_array_and_linkedlist/array.js b/zh-hant/codes/javascript/chapter_array_and_linkedlist/array.js new file mode 100644 index 000000000..d54e06d7e --- /dev/null +++ b/zh-hant/codes/javascript/chapter_array_and_linkedlist/array.js @@ -0,0 +1,97 @@ +/** + * File: array.js + * Created Time: 2022-11-27 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 隨機訪問元素 */ +function randomAccess(nums) { + // 在區間 [0, nums.length) 中隨機抽取一個數字 + const random_index = Math.floor(Math.random() * nums.length); + // 獲取並返回隨機元素 + const random_num = nums[random_index]; + return random_num; +} + +/* 擴展陣列長度 */ +// 請注意,JavaScript 的 Array 是動態陣列,可以直接擴展 +// 為了方便學習,本函式將 Array 看作長度不可變的陣列 +function extend(nums, enlarge) { + // 初始化一個擴展長度後的陣列 + const res = new Array(nums.length + enlarge).fill(0); + // 將原陣列中的所有元素複製到新陣列 + for (let i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + return res; +} + +/* 在陣列的索引 index 處插入元素 num */ +function insert(nums, num, index) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (let i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +/* 刪除索引 index 處的元素 */ +function remove(nums, index) { + // 把索引 index 之後的所有元素向前移動一位 + for (let i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列 */ +function traverse(nums) { + let count = 0; + // 透過索引走訪陣列 + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 直接走訪陣列元素 + for (const num of nums) { + count += num; + } +} + +/* 在陣列中查詢指定元素 */ +function find(nums, target) { + for (let i = 0; i < nums.length; i++) { + if (nums[i] === target) return i; + } + return -1; +} + +/* Driver Code */ +/* 初始化陣列 */ +const arr = new Array(5).fill(0); +console.log('陣列 arr =', arr); +let nums = [1, 3, 2, 5, 4]; +console.log('陣列 nums =', nums); + +/* 隨機訪問 */ +let random_num = randomAccess(nums); +console.log('在 nums 中獲取隨機元素', random_num); + +/* 長度擴展 */ +nums = extend(nums, 3); +console.log('將陣列長度擴展至 8 ,得到 nums =', nums); + +/* 插入元素 */ +insert(nums, 6, 3); +console.log('在索引 3 處插入數字 6 ,得到 nums =', nums); + +/* 刪除元素 */ +remove(nums, 2); +console.log('刪除索引 2 處的元素,得到 nums =', nums); + +/* 走訪陣列 */ +traverse(nums); + +/* 查詢元素 */ +let index = find(nums, 3); +console.log('在 nums 中查詢元素 3 ,得到索引 =', index); diff --git a/zh-hant/codes/javascript/chapter_array_and_linkedlist/linked_list.js b/zh-hant/codes/javascript/chapter_array_and_linkedlist/linked_list.js new file mode 100644 index 000000000..7abb68baa --- /dev/null +++ b/zh-hant/codes/javascript/chapter_array_and_linkedlist/linked_list.js @@ -0,0 +1,82 @@ +/** + * File: linked_list.js + * Created Time: 2022-12-12 + * Author: IsChristina (christinaxia77@foxmail.com), Justin (xiefahit@gmail.com) + */ + +const { printLinkedList } = require('../modules/PrintUtil'); +const { ListNode } = require('../modules/ListNode'); + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +function insert(n0, P) { + const n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +function remove(n0) { + if (!n0.next) return; + // n0 -> P -> n1 + const P = n0.next; + const n1 = P.next; + n0.next = n1; +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +function access(head, index) { + for (let i = 0; i < index; i++) { + if (!head) { + return null; + } + head = head.next; + } + return head; +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +function find(head, target) { + let index = 0; + while (head !== null) { + if (head.val === target) { + return index; + } + head = head.next; + index += 1; + } + return -1; +} + +/* Driver Code */ +/* 初始化鏈結串列 */ +// 初始化各個節點 +const n0 = new ListNode(1); +const n1 = new ListNode(3); +const n2 = new ListNode(2); +const n3 = new ListNode(5); +const n4 = new ListNode(4); +// 構建節點之間的引用 +n0.next = n1; +n1.next = n2; +n2.next = n3; +n3.next = n4; +console.log('初始化的鏈結串列為'); +printLinkedList(n0); + +/* 插入節點 */ +insert(n0, new ListNode(0)); +console.log('插入節點後的鏈結串列為'); +printLinkedList(n0); + +/* 刪除節點 */ +remove(n0); +console.log('刪除節點後的鏈結串列為'); +printLinkedList(n0); + +/* 訪問節點 */ +const node = access(n0, 3); +console.log('鏈結串列中索引 3 處的節點的值 = ' + node.val); + +/* 查詢節點 */ +const index = find(n0, 2); +console.log('鏈結串列中值為 2 的節點的索引 = ' + index); diff --git a/zh-hant/codes/javascript/chapter_array_and_linkedlist/list.js b/zh-hant/codes/javascript/chapter_array_and_linkedlist/list.js new file mode 100644 index 000000000..80f145757 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_array_and_linkedlist/list.js @@ -0,0 +1,57 @@ +/** + * File: list.js + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 初始化串列 */ +const nums = [1, 3, 2, 5, 4]; +console.log(`串列 nums = ${nums}`); + +/* 訪問元素 */ +const num = nums[1]; +console.log(`訪問索引 1 處的元素,得到 num = ${num}`); + +/* 更新元素 */ +nums[1] = 0; +console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums}`); + +/* 清空串列 */ +nums.length = 0; +console.log(`清空串列後 nums = ${nums}`); + +/* 在尾部新增元素 */ +nums.push(1); +nums.push(3); +nums.push(2); +nums.push(5); +nums.push(4); +console.log(`新增元素後 nums = ${nums}`); + +/* 在中間插入元素 */ +nums.splice(3, 0, 6); +console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums}`); + +/* 刪除元素 */ +nums.splice(3, 1); +console.log(`刪除索引 3 處的元素,得到 nums = ${nums}`); + +/* 透過索引走訪串列 */ +let count = 0; +for (let i = 0; i < nums.length; i++) { + count += nums[i]; +} +/* 直接走訪串列元素 */ +count = 0; +for (const x of nums) { + count += x; +} + +/* 拼接兩個串列 */ +const nums1 = [6, 8, 7, 10, 9]; +nums.push(...nums1); +console.log(`將串列 nums1 拼接到 nums 之後,得到 nums = ${nums}`); + +/* 排序串列 */ +nums.sort((a, b) => a - b); +console.log(`排序串列後 nums = ${nums}`); diff --git a/zh-hant/codes/javascript/chapter_array_and_linkedlist/my_list.js b/zh-hant/codes/javascript/chapter_array_and_linkedlist/my_list.js new file mode 100644 index 000000000..0018da834 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_array_and_linkedlist/my_list.js @@ -0,0 +1,141 @@ +/** + * File: my_list.js + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 串列類別 */ +class MyList { + #arr = new Array(); // 陣列(儲存串列元素) + #capacity = 10; // 串列容量 + #size = 0; // 串列長度(當前元素數量) + #extendRatio = 2; // 每次串列擴容的倍數 + + /* 建構子 */ + constructor() { + this.#arr = new Array(this.#capacity); + } + + /* 獲取串列長度(當前元素數量)*/ + size() { + return this.#size; + } + + /* 獲取串列容量 */ + capacity() { + return this.#capacity; + } + + /* 訪問元素 */ + get(index) { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + return this.#arr[index]; + } + + /* 更新元素 */ + set(index, num) { + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + this.#arr[index] = num; + } + + /* 在尾部新增元素 */ + add(num) { + // 如果長度等於容量,則需要擴容 + if (this.#size === this.#capacity) { + this.extendCapacity(); + } + // 將新元素新增到串列尾部 + this.#arr[this.#size] = num; + this.#size++; + } + + /* 在中間插入元素 */ + insert(index, num) { + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + // 元素數量超出容量時,觸發擴容機制 + if (this.#size === this.#capacity) { + this.extendCapacity(); + } + // 將索引 index 以及之後的元素都向後移動一位 + for (let j = this.#size - 1; j >= index; j--) { + this.#arr[j + 1] = this.#arr[j]; + } + // 更新元素數量 + this.#arr[index] = num; + this.#size++; + } + + /* 刪除元素 */ + remove(index) { + if (index < 0 || index >= this.#size) throw new Error('索引越界'); + let num = this.#arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for (let j = index; j < this.#size - 1; j++) { + this.#arr[j] = this.#arr[j + 1]; + } + // 更新元素數量 + this.#size--; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + extendCapacity() { + // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 + this.#arr = this.#arr.concat( + new Array(this.capacity() * (this.#extendRatio - 1)) + ); + // 更新串列容量 + this.#capacity = this.#arr.length; + } + + /* 將串列轉換為陣列 */ + toArray() { + let size = this.size(); + // 僅轉換有效長度範圍內的串列元素 + const arr = new Array(size); + for (let i = 0; i < size; i++) { + arr[i] = this.get(i); + } + return arr; + } +} + +/* Driver Code */ +/* 初始化串列 */ +const nums = new MyList(); +/* 在尾部新增元素 */ +nums.add(1); +nums.add(3); +nums.add(2); +nums.add(5); +nums.add(4); +console.log( + `串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` +); + +/* 在中間插入元素 */ +nums.insert(3, 6); +console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums.toArray()}`); + +/* 刪除元素 */ +nums.remove(3); +console.log(`刪除索引 3 處的元素,得到 nums = ${nums.toArray()}`); + +/* 訪問元素 */ +const num = nums.get(1); +console.log(`訪問索引 1 處的元素,得到 num = ${num}`); + +/* 更新元素 */ +nums.set(1, 0); +console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums.toArray()}`); + +/* 測試擴容機制 */ +for (let i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i); +} +console.log( + `擴容後的串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` +); diff --git a/zh-hant/codes/javascript/chapter_backtracking/n_queens.js b/zh-hant/codes/javascript/chapter_backtracking/n_queens.js new file mode 100644 index 000000000..90b089668 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/n_queens.js @@ -0,0 +1,55 @@ +/** + * File: n_queens.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:n 皇后 */ +function backtrack(row, n, state, res, cols, diags1, diags2) { + // 當放置完所有行時,記錄解 + if (row === n) { + res.push(state.map((row) => row.slice())); + return; + } + // 走訪所有列 + for (let col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + const diag1 = row - col + n - 1; + const diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +function nQueens(n) { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + const state = Array.from({ length: n }, () => Array(n).fill('#')); + const cols = Array(n).fill(false); // 記錄列是否有皇后 + const diags1 = Array(2 * n - 1).fill(false); // 記錄主對角線上是否有皇后 + const diags2 = Array(2 * n - 1).fill(false); // 記錄次對角線上是否有皇后 + const res = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + return res; +} + +// Driver Code +const n = 4; +const res = nQueens(n); + +console.log(`輸入棋盤長寬為 ${n}`); +console.log(`皇后放置方案共有 ${res.length} 種`); +res.forEach((state) => { + console.log('--------------------'); + state.forEach((row) => console.log(row)); +}); diff --git a/zh-hant/codes/javascript/chapter_backtracking/permutations_i.js b/zh-hant/codes/javascript/chapter_backtracking/permutations_i.js new file mode 100644 index 000000000..82826c577 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/permutations_i.js @@ -0,0 +1,42 @@ +/** + * File: permutations_i.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:全排列 I */ +function backtrack(state, choices, selected, res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 走訪所有選擇 + choices.forEach((choice, i) => { + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 I */ +function permutationsI(nums) { + const res = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums = [1, 2, 3]; +const res = permutationsI(nums); + +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); diff --git a/zh-hant/codes/javascript/chapter_backtracking/permutations_ii.js b/zh-hant/codes/javascript/chapter_backtracking/permutations_ii.js new file mode 100644 index 000000000..9e60e7a94 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/permutations_ii.js @@ -0,0 +1,44 @@ +/** + * File: permutations_ii.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:全排列 II */ +function backtrack(state, choices, selected, res) { + // 當狀態長度等於元素數量時,記錄解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 走訪所有選擇 + const duplicated = new Set(); + choices.forEach((choice, i) => { + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.has(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.add(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 II */ +function permutationsII(nums) { + const res = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums = [1, 2, 2]; +const res = permutationsII(nums); + +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); diff --git a/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js new file mode 100644 index 000000000..4b1e6f061 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js @@ -0,0 +1,33 @@ +/** + * File: preorder_traversal_i_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前序走訪:例題一 */ +function preOrder(root, res) { + if (root === null) { + return; + } + if (root.val === 7) { + // 記錄解 + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const res = []; +preOrder(root, res); + +console.log('\n輸出所有值為 7 的節點'); +console.log(res.map((node) => node.val)); diff --git a/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js new file mode 100644 index 000000000..c8b7bf72f --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js @@ -0,0 +1,40 @@ +/** + * File: preorder_traversal_ii_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前序走訪:例題二 */ +function preOrder(root, path, res) { + if (root === null) { + return; + } + // 嘗試 + path.push(root); + if (root.val === 7) { + // 記錄解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const path = []; +const res = []; +preOrder(root, path, res); + +console.log('\n輸出所有根節點到節點 7 的路徑'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js new file mode 100644 index 000000000..cc4ebb99c --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js @@ -0,0 +1,41 @@ +/** + * File: preorder_traversal_iii_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前序走訪:例題三 */ +function preOrder(root, path, res) { + // 剪枝 + if (root === null || root.val === 3) { + return; + } + // 嘗試 + path.push(root); + if (root.val === 7) { + // 記錄解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const path = []; +const res = []; +preOrder(root, path, res); + +console.log('\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js new file mode 100644 index 000000000..48a1c34ac --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js @@ -0,0 +1,68 @@ +/** + * File: preorder_traversal_iii_template.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 判斷當前狀態是否為解 */ +function isSolution(state) { + return state && state[state.length - 1]?.val === 7; +} + +/* 記錄解 */ +function recordSolution(state, res) { + res.push([...state]); +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +function isValid(state, choice) { + return choice !== null && choice.val !== 3; +} + +/* 更新狀態 */ +function makeChoice(state, choice) { + state.push(choice); +} + +/* 恢復狀態 */ +function undoChoice(state) { + state.pop(); +} + +/* 回溯演算法:例題三 */ +function backtrack(state, choices, res) { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + } + // 走訪所有選擇 + for (const choice of choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + // 進行下一輪選擇 + backtrack(state, [choice.left, choice.right], res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state); + } + } +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 回溯演算法 +const res = []; +backtrack([], [root], res); + +console.log('\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i.js b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i.js new file mode 100644 index 000000000..76782849a --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i.js @@ -0,0 +1,46 @@ +/** + * File: subset_sum_i.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +function backtrack(state, target, choices, start, res) { + // 子集和等於 target 時,記錄解 + if (target === 0) { + res.push([...state]); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I */ +function subsetSumI(nums, target) { + const state = []; // 狀態(子集) + nums.sort((a, b) => a - b); // 對 nums 進行排序 + const start = 0; // 走訪起始點 + const res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumI(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); diff --git a/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i_naive.js b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i_naive.js new file mode 100644 index 000000000..5b4d0b253 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_i_naive.js @@ -0,0 +1,44 @@ +/** + * File: subset_sum_i_naive.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +function backtrack(state, target, total, choices, res) { + // 子集和等於 target 時,記錄解 + if (total === target) { + res.push([...state]); + return; + } + // 走訪所有選擇 + for (let i = 0; i < choices.length; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I(包含重複子集) */ +function subsetSumINaive(nums, target) { + const state = []; // 狀態(子集) + const total = 0; // 子集和 + const res = []; // 結果串列(子集串列) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumINaive(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); +console.log('請注意,該方法輸出的結果包含重複集合'); diff --git a/zh-hant/codes/javascript/chapter_backtracking/subset_sum_ii.js b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_ii.js new file mode 100644 index 000000000..282359607 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_backtracking/subset_sum_ii.js @@ -0,0 +1,51 @@ +/** + * File: subset_sum_ii.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 II */ +function backtrack(state, target, choices, start, res) { + // 子集和等於 target 時,記錄解 + if (target === 0) { + res.push([...state]); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] === choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 II */ +function subsetSumII(nums, target) { + const state = []; // 狀態(子集) + nums.sort((a, b) => a - b); // 對 nums 進行排序 + const start = 0; // 走訪起始點 + const res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [4, 4, 5]; +const target = 9; +const res = subsetSumII(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); diff --git a/zh-hant/codes/javascript/chapter_computational_complexity/iteration.js b/zh-hant/codes/javascript/chapter_computational_complexity/iteration.js new file mode 100644 index 000000000..d43abf3e5 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_computational_complexity/iteration.js @@ -0,0 +1,70 @@ +/** + * File: iteration.js + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* for 迴圈 */ +function forLoop(n) { + let res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 迴圈 */ +function whileLoop(n) { + let res = 0; + let i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; +} + +/* while 迴圈(兩次更新) */ +function whileLoopII(n) { + let res = 0; + let i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; +} + +/* 雙層 for 迴圈 */ +function nestedForLoop(n) { + let res = ''; + // 迴圈 i = 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (let j = 1; j <= n; j++) { + res += `(${i}, ${j}), `; + } + } + return res; +} + +/* Driver Code */ +const n = 5; +let res; + +res = forLoop(n); +console.log(`for 迴圈的求和結果 res = ${res}`); + +res = whileLoop(n); +console.log(`while 迴圈的求和結果 res = ${res}`); + +res = whileLoopII(n); +console.log(`while 迴圈(兩次更新)求和結果 res = ${res}`); + +const resStr = nestedForLoop(n); +console.log(`雙層 for 迴圈的走訪結果 ${resStr}`); diff --git a/zh-hant/codes/javascript/chapter_computational_complexity/recursion.js b/zh-hant/codes/javascript/chapter_computational_complexity/recursion.js new file mode 100644 index 000000000..59134cf2a --- /dev/null +++ b/zh-hant/codes/javascript/chapter_computational_complexity/recursion.js @@ -0,0 +1,69 @@ +/** + * File: recursion.js + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 遞迴 */ +function recur(n) { + // 終止條件 + if (n === 1) return 1; + // 遞:遞迴呼叫 + const res = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +/* 使用迭代模擬遞迴 */ +function forLoopRecur(n) { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + const stack = []; + let res = 0; + // 遞:遞迴呼叫 + for (let i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.push(i); + } + // 迴:返回結果 + while (stack.length) { + // 透過“出堆疊操作”模擬“迴” + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾遞迴 */ +function tailRecur(n, res) { + // 終止條件 + if (n === 0) return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +/* 費波那契數列:遞迴 */ +function fib(n) { + // 終止條件 f(1) = 0, f(2) = 1 + if (n === 1 || n === 2) return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + const res = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +/* Driver Code */ +const n = 5; +let res; + +res = recur(n); +console.log(`遞迴函式的求和結果 res = ${res}`); + +res = forLoopRecur(n); +console.log(`使用迭代模擬遞迴的求和結果 res = ${res}`); + +res = tailRecur(n, 0); +console.log(`尾遞迴函式的求和結果 res = ${res}`); + +res = fib(n); +console.log(`費波那契數列的第 ${n} 項為 ${res}`); + diff --git a/zh-hant/codes/javascript/chapter_computational_complexity/space_complexity.js b/zh-hant/codes/javascript/chapter_computational_complexity/space_complexity.js new file mode 100644 index 000000000..4c7bebba1 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_computational_complexity/space_complexity.js @@ -0,0 +1,103 @@ +/** + * File: space_complexity.js + * Created Time: 2023-02-05 + * Author: Justin (xiefahit@gmail.com) + */ + +const { ListNode } = require('../modules/ListNode'); +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 函式 */ +function constFunc() { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +function constant(n) { + // 常數、變數、物件佔用 O(1) 空間 + const a = 0; + const b = 0; + const nums = new Array(10000); + const node = new ListNode(0); + // 迴圈中的變數佔用 O(1) 空間 + for (let i = 0; i < n; i++) { + const c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (let i = 0; i < n; i++) { + constFunc(); + } +} + +/* 線性階 */ +function linear(n) { + // 長度為 n 的陣列佔用 O(n) 空間 + const nums = new Array(n); + // 長度為 n 的串列佔用 O(n) 空間 + const nodes = []; + for (let i = 0; i < n; i++) { + nodes.push(new ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + const map = new Map(); + for (let i = 0; i < n; i++) { + map.set(i, i.toString()); + } +} + +/* 線性階(遞迴實現) */ +function linearRecur(n) { + console.log(`遞迴 n = ${n}`); + if (n === 1) return; + linearRecur(n - 1); +} + +/* 平方階 */ +function quadratic(n) { + // 矩陣佔用 O(n^2) 空間 + const numMatrix = Array(n) + .fill(null) + .map(() => Array(n).fill(null)); + // 二維串列佔用 O(n^2) 空間 + const numList = []; + for (let i = 0; i < n; i++) { + const tmp = []; + for (let j = 0; j < n; j++) { + tmp.push(0); + } + numList.push(tmp); + } +} + +/* 平方階(遞迴實現) */ +function quadraticRecur(n) { + if (n <= 0) return 0; + const nums = new Array(n); + console.log(`遞迴 n = ${n} 中的 nums 長度 = ${nums.length}`); + return quadraticRecur(n - 1); +} + +/* 指數階(建立滿二元樹) */ +function buildTree(n) { + if (n === 0) return null; + const root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +const n = 5; +// 常數階 +constant(n); +// 線性階 +linear(n); +linearRecur(n); +// 平方階 +quadratic(n); +quadraticRecur(n); +// 指數階 +const root = buildTree(n); +printTree(root); diff --git a/zh-hant/codes/javascript/chapter_computational_complexity/time_complexity.js b/zh-hant/codes/javascript/chapter_computational_complexity/time_complexity.js new file mode 100644 index 000000000..fc55ec7c1 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_computational_complexity/time_complexity.js @@ -0,0 +1,155 @@ +/** + * File: time_complexity.js + * Created Time: 2023-01-02 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 常數階 */ +function constant(n) { + let count = 0; + const size = 100000; + for (let i = 0; i < size; i++) count++; + return count; +} + +/* 線性階 */ +function linear(n) { + let count = 0; + for (let i = 0; i < n; i++) count++; + return count; +} + +/* 線性階(走訪陣列) */ +function arrayTraversal(nums) { + let count = 0; + // 迴圈次數與陣列長度成正比 + for (let i = 0; i < nums.length; i++) { + count++; + } + return count; +} + +/* 平方階 */ +function quadratic(n) { + let count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方階(泡沫排序) */ +function bubbleSort(nums) { + let count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +/* 指數階(迴圈實現) */ +function exponential(n) { + let count = 0, + base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (let i = 0; i < n; i++) { + for (let j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指數階(遞迴實現) */ +function expRecur(n) { + if (n === 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 對數階(迴圈實現) */ +function logarithmic(n) { + let count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 對數階(遞迴實現) */ +function logRecur(n) { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +/* 線性對數階 */ +function linearLogRecur(n) { + if (n <= 1) return 1; + let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (let i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乘階(遞迴實現) */ +function factorialRecur(n) { + if (n === 0) return 1; + let count = 0; + // 從 1 個分裂出 n 個 + for (let i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +// 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 +const n = 8; +console.log('輸入資料大小 n = ' + n); + +let count = constant(n); +console.log('常數階的操作數量 = ' + count); + +count = linear(n); +console.log('線性階的操作數量 = ' + count); +count = arrayTraversal(new Array(n)); +console.log('線性階(走訪陣列)的操作數量 = ' + count); + +count = quadratic(n); +console.log('平方階的操作數量 = ' + count); +let nums = new Array(n); +for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] +count = bubbleSort(nums); +console.log('平方階(泡沫排序)的操作數量 = ' + count); + +count = exponential(n); +console.log('指數階(迴圈實現)的操作數量 = ' + count); +count = expRecur(n); +console.log('指數階(遞迴實現)的操作數量 = ' + count); + +count = logarithmic(n); +console.log('對數階(迴圈實現)的操作數量 = ' + count); +count = logRecur(n); +console.log('對數階(遞迴實現)的操作數量 = ' + count); + +count = linearLogRecur(n); +console.log('線性對數階(遞迴實現)的操作數量 = ' + count); + +count = factorialRecur(n); +console.log('階乘階(遞迴實現)的操作數量 = ' + count); diff --git a/zh-hant/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js b/zh-hant/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js new file mode 100644 index 000000000..114a9fe21 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js @@ -0,0 +1,43 @@ +/** + * File: worst_best_time_complexity.js + * Created Time: 2023-01-05 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +function randomNumbers(n) { + const nums = Array(n); + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (let i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 隨機打亂陣列元素 + for (let i = 0; i < n; i++) { + const r = Math.floor(Math.random() * (i + 1)); + const temp = nums[i]; + nums[i] = nums[r]; + nums[r] = temp; + } + return nums; +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +function findOne(nums) { + for (let i = 0; i < nums.length; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] === 1) { + return i; + } + } + return -1; +} + +/* Driver Code */ +for (let i = 0; i < 10; i++) { + const n = 100; + const nums = randomNumbers(n); + const index = findOne(nums); + console.log('\n陣列 [ 1, 2, ..., n ] 被打亂後 = [' + nums.join(', ') + ']'); + console.log('數字 1 的索引為 ' + index); +} diff --git a/zh-hant/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js b/zh-hant/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js new file mode 100644 index 000000000..d340949f5 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js @@ -0,0 +1,39 @@ +/** + * File: binary_search_recur.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 二分搜尋:問題 f(i, j) */ +function dfs(nums, target, i, j) { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + const m = i + ((j - i) >> 1); + if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } +} + +/* 二分搜尋 */ +function binarySearch(nums, target) { + const n = nums.length; + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +// 二分搜尋(雙閉區間) +const index = binarySearch(nums, target); +console.log(`目標元素 6 的索引 = ${index}`); diff --git a/zh-hant/codes/javascript/chapter_divide_and_conquer/build_tree.js b/zh-hant/codes/javascript/chapter_divide_and_conquer/build_tree.js new file mode 100644 index 000000000..e89d5ce51 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_divide_and_conquer/build_tree.js @@ -0,0 +1,44 @@ +/** + * File: build_tree.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { printTree } = require('../modules/PrintUtil'); +const { TreeNode } = require('../modules/TreeNode'); + +/* 構建二元樹:分治 */ +function dfs(preorder, inorderMap, i, l, r) { + // 子樹區間為空時終止 + if (r - l < 0) return null; + // 初始化根節點 + const root = new TreeNode(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + const m = inorderMap.get(preorder[i]); + // 子問題:構建左子樹 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; +} + +/* 構建二元樹 */ +function buildTree(preorder, inorder) { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + let inorderMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + inorderMap.set(inorder[i], i); + } + const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +const preorder = [3, 9, 2, 1, 7]; +const inorder = [9, 3, 1, 2, 7]; +console.log('前序走訪 = ' + JSON.stringify(preorder)); +console.log('中序走訪 = ' + JSON.stringify(inorder)); +const root = buildTree(preorder, inorder); +console.log('構建的二元樹為:'); +printTree(root); diff --git a/zh-hant/codes/javascript/chapter_divide_and_conquer/hanota.js b/zh-hant/codes/javascript/chapter_divide_and_conquer/hanota.js new file mode 100644 index 000000000..e30775b27 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_divide_and_conquer/hanota.js @@ -0,0 +1,52 @@ +/** + * File: hanota.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 移動一個圓盤 */ +function move(src, tar) { + // 從 src 頂部拿出一個圓盤 + const pan = src.pop(); + // 將圓盤放入 tar 頂部 + tar.push(pan); +} + +/* 求解河內塔問題 f(i) */ +function dfs(i, src, buf, tar) { + // 若 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); +} + +/* 求解河內塔問題 */ +function solveHanota(A, B, C) { + const n = A.length; + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +// 串列尾部是柱子頂部 +const A = [5, 4, 3, 2, 1]; +const B = []; +const C = []; +console.log('初始狀態下:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); + +solveHanota(A, B, C); + +console.log('圓盤移動完成後:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js new file mode 100644 index 000000000..047238c85 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js @@ -0,0 +1,34 @@ +/** + * File: climbing_stairs_backtrack.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯 */ +function backtrack(choices, state, n, res) { + // 當爬到第 n 階時,方案數量加 1 + if (state === n) res.set(0, res.get(0) + 1); + // 走訪所有選擇 + for (const choice of choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +function climbingStairsBacktrack(n) { + const choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 + const state = 0; // 從第 0 階開始爬 + const res = new Map(); + res.set(0, 0); // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res); + return res.get(0); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsBacktrack(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js new file mode 100644 index 000000000..b0db6cf78 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_constraint_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 帶約束爬樓梯:動態規劃 */ +function climbingStairsConstraintDP(n) { + if (n === 1 || n === 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + const dp = Array.from(new Array(n + 1), () => new Array(3)); + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + 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 */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js new file mode 100644 index 000000000..3b5ad9afb --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js @@ -0,0 +1,24 @@ +/** + * File: climbing_stairs_dfs.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 搜尋 */ +function dfs(i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i === 1 || i === 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬樓梯:搜尋 */ +function climbingStairsDFS(n) { + return dfs(n); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFS(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js new file mode 100644 index 000000000..bbffc533a --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_dfs_mem.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 記憶化搜尋 */ +function dfs(i, mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i === 1 || i === 2) return i; + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +/* 爬樓梯:記憶化搜尋 */ +function climbingStairsDFSMem(n) { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + const mem = new Array(n + 1).fill(-1); + return dfs(n, mem); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFSMem(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js new file mode 100644 index 000000000..abdf106bf --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dp.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 爬樓梯:動態規劃 */ +function climbingStairsDP(n) { + if (n === 1 || n === 2) return n; + // 初始化 dp 表,用於儲存子問題的解 + const dp = new Array(n + 1).fill(-1); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +function climbingStairsDPComp(n) { + if (n === 1 || n === 2) return n; + let a = 1, + b = 2; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +const n = 9; +let res = climbingStairsDP(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); +res = climbingStairsDPComp(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js new file mode 100644 index 000000000..27660a9a0 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change.js @@ -0,0 +1,66 @@ +/** + * File: coin_change.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零錢兌換:動態規劃 */ +function coinChangeDP(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 狀態轉移:首行首列 + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* 零錢兌換:狀態壓縮後的動態規劃 */ +function coinChangeDPComp(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// 動態規劃 +let res = coinChangeDP(coins, amt); +console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); + +// 狀態壓縮後的動態規劃 +res = coinChangeDPComp(coins, amt); +console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js new file mode 100644 index 000000000..c4346524d --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/coin_change_ii.js @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零錢兌換 II:動態規劃 */ +function coinChangeIIDP(coins, amt) { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 初始化首列 + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + 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]; +} + +/* 零錢兌換 II:狀態壓縮後的動態規劃 */ +function coinChangeIIDPComp(coins, amt) { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + 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 */ +const coins = [1, 2, 5]; +const amt = 5; + +// 動態規劃 +let res = coinChangeIIDP(coins, amt); +console.log(`湊出目標金額的硬幣組合數量為 ${res}`); + +// 狀態壓縮後的動態規劃 +res = coinChangeIIDPComp(coins, amt); +console.log(`湊出目標金額的硬幣組合數量為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js b/zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js new file mode 100644 index 000000000..0a121c5d7 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/edit_distance.js @@ -0,0 +1,135 @@ +/** + * File: edit_distance.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 編輯距離:暴力搜尋 */ +function editDistanceDFS(s, t, i, j) { + // 若 s 和 t 都為空,則返回 0 + if (i === 0 && j === 0) return 0; + + // 若 s 為空,則返回 t 長度 + if (i === 0) return j; + + // 若 t 為空,則返回 s 長度 + if (j === 0) return i; + + // 若兩字元相等,則直接跳過此兩字元 + if (s.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return Math.min(insert, del, replace) + 1; +} + +/* 編輯距離:記憶化搜尋 */ +function editDistanceDFSMem(s, t, mem, i, j) { + // 若 s 和 t 都為空,則返回 0 + if (i === 0 && 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = Math.min(insert, del, replace) + 1; + return mem[i][j]; +} + +/* 編輯距離:動態規劃 */ +function editDistanceDP(s, t) { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); + // 狀態轉移:首行首列 + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = + Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編輯距離:狀態壓縮後的動態規劃 */ +function editDistanceDPComp(s, t) { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // 狀態轉移:首行 + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (let i = 1; i <= n; i++) { + // 狀態轉移:首列 + let leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; +} + +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// 暴力搜尋 +let res = editDistanceDFS(s, t, n, m); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 記憶化搜尋 +const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 動態規劃 +res = editDistanceDP(s, t); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 狀態壓縮後的動態規劃 +res = editDistanceDPComp(s, t); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js b/zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js new file mode 100644 index 000000000..54f112200 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/knapsack.js @@ -0,0 +1,113 @@ +/** + * File: knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 0-1 背包:暴力搜尋 */ +function knapsackDFS(wgt, val, i, c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return Math.max(no, yes); +} + +/* 0-1 背包:記憶化搜尋 */ +function knapsackDFSMem(wgt, val, mem, i, c) { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] !== -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:動態規劃 */ +function knapsackDP(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(n + 1) + .fill(0) + .map(() => Array(cap + 1).fill(0)); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:狀態壓縮後的動態規劃 */ +function knapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(cap + 1).fill(0); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + // 倒序走訪 + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 暴力搜尋 +let res = knapsackDFS(wgt, val, n, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 記憶化搜尋 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 動態規劃 +res = knapsackDP(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 狀態壓縮後的動態規劃 +res = knapsackDPComp(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js b/zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js new file mode 100644 index 000000000..7f63d01d6 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js @@ -0,0 +1,49 @@ +/** + * File: min_cost_climbing_stairs_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 爬樓梯最小代價:動態規劃 */ +function minCostClimbingStairsDP(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // 初始化 dp 表,用於儲存子問題的解 + const dp = new Array(n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬樓梯最小代價:狀態壓縮後的動態規劃 */ +function minCostClimbingStairsDPComp(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log('輸入樓梯的代價串列為:', cost); + +let res = minCostClimbingStairsDP(cost); +console.log(`爬完樓梯的最低代價為:${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`爬完樓梯的最低代價為:${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js b/zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js new file mode 100644 index 000000000..a1b2f9855 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/min_path_sum.js @@ -0,0 +1,121 @@ +/** + * File: min_path_sum.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 最小路徑和:暴力搜尋 */ +function minPathSumDFS(grid, i, j) { + // 若為左上角單元格,則終止搜尋 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Infinity; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + const up = minPathSumDFS(grid, i - 1, j); + const left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return Math.min(left, up) + grid[i][j]; +} + +/* 最小路徑和:記憶化搜尋 */ +function minPathSumDFSMem(grid, mem, i, j) { + // 若為左上角單元格,則終止搜尋 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Infinity; + } + // 若已有記錄,則直接返回 + if (mem[i][j] !== -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + const up = minPathSumDFSMem(grid, mem, i - 1, j); + const left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路徑和:動態規劃 */ +function minPathSumDP(grid) { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i < n; i++) { + for (let j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路徑和:狀態壓縮後的動態規劃 */ +function minPathSumDPComp(grid) { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = new Array(m); + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (let i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// 暴力搜尋 +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 記憶化搜尋 +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 動態規劃 +res = minPathSumDP(grid); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 狀態壓縮後的動態規劃 +res = minPathSumDPComp(grid); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js b/zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js new file mode 100644 index 000000000..f7f504c54 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 完全背包:動態規劃 */ +function unboundedKnapsackDP(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:狀態壓縮後的動態規劃 */ +function unboundedKnapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: cap + 1 }, () => 0); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// 動態規劃 +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 狀態壓縮後的動態規劃 +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_graph/graph_adjacency_list.js b/zh-hant/codes/javascript/chapter_graph/graph_adjacency_list.js new file mode 100644 index 000000000..6b7ff8c71 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_graph/graph_adjacency_list.js @@ -0,0 +1,141 @@ +/** + * File: graph_adjacency_list.js + * Created Time: 2023-02-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { Vertex } = require('../modules/Vertex'); + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + adjList; + + /* 建構子 */ + constructor(edges) { + this.adjList = new Map(); + // 新增所有頂點和邊 + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + size() { + return this.adjList.size; + } + + /* 新增邊 */ + addEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 新增邊 vet1 - vet2 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* 刪除邊 */ + removeEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 刪除邊 vet1 - vet2 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* 新增頂點 */ + addVertex(vet) { + if (this.adjList.has(vet)) return; + // 在鄰接表中新增一個新鏈結串列 + this.adjList.set(vet, []); + } + + /* 刪除頂點 */ + removeVertex(vet) { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + this.adjList.delete(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (const set of this.adjList.values()) { + const index = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* 列印鄰接表 */ + print() { + console.log('鄰接表 ='); + for (const [key, value] of this.adjList) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } +} + +if (require.main === module) { + /* Driver Code */ + /* 初始化無向圖 */ + const v0 = new Vertex(1), + v1 = new Vertex(3), + v2 = new Vertex(2), + v3 = new Vertex(5), + v4 = new Vertex(4); + const edges = [ + [v0, v1], + [v1, v2], + [v2, v3], + [v0, v3], + [v2, v4], + [v3, v4], + ]; + const graph = new GraphAdjList(edges); + console.log('\n初始化後,圖為'); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v0, v2 + graph.addEdge(v0, v2); + console.log('\n新增邊 1-2 後,圖為'); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v0, v1 + graph.removeEdge(v0, v1); + console.log('\n刪除邊 1-3 後,圖為'); + graph.print(); + + /* 新增頂點 */ + const v5 = new Vertex(6); + graph.addVertex(v5); + console.log('\n新增頂點 6 後,圖為'); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 即 v1 + graph.removeVertex(v1); + console.log('\n刪除頂點 3 後,圖為'); + graph.print(); +} + +module.exports = { + GraphAdjList, +}; diff --git a/zh-hant/codes/javascript/chapter_graph/graph_adjacency_matrix.js b/zh-hant/codes/javascript/chapter_graph/graph_adjacency_matrix.js new file mode 100644 index 000000000..eb1b5245e --- /dev/null +++ b/zh-hant/codes/javascript/chapter_graph/graph_adjacency_matrix.js @@ -0,0 +1,132 @@ +/** + * File: graph_adjacency_matrix.js + * Created Time: 2023-02-09 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + vertices; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + adjMat; // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + constructor(vertices, edges) { + this.vertices = []; + this.adjMat = []; + // 新增頂點 + for (const val of vertices) { + this.addVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* 獲取頂點數量 */ + size() { + return this.vertices.length; + } + + /* 新增頂點 */ + addVertex(val) { + const n = this.size(); + // 向頂點串列中新增新頂點的值 + this.vertices.push(val); + // 在鄰接矩陣中新增一行 + const newRow = []; + for (let j = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // 在鄰接矩陣中新增一列 + for (const row of this.adjMat) { + row.push(0); + } + } + + /* 刪除頂點 */ + removeVertex(index) { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在頂點串列中移除索引 index 的頂點 + this.vertices.splice(index, 1); + + // 在鄰接矩陣中刪除索引 index 的行 + this.adjMat.splice(index, 1); + // 在鄰接矩陣中刪除索引 index 的列 + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + addEdge(i, j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) === (j, i) + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + removeEdge(i, j) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + print() { + console.log('頂點串列 = ', this.vertices); + console.log('鄰接矩陣 =', this.adjMat); + } +} + +/* Driver Code */ +/* 初始化無向圖 */ +// 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 +const vertices = [1, 3, 2, 5, 4]; +const edges = [ + [0, 1], + [1, 2], + [2, 3], + [0, 3], + [2, 4], + [3, 4], +]; +const graph = new GraphAdjMat(vertices, edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 新增邊 */ +// 頂點 1, 2 的索引分別為 0, 2 +graph.addEdge(0, 2); +console.log('\n新增邊 1-2 後,圖為'); +graph.print(); + +/* 刪除邊 */ +// 頂點 1, 3 的索引分別為 0, 1 +graph.removeEdge(0, 1); +console.log('\n刪除邊 1-3 後,圖為'); +graph.print(); + +/* 新增頂點 */ +graph.addVertex(6); +console.log('\n新增頂點 6 後,圖為'); +graph.print(); + +/* 刪除頂點 */ +// 頂點 3 的索引為 1 +graph.removeVertex(1); +console.log('\n刪除頂點 3 後,圖為'); +graph.print(); diff --git a/zh-hant/codes/javascript/chapter_graph/graph_bfs.js b/zh-hant/codes/javascript/chapter_graph/graph_bfs.js new file mode 100644 index 000000000..2fa20cf91 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_graph/graph_bfs.js @@ -0,0 +1,61 @@ +/** + * File: graph_bfs.js + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { GraphAdjList } = require('./graph_adjacency_list'); +const { Vertex } = require('../modules/Vertex'); + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +function graphBFS(graph, startVet) { + // 頂點走訪序列 + const res = []; + // 雜湊表,用於記錄已被訪問過的頂點 + const visited = new Set(); + visited.add(startVet); + // 佇列用於實現 BFS + const que = [startVet]; + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (que.length) { + const vet = que.shift(); // 佇列首頂點出隊 + res.push(vet); // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + que.push(adjVet); // 只入列未訪問的頂點 + visited.add(adjVet); // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res; +} + +/* Driver Code */ +/* 初始化無向圖 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const 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]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 廣度優先走訪 */ +const res = graphBFS(graph, v[0]); +console.log('\n廣度優先走訪(BFS)頂點序列為'); +console.log(Vertex.vetsToVals(res)); diff --git a/zh-hant/codes/javascript/chapter_graph/graph_dfs.js b/zh-hant/codes/javascript/chapter_graph/graph_dfs.js new file mode 100644 index 000000000..ff1a71f5e --- /dev/null +++ b/zh-hant/codes/javascript/chapter_graph/graph_dfs.js @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.js + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { Vertex } = require('../modules/Vertex'); +const { GraphAdjList } = require('./graph_adjacency_list'); + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +function dfs(graph, visited, res, vet) { + res.push(vet); // 記錄訪問頂點 + visited.add(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +function graphDFS(graph, startVet) { + // 頂點走訪序列 + const res = []; + // 雜湊表,用於記錄已被訪問過的頂點 + const visited = new Set(); + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +/* 初始化無向圖 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); +const 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]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 深度優先走訪 */ +const res = graphDFS(graph, v[0]); +console.log('\n深度優先走訪(DFS)頂點序列為'); +console.log(Vertex.vetsToVals(res)); diff --git a/zh-hant/codes/javascript/chapter_greedy/coin_change_greedy.js b/zh-hant/codes/javascript/chapter_greedy/coin_change_greedy.js new file mode 100644 index 000000000..f5bfde287 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_greedy/coin_change_greedy.js @@ -0,0 +1,48 @@ +/** + * File: coin_change_greedy.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 零錢兌換:貪婪 */ +function coinChangeGreedy(coins, amt) { + // 假設 coins 陣列有序 + let i = coins.length - 1; + let count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (amt > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > amt) { + i--; + } + // 選擇 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,則返回 -1 + return amt === 0 ? count : -1; +} + +/* Driver Code */ +// 貪婪:能夠保證找到全域性最優解 +let coins = [1, 5, 10, 20, 50, 100]; +let amt = 186; +let res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); + +// 貪婪:無法保證找到全域性最優解 +coins = [1, 20, 50]; +amt = 60; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); +console.log('實際上需要的最少數量為 3 ,即 20 + 20 + 20'); + +// 貪婪:無法保證找到全域性最優解 +coins = [1, 49, 50]; +amt = 98; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); +console.log('實際上需要的最少數量為 2 ,即 49 + 49'); diff --git a/zh-hant/codes/javascript/chapter_greedy/fractional_knapsack.js b/zh-hant/codes/javascript/chapter_greedy/fractional_knapsack.js new file mode 100644 index 000000000..771aa9170 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_greedy/fractional_knapsack.js @@ -0,0 +1,46 @@ +/** + * File: fractional_knapsack.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 物品 */ +class Item { + constructor(w, v) { + this.w = w; // 物品重量 + this.v = v; // 物品價值 + } +} + +/* 分數背包:貪婪 */ +function fractionalKnapsack(wgt, val, cap) { + // 建立物品串列,包含兩個屬性:重量、價值 + const items = wgt.map((w, i) => new Item(w, val[i])); + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort((a, b) => b.v / b.w - a.v / a.w); + // 迴圈貪婪選擇 + let res = 0; + for (const item of items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (item.v / item.w) * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 貪婪演算法 +const res = fractionalKnapsack(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_greedy/max_capacity.js b/zh-hant/codes/javascript/chapter_greedy/max_capacity.js new file mode 100644 index 000000000..1203ac2de --- /dev/null +++ b/zh-hant/codes/javascript/chapter_greedy/max_capacity.js @@ -0,0 +1,34 @@ +/** + * File: max_capacity.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大容量:貪婪 */ +function maxCapacity(ht) { + // 初始化 i, j,使其分列陣列兩端 + let i = 0, + j = ht.length - 1; + // 初始最大容量為 0 + let res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + const cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i += 1; + } else { + j -= 1; + } + } + return res; +} + +/* Driver Code */ +const ht = [3, 8, 5, 2, 7, 7, 3, 4]; + +// 貪婪演算法 +const res = maxCapacity(ht); +console.log(`最大容量為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_greedy/max_product_cutting.js b/zh-hant/codes/javascript/chapter_greedy/max_product_cutting.js new file mode 100644 index 000000000..b7eefdbe1 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_greedy/max_product_cutting.js @@ -0,0 +1,33 @@ +/** + * File: max_product_cutting.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大切分乘積:貪婪 */ +function maxProductCutting(n) { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + let a = Math.floor(n / 3); + let b = n % 3; + if (b === 1) { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return Math.pow(3, a - 1) * 2 * 2; + } + if (b === 2) { + // 當餘數為 2 時,不做處理 + return Math.pow(3, a) * 2; + } + // 當餘數為 0 時,不做處理 + return Math.pow(3, a); +} + +/* Driver Code */ +let n = 58; + +// 貪婪演算法 +let res = maxProductCutting(n); +console.log(`最大切分乘積為 ${res}`); diff --git a/zh-hant/codes/javascript/chapter_hashing/array_hash_map.js b/zh-hant/codes/javascript/chapter_hashing/array_hash_map.js new file mode 100644 index 000000000..b7525185e --- /dev/null +++ b/zh-hant/codes/javascript/chapter_hashing/array_hash_map.js @@ -0,0 +1,128 @@ +/** + * File: array_hash_map.js + * Created Time: 2022-12-26 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + #buckets; + constructor() { + // 初始化陣列,包含 100 個桶 + this.#buckets = new Array(100).fill(null); + } + + /* 雜湊函式 */ + #hashFunc(key) { + return key % 100; + } + + /* 查詢操作 */ + get(key) { + let index = this.#hashFunc(key); + let pair = this.#buckets[index]; + if (pair === null) return null; + return pair.val; + } + + /* 新增操作 */ + set(key, val) { + let index = this.#hashFunc(key); + this.#buckets[index] = new Pair(key, val); + } + + /* 刪除操作 */ + delete(key) { + let index = this.#hashFunc(key); + // 置為 null ,代表刪除 + this.#buckets[index] = null; + } + + /* 獲取所有鍵值對 */ + entries() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i]); + } + } + return arr; + } + + /* 獲取所有鍵 */ + keys() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i].key); + } + } + return arr; + } + + /* 獲取所有值 */ + values() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i].val); + } + } + return arr; + } + + /* 列印雜湊表 */ + print() { + let pairSet = this.entries(); + for (const pair of pairSet) { + console.info(`${pair.key} -> ${pair.val}`); + } + } +} + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new ArrayHashMap(); +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.set(12836, '小哈'); +map.set(15937, '小囉'); +map.set(16750, '小算'); +map.set(13276, '小法'); +map.set(10583, '小鴨'); +console.info('\n新增完成後,雜湊表為\nKey -> Value'); +map.print(); + +/* 查詢操作 */ +// 向雜湊表中輸入鍵 key ,得到值 value +let name = map.get(15937); +console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); + +/* 刪除操作 */ +// 在雜湊表中刪除鍵值對 (key, value) +map.delete(10583); +console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); +map.print(); + +/* 走訪雜湊表 */ +console.info('\n走訪鍵值對 Key->Value'); +for (const pair of map.entries()) { + if (!pair) continue; + console.info(pair.key + ' -> ' + pair.val); +} +console.info('\n單獨走訪鍵 Key'); +for (const key of map.keys()) { + console.info(key); +} +console.info('\n單獨走訪值 Value'); +for (const val of map.values()) { + console.info(val); +} diff --git a/zh-hant/codes/javascript/chapter_hashing/hash_map.js b/zh-hant/codes/javascript/chapter_hashing/hash_map.js new file mode 100644 index 000000000..4f7f2c344 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_hashing/hash_map.js @@ -0,0 +1,44 @@ +/** + * File: hash_map.js + * Created Time: 2022-12-26 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new Map(); + +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.set(12836, '小哈'); +map.set(15937, '小囉'); +map.set(16750, '小算'); +map.set(13276, '小法'); +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); +console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); +console.info(map); + +/* 走訪雜湊表 */ +console.info('\n走訪鍵值對 Key->Value'); +for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); +} +console.info('\n單獨走訪鍵 Key'); +for (const k of map.keys()) { + console.info(k); +} +console.info('\n單獨走訪值 Value'); +for (const v of map.values()) { + console.info(v); +} diff --git a/zh-hant/codes/javascript/chapter_hashing/hash_map_chaining.js b/zh-hant/codes/javascript/chapter_hashing/hash_map_chaining.js new file mode 100644 index 000000000..741dd4aa4 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_hashing/hash_map_chaining.js @@ -0,0 +1,142 @@ +/** + * File: hash_map_chaining.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + #size; // 鍵值對數量 + #capacity; // 雜湊表容量 + #loadThres; // 觸發擴容的負載因子閾值 + #extendRatio; // 擴容倍數 + #buckets; // 桶陣列 + + /* 建構子 */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2.0 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* 雜湊函式 */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* 負載因子 */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* 查詢操作 */ + get(key) { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 走訪桶,若找到 key ,則返回對應 val + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // 若未找到 key ,則返回 null + return null; + } + + /* 新增操作 */ + put(key, val) { + // 當負載因子超過閾值時,執行擴容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* 刪除操作 */ + remove(key) { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // 走訪桶,從中刪除鍵值對 + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* 擴容雜湊表 */ + #extend() { + // 暫存原雜湊表 + const bucketsTmp = this.#buckets; + // 初始化擴容後的新雜湊表 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + print() { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new HashMapChaining(); + +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.put(12836, '小哈'); +map.put(15937, '小囉'); +map.put(16750, '小算'); +map.put(13276, '小法'); +map.put(10583, '小鴨'); +console.log('\n新增完成後,雜湊表為\nKey -> Value'); +map.print(); + +/* 查詢操作 */ +// 向雜湊表中輸入鍵 key ,得到值 value +const name = map.get(13276); +console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); + +/* 刪除操作 */ +// 在雜湊表中刪除鍵值對 (key, value) +map.remove(12836); +console.log('\n刪除 12836 後,雜湊表為\nKey -> Value'); +map.print(); diff --git a/zh-hant/codes/javascript/chapter_hashing/hash_map_open_addressing.js b/zh-hant/codes/javascript/chapter_hashing/hash_map_open_addressing.js new file mode 100644 index 000000000..55d2b50d7 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_hashing/hash_map_open_addressing.js @@ -0,0 +1,177 @@ +/** + * File: hashMapOpenAddressing.js + * Created Time: 2023-06-13 + * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + #size; // 鍵值對數量 + #capacity; // 雜湊表容量 + #loadThres; // 觸發擴容的負載因子閾值 + #extendRatio; // 擴容倍數 + #buckets; // 桶陣列 + #TOMBSTONE; // 刪除標記 + + /* 建構子 */ + constructor() { + this.#size = 0; // 鍵值對數量 + this.#capacity = 4; // 雜湊表容量 + this.#loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + this.#extendRatio = 2; // 擴容倍數 + this.#buckets = Array(this.#capacity).fill(null); // 桶陣列 + this.#TOMBSTONE = new Pair(-1, '-1'); // 刪除標記 + } + + /* 雜湊函式 */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* 負載因子 */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* 搜尋 key 對應的桶索引 */ + #findBucket(key) { + let index = this.#hashFunc(key); + let firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (this.#buckets[index] !== null) { + // 若遇到 key ,返回對應的桶索引 + if (this.#buckets[index].key === key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone !== -1) { + this.#buckets[firstTombstone] = this.#buckets[index]; + this.#buckets[index] = this.#TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if ( + firstTombstone === -1 && + this.#buckets[index] === this.#TOMBSTONE + ) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % this.#capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone === -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + get(key) { + // 搜尋 key 對應的桶索引 + const index = this.#findBucket(key); + // 若找到鍵值對,則返回對應 val + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + return this.#buckets[index].val; + } + // 若鍵值對不存在,則返回 null + return null; + } + + /* 新增操作 */ + put(key, val) { + // 當負載因子超過閾值時,執行擴容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + // 搜尋 key 對應的桶索引 + const index = this.#findBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index].val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + this.#buckets[index] = new Pair(key, val); + this.#size++; + } + + /* 刪除操作 */ + remove(key) { + // 搜尋 key 對應的桶索引 + const index = this.#findBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index] = this.#TOMBSTONE; + this.#size--; + } + } + + /* 擴容雜湊表 */ + #extend() { + // 暫存原雜湊表 + const bucketsTmp = this.#buckets; + // 初始化擴容後的新雜湊表 + this.#capacity *= this.#extendRatio; + this.#buckets = Array(this.#capacity).fill(null); + this.#size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (const pair of bucketsTmp) { + if (pair !== null && pair !== this.#TOMBSTONE) { + this.put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + print() { + for (const pair of this.#buckets) { + if (pair === null) { + console.log('null'); + } else if (pair === this.#TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); + } + } + } +} + +/* Driver Code */ +// 初始化雜湊表 +const hashmap = new HashMapOpenAddressing(); + +// 新增操作 +// 在雜湊表中新增鍵值對 (key, val) +hashmap.put(12836, '小哈'); +hashmap.put(15937, '小囉'); +hashmap.put(16750, '小算'); +hashmap.put(13276, '小法'); +hashmap.put(10583, '小鴨'); +console.log('\n新增完成後,雜湊表為\nKey -> Value'); +hashmap.print(); + +// 查詢操作 +// 向雜湊表中輸入鍵 key ,得到值 val +const name = hashmap.get(13276); +console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); + +// 刪除操作 +// 在雜湊表中刪除鍵值對 (key, val) +hashmap.remove(16750); +console.log('\n刪除 16750 後,雜湊表為\nKey -> Value'); +hashmap.print(); diff --git a/zh-hant/codes/javascript/chapter_hashing/simple_hash.js b/zh-hant/codes/javascript/chapter_hashing/simple_hash.js new file mode 100644 index 000000000..447718341 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_hashing/simple_hash.js @@ -0,0 +1,60 @@ +/** + * File: simple_hash.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 加法雜湊 */ +function addHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 乘法雜湊 */ +function mulHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 互斥或雜湊 */ +function xorHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash & MODULUS; +} + +/* 旋轉雜湊 */ +function rotHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello 演算法'; + +let hash = addHash(key); +console.log('加法雜湊值為 ' + hash); + +hash = mulHash(key); +console.log('乘法雜湊值為 ' + hash); + +hash = xorHash(key); +console.log('互斥或雜湊值為 ' + hash); + +hash = rotHash(key); +console.log('旋轉雜湊值為 ' + hash); diff --git a/zh-hant/codes/javascript/chapter_heap/my_heap.js b/zh-hant/codes/javascript/chapter_heap/my_heap.js new file mode 100644 index 000000000..303ac2c29 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_heap/my_heap.js @@ -0,0 +1,158 @@ +/** + * File: my_heap.js + * Created Time: 2023-02-06 + * Author: what-is-me (whatisme@outlook.jp) + */ + +const { printHeap } = require('../modules/PrintUtil'); + +/* 最大堆積類別 */ +class MaxHeap { + #maxHeap; + + /* 建構子,建立空堆積或根據輸入串列建堆積 */ + constructor(nums) { + // 將串列元素原封不動新增進堆積 + this.#maxHeap = nums === undefined ? [] : [...nums]; + // 堆積化除葉節點以外的其他所有節點 + for (let i = this.#parent(this.size() - 1); i >= 0; i--) { + this.#siftDown(i); + } + } + + /* 獲取左子節點的索引 */ + #left(i) { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + #right(i) { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + #parent(i) { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 交換元素 */ + #swap(i, j) { + const tmp = this.#maxHeap[i]; + this.#maxHeap[i] = this.#maxHeap[j]; + this.#maxHeap[j] = tmp; + } + + /* 獲取堆積大小 */ + size() { + return this.#maxHeap.length; + } + + /* 判斷堆積是否為空 */ + isEmpty() { + return this.size() === 0; + } + + /* 訪問堆積頂元素 */ + peek() { + return this.#maxHeap[0]; + } + + /* 元素入堆積 */ + push(val) { + // 新增節點 + this.#maxHeap.push(val); + // 從底至頂堆積化 + this.#siftUp(this.size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + #siftUp(i) { + while (true) { + // 獲取節點 i 的父節點 + const p = this.#parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; + // 交換兩節點 + this.#swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + pop() { + // 判空處理 + if (this.isEmpty()) throw new Error('堆積為空'); + // 交換根節點與最右葉節點(交換首元素與尾元素) + this.#swap(0, this.size() - 1); + // 刪除節點 + const val = this.#maxHeap.pop(); + // 從頂至底堆積化 + this.#siftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + #siftDown(i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + const l = this.#left(i), + r = this.#right(i); + let ma = i; + if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; + if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma === i) break; + // 交換兩節點 + this.#swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 列印堆積(二元樹) */ + print() { + printHeap(this.#maxHeap); + } + + /* 取出堆積中元素 */ + getMaxHeap() { + return this.#maxHeap; + } +} + +/* Driver Code */ +if (require.main === module) { + /* 初始化大頂堆積 */ + const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + console.log('\n輸入串列並建堆積後'); + maxHeap.print(); + + /* 獲取堆積頂元素 */ + let peek = maxHeap.peek(); + console.log(`\n堆積頂元素為 ${peek}`); + + /* 元素入堆積 */ + let val = 7; + maxHeap.push(val); + console.log(`\n元素 ${val} 入堆積後`); + maxHeap.print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop(); + console.log(`\n堆積頂元素 ${peek} 出堆積後`); + maxHeap.print(); + + /* 獲取堆積大小 */ + let size = maxHeap.size(); + console.log(`\n堆積元素數量為 ${size}`); + + /* 判斷堆積是否為空 */ + let isEmpty = maxHeap.isEmpty(); + console.log(`\n堆積是否為空 ${isEmpty}`); +} + +module.exports = { + MaxHeap, +}; diff --git a/zh-hant/codes/javascript/chapter_heap/top_k.js b/zh-hant/codes/javascript/chapter_heap/top_k.js new file mode 100644 index 000000000..ba6f6d156 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_heap/top_k.js @@ -0,0 +1,58 @@ +/** + * File: top_k.js + * Created Time: 2023-08-13 + * Author: Justin (xiefahit@gmail.com) + */ + +const { MaxHeap } = require('./my_heap'); + +/* 元素入堆積 */ +function pushMinHeap(maxHeap, val) { + // 元素取反 + maxHeap.push(-val); +} + +/* 元素出堆積 */ +function popMinHeap(maxHeap) { + // 元素取反 + return -maxHeap.pop(); +} + +/* 訪問堆積頂元素 */ +function peekMinHeap(maxHeap) { + // 元素取反 + return -maxHeap.peek(); +} + +/* 取出堆積中元素 */ +function getMinHeap(maxHeap) { + // 元素取反 + return maxHeap.getMaxHeap().map((num) => -num); +} + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +function topKHeap(nums, k) { + // 初始化小頂堆積 + // 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 + const maxHeap = new MaxHeap([]); + // 將陣列的前 k 個元素入堆積 + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (let i = k; i < nums.length; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // 返回堆積中元素 + return getMinHeap(maxHeap); +} + +/* Driver Code */ +const nums = [1, 7, 6, 3, 2]; +const k = 3; +const res = topKHeap(nums, k); +console.log(`最大的 ${k} 個元素為`, res); diff --git a/zh-hant/codes/javascript/chapter_searching/binary_search.js b/zh-hant/codes/javascript/chapter_searching/binary_search.js new file mode 100644 index 000000000..6d3309b0b --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/binary_search.js @@ -0,0 +1,60 @@ +/** + * File: binary_search.js + * Created Time: 2022-12-22 + * Author: JoseHung (szhong@link.cuhk.edu.hk) + */ + +/* 二分搜尋(雙閉區間) */ +function binarySearch(nums, target) { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + let i = 0, + j = nums.length - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + // 計算中點索引 m ,使用 parseInt() 向下取整 + const m = parseInt(i + (j - i) / 2); + if (nums[m] < target) + // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + else if (nums[m] > target) + // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + else return m; // 找到目標元素,返回其索引 + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 二分搜尋(左閉右開區間) */ +function binarySearchLCRO(nums, target) { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + let i = 0, + j = nums.length; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + // 計算中點索引 m ,使用 parseInt() 向下取整 + const m = parseInt(i + (j - i) / 2); + if (nums[m] < target) + // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + else if (nums[m] > target) + // 此情況說明 target 在區間 [i, m) 中 + j = m; + // 找到目標元素,返回其索引 + else return m; + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + +/* 二分搜尋(雙閉區間) */ +let index = binarySearch(nums, target); +console.log('目標元素 6 的索引 = ' + index); + +/* 二分搜尋(左閉右開區間) */ +index = binarySearchLCRO(nums, target); +console.log('目標元素 6 的索引 = ' + index); diff --git a/zh-hant/codes/javascript/chapter_searching/binary_search_edge.js b/zh-hant/codes/javascript/chapter_searching/binary_search_edge.js new file mode 100644 index 000000000..1e0536925 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/binary_search_edge.js @@ -0,0 +1,45 @@ +/** + * File: binary_search_edge.js + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +const { binarySearchInsertion } = require('./binary_search_insertion.js'); + +/* 二分搜尋最左一個 target */ +function binarySearchLeftEdge(nums, target) { + // 等價於查詢 target 的插入點 + const i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i === nums.length || nums[i] !== target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分搜尋最右一個 target */ +function binarySearchRightEdge(nums, target) { + // 轉化為查詢最左一個 target + 1 + const i = binarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + const j = i - 1; + // 未找到 target ,返回 -1 + if (j === -1 || nums[j] !== target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +// 包含重複元素的陣列 +const nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋左邊界和右邊界 +for (const target of [6, 7]) { + let index = binarySearchLeftEdge(nums, target); + console.log('最左一個元素 ' + target + ' 的索引為 ' + index); + index = binarySearchRightEdge(nums, target); + console.log('最右一個元素 ' + target + ' 的索引為 ' + index); +} diff --git a/zh-hant/codes/javascript/chapter_searching/binary_search_insertion.js b/zh-hant/codes/javascript/chapter_searching/binary_search_insertion.js new file mode 100644 index 000000000..32779c54b --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/binary_search_insertion.js @@ -0,0 +1,64 @@ +/** + * File: binary_search_insertion.js + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 二分搜尋插入點(無重複元素) */ +function binarySearchInsertionSimple(nums, target) { + let i = 0, + j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i; +} + +/* 二分搜尋插入點(存在重複元素) */ +function binarySearchInsertion(nums, target) { + let i = 0, + j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; +} + +/* Driver Code */ +// 無重複元素的陣列 +let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋插入點 +for (const target of [6, 9]) { + const index = binarySearchInsertionSimple(nums, target); + console.log('元素 ' + target + ' 的插入點的索引為 ' + index); +} + +// 包含重複元素的陣列 +nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋插入點 +for (const target of [2, 6, 20]) { + const index = binarySearchInsertion(nums, target); + console.log('元素 ' + target + ' 的插入點的索引為 ' + index); +} + +module.exports = { + binarySearchInsertion, +}; diff --git a/zh-hant/codes/javascript/chapter_searching/hashing_search.js b/zh-hant/codes/javascript/chapter_searching/hashing_search.js new file mode 100644 index 000000000..5f9ad6c95 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/hashing_search.js @@ -0,0 +1,45 @@ +/** + * File: hashing_search.js + * Created Time: 2022-12-29 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { arrToLinkedList } = require('../modules/ListNode'); + +/* 雜湊查詢(陣列) */ +function hashingSearchArray(map, target) { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map.has(target) ? map.get(target) : -1; +} + +/* 雜湊查詢(鏈結串列) */ +function hashingSearchLinkedList(map, target) { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map.has(target) ? map.get(target) : null; +} + +/* Driver Code */ +const target = 3; + +/* 雜湊查詢(陣列) */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +// 初始化雜湊表 +const map = new Map(); +for (let i = 0; i < nums.length; i++) { + map.set(nums[i], i); // key: 元素,value: 索引 +} +const index = hashingSearchArray(map, target); +console.log('目標元素 3 的索引 = ' + index); + +/* 雜湊查詢(鏈結串列) */ +let head = arrToLinkedList(nums); +// 初始化雜湊表 +const map1 = new Map(); +while (head != null) { + map1.set(head.val, head); // key: 節點值,value: 節點 + head = head.next; +} +const node = hashingSearchLinkedList(map1, target); +console.log('目標節點值 3 的對應節點物件為', node); diff --git a/zh-hant/codes/javascript/chapter_searching/linear_search.js b/zh-hant/codes/javascript/chapter_searching/linear_search.js new file mode 100644 index 000000000..599b65708 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/linear_search.js @@ -0,0 +1,47 @@ +/** + * File: linear_search.js + * Created Time: 2022-12-22 + * Author: JoseHung (szhong@link.cuhk.edu.hk) + */ + +const { ListNode, arrToLinkedList } = require('../modules/ListNode'); + +/* 線性查詢(陣列) */ +function linearSearchArray(nums, target) { + // 走訪陣列 + for (let i = 0; i < nums.length; i++) { + // 找到目標元素,返回其索引 + if (nums[i] === target) { + return i; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 線性查詢(鏈結串列)*/ +function linearSearchLinkedList(head, target) { + // 走訪鏈結串列 + while (head) { + // 找到目標節點,返回之 + if (head.val === target) { + return head; + } + head = head.next; + } + // 未找到目標節點,返回 null + return null; +} + +/* Driver Code */ +const target = 3; + +/* 在陣列中執行線性查詢 */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +const index = linearSearchArray(nums, target); +console.log('目標元素 3 的索引 = ' + index); + +/* 在鏈結串列中執行線性查詢 */ +const head = arrToLinkedList(nums); +const node = linearSearchLinkedList(head, target); +console.log('目標節點值 3 的對應節點物件為 ', node); diff --git a/zh-hant/codes/javascript/chapter_searching/two_sum.js b/zh-hant/codes/javascript/chapter_searching/two_sum.js new file mode 100644 index 000000000..693498b97 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_searching/two_sum.js @@ -0,0 +1,46 @@ +/** + * File: two_sum.js + * Created Time: 2022-12-15 + * Author: gyt95 (gytkwan@gmail.com) + */ + +/* 方法一:暴力列舉 */ +function twoSumBruteForce(nums, target) { + const n = nums.length; + // 兩層迴圈,時間複雜度為 O(n^2) + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + if (nums[i] + nums[j] === target) { + return [i, j]; + } + } + } + return []; +} + +/* 方法二:輔助雜湊表 */ +function twoSumHashTable(nums, target) { + // 輔助雜湊表,空間複雜度為 O(n) + let m = {}; + // 單層迴圈,時間複雜度為 O(n) + for (let i = 0; i < nums.length; i++) { + if (m[target - nums[i]] !== undefined) { + return [m[target - nums[i]], i]; + } else { + m[nums[i]] = i; + } + } + return []; +} + +/* Driver Code */ +// 方法一 +const nums = [2, 7, 11, 15], + target = 13; + +let res = twoSumBruteForce(nums, target); +console.log('方法一 res = ', res); + +// 方法二 +res = twoSumHashTable(nums, target); +console.log('方法二 res = ', res); diff --git a/zh-hant/codes/javascript/chapter_sorting/bubble_sort.js b/zh-hant/codes/javascript/chapter_sorting/bubble_sort.js new file mode 100644 index 000000000..a90400cb5 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/bubble_sort.js @@ -0,0 +1,49 @@ +/** + * File: bubble_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 泡沫排序 */ +function bubbleSort(nums) { + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +function bubbleSortWithFlag(nums) { + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + let flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 記錄交換元素 + } + } + if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +bubbleSort(nums); +console.log('泡沫排序完成後 nums =', nums); + +const nums1 = [4, 1, 3, 1, 5, 2]; +bubbleSortWithFlag(nums1); +console.log('泡沫排序完成後 nums =', nums1); diff --git a/zh-hant/codes/javascript/chapter_sorting/bucket_sort.js b/zh-hant/codes/javascript/chapter_sorting/bucket_sort.js new file mode 100644 index 000000000..82fb5c33e --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/bucket_sort.js @@ -0,0 +1,39 @@ +/** + * File: bucket_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 桶排序 */ +function bucketSort(nums) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + const k = nums.length / 2; + const buckets = []; + for (let i = 0; i < k; i++) { + buckets.push([]); + } + // 1. 將陣列元素分配到各個桶中 + for (const num of nums) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + const i = Math.floor(num * k); + // 將 num 新增進桶 i + buckets[i].push(num); + } + // 2. 對各個桶執行排序 + for (const bucket of buckets) { + // 使用內建排序函式,也可以替換成其他排序演算法 + bucket.sort((a, b) => a - b); + } + // 3. 走訪桶合併結果 + let i = 0; + for (const bucket of buckets) { + for (const num of bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; +bucketSort(nums); +console.log('桶排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_sorting/counting_sort.js b/zh-hant/codes/javascript/chapter_sorting/counting_sort.js new file mode 100644 index 000000000..bb57a3f07 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/counting_sort.js @@ -0,0 +1,71 @@ +/** + * File: counting_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +function countingSortNaive(nums) { + // 1. 統計陣列最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + const counter = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + let i = 0; + for (let num = 0; num < m + 1; num++) { + for (let j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +function countingSort(nums) { + // 1. 統計陣列最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + const counter = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (let i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + const n = nums.length; + const res = new Array(n); + for (let i = n - 1; i >= 0; i--) { + const num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Driver Code */ +const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSortNaive(nums); +console.log('計數排序(無法排序物件)完成後 nums =', nums); + +const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSort(nums1); +console.log('計數排序完成後 nums1 =', nums1); diff --git a/zh-hant/codes/javascript/chapter_sorting/heap_sort.js b/zh-hant/codes/javascript/chapter_sorting/heap_sort.js new file mode 100644 index 000000000..722470d2b --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/heap_sort.js @@ -0,0 +1,49 @@ +/** + * File: heap_sort.js + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +function siftDown(nums, n, i) { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let ma = i; + if (l < n && nums[l] > nums[ma]) { + ma = l; + } + if (r < n && nums[r] > nums[ma]) { + ma = r; + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma === i) { + break; + } + // 交換兩節點 + [nums[i], nums[ma]] = [nums[ma], nums[i]]; + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +function heapSort(nums) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (let i = nums.length - 1; i > 0; i--) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + [nums[0], nums[i]] = [nums[i], nums[0]]; + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +heapSort(nums); +console.log('堆積排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_sorting/insertion_sort.js b/zh-hant/codes/javascript/chapter_sorting/insertion_sort.js new file mode 100644 index 000000000..8668cb68c --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/insertion_sort.js @@ -0,0 +1,25 @@ +/** + * File: insertion_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 插入排序 */ +function insertionSort(nums) { + // 外迴圈:已排序區間為 [0, i-1] + for (let i = 1; i < nums.length; i++) { + let base = nums[i], + j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = base; // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +insertionSort(nums); +console.log('插入排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_sorting/merge_sort.js b/zh-hant/codes/javascript/chapter_sorting/merge_sort.js new file mode 100644 index 000000000..0a14ea99b --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/merge_sort.js @@ -0,0 +1,52 @@ +/** + * File: merge_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 合併左子陣列和右子陣列 */ +function merge(nums, left, mid, right) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + const tmp = new Array(right - left + 1); + // 初始化左子陣列和右子陣列的起始索引 + let i = left, + j = mid + 1, + k = 0; + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* 合併排序 */ +function mergeSort(nums, left, right) { + // 終止條件 + if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + let mid = Math.floor((left + right) / 2); // 計算中點 + mergeSort(nums, left, mid); // 遞迴左子陣列 + mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +const nums = [7, 3, 2, 6, 0, 1, 5, 4]; +mergeSort(nums, 0, nums.length - 1); +console.log('合併排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_sorting/quick_sort.js b/zh-hant/codes/javascript/chapter_sorting/quick_sort.js new file mode 100644 index 000000000..43e506fa9 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/quick_sort.js @@ -0,0 +1,161 @@ +/** + * File: quick_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 快速排序類別 */ +class QuickSort { + /* 元素交換 */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + partition(nums, left, right) { + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j -= 1; // 從右向左找首個小於基準數的元素 + } + while (i < j && nums[i] <= nums[left]) { + i += 1; // 從左向右找首個大於基準數的元素 + } + // 元素交換 + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + quickSort(nums, left, right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return; + // 哨兵劃分 + const pivot = this.partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + /* 元素交換 */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 選取三個候選元素的中位數 */ + medianThree(nums, left, mid, right) { + let l = nums[left], + m = nums[mid], + r = nums[right]; + // m 在 l 和 r 之間 + if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; + // l 在 m 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) return left; + return right; + } + + /* 哨兵劃分(三數取中值) */ + partition(nums, left, right) { + // 選取三個候選元素的中位數 + let med = this.medianThree( + nums, + left, + Math.floor((left + right) / 2), + right + ); + // 將中位數交換至陣列最左端 + this.swap(nums, left, med); + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + quickSort(nums, left, right) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return; + // 哨兵劃分 + const pivot = this.partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + /* 元素交換 */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + partition(nums, left, right) { + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) i++; // 從左向右找首個大於基準數的元素 + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + quickSort(nums, left, right) { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + let pivot = this.partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + this.quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + this.quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +/* 快速排序 */ +const nums = [2, 4, 1, 0, 3, 5]; +const quickSort = new QuickSort(); +quickSort.quickSort(nums, 0, nums.length - 1); +console.log('快速排序完成後 nums =', nums); + +/* 快速排序(中位基準數最佳化) */ +const nums1 = [2, 4, 1, 0, 3, 5]; +const quickSortMedian = new QuickSortMedian(); +quickSortMedian.quickSort(nums1, 0, nums1.length - 1); +console.log('快速排序(中位基準數最佳化)完成後 nums =', nums1); + +/* 快速排序(尾遞迴最佳化) */ +const nums2 = [2, 4, 1, 0, 3, 5]; +const quickSortTailCall = new QuickSortTailCall(); +quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); +console.log('快速排序(尾遞迴最佳化)完成後 nums =', nums2); diff --git a/zh-hant/codes/javascript/chapter_sorting/radix_sort.js b/zh-hant/codes/javascript/chapter_sorting/radix_sort.js new file mode 100644 index 000000000..b4412d4a4 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/radix_sort.js @@ -0,0 +1,66 @@ +/** + * File: radix_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +function digit(num, exp) { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return Math.floor(num / exp) % 10; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +function countingSortDigit(nums, exp) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + const counter = new Array(10).fill(0); + const n = nums.length; + // 統計 0~9 各數字的出現次數 + for (let i = 0; i < n; i++) { + const d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (let i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + const res = new Array(n).fill(0); + for (let i = n - 1; i >= 0; i--) { + const d = digit(nums[i], exp); + const j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d]--; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* 基數排序 */ +function radixSort(nums) { + // 獲取陣列的最大元素,用於判斷最大位數 + let m = Number.MIN_VALUE; + for (const num of nums) { + if (num > m) { + m = num; + } + } + // 按照從低位到高位的順序走訪 + for (let exp = 1; exp <= m; exp *= 10) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); + } +} + +/* Driver Code */ +const nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, + 30524779, 82060337, 63832996, +]; +radixSort(nums); +console.log('基數排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_sorting/selection_sort.js b/zh-hant/codes/javascript/chapter_sorting/selection_sort.js new file mode 100644 index 000000000..9b223d60a --- /dev/null +++ b/zh-hant/codes/javascript/chapter_sorting/selection_sort.js @@ -0,0 +1,27 @@ +/** + * File: selection_sort.js + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 選擇排序 */ +function selectionSort(nums) { + let n = nums.length; + // 外迴圈:未排序區間為 [i, n-1] + for (let i = 0; i < n - 1; i++) { + // 內迴圈:找到未排序區間內的最小元素 + let k = i; + for (let j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) { + k = j; // 記錄最小元素的索引 + } + } + // 將該最小元素與未排序區間的首個元素交換 + [nums[i], nums[k]] = [nums[k], nums[i]]; + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +selectionSort(nums); +console.log('選擇排序完成後 nums =', nums); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/array_deque.js b/zh-hant/codes/javascript/chapter_stack_and_queue/array_deque.js new file mode 100644 index 000000000..23ea4c892 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/array_deque.js @@ -0,0 +1,156 @@ +/** + * File: array_deque.js + * Created Time: 2023-02-28 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + #nums; // 用於儲存雙向佇列元素的陣列 + #front; // 佇列首指標,指向佇列首元素 + #queSize; // 雙向佇列長度 + + /* 建構子 */ + constructor(capacity) { + this.#nums = new Array(capacity); + this.#front = 0; + this.#queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + capacity() { + return this.#nums.length; + } + + /* 獲取雙向佇列的長度 */ + size() { + return this.#queSize; + } + + /* 判斷雙向佇列是否為空 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 計算環形陣列索引 */ + index(i) { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + this.capacity()) % this.capacity(); + } + + /* 佇列首入列 */ + pushFirst(num) { + if (this.#queSize === this.capacity()) { + console.log('雙向佇列已滿'); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + this.#front = this.index(this.#front - 1); + // 將 num 新增至佇列首 + this.#nums[this.#front] = num; + this.#queSize++; + } + + /* 佇列尾入列 */ + pushLast(num) { + if (this.#queSize === this.capacity()) { + console.log('雙向佇列已滿'); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + const rear = this.index(this.#front + this.#queSize); + // 將 num 新增至佇列尾 + this.#nums[rear] = num; + this.#queSize++; + } + + /* 佇列首出列 */ + popFirst() { + const num = this.peekFirst(); + // 佇列首指標向後移動一位 + this.#front = this.index(this.#front + 1); + this.#queSize--; + return num; + } + + /* 佇列尾出列 */ + popLast() { + const num = this.peekLast(); + this.#queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peekFirst() { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + return this.#nums[this.#front]; + } + + /* 訪問佇列尾元素 */ + peekLast() { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + // 計算尾元素索引 + const last = this.index(this.#front + this.#queSize - 1); + return this.#nums[last]; + } + + /* 返回陣列用於列印 */ + toArray() { + // 僅轉換有效長度範圍內的串列元素 + const res = []; + for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) { + res[i] = this.#nums[this.index(j)]; + } + return res; + } +} + +/* Driver Code */ +/* 初始化雙向佇列 */ +const capacity = 5; +const deque = new ArrayDeque(capacity); +deque.pushLast(3); +deque.pushLast(2); +deque.pushLast(5); +console.log('雙向佇列 deque = [' + deque.toArray() + ']'); + +/* 訪問元素 */ +const peekFirst = deque.peekFirst(); +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast = deque.peekLast(); +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素入列 */ +deque.pushLast(4); +console.log('元素 4 佇列尾入列後 deque = [' + deque.toArray() + ']'); +deque.pushFirst(1); +console.log('元素 1 佇列首入列後 deque = [' + deque.toArray() + ']'); + +/* 元素出列 */ +const popLast = deque.popLast(); +console.log( + '佇列尾出列元素 = ' + + popLast + + ',佇列尾出列後 deque = [' + + deque.toArray() + + ']' +); +const popFirst = deque.popFirst(); +console.log( + '佇列首出列元素 = ' + + popFirst + + ',佇列首出列後 deque = [' + + deque.toArray() + + ']' +); + +/* 獲取雙向佇列的長度 */ +const size = deque.size(); +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty = deque.isEmpty(); +console.log('雙向佇列是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/array_queue.js b/zh-hant/codes/javascript/chapter_stack_and_queue/array_queue.js new file mode 100644 index 000000000..b2ce12918 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/array_queue.js @@ -0,0 +1,106 @@ +/** + * File: array_queue.js + * Created Time: 2022-12-13 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + #nums; // 用於儲存佇列元素的陣列 + #front = 0; // 佇列首指標,指向佇列首元素 + #queSize = 0; // 佇列長度 + + constructor(capacity) { + this.#nums = new Array(capacity); + } + + /* 獲取佇列的容量 */ + get capacity() { + return this.#nums.length; + } + + /* 獲取佇列的長度 */ + get size() { + return this.#queSize; + } + + /* 判斷佇列是否為空 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 入列 */ + push(num) { + if (this.size === this.capacity) { + console.log('佇列已滿'); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + const rear = (this.#front + this.size) % this.capacity; + // 將 num 新增至佇列尾 + this.#nums[rear] = num; + this.#queSize++; + } + + /* 出列 */ + pop() { + const num = this.peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + this.#front = (this.#front + 1) % this.capacity; + this.#queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peek() { + if (this.isEmpty()) throw new Error('佇列為空'); + return this.#nums[this.#front]; + } + + /* 返回 Array */ + toArray() { + // 僅轉換有效長度範圍內的串列元素 + const arr = new Array(this.size); + for (let i = 0, j = this.#front; i < this.size; i++, j++) { + arr[i] = this.#nums[j % this.capacity]; + } + return arr; + } +} + +/* Driver Code */ +/* 初始化佇列 */ +const capacity = 10; +const queue = new ArrayQueue(capacity); + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue =', queue.toArray()); + +/* 訪問佇列首元素 */ +const peek = queue.peek(); +console.log('佇列首元素 peek = ' + peek); + +/* 元素出列 */ +const pop = queue.pop(); +console.log('出列元素 pop = ' + pop + ',出列後 queue =', queue.toArray()); + +/* 獲取佇列的長度 */ +const size = queue.size; +console.log('佇列長度 size = ' + size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.isEmpty(); +console.log('佇列是否為空 = ' + isEmpty); + +/* 測試環形陣列 */ +for (let i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + console.log('第 ' + i + ' 輪入列 + 出列後 queue =', queue.toArray()); +} diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/array_stack.js b/zh-hant/codes/javascript/chapter_stack_and_queue/array_stack.js new file mode 100644 index 000000000..391978eab --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/array_stack.js @@ -0,0 +1,75 @@ +/** + * File: array_stack.js + * Created Time: 2022-12-09 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + #stack; + constructor() { + this.#stack = []; + } + + /* 獲取堆疊的長度 */ + get size() { + return this.#stack.length; + } + + /* 判斷堆疊是否為空 */ + isEmpty() { + return this.#stack.length === 0; + } + + /* 入堆疊 */ + push(num) { + this.#stack.push(num); + } + + /* 出堆疊 */ + pop() { + if (this.isEmpty()) throw new Error('堆疊為空'); + return this.#stack.pop(); + } + + /* 訪問堆疊頂元素 */ + top() { + if (this.isEmpty()) throw new Error('堆疊為空'); + return this.#stack[this.#stack.length - 1]; + } + + /* 返回 Array */ + toArray() { + return this.#stack; + } +} + +/* Driver Code */ +/* 初始化堆疊 */ +const stack = new ArrayStack(); + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack = '); +console.log(stack.toArray()); + +/* 訪問堆疊頂元素 */ +const top = stack.top(); +console.log('堆疊頂元素 top = ' + top); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = '); +console.log(stack.toArray()); + +/* 獲取堆疊的長度 */ +const size = stack.size; +console.log('堆疊的長度 size = ' + size); + +/* 判斷是否為空 */ +const isEmpty = stack.isEmpty(); +console.log('堆疊是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/deque.js b/zh-hant/codes/javascript/chapter_stack_and_queue/deque.js new file mode 100644 index 000000000..90e0f5fe9 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/deque.js @@ -0,0 +1,44 @@ +/** + * File: deque.js + * Created Time: 2023-01-17 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Driver Code */ +/* 初始化雙向佇列 */ +// JavaScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 +const deque = []; + +/* 元素入列 */ +deque.push(2); +deque.push(5); +deque.push(4); +// 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) +deque.unshift(3); +deque.unshift(1); +console.log('雙向佇列 deque = ', deque); + +/* 訪問元素 */ +const peekFirst = deque[0]; +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast = deque[deque.length - 1]; +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素出列 */ +// 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) +const popFront = deque.shift(); +console.log( + '佇列首出列元素 popFront = ' + popFront + ',佇列首出列後 deque = ' + deque +); +const popBack = deque.pop(); +console.log( + '佇列尾出列元素 popBack = ' + popBack + ',佇列尾出列後 deque = ' + deque +); + +/* 獲取雙向佇列的長度 */ +const size = deque.length; +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty = size === 0; +console.log('雙向佇列是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js new file mode 100644 index 000000000..f640433e5 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.js + * Created Time: 2023-02-04 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 雙向鏈結串列節點 */ +class ListNode { + prev; // 前驅節點引用 (指標) + next; // 後繼節點引用 (指標) + val; // 節點值 + + constructor(val) { + this.val = val; + this.next = null; + this.prev = null; + } +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + #front; // 頭節點 front + #rear; // 尾節點 rear + #queSize; // 雙向佇列的長度 + + constructor() { + this.#front = null; + this.#rear = null; + this.#queSize = 0; + } + + /* 佇列尾入列操作 */ + pushLast(val) { + const node = new ListNode(val); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // 將 node 新增至鏈結串列尾部 + this.#rear.next = node; + node.prev = this.#rear; + this.#rear = node; // 更新尾節點 + } + this.#queSize++; + } + + /* 佇列首入列操作 */ + pushFirst(val) { + const node = new ListNode(val); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // 將 node 新增至鏈結串列頭部 + this.#front.prev = node; + node.next = this.#front; + this.#front = node; // 更新頭節點 + } + this.#queSize++; + } + + /* 佇列尾出列操作 */ + popLast() { + if (this.#queSize === 0) { + return null; + } + const value = this.#rear.val; // 儲存尾節點值 + // 刪除尾節點 + let temp = this.#rear.prev; + if (temp !== null) { + temp.next = null; + this.#rear.prev = null; + } + this.#rear = temp; // 更新尾節點 + this.#queSize--; + return value; + } + + /* 佇列首出列操作 */ + popFirst() { + if (this.#queSize === 0) { + return null; + } + const value = this.#front.val; // 儲存尾節點值 + // 刪除頭節點 + let temp = this.#front.next; + if (temp !== null) { + temp.prev = null; + this.#front.next = null; + } + this.#front = temp; // 更新頭節點 + this.#queSize--; + return value; + } + + /* 訪問佇列尾元素 */ + peekLast() { + return this.#queSize === 0 ? null : this.#rear.val; + } + + /* 訪問佇列首元素 */ + peekFirst() { + return this.#queSize === 0 ? null : this.#front.val; + } + + /* 獲取雙向佇列的長度 */ + size() { + return this.#queSize; + } + + /* 判斷雙向佇列是否為空 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 列印雙向佇列 */ + print() { + const arr = []; + let temp = this.#front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } +} + +/* Driver Code */ +/* 初始化雙向佇列 */ +const linkedListDeque = new LinkedListDeque(); +linkedListDeque.pushLast(3); +linkedListDeque.pushLast(2); +linkedListDeque.pushLast(5); +console.log('雙向佇列 linkedListDeque = '); +linkedListDeque.print(); + +/* 訪問元素 */ +const peekFirst = linkedListDeque.peekFirst(); +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast = linkedListDeque.peekLast(); +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素入列 */ +linkedListDeque.pushLast(4); +console.log('元素 4 佇列尾入列後 linkedListDeque = '); +linkedListDeque.print(); +linkedListDeque.pushFirst(1); +console.log('元素 1 佇列首入列後 linkedListDeque = '); +linkedListDeque.print(); + +/* 元素出列 */ +const popLast = linkedListDeque.popLast(); +console.log('佇列尾出列元素 = ' + popLast + ',佇列尾出列後 linkedListDeque = '); +linkedListDeque.print(); +const popFirst = linkedListDeque.popFirst(); +console.log('佇列首出列元素 = ' + popFirst + ',佇列首出列後 linkedListDeque = '); +linkedListDeque.print(); + +/* 獲取雙向佇列的長度 */ +const size = linkedListDeque.size(); +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty = linkedListDeque.isEmpty(); +console.log('雙向佇列是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js new file mode 100644 index 000000000..bc6c6178d --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js @@ -0,0 +1,99 @@ +/** + * File: linkedlist_queue.js + * Created Time: 2022-12-20 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +const { ListNode } = require('../modules/ListNode'); + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + #front; // 頭節點 #front + #rear; // 尾節點 #rear + #queSize = 0; + + constructor() { + this.#front = null; + this.#rear = null; + } + + /* 獲取佇列的長度 */ + get size() { + return this.#queSize; + } + + /* 判斷佇列是否為空 */ + isEmpty() { + return this.size === 0; + } + + /* 入列 */ + push(num) { + // 在尾節點後新增 num + const node = new ListNode(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (!this.#front) { + this.#front = node; + this.#rear = node; + // 如果佇列不為空,則將該節點新增到尾節點後 + } else { + this.#rear.next = node; + this.#rear = node; + } + this.#queSize++; + } + + /* 出列 */ + pop() { + const num = this.peek(); + // 刪除頭節點 + this.#front = this.#front.next; + this.#queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peek() { + if (this.size === 0) throw new Error('佇列為空'); + return this.#front.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + toArray() { + let node = this.#front; + const res = new Array(this.size); + for (let i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +/* Driver Code */ +/* 初始化佇列 */ +const queue = new LinkedListQueue(); + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue = ' + queue.toArray()); + +/* 訪問佇列首元素 */ +const peek = queue.peek(); +console.log('佇列首元素 peek = ' + peek); + +/* 元素出列 */ +const pop = queue.pop(); +console.log('出列元素 pop = ' + pop + ',出列後 queue = ' + queue.toArray()); + +/* 獲取佇列的長度 */ +const size = queue.size; +console.log('佇列長度 size = ' + size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.isEmpty(); +console.log('佇列是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js new file mode 100644 index 000000000..11f257af6 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js @@ -0,0 +1,88 @@ +/** + * File: linkedlist_stack.js + * Created Time: 2022-12-22 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +const { ListNode } = require('../modules/ListNode'); + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + #stackPeek; // 將頭節點作為堆疊頂 + #stkSize = 0; // 堆疊的長度 + + constructor() { + this.#stackPeek = null; + } + + /* 獲取堆疊的長度 */ + get size() { + return this.#stkSize; + } + + /* 判斷堆疊是否為空 */ + isEmpty() { + return this.size === 0; + } + + /* 入堆疊 */ + push(num) { + const node = new ListNode(num); + node.next = this.#stackPeek; + this.#stackPeek = node; + this.#stkSize++; + } + + /* 出堆疊 */ + pop() { + const num = this.peek(); + this.#stackPeek = this.#stackPeek.next; + this.#stkSize--; + return num; + } + + /* 訪問堆疊頂元素 */ + peek() { + if (!this.#stackPeek) throw new Error('堆疊為空'); + return this.#stackPeek.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + toArray() { + let node = this.#stackPeek; + const res = new Array(this.size); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +/* Driver Code */ +/* 初始化堆疊 */ +const stack = new LinkedListStack(); + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack = ' + stack.toArray()); + +/* 訪問堆疊頂元素 */ +const peek = stack.peek(); +console.log('堆疊頂元素 peek = ' + peek); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = ' + stack.toArray()); + +/* 獲取堆疊的長度 */ +const size = stack.size; +console.log('堆疊的長度 size = ' + size); + +/* 判斷是否為空 */ +const isEmpty = stack.isEmpty(); +console.log('堆疊是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/queue.js b/zh-hant/codes/javascript/chapter_stack_and_queue/queue.js new file mode 100644 index 000000000..8a5fffc8f --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/queue.js @@ -0,0 +1,35 @@ +/** + * File: queue.js + * Created Time: 2022-12-05 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* 初始化佇列 */ +// JavaScript 沒有內建的佇列,可以把 Array 當作佇列來使用 +const queue = []; + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue =', queue); + +/* 訪問佇列首元素 */ +const peek = queue[0]; +console.log('佇列首元素 peek =', peek); + +/* 元素出列 */ +// 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) +const pop = queue.shift(); +console.log('出列元素 pop =', pop, ',出列後 queue = ', queue); + +/* 獲取佇列的長度 */ +const size = queue.length; +console.log('佇列長度 size =', size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.length === 0; +console.log('佇列是否為空 = ', isEmpty); diff --git a/zh-hant/codes/javascript/chapter_stack_and_queue/stack.js b/zh-hant/codes/javascript/chapter_stack_and_queue/stack.js new file mode 100644 index 000000000..32bfc2f5a --- /dev/null +++ b/zh-hant/codes/javascript/chapter_stack_and_queue/stack.js @@ -0,0 +1,35 @@ +/** + * File: stack.js + * Created Time: 2022-12-04 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* 初始化堆疊 */ +// JavaScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 +const stack = []; + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack =', stack); + +/* 訪問堆疊頂元素 */ +const peek = stack[stack.length - 1]; +console.log('堆疊頂元素 peek =', peek); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop =', pop); +console.log('出堆疊後 stack =', stack); + +/* 獲取堆疊的長度 */ +const size = stack.length; +console.log('堆疊的長度 size =', size); + +/* 判斷是否為空 */ +const isEmpty = stack.length === 0; +console.log('堆疊是否為空 =', isEmpty); diff --git a/zh-hant/codes/javascript/chapter_tree/array_binary_tree.js b/zh-hant/codes/javascript/chapter_tree/array_binary_tree.js new file mode 100644 index 000000000..8f588f0b7 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/array_binary_tree.js @@ -0,0 +1,147 @@ +/** + * File: array_binary_tree.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + #tree; + + /* 建構子 */ + constructor(arr) { + this.#tree = arr; + } + + /* 串列容量 */ + size() { + return this.#tree.length; + } + + /* 獲取索引為 i 節點的值 */ + val(i) { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + left(i) { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + right(i) { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + parent(i) { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 層序走訪 */ + levelOrder() { + let res = []; + // 直接走訪陣列 + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* 深度優先走訪 */ + #dfs(i, order, res) { + // 若為空位,則返回 + if (this.val(i) === null) return; + // 前序走訪 + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // 中序走訪 + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // 後序走訪 + if (order === 'post') res.push(this.val(i)); + } + + /* 前序走訪 */ + preOrder() { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* 中序走訪 */ + inOrder() { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* 後序走訪 */ + postOrder() { + const res = []; + this.#dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +// 初始化二元樹 +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const arr = Array.of( + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 +); + +const root = arrToTree(arr); +console.log('\n初始化二元樹\n'); +console.log('二元樹的陣列表示:'); +console.log(arr); +console.log('二元樹的鏈結串列表示:'); +printTree(root); + +// 陣列表示下的二元樹類別 +const abt = new ArrayBinaryTree(arr); + +// 訪問節點 +const i = 1; +const l = abt.left(i); +const r = abt.right(i); +const p = abt.parent(i); +console.log('\n當前節點的索引為 ' + i + ' ,值為 ' + abt.val(i)); +console.log( + '其左子節點的索引為 ' + l + ' ,值為 ' + (l === null ? 'null' : abt.val(l)) +); +console.log( + '其右子節點的索引為 ' + r + ' ,值為 ' + (r === null ? 'null' : abt.val(r)) +); +console.log( + '其父節點的索引為 ' + p + ' ,值為 ' + (p === null ? 'null' : abt.val(p)) +); + +// 走訪樹 +let res = abt.levelOrder(); +console.log('\n層序走訪為:' + res); +res = abt.preOrder(); +console.log('前序走訪為:' + res); +res = abt.inOrder(); +console.log('中序走訪為:' + res); +res = abt.postOrder(); +console.log('後序走訪為:' + res); diff --git a/zh-hant/codes/javascript/chapter_tree/avl_tree.js b/zh-hant/codes/javascript/chapter_tree/avl_tree.js new file mode 100644 index 000000000..bdcb8be3f --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/avl_tree.js @@ -0,0 +1,208 @@ +/** + * File: avl_tree.js + * Created Time: 2023-02-05 + * Author: what-is-me (whatisme@outlook.jp) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* AVL 樹*/ +class AVLTree { + /* 建構子 */ + constructor() { + this.root = null; //根節點 + } + + /* 獲取節點高度 */ + height(node) { + // 空節點高度為 -1 ,葉節點高度為 0 + return node === null ? -1 : node.height; + } + + /* 更新節點高度 */ + #updateHeight(node) { + // 節點高度等於最高子樹高度 + 1 + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + + /* 獲取平衡因子 */ + balanceFactor(node) { + // 空節點平衡因子為 0 + if (node === null) return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return this.height(node.left) - this.height(node.right); + } + + /* 右旋操作 */ + #rightRotate(node) { + const child = node.left; + const grandChild = child.right; + // 以 child 為原點,將 node 向右旋轉 + child.right = node; + node.left = grandChild; + // 更新節點高度 + this.#updateHeight(node); + this.#updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + #leftRotate(node) { + const child = node.right; + const grandChild = child.left; + // 以 child 為原點,將 node 向左旋轉 + child.left = node; + node.right = grandChild; + // 更新節點高度 + this.#updateHeight(node); + this.#updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + #rotate(node) { + // 獲取節點 node 的平衡因子 + const balanceFactor = this.balanceFactor(node); + // 左偏樹 + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // 右旋 + return this.#rightRotate(node); + } else { + // 先左旋後右旋 + node.left = this.#leftRotate(node.left); + return this.#rightRotate(node); + } + } + // 右偏樹 + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // 左旋 + return this.#leftRotate(node); + } else { + // 先右旋後左旋 + node.right = this.#rightRotate(node.right); + return this.#leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 插入節點 */ + insert(val) { + this.root = this.#insertHelper(this.root, val); + } + + /* 遞迴插入節點(輔助方法) */ + #insertHelper(node, val) { + if (node === null) return new TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node.val) node.left = this.#insertHelper(node.left, val); + else if (val > node.val) + node.right = this.#insertHelper(node.right, val); + else return node; // 重複節點不插入,直接返回 + this.#updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = this.#rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 刪除節點 */ + remove(val) { + this.root = this.#removeHelper(this.root, val); + } + + /* 遞迴刪除節點(輔助方法) */ + #removeHelper(node, val) { + if (node === null) return null; + /* 1. 查詢節點並刪除 */ + if (val < node.val) node.left = this.#removeHelper(node.left, val); + else if (val > node.val) + node.right = this.#removeHelper(node.right, val); + else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child === null) return null; + // 子節點數量 = 1 ,直接刪除 node + else node = child; + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.#removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.#updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = this.#rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 查詢節點 */ + search(val) { + let cur = this.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < val) cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > val) cur = cur.left; + // 找到目標節點,跳出迴圈 + else break; + } + // 返回目標節點 + return cur; + } +} + +function testInsert(tree, val) { + tree.insert(val); + console.log('\n插入節點 ' + val + ' 後,AVL 樹為'); + printTree(tree.root); +} + +function testRemove(tree, val) { + tree.remove(val); + console.log('\n刪除節點 ' + val + ' 後,AVL 樹為'); + printTree(tree.root); +} + +/* Driver Code */ +/* 初始化空 AVL 樹 */ +const avlTree = new AVLTree(); +/* 插入節點 */ +// 請關注插入節點後,AVL 樹是如何保持平衡的 +testInsert(avlTree, 1); +testInsert(avlTree, 2); +testInsert(avlTree, 3); +testInsert(avlTree, 4); +testInsert(avlTree, 5); +testInsert(avlTree, 8); +testInsert(avlTree, 7); +testInsert(avlTree, 9); +testInsert(avlTree, 10); +testInsert(avlTree, 6); + +/* 插入重複節點 */ +testInsert(avlTree, 7); + +/* 刪除節點 */ +// 請關注刪除節點後,AVL 樹是如何保持平衡的 +testRemove(avlTree, 8); // 刪除度為 0 的節點 +testRemove(avlTree, 5); // 刪除度為 1 的節點 +testRemove(avlTree, 4); // 刪除度為 2 的節點 + +/* 查詢節點 */ +const node = avlTree.search(7); +console.log('\n查詢到的節點物件為', node, ',節點值 = ' + node.val); diff --git a/zh-hant/codes/javascript/chapter_tree/binary_search_tree.js b/zh-hant/codes/javascript/chapter_tree/binary_search_tree.js new file mode 100644 index 000000000..ee0f50cc0 --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/binary_search_tree.js @@ -0,0 +1,139 @@ +/** + * File: binary_search_tree.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 二元搜尋樹 */ +class BinarySearchTree { + /* 建構子 */ + constructor() { + // 初始化空樹 + this.root = null; + } + + /* 獲取二元樹根節點 */ + getRoot() { + return this.root; + } + + /* 查詢節點 */ + search(num) { + let cur = this.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > num) cur = cur.left; + // 找到目標節點,跳出迴圈 + else break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + insert(num) { + // 若樹為空,則初始化根節點 + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur = this.root, + pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 找到重複節點,直接返回 + if (cur.val === num) return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 插入位置在 cur 的左子樹中 + else cur = cur.left; + } + // 插入節點 + const node = new TreeNode(num); + if (pre.val < num) pre.right = node; + else pre.left = node; + } + + /* 刪除節點 */ + remove(num) { + // 若樹為空,直接提前返回 + if (this.root === null) return; + let cur = this.root, + pre = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 找到待刪除節點,跳出迴圈 + if (cur.val === num) break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 待刪除節點在 cur 的左子樹中 + else cur = cur.left; + } + // 若無待刪除節點,則直接返回 + if (cur === null) return; + // 子節點數量 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + const child = cur.left !== null ? cur.left : cur.right; + // 刪除節點 cur + if (cur !== this.root) { + if (pre.left === cur) pre.left = child; + else pre.right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + this.root = child; + } + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + let tmp = cur.right; + while (tmp.left !== null) { + tmp = tmp.left; + } + // 遞迴刪除節點 tmp + this.remove(tmp.val); + // 用 tmp 覆蓋 cur + cur.val = tmp.val; + } + } +} + +/* Driver Code */ +/* 初始化二元搜尋樹 */ +const bst = new BinarySearchTree(); +// 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 +const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; +for (const num of nums) { + bst.insert(num); +} +console.log('\n初始化的二元樹為\n'); +printTree(bst.getRoot()); + +/* 查詢節點 */ +const node = bst.search(7); +console.log('\n查詢到的節點物件為 ' + node + ',節點值 = ' + node.val); + +/* 插入節點 */ +bst.insert(16); +console.log('\n插入節點 16 後,二元樹為\n'); +printTree(bst.getRoot()); + +/* 刪除節點 */ +bst.remove(1); +console.log('\n刪除節點 1 後,二元樹為\n'); +printTree(bst.getRoot()); +bst.remove(2); +console.log('\n刪除節點 2 後,二元樹為\n'); +printTree(bst.getRoot()); +bst.remove(4); +console.log('\n刪除節點 4 後,二元樹為\n'); +printTree(bst.getRoot()); diff --git a/zh-hant/codes/javascript/chapter_tree/binary_tree.js b/zh-hant/codes/javascript/chapter_tree/binary_tree.js new file mode 100644 index 000000000..818dbf30a --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/binary_tree.js @@ -0,0 +1,35 @@ +/** + * File: binary_tree.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 初始化二元樹 */ +// 初始化節點 +let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); +// 構建節點之間的引用(指標) +n1.left = n2; +n1.right = n3; +n2.left = n4; +n2.right = n5; +console.log('\n初始化二元樹\n'); +printTree(n1); + +/* 插入與刪除節點 */ +const P = new TreeNode(0); +// 在 n1 -> n2 中間插入節點 P +n1.left = P; +P.left = n2; +console.log('\n插入節點 P 後\n'); +printTree(n1); +// 刪除節點 P +n1.left = n2; +console.log('\n刪除節點 P 後\n'); +printTree(n1); diff --git a/zh-hant/codes/javascript/chapter_tree/binary_tree_bfs.js b/zh-hant/codes/javascript/chapter_tree/binary_tree_bfs.js new file mode 100644 index 000000000..8bf3e526e --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/binary_tree_bfs.js @@ -0,0 +1,34 @@ +/** + * File: binary_tree_bfs.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 層序走訪 */ +function levelOrder(root) { + // 初始化佇列,加入根節點 + const queue = [root]; + // 初始化一個串列,用於儲存走訪序列 + const list = []; + while (queue.length) { + let node = queue.shift(); // 隊列出隊 + list.push(node.val); // 儲存節點值 + if (node.left) queue.push(node.left); // 左子節點入列 + if (node.right) queue.push(node.right); // 右子節點入列 + } + return list; +} + +/* Driver Code */ +/* 初始化二元樹 */ +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹\n'); +printTree(root); + +/* 層序走訪 */ +const list = levelOrder(root); +console.log('\n層序走訪的節點列印序列 = ' + list); diff --git a/zh-hant/codes/javascript/chapter_tree/binary_tree_dfs.js b/zh-hant/codes/javascript/chapter_tree/binary_tree_dfs.js new file mode 100644 index 000000000..4b45affbf --- /dev/null +++ b/zh-hant/codes/javascript/chapter_tree/binary_tree_dfs.js @@ -0,0 +1,60 @@ +/** + * File: binary_tree_dfs.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +// 初始化串列,用於儲存走訪序列 +const list = []; + +/* 前序走訪 */ +function preOrder(root) { + if (root === null) return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.push(root.val); + preOrder(root.left); + preOrder(root.right); +} + +/* 中序走訪 */ +function inOrder(root) { + if (root === null) return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root.left); + list.push(root.val); + inOrder(root.right); +} + +/* 後序走訪 */ +function postOrder(root) { + if (root === null) return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root.left); + postOrder(root.right); + list.push(root.val); +} + +/* Driver Code */ +/* 初始化二元樹 */ +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹\n'); +printTree(root); + +/* 前序走訪 */ +list.length = 0; +preOrder(root); +console.log('\n前序走訪的節點列印序列 = ' + list); + +/* 中序走訪 */ +list.length = 0; +inOrder(root); +console.log('\n中序走訪的節點列印序列 = ' + list); + +/* 後序走訪 */ +list.length = 0; +postOrder(root); +console.log('\n後序走訪的節點列印序列 = ' + list); diff --git a/zh-hant/codes/javascript/modules/ListNode.js b/zh-hant/codes/javascript/modules/ListNode.js new file mode 100755 index 000000000..e6f8b3d88 --- /dev/null +++ b/zh-hant/codes/javascript/modules/ListNode.js @@ -0,0 +1,31 @@ +/** + * File: ListNode.js + * Created Time: 2022-12-12 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 鏈結串列節點 */ +class ListNode { + val; // 節點值 + next; // 指向下一節點的引用(指標) + constructor(val, next) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/* 將串列反序列化為鏈結串列 */ +function arrToLinkedList(arr) { + const dum = new ListNode(0); + let head = dum; + for (const val of arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; +} + +module.exports = { + ListNode, + arrToLinkedList, +}; diff --git a/zh-hant/codes/javascript/modules/PrintUtil.js b/zh-hant/codes/javascript/modules/PrintUtil.js new file mode 100644 index 000000000..1803f4533 --- /dev/null +++ b/zh-hant/codes/javascript/modules/PrintUtil.js @@ -0,0 +1,86 @@ +/** + * File: PrintUtil.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('./TreeNode'); + +/* 列印鏈結串列 */ +function printLinkedList(head) { + let list = []; + while (head !== null) { + list.push(head.val.toString()); + head = head.next; + } + console.log(list.join(' -> ')); +} + +function Trunk(prev, str) { + this.prev = prev; + this.str = str; +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +function printTree(root) { + printTree(root, null, false); +} + +/* 列印二元樹 */ +function printTree(root, prev, isRight) { + if (root === null) { + return; + } + + let prev_str = ' '; + let trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (!prev) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTree(root.left, trunk, false); +} + +function showTrunks(p) { + if (!p) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); +} + +/* 列印堆積 */ +function printHeap(arr) { + console.log('堆積的陣列表示:'); + console.log(arr); + console.log('堆積的樹狀表示:'); + printTree(arrToTree(arr)); +} + +module.exports = { + printLinkedList, + printTree, + printHeap, +}; diff --git a/zh-hant/codes/javascript/modules/TreeNode.js b/zh-hant/codes/javascript/modules/TreeNode.js new file mode 100644 index 000000000..b2ad065b5 --- /dev/null +++ b/zh-hant/codes/javascript/modules/TreeNode.js @@ -0,0 +1,35 @@ +/** + * File: TreeNode.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 二元樹節點 */ +class TreeNode { + val; // 節點值 + left; // 左子節點指標 + right; // 右子節點指標 + height; //節點高度 + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + this.height = height === undefined ? 0 : height; + } +} + +/* 將陣列反序列化為二元樹 */ +function arrToTree(arr, i = 0) { + if (i < 0 || i >= arr.length || arr[i] === null) { + return null; + } + let root = new TreeNode(arr[i]); + root.left = arrToTree(arr, 2 * i + 1); + root.right = arrToTree(arr, 2 * i + 2); + return root; +} + +module.exports = { + TreeNode, + arrToTree, +}; diff --git a/zh-hant/codes/javascript/modules/Vertex.js b/zh-hant/codes/javascript/modules/Vertex.js new file mode 100644 index 000000000..22eacbbde --- /dev/null +++ b/zh-hant/codes/javascript/modules/Vertex.js @@ -0,0 +1,35 @@ +/** + * File: Vertex.js + * Created Time: 2023-02-15 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 頂點類別 */ +class Vertex { + val; + constructor(val) { + this.val = val; + } + + /* 輸入值串列 vals ,返回頂點串列 vets */ + static valsToVets(vals) { + const vets = []; + for (let i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + static vetsToVals(vets) { + const vals = []; + for (const vet of vets) { + vals.push(vet.val); + } + return vals; + } +} + +module.exports = { + Vertex, +}; diff --git a/zh-hant/codes/kotlin/chapter_array_and_linkedlist/array.kt b/zh-hant/codes/kotlin/chapter_array_and_linkedlist/array.kt new file mode 100644 index 000000000..81d6c7e6a --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_array_and_linkedlist/array.kt @@ -0,0 +1,101 @@ +/** + * File: array.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_array_and_linkedlist + +import java.util.concurrent.ThreadLocalRandom + +/* 隨機訪問元素 */ +fun randomAccess(nums: IntArray): Int { + // 在區間 [0, nums.size) 中隨機抽取一個數字 + val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size) + // 獲取並返回隨機元素 + val randomNum = nums[randomIndex] + return randomNum +} + +/* 擴展陣列長度 */ +fun extend(nums: IntArray, enlarge: Int): IntArray { + // 初始化一個擴展長度後的陣列 + val res = IntArray(nums.size + enlarge) + // 將原陣列中的所有元素複製到新陣列 + for (i in nums.indices) { + res[i] = nums[i] + } + // 返回擴展後的新陣列 + return res +} + +/* 在陣列的索引 index 處插入元素 num */ +fun insert(nums: IntArray, num: Int, index: Int) { + // 把索引 index 以及之後的所有元素向後移動一位 + for (i in nums.size - 1 downTo index + 1) { + nums[i] = nums[i - 1] + } + // 將 num 賦給 index 處的元素 + nums[index] = num +} + +/* 刪除索引 index 處的元素 */ +fun remove(nums: IntArray, index: Int) { + // 把索引 index 之後的所有元素向前移動一位 + for (i in index..(numbers) + println("串列 nums = $nums") + + /* 訪問元素 */ + val num = nums[1] + println("訪問索引 1 處的元素,得到 num = $num") + + /* 更新元素 */ + nums[1] = 0 + println("將索引 1 處的元素更新為 0 ,得到 nums = $nums") + + /* 清空串列 */ + nums.clear() + println("清空串列後 nums = $nums") + + /* 在尾部新增元素 */ + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + println("新增元素後 nums = $nums") + + /* 在中間插入元素 */ + nums.add(3, 6) + println("在索引 3 處插入數字 6 ,得到 nums = $nums") + + /* 刪除元素 */ + nums.removeAt(3) + println("刪除索引 3 處的元素,得到 nums = $nums") + + /* 透過索引走訪串列 */ + var count = 0 + for (i in nums.indices) { + count += nums[i] + } + + /* 直接走訪串列元素 */ + for (j in nums) { + count += j + } + + /* 拼接兩個串列*/ + val nums1 = ArrayList(listOf(6, 8, 7, 10, 9)) + nums.addAll(nums1) + println("將串列 nums1 拼接到 nums 之後,得到 nums = $nums") + + /* 排序串列 */ + nums.sort() //排序後,串列元素從小到大排列 + println("排序串列後 nums = $nums") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_array_and_linkedlist/my_list.kt b/zh-hant/codes/kotlin/chapter_array_and_linkedlist/my_list.kt new file mode 100644 index 000000000..3eed93c44 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_array_and_linkedlist/my_list.kt @@ -0,0 +1,139 @@ +/** + * File: my_list.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_array_and_linkedlist + +/* 串列類別 */ +class MyList { + private var arr: IntArray = intArrayOf() // 陣列(儲存串列元素) + private var capacity = 10 // 串列容量 + private var size = 0 // 串列長度(當前元素數量) + private var extendRatio = 2 // 每次串列擴容的倍數 + + /* 建構子 */ + init { + arr = IntArray(capacity) + } + + /* 獲取串列長度(當前元素數量) */ + fun size(): Int { + return size + } + + /* 獲取串列容量 */ + fun capacity(): Int { + return capacity + } + + /* 訪問元素 */ + fun get(index: Int): Int { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= size) + throw IndexOutOfBoundsException() + return arr[index] + } + + /* 更新元素 */ + fun set(index: Int, num: Int) { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("索引越界") + arr[index] = num + } + + /* 在尾部新增元素 */ + fun add(num: Int) { + // 元素數量超出容量時,觸發擴容機制 + if (size == capacity()) + extendCapacity() + arr[size] = num + // 更新元素數量 + size++ + } + + /* 在中間插入元素 */ + fun insert(index: Int, num: Int) { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("索引越界") + // 元素數量超出容量時,觸發擴容機制 + if (size == capacity()) + extendCapacity() + // 將索引 index 以及之後的元素都向後移動一位 + for (j in size - 1 downTo index) + arr[j + 1] = arr[j] + arr[index] = num + // 更新元素數量 + size++ + } + + /* 刪除元素 */ + fun remove(index: Int): Int { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("索引越界") + val num: Int = arr[index] + // 將將索引 index 之後的元素都向前移動一位 + for (j in index..>, + res: MutableList>?>, + cols: BooleanArray, + diags1: BooleanArray, + diags2: BooleanArray +) { + // 當放置完所有行時,記錄解 + if (row == n) { + val copyState: MutableList> = ArrayList() + for (sRow in state) { + copyState.add(ArrayList(sRow)) + } + res.add(copyState) + return + } + // 走訪所有列 + for (col in 0..>?> { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + val state: MutableList> = ArrayList() + for (i in 0.. = ArrayList() + for (j in 0..>?> = ArrayList() + + backtrack(0, n, state, res, cols, diags1, diags2) + + return res +} + +/* Driver Code */ +fun main() { + val n = 4 + val res: List?>?> = nQueens(n) + + println("輸入棋盤長寬為 $n") + println("皇后放置方案共有 ${res.size} 種") + for (state in res) { + println("--------------------") + for (row in state!!) { + println(row) + } + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/permutations_i.kt b/zh-hant/codes/kotlin/chapter_backtracking/permutations_i.kt new file mode 100644 index 000000000..34b823cb1 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/permutations_i.kt @@ -0,0 +1,53 @@ +/** + * File: permutations_i.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.permutations_i + +/* 回溯演算法:全排列 I */ +fun backtrack( + state: MutableList, + choices: IntArray, + selected: BooleanArray, + res: MutableList?> +) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size == choices.size) { + res.add(ArrayList(state)) + return + } + // 走訪所有選擇 + for (i in choices.indices) { + val choice = choices[i] + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true + state.add(choice) + // 進行下一輪選擇 + backtrack(state, choices, selected, res) + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.removeAt(state.size - 1) + } + } +} + +/* 全排列 I */ +fun permutationsI(nums: IntArray): List?> { + val res: MutableList?> = ArrayList() + backtrack(ArrayList(), nums, BooleanArray(nums.size), res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 2, 3) + + val res = permutationsI(nums) + + println("輸入陣列 nums = ${nums.contentToString()}") + println("所有排列 res = $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/permutations_ii.kt b/zh-hant/codes/kotlin/chapter_backtracking/permutations_ii.kt new file mode 100644 index 000000000..7f5d38f7a --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/permutations_ii.kt @@ -0,0 +1,55 @@ +/** + * File: permutations_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.permutations_ii + +/* 回溯演算法:全排列 II */ +fun backtrack( + state: MutableList, + choices: IntArray, + selected: BooleanArray, + res: MutableList?> +) { + // 當狀態長度等於元素數量時,記錄解 + if (state.size == choices.size) { + res.add(ArrayList(state)) + return + } + // 走訪所有選擇 + val duplicated: MutableSet = HashSet() + for (i in choices.indices) { + val choice = choices[i] + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.contains(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.add(choice) // 記錄選擇過的元素值 + selected[i] = true + state.add(choice) + // 進行下一輪選擇 + backtrack(state, choices, selected, res) + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.removeAt(state.size - 1) + } + } +} + +/* 全排列 II */ +fun permutationsII(nums: IntArray): MutableList?> { + val res: MutableList?> = ArrayList() + backtrack(ArrayList(), nums, BooleanArray(nums.size), res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 2, 2) + + val res = permutationsII(nums) + + println("輸入陣列 nums = ${nums.contentToString()}") + println("所有排列 res = $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt new file mode 100644 index 000000000..0f279f6e9 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_i_compact + +import utils.TreeNode +import utils.printTree + +var res: MutableList? = null + +/* 前序走訪:例題一 */ +fun preOrder(root: TreeNode?) { + if (root == null) { + return + } + if (root.value == 7) { + // 記錄解 + res!!.add(root) + } + preOrder(root.left) + preOrder(root.right) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二元樹") + printTree(root) + + // 前序走訪 + res = ArrayList() + preOrder(root) + + println("\n輸出所有值為 7 的節點") + val vals: MutableList = ArrayList() + for (node in res as ArrayList) { + vals.add(node.value) + } + println(vals) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt new file mode 100644 index 000000000..af4f4e408 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_ii_compact + +import utils.TreeNode +import utils.printTree + +var path: MutableList? = null +var res: MutableList>? = null + +/* 前序走訪:例題二 */ +fun preOrder(root: TreeNode?) { + if (root == null) { + return + } + // 嘗試 + path!!.add(root) + if (root.value == 7) { + // 記錄解 + res!!.add(ArrayList(path!!)) + } + preOrder(root.left) + preOrder(root.right) + // 回退 + path!!.removeAt(path!!.size - 1) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二元樹") + printTree(root) + + // 前序走訪 + path = java.util.ArrayList() + res = java.util.ArrayList>() + preOrder(root) + + println("\n輸出所有根節點到節點 7 的路徑") + for (path in res as ArrayList>) { + val values: MutableList = ArrayList() + for (node in path) { + values.add(node.value) + } + println(values) + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt new file mode 100644 index 000000000..7cc8f0a32 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_iii_compact + +import utils.TreeNode +import utils.printTree + +var path: MutableList? = null +var res: MutableList>? = null + +/* 前序走訪:例題三 */ +fun preOrder(root: TreeNode?) { + // 剪枝 + if (root == null || root.value == 3) { + return + } + // 嘗試 + path!!.add(root) + if (root.value == 7) { + // 記錄解 + res!!.add(ArrayList(path!!)) + } + preOrder(root.left) + preOrder(root.right) + // 回退 + path!!.removeAt(path!!.size - 1) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二元樹") + printTree(root) + + // 前序走訪 + path = ArrayList() + res = ArrayList() + preOrder(root) + + println("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for (path in res as ArrayList>) { + val values: MutableList = ArrayList() + for (node in path) { + values.add(node.value) + } + println(values) + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt new file mode 100644 index 000000000..33be1a95b --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt @@ -0,0 +1,83 @@ +/** + * File: preorder_traversal_iii_template.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_iii_template + +import utils.TreeNode +import utils.printTree +import java.util.* + +/* 判斷當前狀態是否為解 */ +fun isSolution(state: List): Boolean { + return state.isNotEmpty() && state[state.size - 1]?.value == 7 +} + +/* 記錄解 */ +fun recordSolution(state: MutableList?, res: MutableList?>) { + res.add(state?.let { ArrayList(it) }) +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +fun isValid(state: List?, choice: TreeNode?): Boolean { + return choice != null && choice.value != 3 +} + +/* 更新狀態 */ +fun makeChoice(state: MutableList, choice: TreeNode?) { + state.add(choice) +} + +/* 恢復狀態 */ +fun undoChoice(state: MutableList, choice: TreeNode?) { + state.removeLast() +} + +/* 回溯演算法:例題三 */ +fun backtrack( + state: MutableList, + choices: List, + res: MutableList?> +) { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res) + } + // 走訪所有選擇 + for (choice in choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice) + // 進行下一輪選擇 + backtrack(state, listOf(choice!!.left, choice.right), res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice) + } + } +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n初始化二元樹") + printTree(root) + + // 回溯演算法 + val res: MutableList?> = ArrayList() + backtrack(ArrayList(), mutableListOf(root), res) + + println("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點") + for (path in res) { + val vals = ArrayList() + for (node in path!!) { + if (node != null) { + vals.add(node.value) + } + } + println(vals) + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i.kt b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i.kt new file mode 100644 index 000000000..a4bafc141 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i.kt @@ -0,0 +1,60 @@ +/** + * File: subset_sum_i.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_i + +import java.util.* + +/* 回溯演算法:子集和 I */ +fun backtrack( + state: MutableList, + target: Int, + choices: IntArray, + start: Int, + res: MutableList?> +) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(ArrayList(state)) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (i in start..?> { + val state: MutableList = ArrayList() // 狀態(子集) + Arrays.sort(nums) // 對 nums 進行排序 + val start = 0 // 走訪起始點 + val res: MutableList?> = ArrayList() // 結果串列(子集串列) + backtrack(state, target, nums, start, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(3, 4, 5) + val target = 9 + + val res = subsetSumI(nums, target) + + println("輸入陣列 nums = ${nums.contentToString()}, target = $target") + println("所有和等於 $target 的子集 res = $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt new file mode 100644 index 000000000..418cfc971 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt @@ -0,0 +1,56 @@ +/** + * File: subset_sum_i_native.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_i_naive + +/* 回溯演算法:子集和 I */ +fun backtrack( + state: MutableList, + target: Int, + total: Int, + choices: IntArray, + res: MutableList?> +) { + // 子集和等於 target 時,記錄解 + if (total == target) { + res.add(ArrayList(state)) + return + } + // 走訪所有選擇 + for (i in choices.indices) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue + } + // 嘗試:做出選擇,更新元素和 total + state.add(choices[i]) + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeAt(state.size - 1) + } +} + +/* 求解子集和 I(包含重複子集) */ +fun subsetSumINaive(nums: IntArray, target: Int): List?> { + val state: MutableList = ArrayList() // 狀態(子集) + val total = 0 // 子集和 + val res: MutableList?> = ArrayList() // 結果串列(子集串列) + backtrack(state, target, total, nums, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(3, 4, 5) + val target = 9 + + val res: List?> = subsetSumINaive(nums, target) + + println("輸入陣列 nums = ${nums.contentToString()}, target = $target") + println("所有和等於 $target 的子集 res = $res") + println("請注意,該方法輸出的結果包含重複集合") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_ii.kt b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_ii.kt new file mode 100644 index 000000000..9545223e9 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_backtracking/subset_sum_ii.kt @@ -0,0 +1,65 @@ +/** + * File: subset_sum_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_ii + +import java.util.* + +/* 回溯演算法:子集和 II */ +fun backtrack( + state: MutableList, + target: Int, + choices: IntArray, + start: Int, + res: MutableList?> +) { + // 子集和等於 target 時,記錄解 + if (target == 0) { + res.add(ArrayList(state)) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (i in start.. start && choices[i] == choices[i - 1]) { + continue + } + // 嘗試:做出選擇,更新 target, start + state.add(choices[i]) + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res) + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeAt(state.size - 1) + } +} + +/* 求解子集和 II */ +fun subsetSumII(nums: IntArray, target: Int): List?> { + val state: MutableList = ArrayList() // 狀態(子集) + Arrays.sort(nums) // 對 nums 進行排序 + val start = 0 // 走訪起始點 + val res: MutableList?> = ArrayList() // 結果串列(子集串列) + backtrack(state, target, nums, start, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 4, 5) + val target = 9 + + val res = subsetSumII(nums, target) + + println("輸入陣列 nums = ${nums.contentToString()}, target = $target") + println("所有和等於 $target 的子集 res = $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_computational_complexity/iteration.kt b/zh-hant/codes/kotlin/chapter_computational_complexity/iteration.kt new file mode 100644 index 000000000..d387e4e60 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_computational_complexity/iteration.kt @@ -0,0 +1,74 @@ +/** + * File: iteration.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.iteration + +/* for 迴圈 */ +fun forLoop(n: Int): Int { + var res = 0 + // 迴圈求和 1, 2, ..., n-1, n + for (i in 1..n) { + res += i + } + return res +} + +/* while 迴圈 */ +fun whileLoop(n: Int): Int { + var res = 0 + var i = 1 // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i + i++ // 更新條件變數 + } + return res +} + +/* while 迴圈(兩次更新) */ +fun whileLoopII(n: Int): Int { + var res = 0 + var i = 1 // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i + // 更新條件變數 + i++ + i *= 2 + } + return res +} + +/* 雙層 for 迴圈 */ +fun nestedForLoop(n: Int): String { + val res = StringBuilder() + // 迴圈 i = 1, 2, ..., n-1, n + for (i in 1..n) { + // 迴圈 j = 1, 2, ..., n-1, n + for (j in 1..n) { + res.append(" ($i, $j), ") + } + } + return res.toString() +} + +/* Driver Code */ +fun main() { + val n = 5 + var res: Int + + res = forLoop(n) + println("\nfor 迴圈的求和結果 res = $res") + + res = whileLoop(n) + println("\nwhile 迴圈的求和結果 res = $res") + + res = whileLoopII(n) + println("\nwhile 迴圈 (兩次更新) 求和結果 res = $res") + + val resStr = nestedForLoop(n) + println("\n雙層 for 迴圈的走訪結果 $resStr") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_computational_complexity/recursion.kt b/zh-hant/codes/kotlin/chapter_computational_complexity/recursion.kt new file mode 100644 index 000000000..b790439cb --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_computational_complexity/recursion.kt @@ -0,0 +1,77 @@ +/** + * File: recursion.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.recursion + +import java.util.* + +/* 遞迴 */ +fun recur(n: Int): Int { + // 終止條件 + if (n == 1) + return 1 + // 遞: 遞迴呼叫 + val res = recur(n - 1) + // 迴: 返回結果 + return n + res +} + +/* 使用迭代模擬遞迴 */ +fun forLoopRecur(n: Int): Int { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + val stack = Stack() + var res = 0 + // 遞: 遞迴呼叫 + for (i in n downTo 0) { + stack.push(i) + } + // 迴: 返回結果 + while (stack.isNotEmpty()) { + // 透過“出堆疊操作”模擬“迴” + res += stack.pop() + } + // res = 1+2+3+...+n + return res +} + +/* 尾遞迴 */ +tailrec fun tailRecur(n: Int, res: Int): Int { + // 新增 tailrec 關鍵詞,以開啟尾遞迴最佳化 + // 終止條件 + if (n == 0) + return res + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n) +} + +/* 費波那契數列:遞迴 */ +fun fib(n: Int): Int { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1 + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + val res = fib(n - 1) + fib(n - 2) + // 返回結果 f(n) + return res +} + +/* Driver Code */ +fun main() { + val n = 5 + var res: Int + + res = recur(n) + println("\n遞迴函式的求和結果 res = $res") + + res = forLoopRecur(n) + println("\n使用迭代模擬遞迴求和結果 res = $res") + + res = tailRecur(n, 0) + println("\n尾遞迴函式的求和結果 res = $res") + + res = fib(n) + println("\n費波那契數列的第 $n 項為 $res") +} diff --git a/zh-hant/codes/kotlin/chapter_computational_complexity/space_complexity.kt b/zh-hant/codes/kotlin/chapter_computational_complexity/space_complexity.kt new file mode 100644 index 000000000..e16b18a94 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_computational_complexity/space_complexity.kt @@ -0,0 +1,109 @@ +/** + * File: space_complexity.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.space_complexity + +import utils.ListNode +import utils.TreeNode +import utils.printTree + +/* 函式 */ +fun function(): Int { + // 執行某些操作 + return 0 +} + +/* 常數階 */ +fun constant(n: Int) { + // 常數、變數、物件佔用 O(1) 空間 + val a = 0 + var b = 0 + val nums = Array(10000) { 0 } + val node = ListNode(0) + // 迴圈中的變數佔用 O(1) 空間 + for (i in 0..() + for (i in 0..() + for (i in 0..?> = arrayOfNulls(n) + // 二維串列佔用 O(n^2) 空間 + val numList: MutableList> = arrayListOf() + for (i in 0..() + for (j in 0.. nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + nums[j] = nums[j + 1].also { nums[j + 1] = nums[j] } + count += 3 // 元素交換包含 3 個單元操作 + } + } + } + return count +} + +/* 指數階(迴圈實現) */ +fun exponential(n: Int): Int { + var count = 0 + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + var base = 1 + for (i in 0.. 1) { + n1 /= 2 + count++ + } + return count +} + +/* 對數階(遞迴實現) */ +fun logRecur(n: Int): Int { + if (n <= 1) + return 0 + return logRecur(n / 2) + 1 +} + +/* 線性對數階 */ +fun linearLogRecur(n: Int): Int { + if (n <= 1) + return 1 + var count = linearLogRecur(n / 2) + linearLogRecur(n / 2) + for (i in 0.. { + val nums = IntArray(n) + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (i in 0.. int[] + val res = arrayOfNulls(n) + for (i in 0..): Int { + for (i in nums.indices) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] == 1) + return i + } + return -1 +} + +/* Driver Code */ +fun main() { + for (i in 0..9) { + val n = 100 + val nums: Array = randomNumbers(n) + val index: Int = findOne(nums) + println("\n陣列 [ 1, 2, ..., n ] 被打亂後 = ${nums.contentToString()}") + println("數字 1 的索引為 $index") + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt b/zh-hant/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt new file mode 100644 index 000000000..56d303484 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt @@ -0,0 +1,49 @@ +/** + * File: binary_search_recur.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.binary_search_recur + +/* 二分搜尋:問題 f(i, j) */ +fun dfs( + nums: IntArray, + target: Int, + i: Int, + j: Int +): Int { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1 + } + // 計算中點索引 m + val m = (i + j) / 2 + return if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + dfs(nums, target, m + 1, j) + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + dfs(nums, target, i, m - 1) + } else { + // 找到目標元素,返回其索引 + m + } +} + +/* 二分搜尋 */ +fun binarySearch(nums: IntArray, target: Int): Int { + val n = nums.size + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1) +} + +/* Driver Code */ +fun main() { + val target = 6 + val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + + // 二分搜尋(雙閉區間) + val index = binarySearch(nums, target) + println("目標元素 6 的索引 = $index") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_divide_and_conquer/build_tree.kt b/zh-hant/codes/kotlin/chapter_divide_and_conquer/build_tree.kt new file mode 100644 index 000000000..9d9858a23 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_divide_and_conquer/build_tree.kt @@ -0,0 +1,49 @@ +/** + * File: build_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.build_tree + +import utils.TreeNode +import utils.printTree + +/* 構建二元樹:分治 */ +fun dfs(preorder: IntArray, inorderMap: Map, i: Int, l: Int, r: Int): TreeNode? { + // 子樹區間為空時終止 + if (r - l < 0) return null + // 初始化根節點 + val root = TreeNode(preorder[i]) + // 查詢 m ,從而劃分左右子樹 + val m = inorderMap[preorder[i]]!! + // 子問題:構建左子樹 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1) + // 子問題:構建右子樹 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r) + // 返回根節點 + return root +} + +/* 構建二元樹 */ +fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + val inorderMap: MutableMap = HashMap() + for (i in inorder.indices) { + inorderMap[inorder[i]] = i + } + val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1) + return root +} + +/* Driver Code */ +fun main() { + val preorder = intArrayOf(3, 9, 2, 1, 7) + val inorder = intArrayOf(9, 3, 1, 2, 7) + println("前序走訪 = " + preorder.contentToString()) + println("中序走訪 = " + inorder.contentToString()) + + val root = buildTree(preorder, inorder) + println("構建的二元樹為:") + printTree(root) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_divide_and_conquer/hanota.kt b/zh-hant/codes/kotlin/chapter_divide_and_conquer/hanota.kt new file mode 100644 index 000000000..cb842e559 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_divide_and_conquer/hanota.kt @@ -0,0 +1,56 @@ +/** + * File: hanota.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.hanota + +/* 移動一個圓盤 */ +fun move(src: MutableList, tar: MutableList) { + // 從 src 頂部拿出一個圓盤 + val pan: Int = src.removeAt(src.size - 1) + // 將圓盤放入 tar 頂部 + tar.add(pan) +} + +/* 求解河內塔問題 f(i) */ +fun dfs(i: Int, src: MutableList, buf: MutableList, tar: MutableList) { + // 若 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) +} + +/* 求解河內塔問題 */ +fun solveHanota(A: MutableList, B: MutableList, C: MutableList) { + val n = A.size + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C) +} + +/* Driver Code */ +fun main() { + // 串列尾部是柱子頂部 + val A: MutableList = ArrayList(mutableListOf(5, 4, 3, 2, 1)) + val B: MutableList = ArrayList() + val C: MutableList = ArrayList() + println("初始狀態下:") + println("A = $A") + println("B = $B") + println("C = $C") + + solveHanota(A, B, C) + + println("圓盤移動完成後:") + println("A = $A") + println("B = $B") + println("C = $C") +} diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt new file mode 100644 index 000000000..2c3457ddb --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 回溯 */ +fun backtrack( + choices: List, + state: Int, + n: Int, + res: MutableList +) { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) res[0] = res[0] + 1 + // 走訪所有選擇 + for (choice in choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) continue + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res) + // 回退 + } +} + +/* 爬樓梯:回溯 */ +fun climbingStairsBacktrack(n: Int): Int { + val choices = mutableListOf(1, 2) // 可選擇向上爬 1 階或 2 階 + val state = 0 // 從第 0 階開始爬 + val res = ArrayList() + res.add(0) // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res) + return res[0] +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsBacktrack(n) + println("爬 $n 階樓梯共有 $res 種方案") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt new file mode 100644 index 000000000..3691a5023 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt @@ -0,0 +1,35 @@ +/** + * File: climbing_stairs_constraint_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 帶約束爬樓梯:動態規劃 */ +fun climbingStairsConstraintDP(n: Int): Int { + if (n == 1 || n == 2) { + return 1 + } + // 初始化 dp 表,用於儲存子問題的解 + val dp = Array(n + 1) { IntArray(3) } + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (i in 3..n) { + 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 */ +fun main() { + val n = 9 + + val res = climbingStairsConstraintDP(n) + println("爬 $n 階樓梯共有 $res 種方案") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt new file mode 100644 index 000000000..f8f252823 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt @@ -0,0 +1,29 @@ +/** + * File: climbing_stairs_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 搜尋 */ +fun dfs(i: Int): Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) return i + // dp[i] = dp[i-1] + dp[i-2] + val count = dfs(i - 1) + dfs(i - 2) + return count +} + +/* 爬樓梯:搜尋 */ +fun climbingStairsDFS(n: Int): Int { + return dfs(n) +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsDFS(n) + println("爬 $n 階樓梯共有 $res 種方案") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt new file mode 100644 index 000000000..dfb0fa4f7 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt @@ -0,0 +1,38 @@ +/** + * File: climbing_stairs_dfs_mem.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import java.util.* + +/* 記憶化搜尋 */ +fun dfs(i: Int, mem: IntArray): Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) return i + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) return mem[i] + // dp[i] = dp[i-1] + dp[i-2] + val count = dfs(i - 1, mem) + dfs(i - 2, mem) + // 記錄 dp[i] + mem[i] = count + return count +} + +/* 爬樓梯:記憶化搜尋 */ +fun climbingStairsDFSMem(n: Int): Int { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + val mem = IntArray(n + 1) + Arrays.fill(mem, -1) + return dfs(n, mem) +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res: Int = climbingStairsDFSMem(n) + println("爬 $n 階樓梯共有 $res 種方案") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt new file mode 100644 index 000000000..a6c9bdfc6 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt @@ -0,0 +1,46 @@ +/** + * File: climbing_stairs_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 爬樓梯:動態規劃 */ +fun climbingStairsDP(n: Int): Int { + if (n == 1 || n == 2) return n + // 初始化 dp 表,用於儲存子問題的解 + val dp = IntArray(n + 1) + // 初始狀態:預設最小子問題的解 + dp[1] = 1 + dp[2] = 2 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (i in 3..n) { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +fun climbingStairsDPComp(n: Int): Int { + if (n == 1 || n == 2) return n + var a = 1 + var b = 2 + for (i in 3..n) { + val tmp = b + b += a + a = tmp + } + return b +} + +/* Driver Code */ +fun main() { + val n = 9 + + var res = climbingStairsDP(n) + println("爬 $n 階樓梯共有 $res 種方案") + + res = climbingStairsDPComp(n) + println("爬 $n 階樓梯共有 $res 種方案") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change.kt new file mode 100644 index 000000000..e7fc7094d --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change.kt @@ -0,0 +1,73 @@ +/** + * File: coin_change.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import java.util.* +import kotlin.math.min + +/* 零錢兌換:動態規劃 */ +fun coinChangeDP(coins: IntArray, amt: Int): Int { + val n = coins.size + val MAX = amt + 1 + // 初始化 dp 表 + val dp = Array(n + 1) { IntArray(amt + 1) } + // 狀態轉移:首行首列 + for (a in 1..amt) { + dp[0][a] = MAX + } + // 狀態轉移:其餘行和列 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a] + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = min(dp[i - 1][a].toDouble(), (dp[i][a - coins[i - 1]] + 1).toDouble()) + .toInt() + } + } + } + return if (dp[n][amt] != MAX) dp[n][amt] else -1 +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +fun coinChangeDPComp(coins: IntArray, amt: Int): Int { + val n = coins.size + val MAX = amt + 1 + // 初始化 dp 表 + val dp = IntArray(amt + 1) + Arrays.fill(dp, MAX) + dp[0] = 0 + // 狀態轉移 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = min(dp[a].toDouble(), (dp[a - coins[i - 1]] + 1).toDouble()).toInt() + } + } + } + return if (dp[amt] != MAX) dp[amt] else -1 +} + +/* Driver Code */ +fun main() { + val coins = intArrayOf(1, 2, 5) + val amt = 4 + + // 動態規劃 + var res = coinChangeDP(coins, amt) + println("湊到目標金額所需的最少硬幣數量為 $res") + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins, amt) + println("湊到目標金額所需的最少硬幣數量為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt new file mode 100644 index 000000000..d3c1ecded --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 零錢兌換 II:動態規劃 */ +fun coinChangeIIDP(coins: IntArray, amt: Int): Int { + val n = coins.size + // 初始化 dp 表 + val dp = Array(n + 1) { IntArray(amt + 1) } + // 初始化首列 + for (i in 0..n) { + dp[i][0] = 1 + } + // 狀態轉移 + for (i in 1..n) { + for (a in 1..amt) { + 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] +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { + val n = coins.size + // 初始化 dp 表 + val dp = IntArray(amt + 1) + dp[0] = 1 + // 狀態轉移 + for (i in 1..n) { + for (a in 1..amt) { + 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 */ +fun main() { + val coins = intArrayOf(1, 2, 5) + val amt = 5 + + // 動態規劃 + var res = coinChangeIIDP(coins, amt) + println("湊出目標金額的硬幣組合數量為 $res") + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins, amt) + println("湊出目標金額的硬幣組合數量為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/edit_distance.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/edit_distance.kt new file mode 100644 index 000000000..a4fd06734 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/edit_distance.kt @@ -0,0 +1,147 @@ +/** + * File: edit_distance.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import java.util.* +import kotlin.math.min + +/* 編輯距離:暴力搜尋 */ +fun editDistanceDFS( + s: String, + t: String, + i: Int, + j: Int +): Int { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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 editDistanceDFS(s, t, i - 1, j - 1) + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + val insert = editDistanceDFS(s, t, i, j - 1) + val delete = editDistanceDFS(s, t, i - 1, j) + val replace = editDistanceDFS(s, t, i - 1, j - 1) + // 返回最少編輯步數 + return (min(min(insert.toDouble(), delete.toDouble()), replace.toDouble()) + 1).toInt() +} + +/* 編輯距離:記憶化搜尋 */ +fun editDistanceDFSMem( + s: String, + t: String, + mem: Array, + i: Int, + j: Int +): Int { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 && 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 editDistanceDFSMem(s, t, mem, i - 1, j - 1) + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + val insert = editDistanceDFSMem(s, t, mem, i, j - 1) + val delete = editDistanceDFSMem(s, t, mem, i - 1, j) + val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1) + // 記錄並返回最少編輯步數 + mem[i][j] = (min(min(insert.toDouble(), delete.toDouble()), replace.toDouble()) + 1).toInt() + return mem[i][j] +} + +/* 編輯距離:動態規劃 */ +fun editDistanceDP(s: String, t: String): Int { + val n = s.length + val m = t.length + val dp = Array(n + 1) { IntArray(m + 1) } + // 狀態轉移:首行首列 + for (i in 1..n) { + dp[i][0] = i + } + for (j in 1..m) { + dp[0][j] = j + } + // 狀態轉移:其餘行和列 + for (i in 1..n) { + for (j in 1..m) { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1] + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = + (min( + min(dp[i][j - 1].toDouble(), dp[i - 1][j].toDouble()), + dp[i - 1][j - 1].toDouble() + ) + 1).toInt() + } + } + } + return dp[n][m] +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +fun editDistanceDPComp(s: String, t: String): Int { + val n = s.length + val m = t.length + val dp = IntArray(m + 1) + // 狀態轉移:首行 + for (j in 1..m) { + dp[j] = j + } + // 狀態轉移:其餘行 + for (i in 1..n) { + // 狀態轉移:首列 + var leftup = dp[0] // 暫存 dp[i-1, j-1] + dp[0] = i + // 狀態轉移:其餘列 + for (j in 1..m) { + val temp = dp[j] + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = (min(min(dp[j - 1].toDouble(), dp[j].toDouble()), leftup.toDouble()) + 1).toInt() + } + leftup = temp // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m] +} + +/* Driver Code */ +fun main() { + val s = "bag" + val t = "pack" + val n = s.length + val m = t.length + + // 暴力搜尋 + var res = editDistanceDFS(s, t, n, m) + println("將 $s 更改為 $t 最少需要編輯 $res 步") + + // 記憶化搜尋 + val mem = Array(n + 1) { IntArray(m + 1) } + for (row in mem) Arrays.fill(row, -1) + res = editDistanceDFSMem(s, t, mem, n, m) + println("將 $s 更改為 $t 最少需要編輯 $res 步") + + // 動態規劃 + res = editDistanceDP(s, t) + println("將 $s 更改為 $t 最少需要編輯 $res 步") + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t) + println("將 $s 更改為 $t 最少需要編輯 $res 步") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/knapsack.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/knapsack.kt new file mode 100644 index 000000000..a6801dae4 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/knapsack.kt @@ -0,0 +1,136 @@ +/** + * File: knapsack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import java.util.* +import kotlin.math.max + +/* 0-1 背包:暴力搜尋 */ +fun knapsackDFS( + wgt: IntArray, + value: IntArray, + i: Int, + c: Int +): Int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0 + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, value, i - 1, c) + } + // 計算不放入和放入物品 i 的最大價值 + val no = knapsackDFS(wgt, value, i - 1, c) + val yes = knapsackDFS(wgt, value, i - 1, c - wgt[i - 1]) + value[i - 1] + // 返回兩種方案中價值更大的那一個 + return max(no.toDouble(), yes.toDouble()).toInt() +} + +/* 0-1 背包:記憶化搜尋 */ +fun knapsackDFSMem( + wgt: IntArray, + value: IntArray, + mem: Array, + i: Int, + c: Int +): Int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 || c == 0) { + return 0 + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c] + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, value, mem, i - 1, c) + } + // 計算不放入和放入物品 i 的最大價值 + val no = knapsackDFSMem(wgt, value, mem, i - 1, c) + val yes = knapsackDFSMem(wgt, value, mem, i - 1, c - wgt[i - 1]) + value[i - 1] + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = max(no.toDouble(), yes.toDouble()).toInt() + return mem[i][c] +} + +/* 0-1 背包:動態規劃 */ +fun knapsackDP( + wgt: IntArray, + value: IntArray, + cap: Int +): Int { + val n = wgt.size + // 初始化 dp 表 + val dp = Array(n + 1) { IntArray(cap + 1) } + // 狀態轉移 + for (i in 1..n) { + for (c in 1..cap) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c].toDouble(), (dp[i - 1][c - wgt[i - 1]] + value[i - 1]).toDouble()) + .toInt() + } + } + } + return dp[n][cap] +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +fun knapsackDPComp( + wgt: IntArray, + value: IntArray, + cap: Int +): Int { + val n = wgt.size + // 初始化 dp 表 + val dp = IntArray(cap + 1) + // 狀態轉移 + for (i in 1..n) { + // 倒序走訪 + for (c in cap downTo 1) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = + max(dp[c].toDouble(), (dp[c - wgt[i - 1]] + value[i - 1]).toDouble()).toInt() + } + } + } + return dp[cap] +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(10, 20, 30, 40, 50) + val value = intArrayOf(50, 120, 150, 210, 240) + val cap = 50 + val n = wgt.size + + // 暴力搜尋 + var res = knapsackDFS(wgt, value, n, cap) + println("不超過背包容量的最大物品價值為 $res") + + // 記憶化搜尋 + val mem = Array(n + 1) { IntArray(cap + 1) } + for (row in mem) { + Arrays.fill(row, -1) + } + res = knapsackDFSMem(wgt, value, mem, n, cap) + println("不超過背包容量的最大物品價值為 $res") + + // 動態規劃 + res = knapsackDP(wgt, value, cap) + println("不超過背包容量的最大物品價值為 $res") + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt, value, cap) + println("不超過背包容量的最大物品價值為 $res") +} diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt new file mode 100644 index 000000000..f2ea7d3f7 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 爬樓梯最小代價:動態規劃 */ +fun minCostClimbingStairsDP(cost: IntArray): Int { + val n = cost.size - 1 + if (n == 1 || n == 2) return cost[n] + // 初始化 dp 表,用於儲存子問題的解 + val dp = IntArray(n + 1) + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1] + dp[2] = cost[2] + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (i in 3..n) { + dp[i] = (min(dp[i - 1].toDouble(), dp[i - 2].toDouble()) + cost[i]).toInt() + } + return dp[n] +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +fun minCostClimbingStairsDPComp(cost: IntArray): Int { + val n = cost.size - 1 + if (n == 1 || n == 2) return cost[n] + var a = cost[1] + var b = cost[2] + for (i in 3..n) { + val tmp = b + b = (min(a.toDouble(), tmp.toDouble()) + cost[i]).toInt() + a = tmp + } + return b +} + +/* Driver Code */ +fun main() { + val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1) + println("輸入樓梯的代價串列為 ${cost.contentToString()}") + + var res = minCostClimbingStairsDP(cost) + println("爬完樓梯的最低代價為 $res") + + res = minCostClimbingStairsDPComp(cost) + println("爬完樓梯的最低代價為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt b/zh-hant/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt new file mode 100644 index 000000000..c78c1d10c --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt @@ -0,0 +1,138 @@ +/** + * File: min_path_sum.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import java.util.* +import kotlin.math.min + +/* 最小路徑和:暴力搜尋 */ +fun minPathSumDFS( + grid: Array>, + i: Int, + j: Int +): Int { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Int.MAX_VALUE + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + val up = minPathSumDFS(grid, i - 1, j) + val left = minPathSumDFS(grid, i, j - 1) + // 返回從左上角到 (i, j) 的最小路徑代價 + return (min(left.toDouble(), up.toDouble()) + grid[i][j]).toInt() +} + +/* 最小路徑和:記憶化搜尋 */ +fun minPathSumDFSMem( + grid: Array>, + mem: Array>, + i: Int, + j: Int +): Int { + // 若為左上角單元格,則終止搜尋 + if (i == 0 && j == 0) { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Int.MAX_VALUE + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j] + } + // 左邊和上邊單元格的最小路徑代價 + val up = minPathSumDFSMem(grid, mem, i - 1, j) + val left = minPathSumDFSMem(grid, mem, i, j - 1) + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = (min(left.toDouble(), up.toDouble()) + grid[i][j]).toInt() + return mem[i][j] +} + +/* 最小路徑和:動態規劃 */ +fun minPathSumDP(grid: Array>): Int { + val n = grid.size + val m = grid[0].size + // 初始化 dp 表 + val dp = Array(n) { IntArray(m) } + dp[0][0] = grid[0][0] + // 狀態轉移:首行 + for (j in 1..>): Int { + val n = grid.size + val m = grid[0].size + // 初始化 dp 表 + val dp = IntArray(m) + // 狀態轉移:首行 + dp[0] = grid[0][0] + for (j in 1.. c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = max(dp[i - 1][c].toDouble(), (dp[i][c - wgt[i - 1]] + value[i - 1]).toDouble()) + .toInt() + } + } + } + return dp[n][cap] +} + +/* 完全背包:空間最佳化後的動態規劃 */ +fun unboundedKnapsackDPComp( + wgt: IntArray, + value: IntArray, + cap: Int +): Int { + val n = wgt.size + // 初始化 dp 表 + val dp = IntArray(cap + 1) + // 狀態轉移 + for (i in 1..n) { + for (c in 1..cap) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c] + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = + max(dp[c].toDouble(), (dp[c - wgt[i - 1]] + value[i - 1]).toDouble()).toInt() + } + } + } + return dp[cap] +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(1, 2, 3) + val value = intArrayOf(5, 11, 15) + val cap = 4 + + // 動態規劃 + var res = unboundedKnapsackDP(wgt, value, cap) + println("不超過背包容量的最大物品價值為 $res") + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt, value, cap) + println("不超過背包容量的最大物品價值為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_list.kt b/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_list.kt new file mode 100644 index 000000000..09ef42a36 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_list.kt @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList(edges: Array>) { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + val adjList: MutableMap> = HashMap() + + /* 建構子 */ + init { + // 新增所有頂點和邊 + for (edge in edges) { + addVertex(edge[0]!!); + addVertex(edge[1]!!); + addEdge(edge[0]!!, edge[1]!!); + } + } + + /* 獲取頂點數量 */ + fun size(): Int { + return adjList.size + } + + /* 新增邊 */ + fun addEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // 新增邊 vet1 - vet2 + adjList[vet1]?.add(vet2) + adjList[vet2]?.add(vet1); + } + + /* 刪除邊 */ + fun removeEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // 刪除邊 vet1 - vet2 + adjList[vet1]?.remove(vet2); + adjList[vet2]?.remove(vet1); + } + + /* 新增頂點 */ + fun addVertex(vet: Vertex) { + if (adjList.containsKey(vet)) + return + // 在鄰接表中新增一個新鏈結串列 + adjList[vet] = mutableListOf() + } + + /* 刪除頂點 */ + fun removeVertex(vet: Vertex) { + if (!adjList.containsKey(vet)) + throw IllegalArgumentException() + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.remove(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (list in adjList.values) { + list.remove(vet) + } + } + + /* 列印鄰接表 */ + fun print() { + println("鄰接表 =") + for (pair in adjList.entries) { + val tmp = ArrayList() + for (vertex in pair.value) { + tmp.add(vertex.value) + } + println("${pair.key.value}: $tmp,") + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化無向圖 */ + val v: Array = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[2], v[3]), + arrayOf(v[2], v[4]), + arrayOf(v[3], v[4]) + ) + val graph = GraphAdjList(edges) + println("\n初始化後,圖為") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(v[0]!!, v[2]!!) + println("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(v[0]!!, v[1]!!) + println("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + val v5 = Vertex(6) + graph.addVertex(v5) + println("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(v[1]!!) + println("\n刪除頂點 3 後,圖為") + graph.print() +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt b/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt new file mode 100644 index 000000000..b4df6320d --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt @@ -0,0 +1,131 @@ +/** + * File: graph_adjacency_matrix.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.printMatrix + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat(vertices: IntArray, edges: Array) { + val vertices: MutableList = ArrayList() // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + val adjMat: MutableList> = ArrayList() // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + init { + // 新增頂點 + for (vertex in vertices) { + addVertex(vertex) + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (edge in edges) { + addEdge(edge[0], edge[1]) + } + } + + /* 獲取頂點數量 */ + fun size(): Int { + return vertices.size + } + + /* 新增頂點 */ + fun addVertex(value: Int) { + val n = size() + // 向頂點串列中新增新頂點的值 + vertices.add(value) + // 在鄰接矩陣中新增一行 + val newRow: MutableList = mutableListOf() + for (j in 0..= size()) throw IndexOutOfBoundsException() + // 在頂點串列中移除索引 index 的頂點 + vertices.removeAt(index) + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.removeAt(index) + // 在鄰接矩陣中刪除索引 index 的列 + for (row in adjMat) { + row.removeAt(index) + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + fun addEdge(i: Int, j: Int) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw java.lang.IndexOutOfBoundsException() + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + fun removeEdge(i: Int, j: Int) { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw java.lang.IndexOutOfBoundsException() + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + fun print() { + print("頂點串列 = ") + println(vertices); + println("鄰接矩陣 ="); + printMatrix(adjMat) + } +} + +/* Driver Code */ +fun main() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + val vertices = intArrayOf(1, 3, 2, 5, 4) + val edges = arrayOf( + intArrayOf(0, 1), + intArrayOf(0, 3), + intArrayOf(1, 2), + intArrayOf(2, 3), + intArrayOf(2, 4), + intArrayOf(3, 4) + ) + val graph = GraphAdjMat(vertices, edges) + println("\n初始化後,圖為") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(0, 2) + println("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(0, 1) + println("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + graph.addVertex(6) + println("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(1) + println("\n刪除頂點 3 後,圖為") + graph.print() +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_graph/graph_bfs.kt b/zh-hant/codes/kotlin/chapter_graph/graph_bfs.kt new file mode 100644 index 000000000..4473e617c --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_graph/graph_bfs.kt @@ -0,0 +1,65 @@ +/** + * File: graph_bfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex +import java.util.* + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +fun graphBFS(graph: GraphAdjList, startVet: Vertex): List { + // 頂點走訪序列 + val res: MutableList = ArrayList() + // 雜湊表,用於記錄已被訪問過的頂點 + val visited: MutableSet = HashSet() + visited.add(startVet) + // 佇列用於實現 BFS + val que: Queue = LinkedList() + que.offer(startVet) + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (!que.isEmpty()) { + val vet = que.poll() // 佇列首頂點出隊 + res.add(vet) // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) continue // 跳過已被訪問的頂點 + + que.offer(adjVet) // 只入列未訪問的頂點 + visited.add(adjVet) // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res +} + +/* Driver Code */ +fun main() { + /* 初始化無向圖 */ + val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[1], v[4]), + arrayOf(v[2], v[5]), + arrayOf(v[3], v[4]), + arrayOf(v[3], v[6]), + arrayOf(v[4], v[5]), + arrayOf(v[4], v[7]), + arrayOf(v[5], v[8]), + arrayOf(v[6], v[7]), + arrayOf(v[7], v[8]) + ) + val graph = GraphAdjList(edges) + println("\n初始化後,圖為") + graph.print() + + /* 廣度優先走訪 */ + val res = graphBFS(graph, v[0]!!) + println("\n廣度優先走訪(BFS)頂點序列為") + println(Vertex.vetsToVals(res)) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_graph/graph_dfs.kt b/zh-hant/codes/kotlin/chapter_graph/graph_dfs.kt new file mode 100644 index 000000000..b1f1c3956 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_graph/graph_dfs.kt @@ -0,0 +1,62 @@ +/** + * File: graph_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex + +/* 深度優先走訪輔助函式 */ +fun dfs( + graph: GraphAdjList, + visited: MutableSet, + res: MutableList, + vet: Vertex? +) { + res.add(vet) // 記錄訪問頂點 + visited.add(vet) // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) continue // 跳過已被訪問的頂點 + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet) + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +fun graphDFS( + graph: GraphAdjList, + startVet: Vertex? +): List { + // 頂點走訪序列 + val res: MutableList = ArrayList() + // 雜湊表,用於記錄已被訪問過的頂點 + val visited: MutableSet = HashSet() + dfs(graph, visited, res, startVet) + return res +} + +/* Driver Code */ +fun main() { + /* 初始化無向圖 */ + val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[2], v[5]), + arrayOf(v[4], v[5]), + arrayOf(v[5], v[6]) + ) + val graph = GraphAdjList(edges) + println("\n初始化後,圖為") + graph.print() + + /* 深度優先走訪 */ + val res = graphDFS(graph, v[0]) + println("\n深度優先走訪(DFS)頂點序列為") + println(Vertex.vetsToVals(res)) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_greedy/coin_change_greedy.kt b/zh-hant/codes/kotlin/chapter_greedy/coin_change_greedy.kt new file mode 100644 index 000000000..13e4310e5 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_greedy/coin_change_greedy.kt @@ -0,0 +1,53 @@ +/** + * File: coin_change_greedy.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +/* 零錢兌換:貪婪 */ +fun coinChangeGreedy(coins: IntArray, amt: Int): Int { + // 假設 coins 串列有序 + var am = amt + var i = coins.size - 1 + var count = 0 + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (am > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > am) { + i-- + } + // 選擇 coins[i] + am -= coins[i] + count++ + } + // 若未找到可行方案,則返回 -1 + return if (am == 0) count else -1 +} + +/* Driver Code */ +fun main() { + // 貪婪:能夠保證找到全域性最優解 + var coins = intArrayOf(1, 5, 10, 20, 50, 100) + var amt = 186 + var res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("湊到 $amt 所需的最少硬幣數量為 $res") + + // 貪婪:無法保證找到全域性最優解 + coins = intArrayOf(1, 20, 50) + amt = 60 + res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("湊到 $amt 所需的最少硬幣數量為 $res") + println("實際上需要的最少數量為 3 ,即 20 + 20 + 20") + + // 貪婪:無法保證找到全域性最優解 + coins = intArrayOf(1, 49, 50) + amt = 98 + res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("湊到 $amt 所需的最少硬幣數量為 $res") + println("實際上需要的最少數量為 2 ,即 49 + 49") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_greedy/fractional_knapsack.kt b/zh-hant/codes/kotlin/chapter_greedy/fractional_knapsack.kt new file mode 100644 index 000000000..8cf66e86c --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_greedy/fractional_knapsack.kt @@ -0,0 +1,57 @@ +/** + * File: fractional_knapsack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import java.util.* + +/* 物品 */ +class Item( + val w: Int, // 物品 + val v: Int // 物品價值 +) + +/* 分數背包:貪婪 */ +fun fractionalKnapsack( + wgt: IntArray, + value: IntArray, + c: Int +): Double { + // 建立物品串列,包含兩個屬性:重量、價值 + var cap = c + val items = arrayOfNulls(wgt.size) + for (i in wgt.indices) { + items[i] = Item(wgt[i], value[i]) + } + // 按照單位價值 item.v / item.w 從高到低進行排序 + Arrays.sort(items, Comparator.comparingDouble { item: Item -> -(item.v.toDouble() / item.w) }) + // 迴圈貪婪選擇 + var res = 0.0 + for (item in items) { + if (item!!.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v.toDouble() + cap -= item.w + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += item.v.toDouble() / item.w * cap + // 已無剩餘容量,因此跳出迴圈 + break + } + } + return res +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(10, 20, 30, 40, 50) + val values = intArrayOf(50, 120, 150, 210, 240) + val cap = 50 + + // 貪婪演算法 + val res = fractionalKnapsack(wgt, values, cap) + println("不超過背包容量的最大物品價值為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_greedy/max_capacity.kt b/zh-hant/codes/kotlin/chapter_greedy/max_capacity.kt new file mode 100644 index 000000000..aaa1a5b3f --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_greedy/max_capacity.kt @@ -0,0 +1,41 @@ +/** + * File: max_capacity.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import kotlin.math.max +import kotlin.math.min + +/* 最大容量:貪婪 */ +fun maxCapacity(ht: IntArray): Int { + // 初始化 i, j,使其分列陣列兩端 + var i = 0 + var j = ht.size - 1 + // 初始最大容量為 0 + var res = 0 + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + val cap = (min(ht[i].toDouble(), ht[j].toDouble()) * (j - i)).toInt() + res = max(res.toDouble(), cap.toDouble()).toInt() + // 向內移動短板 + if (ht[i] < ht[j]) { + i++ + } else { + j-- + } + } + return res +} + +/* Driver Code */ +fun main() { + val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4) + + // 貪婪演算法 + val res = maxCapacity(ht) + println("最大容量為 $res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_greedy/max_product_cutting.kt b/zh-hant/codes/kotlin/chapter_greedy/max_product_cutting.kt new file mode 100644 index 000000000..db7915a5b --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_greedy/max_product_cutting.kt @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import kotlin.math.pow + +/* 最大切分乘積:貪婪 */ +fun maxProductCutting(n: Int): Int { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1) + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + val a = n / 3 + val b = n % 3 + if (b == 1) { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return 3.0.pow((a - 1).toDouble()).toInt() * 2 * 2 + } + if (b == 2) { + // 當餘數為 2 時,不做處理 + return 3.0.pow(a.toDouble()).toInt() * 2 * 2 + } + // 當餘數為 0 時,不做處理 + return 3.0.pow(a.toDouble()).toInt() +} + +/* Driver Code */ +fun main() { + val n = 58 + + // 貪婪演算法 + val res = maxProductCutting(n) + println("最大切分乘積為 $res") +} diff --git a/zh-hant/codes/kotlin/chapter_hashing/array_hash_map.kt b/zh-hant/codes/kotlin/chapter_hashing/array_hash_map.kt new file mode 100644 index 000000000..3413b8112 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/array_hash_map.kt @@ -0,0 +1,129 @@ +/** + * File: array_hash_map.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 鍵值對 */ +class Pair( + var key: Int, + var value: String +) + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + private val buckets = arrayOfNulls(100) + + init { + // 初始化陣列,包含 100 個桶 + for (i in 0..<100) { + buckets[i] = null + } + } + + /* 雜湊函式 */ + fun hashFunc(key: Int): Int { + val index = key % 100 + return index + } + + /* 查詢操作 */ + fun get(key: Int): String? { + val index = hashFunc(key) + val pair = buckets[index] ?: return null + return pair.value + } + + /* 新增操作 */ + fun put(key: Int, value: String) { + val pair = Pair(key, value) + val index = hashFunc(key) + buckets[index] = pair + } + + /* 刪除操作 */ + fun remove(key: Int) { + val index = hashFunc(key) + // 置為 null ,代表刪除 + buckets[index] = null + } + + /* 獲取所有鍵值對 */ + fun pairSet(): MutableList { + val pairSet = ArrayList() + for (pair in buckets) { + if (pair != null) pairSet.add(pair) + } + return pairSet + } + + /* 獲取所有鍵 */ + fun keySet(): MutableList { + val keySet = ArrayList() + for (pair in buckets) { + if (pair != null) keySet.add(pair.key) + } + return keySet + } + + /* 獲取所有值 */ + fun valueSet(): MutableList { + val valueSet = ArrayList() + for (pair in buckets) { + pair?.let { valueSet.add(it.value) } + } + return valueSet + } + + /* 列印雜湊表 */ + fun print() { + for (kv in pairSet()) { + val key = kv.key + val value = kv.value + println("${key}->${value}") + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化雜湊表 */ + val map = ArrayHashMap() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈") + map.put(15937, "小囉") + map.put(16750, "小算") + map.put(13276, "小法") + map.put(10583, "小鴨") + println("\n新增完成後,雜湊表為\nKey -> Value") + map.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + val name: String? = map.get(15937) + println("\n輸入學號 15937 ,查詢到姓名 $name") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583) + println("\n刪除 10583 後,雜湊表為\nKey -> Value") + map.print() + + /* 走訪雜湊表 */ + println("\n走訪鍵值對 Key->Value") + for (kv in map.pairSet()) { + println("${kv.key} -> ${kv.value}") + } + println("\n單獨走訪鍵 Key") + for (key in map.keySet()) { + println(key) + } + println("\n單獨走訪值 Value") + for (value in map.valueSet()) { + println(value) + } +} diff --git a/zh-hant/codes/kotlin/chapter_hashing/built_in_hash.kt b/zh-hant/codes/kotlin/chapter_hashing/built_in_hash.kt new file mode 100644 index 000000000..ac4118e42 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/built_in_hash.kt @@ -0,0 +1,36 @@ +/** + * File: built_in_hash.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +import utils.ListNode + +/* Driver Code */ +fun main() { + val num = 3 + val hashNum = Integer.hashCode(num) + println("整數 $num 的雜湊值為 $hashNum") + + val bol = true + val hashBol = Boolean.hashCode() + println("布林量 $bol 的雜湊值為 $hashBol") + + val dec = 3.14159 + val hashDec = java.lang.Double.hashCode(dec) + println("小數 $dec 的雜湊值為 $hashDec") + + val str = "Hello 演算法" + val hashStr = str.hashCode() + println("字串 $str 的雜湊值為 $hashStr") + + val arr = arrayOf(12836, "小哈") + val hashTup = arr.contentHashCode() + println("陣列 ${arr.contentToString()} 的雜湊值為 ${hashTup}") + + val obj = ListNode(0) + val hashObj = obj.hashCode() + println("節點物件 $obj 的雜湊值為 $hashObj") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_hashing/hash_map.kt b/zh-hant/codes/kotlin/chapter_hashing/hash_map.kt new file mode 100644 index 000000000..719414b68 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/hash_map.kt @@ -0,0 +1,50 @@ +/** + * File: hash_map.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +import utils.printHashMap + +/* Driver Code */ +fun main() { + /* 初始化雜湊表 */ + val map: MutableMap = HashMap() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈" + map[15937] = "小囉" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鴨" + println("\n新增完成後,雜湊表為\nKey -> Value") + printHashMap(map) + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + val name = map[15937] + println("\n輸入學號 15937 ,查詢到姓名 $name") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583) + println("\n刪除 10583 後,雜湊表為\nKey -> Value") + printHashMap(map) + + /* 走訪雜湊表 */ + println("\n走訪鍵值對 Key->Value") + for ((key, value) in map) { + println("$key -> $value") + } + println("\n單獨走訪鍵 Key") + for (key in map.keys) { + println(key) + } + println("\n單獨走訪值 Value") + for (value in map.values) { + println(value) + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_hashing/hash_map_chaining.kt b/zh-hant/codes/kotlin/chapter_hashing/hash_map_chaining.kt new file mode 100644 index 000000000..f4ec453a3 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/hash_map_chaining.kt @@ -0,0 +1,145 @@ +/** + * File: hash_map_chaining.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 鏈式位址雜湊表 */ +class HashMapChaining() { + var size: Int // 鍵值對數量 + var capacity: Int // 雜湊表容量 + val loadThres: Double // 觸發擴容的負載因子閾值 + val extendRatio: Int // 擴容倍數 + var buckets: MutableList> // 桶陣列 + + /* 建構子 */ + init { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = ArrayList(capacity) + for (i in 0.. loadThres) { + extend() + } + val index = hashFunc(key) + val bucket = buckets[index] + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (pair in bucket) { + if (pair.key == key) { + pair.value = value + return + } + } + // 若無該 key ,則將鍵值對新增至尾部 + val pair = Pair(key, value) + bucket.add(pair) + size++ + } + + /* 刪除操作 */ + fun remove(key: Int) { + val index = hashFunc(key) + val bucket = buckets[index] + // 走訪桶,從中刪除鍵值對 + for (pair in bucket) { + if (pair.key == key) { + bucket.remove(pair) + size-- + break + } + } + } + + /* 擴容雜湊表 */ + fun extend() { + // 暫存原雜湊表 + val bucketsTmp = buckets + // 初始化擴容後的新雜湊表 + capacity *= extendRatio + // mutablelist 無固定大小 + buckets = mutableListOf() + for (i in 0..() + for (pair in bucket) { + val k = pair.key + val v = pair.value + res.add("$k -> $v") + } + println(res) + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化雜湊表 */ + val map = HashMapChaining() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈") + map.put(15937, "小囉") + map.put(16750, "小算") + map.put(13276, "小法") + map.put(10583, "小鴨") + println("\n新增完成後,雜湊表為\nKey -> Value") + map.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + val name = map.get(13276) + println("\n輸入學號 13276 ,查詢到姓名 $name") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(12836) + println("\n刪除 12836 後,雜湊表為\nKey -> Value") + map.print() +} diff --git a/zh-hant/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt b/zh-hant/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt new file mode 100644 index 000000000..7a9c008f6 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt @@ -0,0 +1,156 @@ +/** + * File: hash_map_open_addressing.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + private var size: Int = 0 // 鍵值對數量 + private var capacity = 4 // 雜湊表容量 + private val loadThres: Double = 2.0 / 3.0 // 觸發擴容的負載因子閾值 + private val extendRatio = 2 // 擴容倍數 + private var buckets: Array // 桶陣列 + private val TOMBSTONE = Pair(-1, "-1") // 刪除標記 + + /* 建構子 */ + init { + buckets = arrayOfNulls(capacity) + } + + /* 雜湊函式 */ + fun hashFunc(key: Int): Int { + return key % capacity + } + + /* 負載因子 */ + fun loadFactor(): Double { + return (size / capacity).toDouble() + } + + /* 搜尋 key 對應的桶索引 */ + fun findBucket(key: Int): Int { + var index = hashFunc(key) + var firstTombstone = -1 + // 線性探查,當遇到空桶時跳出 + while (buckets[index] != null) { + // 若遇到 key ,返回對應的桶索引 + if (buckets[index]?.key == key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index] + buckets[index] = TOMBSTONE + return firstTombstone // 返回移動後的桶索引 + } + return index // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % capacity + } + // 若 key 不存在,則返回新增點的索引 + return if (firstTombstone == -1) index else firstTombstone + } + + /* 查詢操作 */ + fun get(key: Int): String? { + // 搜尋 key 對應的桶索引 + val index = findBucket(key) + // 若找到鍵值對,則返回對應 val + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index]?.value + } + // 若鍵值對不存在,則返回 null + return null + } + + /* 新增操作 */ + fun put(key: Int, value: String) { + // 當負載因子超過閾值時,執行擴容 + if (loadFactor() > loadThres) { + extend() + } + // 搜尋 key 對應的桶索引 + val index = findBucket(key) + // 若找到鍵值對,則覆蓋 val 並返回 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index]!!.value = value + return + } + // 若鍵值對不存在,則新增該鍵值對 + buckets[index] = Pair(key, value) + size++ + } + + /* 刪除操作 */ + fun remove(key: Int) { + // 搜尋 key 對應的桶索引 + val index = findBucket(key) + // 若找到鍵值對,則用刪除標記覆蓋它 + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE + size-- + } + } + + /* 擴容雜湊表 */ + fun extend() { + // 暫存原雜湊表 + val bucketsTmp = buckets + // 初始化擴容後的新雜湊表 + capacity *= extendRatio + buckets = arrayOfNulls(capacity) + size = 0 + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (pair in bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + put(pair.key, pair.value) + } + } + } + + /* 列印雜湊表 */ + fun print() { + for (pair in buckets) { + if (pair == null) { + println("null") + } else if (pair == TOMBSTONE) { + println("TOMESTOME") + } else { + println("${pair.key} -> ${pair.value}") + } + } + } +} + +/* Driver Code */ +fun main() { + // 初始化雜湊表 + val hashmap = HashMapOpenAddressing() + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, val) + hashmap.put(12836, "小哈") + hashmap.put(15937, "小囉") + hashmap.put(16750, "小算") + hashmap.put(13276, "小法") + hashmap.put(10583, "小鴨") + println("\n新增完成後,雜湊表為\nKey -> Value") + hashmap.print() + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 val + val name = hashmap.get(13276) + println("\n輸入學號 13276 ,查詢到姓名 $name") + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, val) + hashmap.remove(16750) + println("\n刪除 16750 後,雜湊表為\nKey -> Value") + hashmap.print() +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_hashing/simple_hash.kt b/zh-hant/codes/kotlin/chapter_hashing/simple_hash.kt new file mode 100644 index 000000000..0a6f84ee9 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_hashing/simple_hash.kt @@ -0,0 +1,62 @@ +/** + * File: simple_hash.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +const val MODULUS = 10_0000_0007 + +/* 加法雜湊 */ +fun addHash(key: String): Int { + var hash = 0L + for (c in key.toCharArray()) { + hash = (hash + c.code) % MODULUS + } + return hash.toInt() +} + +/* 乘法雜湊 */ +fun mulHash(key: String): Int { + var hash = 0L + for (c in key.toCharArray()) { + hash = (31 * hash + c.code) % MODULUS + } + return hash.toInt() +} + +/* 互斥或雜湊 */ +fun xorHash(key: String): Int { + var hash = 0 + for (c in key.toCharArray()) { + hash = hash xor c.code + } + return hash and MODULUS +} + +/* 旋轉雜湊 */ +fun rotHash(key: String): Int { + var hash = 0L + for (c in key.toCharArray()) { + hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS + } + return hash.toInt() +} + +/* Driver Code */ +fun main() { + val key = "Hello 演算法" + + var hash: Int = addHash(key) + println("加法雜湊值為 $hash") + + hash = mulHash(key) + println("乘法雜湊值為 $hash") + + hash = xorHash(key) + println("互斥或雜湊值為 $hash") + + hash = rotHash(key) + println("旋轉雜湊值為 $hash") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_heap/heap.kt b/zh-hant/codes/kotlin/chapter_heap/heap.kt new file mode 100644 index 000000000..f2d3bdf68 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_heap/heap.kt @@ -0,0 +1,66 @@ +/** + * File: heap.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +fun testPush(heap: Queue, value: Int) { + heap.offer(value) // 元素入堆積 + print("\n元素 $value 入堆積後\n") + printHeap(heap) +} + +fun testPop(heap: Queue) { + val value = heap.poll() // 堆積頂元素出堆積 + print("\n堆積頂元素 $value 出堆積後\n") + printHeap(heap) +} + +/* Driver Code */ +fun main() { + /* 初始化堆積 */ + // 初始化小頂堆積 + val minHeap: PriorityQueue + + // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } + + println("\n以下測試樣例為大頂堆積") + + /* 元素入堆積 */ + testPush(maxHeap, 1) + testPush(maxHeap, 3) + testPush(maxHeap, 2) + testPush(maxHeap, 5) + testPush(maxHeap, 4) + + /* 獲取堆積頂元素 */ + val peek = maxHeap.peek() + print("\n堆積頂元素為 $peek\n") + + /* 堆積頂元素出堆積 */ + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + + /* 獲取堆積大小 */ + val size = maxHeap.size + print("\n堆積元素數量為 $size\n") + + /* 判斷堆積是否為空 */ + val isEmpty = maxHeap.isEmpty() + print("\n堆積是否為空 $isEmpty\n") + + /* 輸入串列並建堆積 */ + // 時間複雜度為 O(n) ,而非 O(nlogn) + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) + println("\n輸入串列並建立小頂堆積後") + printHeap(minHeap) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_heap/my_heap.kt b/zh-hant/codes/kotlin/chapter_heap/my_heap.kt new file mode 100644 index 000000000..2c4bb57ab --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_heap/my_heap.kt @@ -0,0 +1,157 @@ +/** + * File: my_heap.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +/* 大頂堆積 */ +class MaxHeap(nums: List?) { + // 使用串列而非陣列,這樣無須考慮擴容問題 + // 將串列元素原封不動新增進堆積 + private val maxHeap = ArrayList(nums!!) + + /* 建構子,根據輸入串列建堆積 */ + init { + // 堆積化除葉節點以外的其他所有節點 + for (i in parent(size() - 1) downTo 0) { + siftDown(i) + } + } + + /* 獲取左子節點的索引 */ + private fun left(i: Int): Int { + return 2 * i + 1 + } + + /* 獲取右子節點的索引 */ + private fun right(i: Int): Int { + return 2 * i + 2 + } + + /* 獲取父節點的索引 */ + private fun parent(i: Int): Int { + return (i - 1) / 2 // 向下整除 + } + + /* 交換元素 */ + private fun swap(i: Int, j: Int) { + maxHeap[i] = maxHeap[j].also { maxHeap[j] = maxHeap[i] } + } + + /* 獲取堆積大小 */ + fun size(): Int { + return maxHeap.size + } + + /* 判斷堆積是否為空 */ + fun isEmpty(): Boolean { + /* 判斷堆積是否為空 */ + return size() == 0 + } + + /* 訪問堆積頂元素 */ + fun peek(): Int { + return maxHeap[0] + } + + /* 元素入堆積 */ + fun push(value: Int) { + // 新增節點 + maxHeap.add(value) + // 從底至頂堆積化 + siftUp(size() - 1) + } + + /* 從節點 i 開始,從底至頂堆積化 */ + private fun siftUp(it: Int) { + // Kotlin的函式參數不可變,因此建立臨時變數 + var i = it + while (true) { + // 獲取節點 i 的父節點 + val p = parent(i) + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) break + // 交換兩節點 + swap(i, p) + // 迴圈向上堆積化 + i = p + } + } + + /* 元素出堆積 */ + fun pop(): Int { + // 判空處理 + if (isEmpty()) throw IndexOutOfBoundsException() + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(0, size() - 1) + // 刪除節點 + val value = maxHeap.removeAt(size() - 1) + // 從頂至底堆積化 + siftDown(0) + // 返回堆積頂元素 + return value + } + + /* 從節點 i 開始,從頂至底堆積化 */ + private fun siftDown(it: Int) { + // Kotlin的函式參數不可變,因此建立臨時變數 + var i = it + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + val l = left(i) + val r = right(i) + var ma = i + if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l + if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) break + // 交換兩節點 + swap(i, ma) + // 迴圈向下堆積化 + i = ma + } + } + + /* 列印堆積(二元樹) */ + fun print() { + val queue = PriorityQueue { a: Int, b: Int -> b - a } + queue.addAll(maxHeap) + printHeap(queue) + } +} + +/* Driver Code */ +fun main() { + /* 初始化大頂堆積 */ + val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)) + println("\n輸入串列並建堆積後") + maxHeap.print() + + /* 獲取堆積頂元素 */ + var peek = maxHeap.peek() + print("\n堆積頂元素為 $peek\n") + + /* 元素入堆積 */ + val value = 7 + maxHeap.push(value) + print("\n元素 $value 入堆積後\n") + maxHeap.print() + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop() + print("\n堆積頂元素 $peek 出堆積後\n") + maxHeap.print() + + /* 獲取堆積大小 */ + val size = maxHeap.size() + print("\n堆積元素數量為 $size\n") + + /* 判斷堆積是否為空 */ + val isEmpty = maxHeap.isEmpty() + print("\n堆積是否為空 $isEmpty\n") +} diff --git a/zh-hant/codes/kotlin/chapter_heap/top_k.kt b/zh-hant/codes/kotlin/chapter_heap/top_k.kt new file mode 100644 index 000000000..51bc703cc --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_heap/top_k.kt @@ -0,0 +1,38 @@ +/** + * File: top_k.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +fun topKHeap(nums: IntArray, k: Int): Queue { + // 初始化小頂堆積 + val heap = PriorityQueue() + // 將陣列的前 k 個元素入堆積 + for (i in 0.. heap.peek()) { + heap.poll() + heap.offer(nums[i]) + } + } + return heap +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 7, 6, 3, 2) + val k = 3 + val res = topKHeap(nums, k) + println("最大的 $k 個元素為") + printHeap(res) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/binary_search.kt b/zh-hant/codes/kotlin/chapter_searching/binary_search.kt new file mode 100644 index 000000000..d146f1fe6 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/binary_search.kt @@ -0,0 +1,59 @@ +/** + * File: binary_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分搜尋(雙閉區間) */ +fun binarySearch(nums: IntArray, target: Int): Int { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + var i = 0 + var j = nums.size - 1 + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + val m = i + (j - i) / 2 // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1 + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1 + else // 找到目標元素,返回其索引 + return m + } + // 未找到目標元素,返回 -1 + return -1 +} + +/* 二分搜尋(左閉右開區間) */ +fun binarySearchLCRO(nums: IntArray, target: Int): Int { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + var i = 0 + var j = nums.size + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + val m = i + (j - i) / 2 // 計算中點索引 m + if (nums[m] < target) // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1 + else if (nums[m] > target) // 此情況說明 target 在區間 [i, m) 中 + j = m + else // 找到目標元素,返回其索引 + return m + } + // 未找到目標元素,返回 -1 + return -1 +} + +/* Driver Code */ +fun main() { + val target = 6 + val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + + /* 二分搜尋(雙閉區間) */ + var index = binarySearch(nums, target) + println("目標元素 6 的索引 = $index") + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums, target) + println("目標元素 6 的索引 = $index") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/binary_search_edge.kt b/zh-hant/codes/kotlin/chapter_searching/binary_search_edge.kt new file mode 100644 index 000000000..08eddce27 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/binary_search_edge.kt @@ -0,0 +1,48 @@ +/** + * File: binary_search_edge.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分搜尋最左一個 target */ +fun binarySearchLeftEdge(nums: IntArray, target: Int): Int { + // 等價於查詢 target 的插入點 + val i = binarySearchInsertion(nums, target) + // 未找到 target ,返回 -1 + if (i == nums.size || nums[i] != target) { + return -1 + } + // 找到 target ,返回索引 i + return i +} + +/* 二分搜尋最右一個 target */ +fun binarySearchRightEdge(nums: IntArray, target: Int): Int { + // 轉化為查詢最左一個 target + 1 + val i = binarySearchInsertion(nums, target + 1) + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + val j = i - 1 + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1 + } + // 找到 target ,返回索引 j + return j +} + +/* Driver Code */ +fun main() { + // 包含重複元素的陣列 + val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) + println("\n陣列 nums = ${nums.contentToString()}") + + // 二分搜尋左邊界和右邊界 + for (target in intArrayOf(6, 7)) { + var index = binarySearchLeftEdge(nums, target) + println("最左一個元素 $target 的索引為 $index") + index = binarySearchRightEdge(nums, target) + println("最右一個元素 $target 的索引為 $index") + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/binary_search_insertion.kt b/zh-hant/codes/kotlin/chapter_searching/binary_search_insertion.kt new file mode 100644 index 000000000..366d83604 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/binary_search_insertion.kt @@ -0,0 +1,65 @@ +/** + * File: binary_search_insertion.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分搜尋插入點(無重複元素) */ +fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int { + var i = 0 + var j = nums.size - 1 // 初始化雙閉區間 [0, n-1] + while (i <= j) { + val m = i + (j - i) / 2 // 計算中點索引 m + if (nums[m] < target) { + i = m + 1 // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1 // target 在區間 [i, m-1] 中 + } else { + return m // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i +} + +/* 二分搜尋插入點(存在重複元素) */ +fun binarySearchInsertion(nums: IntArray, target: Int): Int { + var i = 0 + var j = nums.size - 1 // 初始化雙閉區間 [0, n-1] + while (i <= j) { + val m = i + (j - i) / 2 // 計算中點索引 m + if (nums[m] < target) { + i = m + 1 // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1 // target 在區間 [i, m-1] 中 + } else { + j = m - 1 // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i +} + +/* Driver Code */ +fun main() { + // 無重複元素的陣列 + var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + println("\n陣列 nums = ${nums.contentToString()}") + // 二分搜尋插入點 + for (target in intArrayOf(6, 9)) { + val index = binarySearchInsertionSimple(nums, target) + println("元素 $target 的插入點的索引為 $index") + } + + // 包含重複元素的陣列 + nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) + println("\n陣列 nums = ${nums.contentToString()}") + + // 二分搜尋插入點 + for (target in intArrayOf(2, 6, 20)) { + val index = binarySearchInsertion(nums, target) + println("元素 $target 的插入點的索引為 $index") + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/hashing_search.kt b/zh-hant/codes/kotlin/chapter_searching/hashing_search.kt new file mode 100644 index 000000000..a03aacbdb --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/hashing_search.kt @@ -0,0 +1,50 @@ +/** + * File: hashing_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +import utils.ListNode +import java.util.HashMap + +/* 雜湊查詢(陣列) */ +fun hashingSearchArray(map: Map, target: Int): Int { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map.getOrDefault(target, -1) +} + +/* 雜湊查詢(鏈結串列) */ +fun hashingSearchLinkedList(map: Map, target: Int): ListNode? { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map.getOrDefault(target, null) +} + +/* Driver Code */ +fun main() { + val target = 3 + + /* 雜湊查詢(陣列) */ + val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) + // 初始化雜湊表 + val map = HashMap() + for (i in nums.indices) { + map[nums[i]] = i // key: 元素,value: 索引 + } + val index = hashingSearchArray(map, target) + println("目標元素 3 的索引 = $index") + + /* 雜湊查詢(鏈結串列) */ + var head = ListNode.arrToLinkedList(nums) + // 初始化雜湊表 + val map1 = HashMap() + while (head != null) { + map1[head.value] = head // key: 節點值,value: 節點 + head = head.next + } + val node = hashingSearchLinkedList(map1, target) + println("目標節點值 3 的對應節點物件為 $node") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/linear_search.kt b/zh-hant/codes/kotlin/chapter_searching/linear_search.kt new file mode 100644 index 000000000..5c97980a2 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/linear_search.kt @@ -0,0 +1,50 @@ +/** + * File: linear_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +import utils.ListNode + +/* 線性查詢(陣列) */ +fun linearSearchArray(nums: IntArray, target: Int): Int { + // 走訪陣列 + for (i in nums.indices) { + // 找到目標元素,返回其索引 + if (nums[i] == target) + return i + } + // 未找到目標元素,返回 -1 + return -1 +} + +/* 線性查詢(鏈結串列) */ +fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? { + // 走訪鏈結串列 + var head = h + while (head != null) { + // 找到目標節點,返回之 + if (head.value == target) + return head + head = head.next + } + // 未找到目標節點,返回 null + return null +} + +/* Driver Code */ +fun main() { + val target = 3 + + /* 在陣列中執行線性查詢 */ + val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) + val index = linearSearchArray(nums, target) + println("目標元素 3 的索引 = $index") + + /* 在鏈結串列中執行線性查詢 */ + val head = ListNode.arrToLinkedList(nums) + val node = linearSearchLinkedList(head, target) + println("目標節點值 3 的對應節點物件為 $node") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_searching/two_sum.kt b/zh-hant/codes/kotlin/chapter_searching/two_sum.kt new file mode 100644 index 000000000..2eec6f928 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_searching/two_sum.kt @@ -0,0 +1,49 @@ +/** + * File: two_sum.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 方法一:暴力列舉 */ +fun twoSumBruteForce(nums: IntArray, target: Int): IntArray { + val size = nums.size + // 兩層迴圈,時間複雜度為 O(n^2) + for (i in 0..() + // 單層迴圈,時間複雜度為 O(n) + for (i in 0.. nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + nums[j] = nums[j+1].also { nums[j+1] = nums[j] } + } + } + } +} + +/* 泡沫排序(標誌最佳化) */ +fun bubbleSortWithFlag(nums: IntArray) { + // 外迴圈:未排序區間為 [0, i] + for (i in nums.size - 1 downTo 1) { + var flag = false // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (j in 0.. nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + nums[j] = nums[j + 1].also { nums[j] = nums[j + 1] } + flag = true // 記錄交換元素 + } + } + if (!flag) break // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + bubbleSort(nums) + println("泡沫排序完成後 nums = ${nums.contentToString()}") + + val nums1 = intArrayOf(4, 1, 3, 1, 5, 2) + bubbleSortWithFlag(nums1) + println("泡沫排序完成後 nums1 = ${nums1.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/bucket_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/bucket_sort.kt new file mode 100644 index 000000000..7535191ed --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/bucket_sort.kt @@ -0,0 +1,46 @@ +/** + * File: bucket_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +import kotlin.collections.ArrayList + +/* 桶排序 */ +fun bucketSort(nums: FloatArray) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + val k = nums.size / 2 + val buckets = ArrayList>() + for (i in 0.. nums[ma]) ma = l + if (r < n && nums[r] > nums[ma]) ma = r + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) break + // 交換兩節點 + nums[i] = nums[ma].also { nums[ma] = nums[i] } + // 迴圈向下堆積化 + i = ma + } +} + +/* 堆積排序 */ +fun heapSort(nums: IntArray) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (i in nums.size / 2 - 1 downTo 0) { + siftDown(nums, nums.size, i) + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (i in nums.size - 1 downTo 1) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + nums[0] = nums[i].also { nums[i] = nums[0] } + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0) + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + heapSort(nums) + println("堆積排序完成後 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/insertion_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/insertion_sort.kt new file mode 100644 index 000000000..347b0ba8c --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/insertion_sort.kt @@ -0,0 +1,29 @@ +/** + * File: insertion_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 插入排序 */ +fun insertionSort(nums: IntArray) { + //外迴圈: 已排序元素為 1, 2, ..., n + for (i in nums.indices) { + val base = nums[i] + var j = i - 1 + // 內迴圈: 將 base 插入到已排序部分的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j] // 將 nums[j] 向右移動一位 + j-- + } + nums[j + 1] = base // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + insertionSort(nums) + println("插入排序完成後 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/merge_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/merge_sort.kt new file mode 100644 index 000000000..be9f3369a --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/merge_sort.kt @@ -0,0 +1,54 @@ +/** + * File: merge_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 合併左子陣列和右子陣列 */ +fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + val tmp = IntArray(right - left + 1) + // 初始化左子陣列和右子陣列的起始索引 + var i = left + var j = mid + 1 + var k = 0 + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) tmp[k++] = nums[i++] + else tmp[k++] = nums[j++] + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while (i <= mid) { + tmp[k++] = nums[i++] + } + while (j <= right) { + tmp[k++] = nums[j++] + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for (l in tmp.indices) { + nums[left + l] = tmp[l] + } +} + +/* 合併排序 */ +fun mergeSort(nums: IntArray, left: Int, right: Int) { + // 終止條件 + if (left >= right) return // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + val mid = (left + right) / 2 // 計算中點 + mergeSort(nums, left, mid) // 遞迴左子陣列 + mergeSort(nums, mid + 1, right) // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right) +} + +/* Driver Code */ +fun main() { + /* 合併排序 */ + val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4) + mergeSort(nums, 0, nums.size - 1) + println("合併排序完成後 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/quick_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/quick_sort.kt new file mode 100644 index 000000000..7e20bb537 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/quick_sort.kt @@ -0,0 +1,119 @@ +/** + * File: quick_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 元素交換 */ +fun swap(nums: IntArray, i: Int, j: Int) { + nums[i] = nums[j].also { nums[j] = nums[i] } +} + +/* 哨兵劃分 */ +fun partition(nums: IntArray, left: Int, right: Int): Int { + // 以 nums[left] 為基準數 + var i = left + var j = right + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j-- // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++ // 從左向右找首個大於基準數的元素 + swap(nums, i, j) // 交換這兩個元素 + } + swap(nums, i, left) // 將基準數交換至兩子陣列的分界線 + return i // 返回基準數的索引 +} + +/* 快速排序 */ +fun quickSort(nums: IntArray, left: Int, right: Int) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return + // 哨兵劃分 + val pivot = partition(nums, left, right) + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1) + quickSort(nums, pivot + 1, right) +} + +/* 選取三個候選元素的中位數 */ +fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int { + val l = nums[left] + val m = nums[mid] + val r = nums[right] + if ((m in l..r) || (m in r..l)) + return mid // m 在 l 和 r 之間 + if ((l in m..r) || (l in r..m)) + return left // l 在 m 和 r 之間 + return right +} + +/* 哨兵劃分(三數取中值) */ +fun partitionMedian(nums: IntArray, left: Int, right: Int): Int { + // 選取三個候選元素的中位數 + val med = medianThree(nums, left, (left + right) / 2, right) + // 將中位數交換至陣列最左端 + swap(nums, left, med) + // 以 nums[left] 為基準數 + var i = left + var j = right + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j-- // 從右向左找首個小於基準數的元素 + while (i < j && nums[i] <= nums[left]) + i++ // 從左向右找首個大於基準數的元素 + swap(nums, i, j) // 交換這兩個元素 + } + swap(nums, i, left) // 將基準數交換至兩子陣列的分界線 + return i // 返回基準數的索引 +} + +/* 快速排序 */ +fun quickSortMedian(nums: IntArray, left: Int, right: Int) { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return + // 哨兵劃分 + val pivot = partitionMedian(nums, left, right) + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1) + quickSort(nums, pivot + 1, right) +} + +/* 快速排序(尾遞迴最佳化) */ +fun quickSortTailCall(nums: IntArray, left: Int, right: Int) { + // 子陣列長度為 1 時終止 + var l = left + var r = right + while (l < r) { + // 哨兵劃分操作 + val pivot = partition(nums, l, r) + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - l < r - pivot) { + quickSort(nums, l, pivot - 1) // 遞迴排序左子陣列 + l = pivot + 1 // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, r) // 遞迴排序右子陣列 + r = pivot - 1 // 剩餘未排序區間為 [left, pivot - 1] + } + } +} + +/* Driver Code */ +fun main() { + /* 快速排序 */ + val nums = intArrayOf(2, 4, 1, 0, 3, 5) + quickSort(nums, 0, nums.size - 1) + println("快速排序完成後 nums = ${nums.contentToString()}") + + /* 快速排序(中位基準數最佳化) */ + val nums1 = intArrayOf(2, 4, 1, 0, 3, 5) + quickSortMedian(nums1, 0, nums1.size - 1) + println("快速排序(中位基準數最佳化)完成後 nums1 = ${nums1.contentToString()}") + + /* 快速排序(尾遞迴最佳化) */ + val nums2 = intArrayOf(2, 4, 1, 0, 3, 5) + quickSortTailCall(nums2, 0, nums2.size - 1) + println("快速排序(尾遞迴最佳化)完成後 nums2 = ${nums2.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/radix_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/radix_sort.kt new file mode 100644 index 000000000..1dfa39694 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/radix_sort.kt @@ -0,0 +1,67 @@ +/** + * File: radix_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +fun digit(num: Int, exp: Int): Int { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return (num / exp) % 10 +} + +/* 計數排序(根據 nums 第 k 位排序) */ +fun countingSortDigit(nums: IntArray, exp: Int) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + val counter = IntArray(10) + val n = nums.size + // 統計 0~9 各數字的出現次數 + for (i in 0.. m) m = num + var exp = 1 + // 按照從低位到高位的順序走訪 + while (exp <= m) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp) + exp *= 10 + } +} + +/* Driver Code */ +fun main() { + // 基數排序 + val nums = intArrayOf( + 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 + ) + radixSort(nums) + println("基數排序完成後 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_sorting/selection_sort.kt b/zh-hant/codes/kotlin/chapter_sorting/selection_sort.kt new file mode 100644 index 000000000..c0b73ebc9 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_sorting/selection_sort.kt @@ -0,0 +1,29 @@ +/** + * File: selection_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 選擇排序 */ +fun selectionSort(nums: IntArray) { + val n = nums.size + // 外迴圈:未排序區間為 [i, n-1] + for (i in 0..() + + /* 獲取堆疊的長度 */ + fun size(): Int { + return stack.size + } + + /* 判斷堆疊是否為空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入堆疊 */ + fun push(num: Int) { + stack.add(num) + } + + /* 出堆疊 */ + fun pop(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return stack.removeAt(size() - 1) + } + + /* 訪問堆疊頂元素 */ + fun peek(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return stack[size() - 1] + } + + /* 將 List 轉化為 Array 並返回 */ + fun toArray(): Array { + return stack.toArray() + } +} + +/* Driver Code */ +fun main() { + /* 初始化堆疊 */ + val stack = ArrayStack() + + /* 元素入堆疊 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("堆疊 stack = ${stack.toArray().contentToString()}") + + /* 訪問堆疊頂元素 */ + val peek = stack.peek() + println("堆疊頂元素 peek = $peek") + + /* 元素出堆疊 */ + val pop = stack.pop() + println("出堆疊元素 pop = ${pop},出堆疊後 stack = ${stack.toArray().contentToString()}") + + /* 獲取堆疊的長度 */ + val size = stack.size() + println("堆疊的長度 size = $size") + + /* 判斷是否為空 */ + val isEmpty = stack.isEmpty() + println("堆疊是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/deque.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/deque.kt new file mode 100644 index 000000000..3ed10abe8 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/deque.kt @@ -0,0 +1,45 @@ +/** + * File: deque.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* 初始化雙向佇列 */ + val deque = LinkedList() + deque.offerLast(3) + deque.offerLast(2) + deque.offerLast(5) + println("雙向佇列 deque = $deque") + + /* 訪問元素 */ + val peekFirst = deque.peekFirst() + println("佇列首元素 peekFirst = $peekFirst") + val peekLast = deque.peekLast() + println("佇列尾元素 peekLast = $peekLast") + + /* 元素入列 */ + deque.offerLast(4) + println("元素 4 佇列尾入列後 deque = $deque") + deque.offerFirst(1) + println("元素 1 佇列首入列後 deque = $deque") + + /* 元素出列 */ + val popLast = deque.pollLast() + println("佇列尾出列元素 = $popLast,佇列尾出列後 deque = $deque") + val popFirst = deque.pollFirst() + println("佇列首出列元素 = $popFirst,佇列首出列後 deque = $deque") + + /* 獲取雙向佇列的長度 */ + val size = deque.size + println("雙向佇列長度 size = $size") + + /* 判斷雙向佇列是否為空 */ + val isEmpty = deque.isEmpty() + println("雙向佇列是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt new file mode 100644 index 000000000..287b5ecc7 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt @@ -0,0 +1,166 @@ +/** + * File: linkedlist_deque.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 雙向鏈結串列節點 */ +class ListNode(var value: Int) { + // 節點值 + var next: ListNode? = null // 後繼節點引用 + var prev: ListNode? = null // 前驅節點引用 +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + private var front: ListNode? = null // 頭節點 front ,尾節點 rear + private var rear: ListNode? = null + private var queSize = 0 // 雙向佇列的長度 + + /* 獲取雙向佇列的長度 */ + fun size(): Int { + return queSize + } + + /* 判斷雙向佇列是否為空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入列操作 */ + fun push(num: Int, isFront: Boolean) { + val node = ListNode(num) + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (isEmpty()) { + rear = node + front = rear + // 佇列首入列操作 + } else if (isFront) { + // 將 node 新增至鏈結串列頭部 + front?.prev = node + node.next = front + front = node // 更新頭節點 + // 佇列尾入列操作 + } else { + // 將 node 新增至鏈結串列尾部 + rear?.next = node + node.prev = rear + rear = node // 更新尾節點 + } + queSize++ // 更新佇列長度 + } + + /* 佇列首入列 */ + fun pushFirst(num: Int) { + push(num, true) + } + + /* 佇列尾入列 */ + fun pushLast(num: Int) { + push(num, false) + } + + /* 出列操作 */ + fun pop(isFront: Boolean): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + + val value: Int + // 佇列首出列操作 + if (isFront) { + value = front!!.value // 暫存頭節點值 + // 刪除頭節點 + val fNext = front!!.next + if (fNext != null) { + fNext.prev = null + front!!.next = null + } + front = fNext // 更新頭節點 + // 佇列尾出列操作 + } else { + value = rear!!.value // 暫存尾節點值 + // 刪除尾節點 + val rPrev = rear!!.prev + if (rPrev != null) { + rPrev.next = null + rear!!.prev = null + } + rear = rPrev // 更新尾節點 + } + queSize-- // 更新佇列長度 + return value + } + + /* 佇列首出列 */ + fun popFirst(): Int { + return pop(true) + } + + /* 佇列尾出列 */ + fun popLast(): Int { + return pop(false) + } + + /* 訪問佇列首元素 */ + fun peekFirst(): Int { + if (isEmpty()) { + throw IndexOutOfBoundsException() + + } + return front!!.value + } + + /* 訪問佇列尾元素 */ + fun peekLast(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return rear!!.value + } + + /* 返回陣列用於列印 */ + fun toArray(): IntArray { + var node = front + val res = IntArray(size()) + for (i in res.indices) { + res[i] = node!!.value + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* 初始化雙向佇列 */ + val deque = LinkedListDeque() + deque.pushLast(3) + deque.pushLast(2) + deque.pushLast(5) + println("雙向佇列 deque = ${deque.toArray().contentToString()}") + + /* 訪問元素 */ + val peekFirst = deque.peekFirst() + println("佇列首元素 peekFirst = $peekFirst") + val peekLast = deque.peekLast() + println("佇列尾元素 peekLast = $peekLast") + + /* 元素入列 */ + deque.pushLast(4) + println("元素 4 佇列尾入列後 deque = ${deque.toArray().contentToString()}") + deque.pushFirst(1) + println("元素 1 佇列首入列後 deque = ${deque.toArray().contentToString()}") + + /* 元素出列 */ + val popLast = deque.popLast() + println("佇列尾出列元素 = ${popLast},佇列尾出列後 deque = ${deque.toArray().contentToString()}") + val popFirst = deque.popFirst() + println("佇列首出列元素 = ${popFirst},佇列首出列後 deque = ${deque.toArray().contentToString()}") + + /* 獲取雙向佇列的長度 */ + val size = deque.size() + println("雙向佇列長度 size = $size") + + /* 判斷雙向佇列是否為空 */ + val isEmpty = deque.isEmpty() + println("雙向佇列是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt new file mode 100644 index 000000000..f0c5da6a1 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt @@ -0,0 +1,98 @@ +/** + * File: linkedlist_queue.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue( + // 頭節點 front ,尾節點 rear + private var front: ListNode? = null, + private var rear: ListNode? = null, + private var queSize: Int = 0 +) { + + /* 獲取佇列的長度 */ + fun size(): Int { + return queSize + } + + /* 判斷佇列是否為空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入列 */ + fun push(num: Int) { + // 在尾節點後新增 num + val node = ListNode(num) + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (front == null) { + front = node + rear = node + // 如果佇列不為空,則將該節點新增到尾節點後 + } else { + rear?.next = node + rear = node + } + queSize++ + } + + /* 出列 */ + fun pop(): Int { + val num = peek() + // 刪除頭節點 + front = front?.next + queSize-- + return num + } + + /* 訪問佇列首元素 */ + fun peek(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return front!!.value + } + + /* 將鏈結串列轉化為 Array 並返回 */ + fun toArray(): IntArray { + var node = front + val res = IntArray(size()) + for (i in res.indices) { + res[i] = node!!.value + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* 初始化佇列 */ + val queue = LinkedListQueue() + + /* 元素入列 */ + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + println("佇列 queue = ${queue.toArray().contentToString()}") + + /* 訪問佇列首元素 */ + val peek = queue.peek() + println("佇列首元素 peek = $peek") + + /* 元素出列 */ + val pop = queue.pop() + println("出列元素 pop = $pop,出列後 queue = ${queue.toArray().contentToString()}") + + /* 獲取佇列的長度 */ + val size = queue.size() + println("佇列長度 size = $size") + + /* 判斷佇列是否為空 */ + val isEmpty = queue.isEmpty() + println("佇列是否為空 = $isEmpty") +} diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt new file mode 100644 index 000000000..618200910 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt @@ -0,0 +1,87 @@ +/** + * File: linkedlist_stack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack( + private var stackPeek: ListNode? = null, // 將頭節點作為堆疊頂 + private var stkSize: Int = 0 // 堆疊的長度 +) { + + /* 獲取堆疊的長度 */ + fun size(): Int { + return stkSize + } + + /* 判斷堆疊是否為空 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* 入堆疊 */ + fun push(num: Int) { + val node = ListNode(num) + node.next = stackPeek + stackPeek = node + stkSize++ + } + + /* 出堆疊 */ + fun pop(): Int? { + val num = peek() + stackPeek = stackPeek?.next + stkSize--; + return num + } + + /* 訪問堆疊頂元素 */ + fun peek(): Int? { + if (isEmpty()) throw IndexOutOfBoundsException() + return stackPeek?.value + } + + /* 將 List 轉化為 Array 並返回 */ + fun toArray(): IntArray { + var node = stackPeek + val res = IntArray(size()) + for (i in res.size - 1 downTo 0) { + res[i] = node?.value!! + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* 初始化堆疊 */ + val stack = LinkedListStack() + + /* 元素入堆疊 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("堆疊 stack = ${stack.toArray().contentToString()}") + + /* 訪問堆疊頂元素 */ + val peek = stack.peek()!! + println("堆疊頂元素 peek = $peek") + + /* 元素出堆疊 */ + val pop = stack.pop()!! + println("出堆疊元素 pop = $pop,出堆疊後 stack = ${stack.toArray().contentToString()}") + + /* 獲取堆疊的長度 */ + val size = stack.size() + println("堆疊的長度 size = $size") + + /* 判斷是否為空 */ + val isEmpty = stack.isEmpty() + println("堆疊是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/queue.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/queue.kt new file mode 100644 index 000000000..af5ef54ef --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/queue.kt @@ -0,0 +1,39 @@ +/** + * File: queue.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* 初始化佇列 */ + val queue = LinkedList() + + /* 元素入列 */ + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) + println("佇列 queue = $queue") + + /* 訪問佇列首元素 */ + val peek = queue.peek() + println("佇列首元素 peek = $peek") + + /* 元素出列 */ + val pop = queue.poll() + println("出列元素 pop = $pop,出列後 queue = $queue") + + /* 獲取佇列的長度 */ + val size = queue.size + println("佇列長度 size = $size") + + /* 判斷佇列是否為空 */ + val isEmpty = queue.isEmpty() + println("佇列是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_stack_and_queue/stack.kt b/zh-hant/codes/kotlin/chapter_stack_and_queue/stack.kt new file mode 100644 index 000000000..866d1ac17 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_stack_and_queue/stack.kt @@ -0,0 +1,39 @@ +/** + * File: stack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* 初始化堆疊 */ + val stack = Stack() + + /* 元素入堆疊 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("堆疊 stack = $stack") + + /* 訪問堆疊頂元素 */ + val peek = stack.peek() + println("堆疊頂元素 peek = $peek") + + /* 元素出堆疊 */ + val pop = stack.pop() + println("出堆疊元素 pop = $pop,出堆疊後 stack = $stack") + + /* 獲取堆疊的長度 */ + val size = stack.size + println("堆疊的長度 size = $size") + + /* 判斷是否為空 */ + val isEmpty = stack.isEmpty() + println("堆疊是否為空 = $isEmpty") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/array_binary_tree.kt b/zh-hant/codes/kotlin/chapter_tree/array_binary_tree.kt new file mode 100644 index 000000000..0d0720e68 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/array_binary_tree.kt @@ -0,0 +1,122 @@ +/** + * File: array_binary_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree(private val tree: List) { + /* 串列容量 */ + fun size(): Int { + return tree.size + } + + /* 獲取索引為 i 節點的值 */ + fun value(i: Int): Int? { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= size()) return null + return tree[i] + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + fun left(i: Int): Int { + return 2 * i + 1 + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + fun right(i: Int): Int { + return 2 * i + 2 + } + + /* 獲取索引為 i 節點的父節點的索引 */ + fun parent(i: Int): Int { + return (i - 1) / 2 + } + + /* 層序走訪 */ + fun levelOrder(): List { + val res = ArrayList() + // 直接走訪陣列 + for (i in 0..) { + // 若為空位,則返回 + if (value(i) == null) return + // 前序走訪 + if ("pre" == order) res.add(value(i)) + dfs(left(i), order, res) + // 中序走訪 + if ("in" == order) res.add(value(i)) + dfs(right(i), order, res) + // 後序走訪 + if ("post" == order) res.add(value(i)) + } + + /* 前序走訪 */ + fun preOrder(): List { + val res = ArrayList() + dfs(0, "pre", res) + return res + } + + /* 中序走訪 */ + fun inOrder(): List { + val res = ArrayList() + dfs(0, "in", res) + return res + } + + /* 後序走訪 */ + fun postOrder(): List { + val res = ArrayList() + dfs(0, "post", res) + return res + } +} + +/* Driver Code */ +fun main() { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15) + + val root = TreeNode.listToTree(arr) + println("\n初始化二元樹\n") + println("二元樹的陣列表示:") + println(arr) + println("二元樹的鏈結串列表示:") + printTree(root) + + // 陣列表示下的二元樹類別 + val abt = ArrayBinaryTree(arr) + + // 訪問節點 + val i = 1 + val l = abt.left(i) + val r = abt.right(i) + val p = abt.parent(i) + println("當前節點的索引為 $i ,值為 ${abt.value(i)}") + println("其左子節點的索引為 $l ,值為 ${abt.value(l)}") + println("其右子節點的索引為 $r ,值為 ${abt.value(r)}") + println("其父節點的索引為 $p ,值為 ${abt.value(p)}") + + // 走訪樹 + var res = abt.levelOrder() + println("\n層序走訪為:$res") + res = abt.preOrder() + println("前序走訪為:$res") + res = abt.inOrder() + println("中序走訪為:$res") + res = abt.postOrder() + println("後序走訪為:$res") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/avl_tree.kt b/zh-hant/codes/kotlin/chapter_tree/avl_tree.kt new file mode 100644 index 000000000..90f3dad58 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/avl_tree.kt @@ -0,0 +1,208 @@ +/** + * File: avl_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree +import kotlin.math.max + +/* AVL 樹 */ +class AVLTree { + var root: TreeNode? = null // 根節點 + + /* 獲取節點高度 */ + fun height(node: TreeNode?): Int { + // 空節點高度為 -1 ,葉節點高度為 0 + return node?.height ?: -1 + } + + /* 更新節點高度 */ + private fun updateHeight(node: TreeNode?) { + // 節點高度等於最高子樹高度 + 1 + node?.height = (max(height(node?.left).toDouble(), height(node?.right).toDouble()) + 1).toInt() + } + + /* 獲取平衡因子 */ + fun balanceFactor(node: TreeNode?): Int { + // 空節點平衡因子為 0 + if (node == null) return 0 + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node.left) - height(node.right) + } + + /* 右旋操作 */ + private fun rightRotate(node: TreeNode?): TreeNode { + val child = node!!.left + val grandChild = child!!.right + // 以 child 為原點,將 node 向右旋轉 + child.right = node + node.left = grandChild + // 更新節點高度 + updateHeight(node) + updateHeight(child) + // 返回旋轉後子樹的根節點 + return child + } + + /* 左旋操作 */ + private fun leftRotate(node: TreeNode?): TreeNode { + val child = node!!.right + val grandChild = child!!.left + // 以 child 為原點,將 node 向左旋轉 + child.left = node + node.right = grandChild + // 更新節點高度 + updateHeight(node) + updateHeight(child) + // 返回旋轉後子樹的根節點 + return child + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + private fun rotate(node: TreeNode): TreeNode { + // 獲取節點 node 的平衡因子 + val balanceFactor = balanceFactor(node) + // 左偏樹 + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // 右旋 + return rightRotate(node) + } else { + // 先左旋後右旋 + node.left = leftRotate(node.left) + return rightRotate(node) + } + } + // 右偏樹 + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // 左旋 + return leftRotate(node) + } else { + // 先右旋後左旋 + node.right = rightRotate(node.right) + return leftRotate(node) + } + } + // 平衡樹,無須旋轉,直接返回 + return node + } + + /* 插入節點 */ + fun insert(value: Int) { + root = insertHelper(root, value) + } + + /* 遞迴插入節點(輔助方法) */ + private fun insertHelper(n: TreeNode?, value: Int): TreeNode { + if (n == null) + return TreeNode(value) + var node = n + /* 1. 查詢插入位置並插入節點 */ + if (value < node.value) node.left = insertHelper(node.left, value) + else if (value > node.value) node.right = insertHelper(node.right, value) + else return node // 重複節點不插入,直接返回 + + updateHeight(node) // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node) + // 返回子樹的根節點 + return node + } + + /* 刪除節點 */ + fun remove(value: Int) { + root = removeHelper(root, value) + } + + /* 遞迴刪除節點(輔助方法) */ + private fun removeHelper(n: TreeNode?, value: Int): TreeNode? { + var node = n ?: return null + /* 1. 查詢節點並刪除 */ + if (value < node.value) node.left = removeHelper(node.left, value) + else if (value > node.value) node.right = removeHelper(node.right, value) + else { + if (node.left == null || node.right == null) { + val child = if (node.left != null) node.left else node.right + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == null) return null + else node = child + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + var temp = node.right + while (temp!!.left != null) { + temp = temp.left + } + node.right = removeHelper(node.right, temp.value) + node.value = temp.value + } + } + updateHeight(node) // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node) + // 返回子樹的根節點 + return node + } + + /* 查詢節點 */ + fun search(value: Int): TreeNode? { + var cur = root + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + cur = if (cur.value < value) cur.right!! + else (if (cur.value > value) cur.left + else break)!! + } + // 返回目標節點 + return cur + } +} + +fun testInsert(tree: AVLTree, value: Int) { + tree.insert(value) + println("\n插入節點 $value 後,AVL 樹為") + printTree(tree.root) +} + +fun testRemove(tree: AVLTree, value: Int) { + tree.remove(value) + println("\n刪除節點 $value 後,AVL 樹為") + printTree(tree.root) +} + +/* Driver Code */ +fun main() { + /* 初始化空 AVL 樹 */ + val avlTree = AVLTree() + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(avlTree, 1) + testInsert(avlTree, 2) + testInsert(avlTree, 3) + testInsert(avlTree, 4) + testInsert(avlTree, 5) + testInsert(avlTree, 8) + testInsert(avlTree, 7) + testInsert(avlTree, 9) + testInsert(avlTree, 10) + testInsert(avlTree, 6) + + /* 插入重複節點 */ + testInsert(avlTree, 7) + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(avlTree, 8) // 刪除度為 0 的節點 + testRemove(avlTree, 5) // 刪除度為 1 的節點 + testRemove(avlTree, 4) // 刪除度為 2 的節點 + + /* 查詢節點 */ + val node = avlTree.search(7) + println("\n 查詢到的節點物件為 $node,節點值 = ${node?.value}") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/binary_search_tree.kt b/zh-hant/codes/kotlin/chapter_tree/binary_search_tree.kt new file mode 100644 index 000000000..5466fbaec --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/binary_search_tree.kt @@ -0,0 +1,138 @@ +/** + * File: binary_search_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* 二元搜尋樹 */ +class BinarySearchTree { + private var root: TreeNode? = null + + /* 獲取二元樹根節點 */ + fun getRoot(): TreeNode? { + return root + } + + /* 查詢節點 */ + fun search(num: Int): TreeNode? { + var cur = root + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + cur = if (cur.value < num) cur.right + // 目標節點在 cur 的左子樹中 + else if (cur.value > num) cur.left + // 找到目標節點,跳出迴圈 + else break + } + // 返回目標節點 + return cur + } + + /* 插入節點 */ + fun insert(num: Int) { + // 若樹為空,則初始化根節點 + if (root == null) { + root = TreeNode(num) + return + } + var cur = root + var pre: TreeNode? = null + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到重複節點,直接返回 + if (cur.value == num) return + pre = cur + // 插入位置在 cur 的右子樹中 + cur = if (cur.value < num) cur.right + // 插入位置在 cur 的左子樹中 + else cur.left + } + // 插入節點 + val node = TreeNode(num) + if (pre?.value!! < num) pre.right = node + else pre.left = node + } + + /* 刪除節點 */ + fun remove(num: Int) { + // 若樹為空,直接提前返回 + if (root == null) return + var cur = root + var pre: TreeNode? = null + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到待刪除節點,跳出迴圈 + if (cur.value == num) break + pre = cur + // 待刪除節點在 cur 的右子樹中 + cur = if (cur.value < num) cur.right + // 待刪除節點在 cur 的左子樹中 + else cur.left + } + // 若無待刪除節點,則直接返回 + if (cur == null) return + // 子節點數量 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + val child = if (cur.left != null) cur.left else cur.right + // 刪除節點 cur + if (cur != root) { + if (pre!!.left == cur) pre.left = child + else pre.right = child + } else { + // 若刪除節點為根節點,則重新指定根節點 + root = child + } + // 子節點數量 = 2 + } else { + // 獲取中序走訪中 cur 的下一個節點 + var tmp = cur.right + while (tmp!!.left != null) { + tmp = tmp.left + } + // 遞迴刪除節點 tmp + remove(tmp.value) + // 用 tmp 覆蓋 cur + cur.value = tmp.value + } + } +} + +/* Driver Code */ +fun main() { + /* 初始化二元搜尋樹 */ + val bst = BinarySearchTree() + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15) + for (num in nums) { + bst.insert(num) + } + println("\n初始化的二元樹為\n") + printTree(bst.getRoot()) + + /* 查詢節點 */ + val node = bst.search(7) + println("查詢到的節點物件為 $node,節點值 = ${node?.value}") + + /* 插入節點 */ + bst.insert(16) + println("\n插入節點 16 後,二元樹為\n") + printTree(bst.getRoot()) + + /* 刪除節點 */ + bst.remove(1) + println("\n刪除節點 1 後,二元樹為\n") + printTree(bst.getRoot()) + bst.remove(2) + println("\n刪除節點 2 後,二元樹為\n") + printTree(bst.getRoot()) + bst.remove(4) + println("\n刪除節點 4 後,二元樹為\n") + printTree(bst.getRoot()) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/binary_tree.kt b/zh-hant/codes/kotlin/chapter_tree/binary_tree.kt new file mode 100644 index 000000000..79a4130c9 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/binary_tree.kt @@ -0,0 +1,40 @@ +/** + * File: binary_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* Driver Code */ +fun main() { + /* 初始化二元樹 */ + // 初始化節點 + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + println("\n初始化二元樹\n") + printTree(n1) + + /* 插入與刪除節點 */ + val P = TreeNode(0) + // 在 n1 -> n2 中間插入節點 P + n1.left = P + P.left = n2 + println("\n插入節點 P 後\n") + printTree(n1) + // 刪除節點 P + n1.left = n2 + println("\n刪除節點 P 後\n") + printTree(n1) +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/binary_tree_bfs.kt b/zh-hant/codes/kotlin/chapter_tree/binary_tree_bfs.kt new file mode 100644 index 000000000..ac0767786 --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/binary_tree_bfs.kt @@ -0,0 +1,41 @@ +/** + * File: binary_tree_bfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree +import java.util.* + +/* 層序走訪 */ +fun levelOrder(root: TreeNode?): MutableList { + // 初始化佇列,加入根節點 + val queue = LinkedList() + queue.add(root) + // 初始化一個串列,用於儲存走訪序列 + val list = ArrayList() + while (!queue.isEmpty()) { + val node = queue.poll() // 隊列出隊 + list.add(node?.value!!) // 儲存節點值 + if (node.left != null) queue.offer(node.left) // 左子節點入列 + + if (node.right != null) queue.offer(node.right) // 右子節點入列 + } + return list +} + +/* Driver Code */ +fun main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) + println("\n初始化二元樹\n") + printTree(root) + + /* 層序走訪 */ + val list = levelOrder(root) + println("\n層序走訪的節點列印序列 = $list") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/chapter_tree/binary_tree_dfs.kt b/zh-hant/codes/kotlin/chapter_tree/binary_tree_dfs.kt new file mode 100644 index 000000000..590921ddb --- /dev/null +++ b/zh-hant/codes/kotlin/chapter_tree/binary_tree_dfs.kt @@ -0,0 +1,64 @@ +/** + * File: binary_tree_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +// 初始化串列,用於儲存走訪序列 +var list = ArrayList() + +/* 前序走訪 */ +fun preOrder(root: TreeNode?) { + if (root == null) return + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.add(root.value) + preOrder(root.left) + preOrder(root.right) +} + +/* 中序走訪 */ +fun inOrder(root: TreeNode?) { + if (root == null) return + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root.left) + list.add(root.value) + inOrder(root.right) +} + +/* 後序走訪 */ +fun postOrder(root: TreeNode?) { + if (root == null) return + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root.left) + postOrder(root.right) + list.add(root.value) +} + +/* Driver Code */ +fun main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) + println("\n初始化二元樹\n") + printTree(root) + + /* 前序走訪 */ + list.clear() + preOrder(root) + println("\n前序走訪的節點列印序列 = $list") + + /* 中序走訪 */ + list.clear() + inOrder(root) + println("\n中序走訪的節點列印序列 = $list") + + /* 後序走訪 */ + list.clear() + postOrder(root) + println("\n後序走訪的節點列印序列 = $list") +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/utils/ListNode.kt b/zh-hant/codes/kotlin/utils/ListNode.kt new file mode 100644 index 000000000..0cbd5078a --- /dev/null +++ b/zh-hant/codes/kotlin/utils/ListNode.kt @@ -0,0 +1,25 @@ +/** + * File: ListNode.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 鏈結串列節點 */ +class ListNode(var value: Int) { + var next: ListNode? = null + + companion object { + /* 將串列反序列化為鏈結串列 */ + fun arrToLinkedList(arr: IntArray): ListNode? { + val dum = ListNode(0) + var head = dum + for (value in arr) { + head.next = ListNode(value) + head = head.next!! + } + return dum.next + } + } +} diff --git a/zh-hant/codes/kotlin/utils/PrintUtil.kt b/zh-hant/codes/kotlin/utils/PrintUtil.kt new file mode 100644 index 000000000..1f7189ab6 --- /dev/null +++ b/zh-hant/codes/kotlin/utils/PrintUtil.kt @@ -0,0 +1,106 @@ +/** + * File: PrintUtil.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +import java.util.* + +class Trunk(var prev: Trunk?, var str: String) + +/* 列印矩陣(Array) */ +fun printMatrix(matrix: Array>) { + println("[") + for (row in matrix) { + println(" $row,") + } + println("]") +} + +/* 列印矩陣(List) */ +fun printMatrix(matrix: List>) { + println("[") + for (row in matrix) { + println(" $row,") + } + println("]") +} + +/* 列印鏈結串列 */ +fun printLinkedList(h: ListNode?) { + var head = h + val list = ArrayList() + while (head != null) { + list.add(head.value.toString()) + head = head.next + } + println(list.joinToString(separator = " -> ")) +} + +/* 列印二元樹 */ +fun printTree(root: TreeNode?) { + printTree(root, null, false) +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) { + if (root == null) { + return + } + + var prevStr = " " + val trunk = Trunk(prev, prevStr) + + printTree(root.right, trunk, true) + + if (prev == null) { + trunk.str = "———" + } else if (isRight) { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev.str = prevStr + } + + showTrunks(trunk) + println(" ${root.value}") + + if (prev != null) { + prev.str = prevStr + } + trunk.str = " |" + + printTree(root.left, trunk, false) +} + +fun showTrunks(p: Trunk?) { + if (p == null) { + return + } + showTrunks(p.prev) + print(p.str) +} + +/* 列印雜湊表 */ +fun printHashMap(map: Map) { + for ((key, value) in map) { + println(key.toString() + " -> " + value) + } +} + +/* 列印堆積 */ +fun printHeap(queue: Queue?) { + val list = queue?.let { ArrayList(it) } + print("堆積的陣列表示:") + println(list) + println("堆積的樹狀表示:") + val root = list?.let { TreeNode.listToTree(it) } + printTree(root) +} diff --git a/zh-hant/codes/kotlin/utils/TreeNode.kt b/zh-hant/codes/kotlin/utils/TreeNode.kt new file mode 100644 index 000000000..acec7c316 --- /dev/null +++ b/zh-hant/codes/kotlin/utils/TreeNode.kt @@ -0,0 +1,68 @@ +/** + * File: TreeNode.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 二元樹節點類別 */ +class TreeNode( + var value: Int // 節點值 +) { + var height: Int = 0 // 節點高度 + var left: TreeNode? = null // 左子節點引用 + var right: TreeNode? = null // 右子節點引用 + + // 序列化編碼規則請參考: + // 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 + + /* 將串列反序列化為二元樹:遞迴 */ + companion object { + private fun listToTreeDFS(arr: MutableList, i: Int): TreeNode? { + if (i < 0 || i >= arr.size || arr[i] == null) { + return null + } + val root = TreeNode(arr[i]!!) + root.left = listToTreeDFS(arr, 2 * i + 1) + root.right = listToTreeDFS(arr, 2 * i + 2) + return root + } + + /* 將串列反序列化為二元樹 */ + fun listToTree(arr: MutableList): TreeNode? { + return listToTreeDFS(arr, 0) + } + + /* 將二元樹序列化為串列:遞迴 */ + private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList) { + if (root == null) return + while (i >= res.size) { + res.add(null) + } + res[i] = root.value + treeToListDFS(root.left, 2 * i + 1, res) + treeToListDFS(root.right, 2 * i + 2, res) + } + + /* 將二元樹序列化為串列 */ + fun treeToList(root: TreeNode?): List { + val res = ArrayList() + treeToListDFS(root, 0, res) + return res + } + } +} \ No newline at end of file diff --git a/zh-hant/codes/kotlin/utils/Vertex.kt b/zh-hant/codes/kotlin/utils/Vertex.kt new file mode 100644 index 000000000..9f995187f --- /dev/null +++ b/zh-hant/codes/kotlin/utils/Vertex.kt @@ -0,0 +1,30 @@ +/** + * File: Vertex.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 頂點類別 */ +class Vertex(val value: Int) { + companion object { + /* 輸入值串列 vals ,返回頂點串列 vets */ + fun valsToVets(vals: IntArray): Array { + val vets = arrayOfNulls(vals.size) + for (i in vals.indices) { + vets[i] = Vertex(vals[i]) + } + return vets + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + fun vetsToVals(vets: List): List { + val vals = ArrayList() + for (vet in vets) { + vals.add(vet!!.value) + } + return vals + } + } +} \ No newline at end of file diff --git a/zh-hant/codes/python/.gitignore b/zh-hant/codes/python/.gitignore new file mode 100644 index 000000000..bee8a64b7 --- /dev/null +++ b/zh-hant/codes/python/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/zh-hant/codes/python/chapter_array_and_linkedlist/array.py b/zh-hant/codes/python/chapter_array_and_linkedlist/array.py new file mode 100644 index 000000000..9a9e2a7e0 --- /dev/null +++ b/zh-hant/codes/python/chapter_array_and_linkedlist/array.py @@ -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) diff --git a/zh-hant/codes/python/chapter_array_and_linkedlist/linked_list.py b/zh-hant/codes/python/chapter_array_and_linkedlist/linked_list.py new file mode 100644 index 000000000..9ec65d015 --- /dev/null +++ b/zh-hant/codes/python/chapter_array_and_linkedlist/linked_list.py @@ -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)) diff --git a/zh-hant/codes/python/chapter_array_and_linkedlist/list.py b/zh-hant/codes/python/chapter_array_and_linkedlist/list.py new file mode 100644 index 000000000..4d2b31ad2 --- /dev/null +++ b/zh-hant/codes/python/chapter_array_and_linkedlist/list.py @@ -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) diff --git a/zh-hant/codes/python/chapter_array_and_linkedlist/my_list.py b/zh-hant/codes/python/chapter_array_and_linkedlist/my_list.py new file mode 100644 index 000000000..553bbeb13 --- /dev/null +++ b/zh-hant/codes/python/chapter_array_and_linkedlist/my_list.py @@ -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()}") diff --git a/zh-hant/codes/python/chapter_backtracking/n_queens.py b/zh-hant/codes/python/chapter_backtracking/n_queens.py new file mode 100644 index 000000000..3ec29d3de --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/n_queens.py @@ -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) diff --git a/zh-hant/codes/python/chapter_backtracking/permutations_i.py b/zh-hant/codes/python/chapter_backtracking/permutations_i.py new file mode 100644 index 000000000..6b371cd4e --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/permutations_i.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_backtracking/permutations_ii.py b/zh-hant/codes/python/chapter_backtracking/permutations_ii.py new file mode 100644 index 000000000..2206d801c --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/permutations_ii.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_backtracking/preorder_traversal_i_compact.py b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_i_compact.py new file mode 100644 index 000000000..cdaa27a95 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_i_compact.py @@ -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]) diff --git a/zh-hant/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py new file mode 100644 index 000000000..255072126 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py @@ -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]) diff --git a/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py new file mode 100644 index 000000000..d12b8a5d7 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py @@ -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]) diff --git a/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_template.py b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_template.py new file mode 100644 index 000000000..2f9d07be2 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/preorder_traversal_iii_template.py @@ -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]) diff --git a/zh-hant/codes/python/chapter_backtracking/subset_sum_i.py b/zh-hant/codes/python/chapter_backtracking/subset_sum_i.py new file mode 100644 index 000000000..898554710 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/subset_sum_i.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_backtracking/subset_sum_i_naive.py b/zh-hant/codes/python/chapter_backtracking/subset_sum_i_naive.py new file mode 100644 index 000000000..83345f27f --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/subset_sum_i_naive.py @@ -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"請注意,該方法輸出的結果包含重複集合") diff --git a/zh-hant/codes/python/chapter_backtracking/subset_sum_ii.py b/zh-hant/codes/python/chapter_backtracking/subset_sum_ii.py new file mode 100644 index 000000000..8c7edf330 --- /dev/null +++ b/zh-hant/codes/python/chapter_backtracking/subset_sum_ii.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_computational_complexity/iteration.py b/zh-hant/codes/python/chapter_computational_complexity/iteration.py new file mode 100644 index 000000000..d11ee696e --- /dev/null +++ b/zh-hant/codes/python/chapter_computational_complexity/iteration.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_computational_complexity/recursion.py b/zh-hant/codes/python/chapter_computational_complexity/recursion.py new file mode 100644 index 000000000..33bfd37b0 --- /dev/null +++ b/zh-hant/codes/python/chapter_computational_complexity/recursion.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_computational_complexity/space_complexity.py b/zh-hant/codes/python/chapter_computational_complexity/space_complexity.py new file mode 100644 index 000000000..5ecb60948 --- /dev/null +++ b/zh-hant/codes/python/chapter_computational_complexity/space_complexity.py @@ -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) diff --git a/zh-hant/codes/python/chapter_computational_complexity/time_complexity.py b/zh-hant/codes/python/chapter_computational_complexity/time_complexity.py new file mode 100644 index 000000000..aa49bd059 --- /dev/null +++ b/zh-hant/codes/python/chapter_computational_complexity/time_complexity.py @@ -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) diff --git a/zh-hant/codes/python/chapter_computational_complexity/worst_best_time_complexity.py b/zh-hant/codes/python/chapter_computational_complexity/worst_best_time_complexity.py new file mode 100644 index 000000000..0a11e369d --- /dev/null +++ b/zh-hant/codes/python/chapter_computational_complexity/worst_best_time_complexity.py @@ -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) diff --git a/zh-hant/codes/python/chapter_divide_and_conquer/binary_search_recur.py b/zh-hant/codes/python/chapter_divide_and_conquer/binary_search_recur.py new file mode 100644 index 000000000..bcfc36534 --- /dev/null +++ b/zh-hant/codes/python/chapter_divide_and_conquer/binary_search_recur.py @@ -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) diff --git a/zh-hant/codes/python/chapter_divide_and_conquer/build_tree.py b/zh-hant/codes/python/chapter_divide_and_conquer/build_tree.py new file mode 100644 index 000000000..67471ead5 --- /dev/null +++ b/zh-hant/codes/python/chapter_divide_and_conquer/build_tree.py @@ -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) diff --git a/zh-hant/codes/python/chapter_divide_and_conquer/hanota.py b/zh-hant/codes/python/chapter_divide_and_conquer/hanota.py new file mode 100644 index 000000000..d416dd175 --- /dev/null +++ b/zh-hant/codes/python/chapter_divide_and_conquer/hanota.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py new file mode 100644 index 000000000..0012219b5 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py @@ -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} 種方案") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py new file mode 100644 index 000000000..908679816 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py @@ -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} 種方案") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py new file mode 100644 index 000000000..4697129c6 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py @@ -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} 種方案") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py new file mode 100644 index 000000000..af2bd23d6 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py @@ -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} 種方案") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py new file mode 100644 index 000000000..e14ce694e --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py @@ -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} 種方案") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/coin_change.py b/zh-hant/codes/python/chapter_dynamic_programming/coin_change.py new file mode 100644 index 000000000..9d860fd4c --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/coin_change.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/coin_change_ii.py b/zh-hant/codes/python/chapter_dynamic_programming/coin_change_ii.py new file mode 100644 index 000000000..6edb254a9 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/coin_change_ii.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/edit_distance.py b/zh-hant/codes/python/chapter_dynamic_programming/edit_distance.py new file mode 100644 index 000000000..bc361b653 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/edit_distance.py @@ -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} 步") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/knapsack.py b/zh-hant/codes/python/chapter_dynamic_programming/knapsack.py new file mode 100644 index 000000000..a31b0bcee --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/knapsack.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py b/zh-hant/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py new file mode 100644 index 000000000..dd461bef6 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/min_path_sum.py b/zh-hant/codes/python/chapter_dynamic_programming/min_path_sum.py new file mode 100644 index 000000000..97d20e9d8 --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/min_path_sum.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_dynamic_programming/unbounded_knapsack.py b/zh-hant/codes/python/chapter_dynamic_programming/unbounded_knapsack.py new file mode 100644 index 000000000..3472fae3c --- /dev/null +++ b/zh-hant/codes/python/chapter_dynamic_programming/unbounded_knapsack.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_graph/graph_adjacency_list.py b/zh-hant/codes/python/chapter_graph/graph_adjacency_list.py new file mode 100644 index 000000000..4c02d7209 --- /dev/null +++ b/zh-hant/codes/python/chapter_graph/graph_adjacency_list.py @@ -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() diff --git a/zh-hant/codes/python/chapter_graph/graph_adjacency_matrix.py b/zh-hant/codes/python/chapter_graph/graph_adjacency_matrix.py new file mode 100644 index 000000000..e0a4bdd87 --- /dev/null +++ b/zh-hant/codes/python/chapter_graph/graph_adjacency_matrix.py @@ -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() diff --git a/zh-hant/codes/python/chapter_graph/graph_bfs.py b/zh-hant/codes/python/chapter_graph/graph_bfs.py new file mode 100644 index 000000000..6d29057df --- /dev/null +++ b/zh-hant/codes/python/chapter_graph/graph_bfs.py @@ -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)) diff --git a/zh-hant/codes/python/chapter_graph/graph_dfs.py b/zh-hant/codes/python/chapter_graph/graph_dfs.py new file mode 100644 index 000000000..1ef97c63e --- /dev/null +++ b/zh-hant/codes/python/chapter_graph/graph_dfs.py @@ -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)) diff --git a/zh-hant/codes/python/chapter_greedy/coin_change_greedy.py b/zh-hant/codes/python/chapter_greedy/coin_change_greedy.py new file mode 100644 index 000000000..595fd1a13 --- /dev/null +++ b/zh-hant/codes/python/chapter_greedy/coin_change_greedy.py @@ -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") diff --git a/zh-hant/codes/python/chapter_greedy/fractional_knapsack.py b/zh-hant/codes/python/chapter_greedy/fractional_knapsack.py new file mode 100644 index 000000000..dd318c315 --- /dev/null +++ b/zh-hant/codes/python/chapter_greedy/fractional_knapsack.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_greedy/max_capacity.py b/zh-hant/codes/python/chapter_greedy/max_capacity.py new file mode 100644 index 000000000..574f962f8 --- /dev/null +++ b/zh-hant/codes/python/chapter_greedy/max_capacity.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_greedy/max_product_cutting.py b/zh-hant/codes/python/chapter_greedy/max_product_cutting.py new file mode 100644 index 000000000..0a9af8734 --- /dev/null +++ b/zh-hant/codes/python/chapter_greedy/max_product_cutting.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_hashing/array_hash_map.py b/zh-hant/codes/python/chapter_hashing/array_hash_map.py new file mode 100644 index 000000000..1cd2ffa83 --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/array_hash_map.py @@ -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) diff --git a/zh-hant/codes/python/chapter_hashing/built_in_hash.py b/zh-hant/codes/python/chapter_hashing/built_in_hash.py new file mode 100644 index 000000000..31258b421 --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/built_in_hash.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_hashing/hash_map.py b/zh-hant/codes/python/chapter_hashing/hash_map.py new file mode 100644 index 000000000..70cb4c3ba --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/hash_map.py @@ -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) diff --git a/zh-hant/codes/python/chapter_hashing/hash_map_chaining.py b/zh-hant/codes/python/chapter_hashing/hash_map_chaining.py new file mode 100644 index 000000000..edf1a6afc --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/hash_map_chaining.py @@ -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() diff --git a/zh-hant/codes/python/chapter_hashing/hash_map_open_addressing.py b/zh-hant/codes/python/chapter_hashing/hash_map_open_addressing.py new file mode 100644 index 000000000..c1fa21316 --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/hash_map_open_addressing.py @@ -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() diff --git a/zh-hant/codes/python/chapter_hashing/simple_hash.py b/zh-hant/codes/python/chapter_hashing/simple_hash.py new file mode 100644 index 000000000..f3c012def --- /dev/null +++ b/zh-hant/codes/python/chapter_hashing/simple_hash.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_heap/heap.py b/zh-hant/codes/python/chapter_heap/heap.py new file mode 100644 index 000000000..8c077a773 --- /dev/null +++ b/zh-hant/codes/python/chapter_heap/heap.py @@ -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) diff --git a/zh-hant/codes/python/chapter_heap/my_heap.py b/zh-hant/codes/python/chapter_heap/my_heap.py new file mode 100644 index 000000000..5b2c41935 --- /dev/null +++ b/zh-hant/codes/python/chapter_heap/my_heap.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_heap/top_k.py b/zh-hant/codes/python/chapter_heap/top_k.py new file mode 100644 index 000000000..8b43d60ad --- /dev/null +++ b/zh-hant/codes/python/chapter_heap/top_k.py @@ -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) diff --git a/zh-hant/codes/python/chapter_searching/binary_search.py b/zh-hant/codes/python/chapter_searching/binary_search.py new file mode 100644 index 000000000..4adcf70b0 --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/binary_search.py @@ -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) diff --git a/zh-hant/codes/python/chapter_searching/binary_search_edge.py b/zh-hant/codes/python/chapter_searching/binary_search_edge.py new file mode 100644 index 000000000..32f0c03b3 --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/binary_search_edge.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_searching/binary_search_insertion.py b/zh-hant/codes/python/chapter_searching/binary_search_insertion.py new file mode 100644 index 000000000..f793e53b1 --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/binary_search_insertion.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_searching/hashing_search.py b/zh-hant/codes/python/chapter_searching/hashing_search.py new file mode 100644 index 000000000..6d01825f4 --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/hashing_search.py @@ -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) diff --git a/zh-hant/codes/python/chapter_searching/linear_search.py b/zh-hant/codes/python/chapter_searching/linear_search.py new file mode 100644 index 000000000..cb902410d --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/linear_search.py @@ -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) diff --git a/zh-hant/codes/python/chapter_searching/two_sum.py b/zh-hant/codes/python/chapter_searching/two_sum.py new file mode 100644 index 000000000..6b43eea57 --- /dev/null +++ b/zh-hant/codes/python/chapter_searching/two_sum.py @@ -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) diff --git a/zh-hant/codes/python/chapter_sorting/bubble_sort.py b/zh-hant/codes/python/chapter_sorting/bubble_sort.py new file mode 100644 index 000000000..558bb3098 --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/bubble_sort.py @@ -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) diff --git a/zh-hant/codes/python/chapter_sorting/bucket_sort.py b/zh-hant/codes/python/chapter_sorting/bucket_sort.py new file mode 100644 index 000000000..6340577c7 --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/bucket_sort.py @@ -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) diff --git a/zh-hant/codes/python/chapter_sorting/counting_sort.py b/zh-hant/codes/python/chapter_sorting/counting_sort.py new file mode 100644 index 000000000..50bfb38bb --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/counting_sort.py @@ -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}") diff --git a/zh-hant/codes/python/chapter_sorting/heap_sort.py b/zh-hant/codes/python/chapter_sorting/heap_sort.py new file mode 100644 index 000000000..a4eaf0621 --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/heap_sort.py @@ -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) diff --git a/zh-hant/codes/python/chapter_sorting/insertion_sort.py b/zh-hant/codes/python/chapter_sorting/insertion_sort.py new file mode 100644 index 000000000..35175b718 --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/insertion_sort.py @@ -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) diff --git a/zh-hant/codes/python/chapter_sorting/merge_sort.py b/zh-hant/codes/python/chapter_sorting/merge_sort.py new file mode 100644 index 000000000..6eb4db04c --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/merge_sort.py @@ -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) diff --git a/zh-hant/codes/python/chapter_sorting/quick_sort.py b/zh-hant/codes/python/chapter_sorting/quick_sort.py new file mode 100644 index 000000000..0258da54b --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/quick_sort.py @@ -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) diff --git a/zh-hant/codes/python/chapter_sorting/radix_sort.py b/zh-hant/codes/python/chapter_sorting/radix_sort.py new file mode 100644 index 000000000..42706e5f3 --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/radix_sort.py @@ -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) diff --git a/zh-hant/codes/python/chapter_sorting/selection_sort.py b/zh-hant/codes/python/chapter_sorting/selection_sort.py new file mode 100644 index 000000000..98ff9bc4c --- /dev/null +++ b/zh-hant/codes/python/chapter_sorting/selection_sort.py @@ -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) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/array_deque.py b/zh-hant/codes/python/chapter_stack_and_queue/array_deque.py new file mode 100644 index 000000000..f833ce339 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/array_deque.py @@ -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) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/array_queue.py b/zh-hant/codes/python/chapter_stack_and_queue/array_queue.py new file mode 100644 index 000000000..c419be3bc --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/array_queue.py @@ -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()) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/array_stack.py b/zh-hant/codes/python/chapter_stack_and_queue/array_stack.py new file mode 100644 index 000000000..01129b98f --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/array_stack.py @@ -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) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/deque.py b/zh-hant/codes/python/chapter_stack_and_queue/deque.py new file mode 100644 index 000000000..b562721d5 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/deque.py @@ -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) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_deque.py b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_deque.py new file mode 100644 index 000000000..7b706d9fd --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_deque.py @@ -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) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_queue.py b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_queue.py new file mode 100644 index 000000000..62c641b46 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_queue.py @@ -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) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_stack.py b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_stack.py new file mode 100644 index 000000000..25f5521a0 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/linkedlist_stack.py @@ -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) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/queue.py b/zh-hant/codes/python/chapter_stack_and_queue/queue.py new file mode 100644 index 000000000..3d49e232c --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/queue.py @@ -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) diff --git a/zh-hant/codes/python/chapter_stack_and_queue/stack.py b/zh-hant/codes/python/chapter_stack_and_queue/stack.py new file mode 100644 index 000000000..89f4fdac2 --- /dev/null +++ b/zh-hant/codes/python/chapter_stack_and_queue/stack.py @@ -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) diff --git a/zh-hant/codes/python/chapter_tree/array_binary_tree.py b/zh-hant/codes/python/chapter_tree/array_binary_tree.py new file mode 100644 index 000000000..a7d272756 --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/array_binary_tree.py @@ -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) diff --git a/zh-hant/codes/python/chapter_tree/avl_tree.py b/zh-hant/codes/python/chapter_tree/avl_tree.py new file mode 100644 index 000000000..f23d00f3b --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/avl_tree.py @@ -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)) diff --git a/zh-hant/codes/python/chapter_tree/binary_search_tree.py b/zh-hant/codes/python/chapter_tree/binary_search_tree.py new file mode 100644 index 000000000..1e7ea1c69 --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/binary_search_tree.py @@ -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()) diff --git a/zh-hant/codes/python/chapter_tree/binary_tree.py b/zh-hant/codes/python/chapter_tree/binary_tree.py new file mode 100644 index 000000000..7a62f9859 --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/binary_tree.py @@ -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) diff --git a/zh-hant/codes/python/chapter_tree/binary_tree_bfs.py b/zh-hant/codes/python/chapter_tree/binary_tree_bfs.py new file mode 100644 index 000000000..58cdcc4a3 --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/binary_tree_bfs.py @@ -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) diff --git a/zh-hant/codes/python/chapter_tree/binary_tree_dfs.py b/zh-hant/codes/python/chapter_tree/binary_tree_dfs.py new file mode 100644 index 000000000..73f8f605f --- /dev/null +++ b/zh-hant/codes/python/chapter_tree/binary_tree_dfs.py @@ -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) diff --git a/zh-hant/codes/python/modules/__init__.py b/zh-hant/codes/python/modules/__init__.py new file mode 100644 index 000000000..b10799e3c --- /dev/null +++ b/zh-hant/codes/python/modules/__init__.py @@ -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, +) diff --git a/zh-hant/codes/python/modules/list_node.py b/zh-hant/codes/python/modules/list_node.py new file mode 100644 index 000000000..40a5fa458 --- /dev/null +++ b/zh-hant/codes/python/modules/list_node.py @@ -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 diff --git a/zh-hant/codes/python/modules/print_util.py b/zh-hant/codes/python/modules/print_util.py new file mode 100644 index 000000000..ff4da2213 --- /dev/null +++ b/zh-hant/codes/python/modules/print_util.py @@ -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) diff --git a/zh-hant/codes/python/modules/tree_node.py b/zh-hant/codes/python/modules/tree_node.py new file mode 100644 index 000000000..887fe7903 --- /dev/null +++ b/zh-hant/codes/python/modules/tree_node.py @@ -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 diff --git a/zh-hant/codes/python/modules/vertex.py b/zh-hant/codes/python/modules/vertex.py new file mode 100644 index 000000000..bffd3154f --- /dev/null +++ b/zh-hant/codes/python/modules/vertex.py @@ -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] diff --git a/zh-hant/codes/python/test_all.py b/zh-hant/codes/python/test_all.py new file mode 100644 index 000000000..a90c773a4 --- /dev/null +++ b/zh-hant/codes/python/test_all.py @@ -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)) diff --git a/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/array.md b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/array.md new file mode 100644 index 000000000..303c74711 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/array.md @@ -0,0 +1,23 @@ + + + +https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_access%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8F%E6%9C%BA%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5B0,%20len%28nums%29-1%5D%20%E4%B8%AD%E9%9A%8F%E6%9C%BA%E6%8A%BD%E5%8F%96%E4%B8%80%E4%B8%AA%E6%95%B0%E5%AD%97%0A%20%20%20%20random_index%20%3D%20random.randint%280,%20len%28nums%29%20-%201%29%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%B9%B6%E8%BF%94%E5%9B%9E%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0%0A%20%20%20%20random_num%20%3D%20nums%5Brandom_index%5D%0A%20%20%20%20return%20random_num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E9%9A%8F%E6%9C%BA%E8%AE%BF%E9%97%AE%0A%20%20%20%20random_num%3A%20int%20%3D%20random_access%28nums%29%0A%20%20%20%20print%28%22%E5%9C%A8%20nums%20%E4%B8%AD%E8%8E%B7%E5%8F%96%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0%22,%20random_num%29%0A&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20insert%28nums%3A%20list%5Bint%5D,%20num%3A%20int,%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E6%95%B0%E7%BB%84%E7%9A%84%E7%B4%A2%E5%BC%95%20index%20%E5%A4%84%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%20num%22%22%22%0A%20%20%20%20%23%20%E6%8A%8A%E7%B4%A2%E5%BC%95%20index%20%E4%BB%A5%E5%8F%8A%E4%B9%8B%E5%90%8E%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%91%E5%90%8E%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%20index,%20-1%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%B0%86%20num%20%E8%B5%8B%E7%BB%99%20index%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5Bindex%5D%20%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20insert%28nums,%206,%203%29%0A%20%20%20%20print%28%22%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E6%8F%92%E5%85%A5%E6%95%B0%E5%AD%97%206%20%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20remove%28nums%3A%20list%5Bint%5D,%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E6%8A%8A%E7%B4%A2%E5%BC%95%20index%20%E4%B9%8B%E5%90%8E%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%90%91%E5%89%8D%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20for%20i%20in%20range%28index,%20len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20%2B%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20remove%28nums,%202%29%0A%20%20%20%20print%28%22%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%202%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20traverse%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E7%B4%A2%E5%BC%95%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%20%20%20%20%23%20%E5%90%8C%E6%97%B6%E9%81%8D%E5%8E%86%E6%95%B0%E6%8D%AE%E7%B4%A2%E5%BC%95%E5%92%8C%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20i,%20num%20in%20enumerate%28nums%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%0A%20%20%20%20traverse%28nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20find%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E6%8C%87%E5%AE%9A%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20index%3A%20int%20%3D%20find%28nums,%203%29%0A%20%20%20%20print%28%22%E5%9C%A8%20nums%20%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%203%20%EF%BC%8C%E5%BE%97%E5%88%B0%E7%B4%A2%E5%BC%95%20%3D%22,%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8CPython%20%E7%9A%84%20list%20%E6%98%AF%E5%8A%A8%E6%80%81%E6%95%B0%E7%BB%84%EF%BC%8C%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E6%89%A9%E5%B1%95%0A%23%20%E4%B8%BA%E4%BA%86%E6%96%B9%E4%BE%BF%E5%AD%A6%E4%B9%A0%EF%BC%8C%E6%9C%AC%E5%87%BD%E6%95%B0%E5%B0%86%20list%20%E7%9C%8B%E4%BD%9C%E9%95%BF%E5%BA%A6%E4%B8%8D%E5%8F%AF%E5%8F%98%E7%9A%84%E6%95%B0%E7%BB%84%0Adef%20extend%28nums%3A%20list%5Bint%5D,%20enlarge%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%89%A9%E5%B1%95%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E4%B8%AA%E6%89%A9%E5%B1%95%E9%95%BF%E5%BA%A6%E5%90%8E%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20%28len%28nums%29%20%2B%20enlarge%29%0A%20%20%20%20%23%20%E5%B0%86%E5%8E%9F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E6%96%B0%E6%95%B0%E7%BB%84%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%89%A9%E5%B1%95%E5%90%8E%E7%9A%84%E6%96%B0%E6%95%B0%E7%BB%84%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20print%28%22%E6%95%B0%E7%BB%84%20nums%20%3D%22,%20nums%29%0A%0A%20%20%20%20%23%20%E9%95%BF%E5%BA%A6%E6%89%A9%E5%B1%95%0A%20%20%20%20nums%20%3D%20extend%28nums,%203%29%0A%20%20%20%20print%28%22%E5%B0%86%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E6%89%A9%E5%B1%95%E8%87%B3%208%20%EF%BC%8C%E5%BE%97%E5%88%B0%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md new file mode 100644 index 000000000..5637b1be8 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20insert%28n0%3A%20ListNode,%20P%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%93%BE%E8%A1%A8%E7%9A%84%E8%8A%82%E7%82%B9%20n0%20%E4%B9%8B%E5%90%8E%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%22%22%22%0A%20%20%20%20n1%20%3D%20n0.next%0A%20%20%20%20P.next%20%3D%20n1%0A%20%20%20%20n0.next%20%3D%20P%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20ListNode%280%29%0A%20%20%20%20insert%28n0,%20p%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20remove%28n0%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E8%8A%82%E7%82%B9%20n0%20%E4%B9%8B%E5%90%8E%E7%9A%84%E9%A6%96%E4%B8%AA%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20if%20not%20n0.next%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20n0%20-%3E%20P%20-%3E%20n1%0A%20%20%20%20P%20%3D%20n0.next%0A%20%20%20%20n1%20%3D%20P.next%0A%20%20%20%20n0.next%20%3D%20n1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20remove%28n0%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20access%28head%3A%20ListNode,%20index%3A%20int%29%20-%3E%20ListNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%B4%A2%E5%BC%95%E4%B8%BA%20index%20%E7%9A%84%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20for%20_%20in%20range%28index%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20head%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20return%20head%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E8%8A%82%E7%82%B9%0A%20%20%20%20node%20%3D%20access%28n0,%203%29%0A%20%20%20%20print%28%22%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E7%9A%84%E8%8A%82%E7%82%B9%E7%9A%84%E5%80%BC%20%3D%20%7B%7D%22.format%28node.val%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20find%28head%3A%20ListNode,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9C%A8%E9%93%BE%E8%A1%A8%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%80%BC%E4%B8%BA%20target%20%E7%9A%84%E9%A6%96%E4%B8%AA%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20index%20%3D%200%0A%20%20%20%20while%20head%3A%0A%20%20%20%20%20%20%20%20if%20head.val%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20index%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20%20%20%20%20index%20%2B%3D%201%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%0A%20%20%20%20index%20%3D%20find%28n0,%202%29%0A%20%20%20%20print%28%22%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%BC%E4%B8%BA%202%20%E7%9A%84%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%7B%7D%22.format%28index%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/my_list.md b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/my_list.md new file mode 100644 index 000000000..ef3d6f132 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_array_and_linkedlist/my_list.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20MyList%3A%0A%20%20%20%20%22%22%22%E5%88%97%E8%A1%A8%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._capacity%3A%20int%20%3D%2010%0A%20%20%20%20%20%20%20%20self._arr%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20*%20self._capacity%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._extend_ratio%3A%20int%20%3D%202%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%88%97%E8%A1%A8%E9%95%BF%E5%BA%A6%EF%BC%88%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E6%95%B0%E9%87%8F%EF%BC%89%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%88%97%E8%A1%A8%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._capacity%0A%0A%20%20%20%20def%20get%28self,%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20return%20self._arr%5Bindex%5D%0A%0A%20%20%20%20def%20set%28self,%20num%3A%20int,%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%0A%20%20%20%20def%20add%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.size%28%29%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20self._arr%5Bself._size%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int,%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E7%B4%A2%E5%BC%95%20index%20%E4%BB%A5%E5%8F%8A%E4%B9%8B%E5%90%8E%E7%9A%84%E5%85%83%E7%B4%A0%E9%83%BD%E5%90%91%E5%90%8E%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28self._size%20-%201,%20index%20-%201,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%20%2B%201%5D%20%3D%20self._arr%5Bj%5D%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self,%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%22%29%0A%20%20%20%20%20%20%20%20num%20%3D%20self._arr%5Bindex%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%20i%20%E4%B9%8B%E5%90%8E%E7%9A%84%E5%85%83%E7%B4%A0%E9%83%BD%E5%90%91%E5%89%8D%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28index,%20self._size%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%5D%20%3D%20self._arr%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20extend_capacity%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%97%E8%A1%A8%E6%89%A9%E5%AE%B9%22%22%22%0A%20%20%20%20%20%20%20%20self._arr%20%3D%20self._arr%20%2B%20%5B0%5D%20*%20self.capacity%28%29%20*%20%28self._extend_ratio%20-%201%29%0A%20%20%20%20%20%20%20%20self._capacity%20%3D%20len%28self._arr%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20MyList%28%29%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.add%281%29%0A%20%20%20%20nums.add%283%29%0A%20%20%20%20nums.add%282%29%0A%20%20%20%20nums.add%285%29%0A%20%20%20%20nums.add%284%29%0A%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%286,%20index%3D3%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.remove%283%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums.get%281%29%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.set%280,%201%29%0A%0A%20%20%20%20%23%20%E6%B5%8B%E8%AF%95%E6%89%A9%E5%AE%B9%E6%9C%BA%E5%88%B6%0A%20%20%20%20for%20i%20in%20range%2810%29%3A%0A%20%20%20%20%20%20%20%20nums.add%28i%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/n_queens.md b/zh-hant/codes/pythontutor/chapter_backtracking/n_queens.md new file mode 100644 index 000000000..cf72b190c --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/n_queens.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20row%3A%20int,%0A%20%20%20%20n%3A%20int,%0A%20%20%20%20state%3A%20list%5Blist%5Bstr%5D%5D,%0A%20%20%20%20res%3A%20list%5Blist%5Blist%5Bstr%5D%5D%5D,%0A%20%20%20%20cols%3A%20list%5Bbool%5D,%0A%20%20%20%20diags1%3A%20list%5Bbool%5D,%0A%20%20%20%20diags2%3A%20list%5Bbool%5D,%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9AN%20%E7%9A%87%E5%90%8E%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E6%94%BE%E7%BD%AE%E5%AE%8C%E6%89%80%E6%9C%89%E8%A1%8C%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20row%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res.append%28%5Blist%28row%29%20for%20row%20in%20state%5D%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E5%88%97%0A%20%20%20%20for%20col%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E8%AF%A5%E6%A0%BC%E5%AD%90%E5%AF%B9%E5%BA%94%E7%9A%84%E4%B8%BB%E5%AF%B9%E8%A7%92%E7%BA%BF%E5%92%8C%E6%AC%A1%E5%AF%B9%E8%A7%92%E7%BA%BF%0A%20%20%20%20%20%20%20%20diag1%20%3D%20row%20-%20col%20%2B%20n%20-%201%0A%20%20%20%20%20%20%20%20diag2%20%3D%20row%20%2B%20col%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E8%AF%A5%E6%A0%BC%E5%AD%90%E6%89%80%E5%9C%A8%E5%88%97%E3%80%81%E4%B8%BB%E5%AF%B9%E8%A7%92%E7%BA%BF%E3%80%81%E6%AC%A1%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E5%AD%98%E5%9C%A8%E7%9A%87%E5%90%8E%0A%20%20%20%20%20%20%20%20if%20not%20cols%5Bcol%5D%20and%20not%20diags1%5Bdiag1%5D%20and%20not%20diags2%5Bdiag2%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%B0%86%E7%9A%87%E5%90%8E%E6%94%BE%E7%BD%AE%E5%9C%A8%E8%AF%A5%E6%A0%BC%E5%AD%90%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22Q%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%94%BE%E7%BD%AE%E4%B8%8B%E4%B8%80%E8%A1%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28row%20%2B%201,%20n,%20state,%20res,%20cols,%20diags1,%20diags2%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E5%B0%86%E8%AF%A5%E6%A0%BC%E5%AD%90%E6%81%A2%E5%A4%8D%E4%B8%BA%E7%A9%BA%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22%23%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20False%0A%0A%0Adef%20n_queens%28n%3A%20int%29%20-%3E%20list%5Blist%5Blist%5Bstr%5D%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%20N%20%E7%9A%87%E5%90%8E%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20n*n%20%E5%A4%A7%E5%B0%8F%E7%9A%84%E6%A3%8B%E7%9B%98%EF%BC%8C%E5%85%B6%E4%B8%AD%20'Q'%20%E4%BB%A3%E8%A1%A8%E7%9A%87%E5%90%8E%EF%BC%8C'%23'%20%E4%BB%A3%E8%A1%A8%E7%A9%BA%E4%BD%8D%0A%20%20%20%20state%20%3D%20%5B%5B%22%23%22%20for%20_%20in%20range%28n%29%5D%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20cols%20%3D%20%5BFalse%5D%20*%20n%20%20%23%20%E8%AE%B0%E5%BD%95%E5%88%97%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20diags1%20%3D%20%5BFalse%5D%20*%20%282%20*%20n%20-%201%29%20%20%23%20%E8%AE%B0%E5%BD%95%E4%B8%BB%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20diags2%20%3D%20%5BFalse%5D%20*%20%282%20*%20n%20-%201%29%20%20%23%20%E8%AE%B0%E5%BD%95%E6%AC%A1%E5%AF%B9%E8%A7%92%E7%BA%BF%E4%B8%8A%E6%98%AF%E5%90%A6%E6%9C%89%E7%9A%87%E5%90%8E%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%280,%20n,%20state,%20res,%20cols,%20diags1,%20diags2%29%0A%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20res%20%3D%20n_queens%28n%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%A3%8B%E7%9B%98%E9%95%BF%E5%AE%BD%E4%B8%BA%20%7Bn%7D%22%29%0A%20%20%20%20print%28f%22%E7%9A%87%E5%90%8E%E6%94%BE%E7%BD%AE%E6%96%B9%E6%A1%88%E5%85%B1%E6%9C%89%20%7Blen%28res%29%7D%20%E7%A7%8D%22%29%0A%20%20%20%20for%20state%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%22--------------------%22%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20state%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28row%29&cumulative=false&curInstr=61&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/permutations_i.md b/zh-hant/codes/pythontutor/chapter_backtracking/permutations_i.md new file mode 100644 index 000000000..7f283e8b8 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/permutations_i.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20choices%3A%20list%5Bint%5D,%20selected%3A%20list%5Bbool%5D,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%85%A8%E6%8E%92%E5%88%97%20I%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E7%8A%B6%E6%80%81%E9%95%BF%E5%BA%A6%E7%AD%89%E4%BA%8E%E5%85%83%E7%B4%A0%E6%95%B0%E9%87%8F%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20i,%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state,%20choices,%20selected,%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_i%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E6%8E%92%E5%88%97%20I%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D,%20choices%3Dnums,%20selected%3D%5BFalse%5D%20*%20len%28nums%29,%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%202,%203%5D%0A%0A%20%20%20%20res%20%3D%20permutations_i%28nums%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/permutations_ii.md b/zh-hant/codes/pythontutor/chapter_backtracking/permutations_ii.md new file mode 100644 index 000000000..e02bad4b9 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/permutations_ii.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20choices%3A%20list%5Bint%5D,%20selected%3A%20list%5Bbool%5D,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%85%A8%E6%8E%92%E5%88%97%20II%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E7%8A%B6%E6%80%81%E9%95%BF%E5%BA%A6%E7%AD%89%E4%BA%8E%E5%85%83%E7%B4%A0%E6%95%B0%E9%87%8F%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i,%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%85%83%E7%B4%A0%20%E4%B8%94%20%E4%B8%8D%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E7%9B%B8%E7%AD%89%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%E8%AE%B0%E5%BD%95%E9%80%89%E6%8B%A9%E8%BF%87%E7%9A%84%E5%85%83%E7%B4%A0%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state,%20choices,%20selected,%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E6%8E%92%E5%88%97%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D,%20choices%3Dnums,%20selected%3D%5BFalse%5D%20*%20len%28nums%29,%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%202,%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E6%8E%92%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md new file mode 100644 index 000000000..c6d771a33 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%B8%80%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28root%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E5%80%BC%E4%B8%BA%207%20%E7%9A%84%E8%8A%82%E7%82%B9%22%29%0A%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20res%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md new file mode 100644 index 000000000..2bfff0898 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%BA%8C%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E8%8A%82%E7%82%B9%207%20%E7%9A%84%E8%B7%AF%E5%BE%84%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md new file mode 100644 index 000000000..1d5202206 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%B8%89%22%22%22%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%0A%20%20%20%20if%20root%20is%20None%20or%20root.val%20%3D%3D%203%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%20%20%20%20path.pop%28%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%B9%E8%8A%82%E7%82%B9%E5%88%B0%E8%8A%82%E7%82%B9%207%20%E7%9A%84%E8%B7%AF%E5%BE%84%EF%BC%8C%E8%B7%AF%E5%BE%84%E4%B8%AD%E4%B8%8D%E5%8C%85%E5%90%AB%E5%80%BC%E4%B8%BA%203%20%E7%9A%84%E8%8A%82%E7%82%B9%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md new file mode 100644 index 000000000..f9632b251 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20is_solution%28state%3A%20list%5BTreeNode%5D%29%20-%3E%20bool%3A%0A%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%BD%93%E5%89%8D%E7%8A%B6%E6%80%81%E6%98%AF%E5%90%A6%E4%B8%BA%E8%A7%A3%22%22%22%0A%20%20%20%20return%20state%20and%20state%5B-1%5D.val%20%3D%3D%207%0A%0Adef%20record_solution%28state%3A%20list%5BTreeNode%5D,%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%B0%E5%BD%95%E8%A7%A3%22%22%22%0A%20%20%20%20res.append%28list%28state%29%29%0A%0Adef%20is_valid%28state%3A%20list%5BTreeNode%5D,%20choice%3A%20TreeNode%29%20-%3E%20bool%3A%0A%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%9C%A8%E5%BD%93%E5%89%8D%E7%8A%B6%E6%80%81%E4%B8%8B%EF%BC%8C%E8%AF%A5%E9%80%89%E6%8B%A9%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%22%22%22%0A%20%20%20%20return%20choice%20is%20not%20None%20and%20choice.val%20!%3D%203%0A%0Adef%20make_choice%28state%3A%20list%5BTreeNode%5D,%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%22%22%22%0A%20%20%20%20state.append%28choice%29%0A%0Adef%20undo_choice%28state%3A%20list%5BTreeNode%5D,%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E6%81%A2%E5%A4%8D%E7%8A%B6%E6%80%81%22%22%22%0A%20%20%20%20state.pop%28%29%0A%0Adef%20backtrack%28%0A%20%20%20%20state%3A%20list%5BTreeNode%5D,%20choices%3A%20list%5BTreeNode%5D,%20res%3A%20list%5Blist%5BTreeNode%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E4%BE%8B%E9%A2%98%E4%B8%89%22%22%22%0A%20%20%20%20%23%20%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E4%B8%BA%E8%A7%A3%0A%20%20%20%20if%20is_solution%28state%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20%20%20%20%20record_solution%28state,%20res%29%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E6%A3%80%E6%9F%A5%E9%80%89%E6%8B%A9%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%0A%20%20%20%20%20%20%20%20if%20is_valid%28state,%20choice%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20make_choice%28state,%20choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state,%20%5Bchoice.left,%20choice.right%5D,%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20%20%20%20%20undo_choice%28state,%20choice%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1,%207,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D,%20choices%3D%5Broot%5D,%20res%3Dres%29%0A%20%20%20%20print%28%22%5Cn%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%84%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=138&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i.md b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i.md new file mode 100644 index 000000000..5a4d9e06a --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20target%3A%20int,%20choices%3A%20list%5Bint%5D,%20start%3A%20int,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E4%BA%8E%20target%20%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%BA%8C%EF%BC%9A%E4%BB%8E%20start%20%E5%BC%80%E5%A7%8B%E9%81%8D%E5%8E%86%EF%BC%8C%E9%81%BF%E5%85%8D%E7%94%9F%E6%88%90%E9%87%8D%E5%A4%8D%E5%AD%90%E9%9B%86%0A%20%20%20%20for%20i%20in%20range%28start,%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%80%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E8%BF%87%20target%20%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E7%BB%93%E6%9D%9F%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%99%E6%98%AF%E5%9B%A0%E4%B8%BA%E6%95%B0%E7%BB%84%E5%B7%B2%E6%8E%92%E5%BA%8F%EF%BC%8C%E5%90%8E%E8%BE%B9%E5%85%83%E7%B4%A0%E6%9B%B4%E5%A4%A7%EF%BC%8C%E5%AD%90%E9%9B%86%E5%92%8C%E4%B8%80%E5%AE%9A%E8%B6%85%E8%BF%87%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%20target,%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20backtrack%28state,%20target%20-%20choices%5Bi%5D,%20choices,%20i,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%80%81%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E5%AF%B9%20nums%20%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20start%20%3D%200%20%20%23%20%E9%81%8D%E5%8E%86%E8%B5%B7%E5%A7%8B%E7%82%B9%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%BB%93%E6%9E%9C%E5%88%97%E8%A1%A8%EF%BC%88%E5%AD%90%E9%9B%86%E5%88%97%E8%A1%A8%EF%BC%89%0A%20%20%20%20backtrack%28state,%20target,%20nums,%20start,%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3,%204,%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i%28nums,%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D,%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E4%BA%8E%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md new file mode 100644 index 000000000..d073acce7 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%0A%20%20%20%20target%3A%20int,%0A%20%20%20%20total%3A%20int,%0A%20%20%20%20choices%3A%20list%5Bint%5D,%0A%20%20%20%20res%3A%20list%5Blist%5Bint%5D%5D,%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E4%BA%8E%20target%20%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20total%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20i%20in%20range%28len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E8%BF%87%20target%20%EF%BC%8C%E5%88%99%E8%B7%B3%E8%BF%87%E8%AF%A5%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20if%20total%20%2B%20choices%5Bi%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%E5%92%8C%20total%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20backtrack%28state,%20target,%20total%20%2B%20choices%5Bi%5D,%20choices,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i_naive%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20I%EF%BC%88%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%AD%90%E9%9B%86%EF%BC%89%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%80%81%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20total%20%3D%200%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%BB%93%E6%9E%9C%E5%88%97%E8%A1%A8%EF%BC%88%E5%AD%90%E9%9B%86%E5%88%97%E8%A1%A8%EF%BC%89%0A%20%20%20%20backtrack%28state,%20target,%20total,%20nums,%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3,%204,%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i_naive%28nums,%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D,%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E4%BA%8E%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%AF%A5%E6%96%B9%E6%B3%95%E8%BE%93%E5%87%BA%E7%9A%84%E7%BB%93%E6%9E%9C%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E9%9B%86%E5%90%88%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_ii.md b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_ii.md new file mode 100644 index 000000000..2645241c0 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_backtracking/subset_sum_ii.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D,%20target%3A%20int,%20choices%3A%20list%5Bint%5D,%20start%3A%20int,%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%EF%BC%9A%E5%AD%90%E9%9B%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E9%9B%86%E5%92%8C%E7%AD%89%E4%BA%8E%20target%20%E6%97%B6%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%A7%A3%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%BA%8C%EF%BC%9A%E4%BB%8E%20start%20%E5%BC%80%E5%A7%8B%E9%81%8D%E5%8E%86%EF%BC%8C%E9%81%BF%E5%85%8D%E7%94%9F%E6%88%90%E9%87%8D%E5%A4%8D%E5%AD%90%E9%9B%86%0A%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%89%EF%BC%9A%E4%BB%8E%20start%20%E5%BC%80%E5%A7%8B%E9%81%8D%E5%8E%86%EF%BC%8C%E9%81%BF%E5%85%8D%E9%87%8D%E5%A4%8D%E9%80%89%E6%8B%A9%E5%90%8C%E4%B8%80%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20i%20in%20range%28start,%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E4%B8%80%EF%BC%9A%E8%8B%A5%E5%AD%90%E9%9B%86%E5%92%8C%E8%B6%85%E8%BF%87%20target%20%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E7%BB%93%E6%9D%9F%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%99%E6%98%AF%E5%9B%A0%E4%B8%BA%E6%95%B0%E7%BB%84%E5%B7%B2%E6%8E%92%E5%BA%8F%EF%BC%8C%E5%90%8E%E8%BE%B9%E5%85%83%E7%B4%A0%E6%9B%B4%E5%A4%A7%EF%BC%8C%E5%AD%90%E9%9B%86%E5%92%8C%E4%B8%80%E5%AE%9A%E8%B6%85%E8%BF%87%20target%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%E5%9B%9B%EF%BC%9A%E5%A6%82%E6%9E%9C%E8%AF%A5%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B7%A6%E8%BE%B9%E5%85%83%E7%B4%A0%E7%9B%B8%E7%AD%89%EF%BC%8C%E8%AF%B4%E6%98%8E%E8%AF%A5%E6%90%9C%E7%B4%A2%E5%88%86%E6%94%AF%E9%87%8D%E5%A4%8D%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%0A%20%20%20%20%20%20%20%20if%20i%20%3E%20start%20and%20choices%5Bi%5D%20%3D%3D%20choices%5Bi%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%20target,%20start%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%9B%E8%A1%8C%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%80%89%E6%8B%A9%0A%20%20%20%20%20%20%20%20backtrack%28state,%20target%20-%20choices%5Bi%5D,%20choices,%20i%20%2B%201,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%EF%BC%9A%E6%92%A4%E9%94%80%E9%80%89%E6%8B%A9%EF%BC%8C%E6%81%A2%E5%A4%8D%E5%88%B0%E4%B9%8B%E5%89%8D%E7%9A%84%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_ii%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E5%AD%90%E9%9B%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%80%81%EF%BC%88%E5%AD%90%E9%9B%86%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E5%AF%B9%20nums%20%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20start%20%3D%200%20%20%23%20%E9%81%8D%E5%8E%86%E8%B5%B7%E5%A7%8B%E7%82%B9%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%BB%93%E6%9E%9C%E5%88%97%E8%A1%A8%EF%BC%88%E5%AD%90%E9%9B%86%E5%88%97%E8%A1%A8%EF%BC%89%0A%20%20%20%20backtrack%28state,%20target,%20nums,%20start,%20res%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%204,%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_ii%28nums,%20target%29%0A%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%20nums%20%3D%20%7Bnums%7D,%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E6%89%80%E6%9C%89%E5%92%8C%E7%AD%89%E4%BA%8E%20%7Btarget%7D%20%E7%9A%84%E5%AD%90%E9%9B%86%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_computational_complexity/iteration.md b/zh-hant/codes/pythontutor/chapter_computational_complexity/iteration.md new file mode 100644 index 000000000..d743e333e --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_computational_complexity/iteration.md @@ -0,0 +1,29 @@ + + + +https://pythontutor.com/render.html#code=def%20for_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22for%20%E5%BE%AA%E7%8E%AF%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%B1%82%E5%92%8C%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnfor%20%E5%BE%AA%E7%8E%AF%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D& + + +https://pythontutor.com/render.html#code=def%20while_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E5%BE%AA%E7%8E%AF%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%B1%82%E5%92%8C%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E5%BE%AA%E7%8E%AF%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20while_loop_ii%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E5%BE%AA%E7%8E%AF%EF%BC%88%E4%B8%A4%E6%AC%A1%E6%9B%B4%E6%96%B0%EF%BC%89%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%B1%82%E5%92%8C%201,%204,%2010,%20...%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20i%20*%3D%202%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop_ii%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E5%BE%AA%E7%8E%AF%EF%BC%88%E4%B8%A4%E6%AC%A1%E6%9B%B4%E6%96%B0%EF%BC%89%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20nested_for_loop%28n%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%22%22%22%E5%8F%8C%E5%B1%82%20for%20%E5%BE%AA%E7%8E%AF%22%22%22%0A%20%20%20%20res%20%3D%20%22%22%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%20i%20%3D%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%20j%20%3D%201,%202,%20...,%20n-1,%20n%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20f%22%28%7Bi%7D,%20%7Bj%7D%29,%20%22%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20nested_for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%8F%8C%E5%B1%82%20for%20%E5%BE%AA%E7%8E%AF%E7%9A%84%E9%81%8D%E5%8E%86%E7%BB%93%E6%9E%9C%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20tail_recur%28n,%20res%29%3A%0A%20%20%20%20%22%22%22%E5%B0%BE%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E5%B0%BE%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20return%20tail_recur%28n%20-%201,%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n,%200%29%0A%20%20%20%20print%28f%22%5Cn%E5%B0%BE%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%20f%281%29%20%3D%200,%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%E7%9A%84%E7%AC%AC%20%7Bn%7D%20%E9%A1%B9%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E4%B8%80%E4%B8%AA%E6%98%BE%E5%BC%8F%E7%9A%84%E6%A0%88%E6%9D%A5%E6%A8%A1%E6%8B%9F%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E6%A0%88%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20for%20i%20in%20range%28n,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%85%A5%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E9%80%92%E2%80%9D%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%87%BA%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E5%BD%92%E2%80%9D%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_computational_complexity/recursion.md b/zh-hant/codes/pythontutor/chapter_computational_complexity/recursion.md new file mode 100644 index 000000000..c3bfa730d --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_computational_complexity/recursion.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20return%20n%20%2B%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20tail_recur%28n,%20res%29%3A%0A%20%20%20%20%22%22%22%E5%B0%BE%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E5%B0%BE%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20return%20tail_recur%28n%20-%201,%20res%20%2B%20n%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n,%200%29%0A%20%20%20%20print%28f%22%5Cn%E5%B0%BE%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%E7%9A%84%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%20f%281%29%20%3D%200,%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%20f%28n%29%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%E7%9A%84%E7%AC%AC%20%7Bn%7D%20%E9%A1%B9%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E4%B8%80%E4%B8%AA%E6%98%BE%E5%BC%8F%E7%9A%84%E6%A0%88%E6%9D%A5%E6%A8%A1%E6%8B%9F%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E6%A0%88%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E9%80%92%EF%BC%9A%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A8%0A%20%20%20%20for%20i%20in%20range%28n,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%85%A5%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E9%80%92%E2%80%9D%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E5%BD%92%EF%BC%9A%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E2%80%9C%E5%87%BA%E6%A0%88%E6%93%8D%E4%BD%9C%E2%80%9D%E6%A8%A1%E6%8B%9F%E2%80%9C%E5%BD%92%E2%80%9D%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E6%A8%A1%E6%8B%9F%E9%80%92%E5%BD%92%E6%B1%82%E5%92%8C%E7%BB%93%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_computational_complexity/space_complexity.md b/zh-hant/codes/pythontutor/chapter_computational_complexity/space_complexity.md new file mode 100644 index 000000000..11697c5e0 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_computational_complexity/space_complexity.md @@ -0,0 +1,23 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20function%28%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20%23%20%E6%89%A7%E8%A1%8C%E6%9F%90%E4%BA%9B%E6%93%8D%E4%BD%9C%0A%20%20%20%20return%200%0A%0Adef%20constant%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B8%B8%E6%95%B0%E9%98%B6%22%22%22%0A%20%20%20%20%23%20%E5%B8%B8%E9%87%8F%E3%80%81%E5%8F%98%E9%87%8F%E3%80%81%E5%AF%B9%E8%B1%A1%E5%8D%A0%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20a%20%3D%200%0A%20%20%20%20nums%20%3D%20%5B0%5D%20*%2010%0A%20%20%20%20node%20%3D%20ListNode%280%29%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E4%B8%AD%E7%9A%84%E5%8F%98%E9%87%8F%E5%8D%A0%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20c%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E4%B8%AD%E7%9A%84%E5%87%BD%E6%95%B0%E5%8D%A0%E7%94%A8%20O%281%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20function%28%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E5%B8%B8%E6%95%B0%E9%98%B6%0A%20%20%20%20constant%28n%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%22%22%22%0A%20%20%20%20%23%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8D%A0%E7%94%A8%20O%28n%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20nums%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20%23%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%E7%9A%84%E5%93%88%E5%B8%8C%E8%A1%A8%E5%8D%A0%E7%94%A8%20O%28n%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20hmap%20%3D%20dict%5Bint,%20str%5D%28%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20hmap%5Bi%5D%20%3D%20str%28i%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E7%BA%BF%E6%80%A7%E9%98%B6%0A%20%20%20%20linear%28n%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear_recur%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20print%28%22%E9%80%92%E5%BD%92%20n%20%3D%22,%20n%29%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20linear_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E7%BA%BF%E6%80%A7%E9%98%B6%0A%20%20%20%20linear_recur%28n%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%22%22%22%0A%20%20%20%20%23%20%E4%BA%8C%E7%BB%B4%E5%88%97%E8%A1%A8%E5%8D%A0%E7%94%A8%20O%28n%5E2%29%20%E7%A9%BA%E9%97%B4%0A%20%20%20%20num_matrix%20%3D%20%5B%5B0%5D%20*%20n%20for%20_%20in%20range%28n%29%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E5%B9%B3%E6%96%B9%E9%98%B6%0A%20%20%20%20quadratic%28n%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E6%95%B0%E7%BB%84%20nums%20%E9%95%BF%E5%BA%A6%E4%B8%BA%20n,%20n-1,%20...,%202,%201%0A%20%20%20%20nums%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20return%20quadratic_recur%28n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E5%B9%B3%E6%96%B9%E9%98%B6%0A%20%20%20%20quadratic_recur%28n%29&cumulative=false&curInstr=28&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20build_tree%28n%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E5%BB%BA%E7%AB%8B%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%280%29%0A%20%20%20%20root.left%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20root.right%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20return%20root%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20%23%20%E6%8C%87%E6%95%B0%E9%98%B6%0A%20%20%20%20root%20%3D%20build_tree%28n%29&cumulative=false&curInstr=507&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_computational_complexity/time_complexity.md b/zh-hant/codes/pythontutor/chapter_computational_complexity/time_complexity.md new file mode 100644 index 000000000..d32a48260 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_computational_complexity/time_complexity.md @@ -0,0 +1,38 @@ + + + +https://pythontutor.com/render.html#code=def%20constant%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B8%B8%E6%95%B0%E9%98%B6%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20size%20%3D%2010%0A%20%20%20%20for%20_%20in%20range%28size%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20constant%28n%29%0A%20%20%20%20print%28%22%E5%B8%B8%E6%95%B0%E9%98%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20linear%28n%29%0A%20%20%20%20print%28%22%E7%BA%BF%E6%80%A7%E9%98%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20array_traversal%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E9%98%B6%EF%BC%88%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%AC%A1%E6%95%B0%E4%B8%8E%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E6%88%90%E6%AD%A3%E6%AF%94%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20array_traversal%28%5B0%5D%20*%20n%29%0A%20%20%20%20print%28%22%E7%BA%BF%E6%80%A7%E9%98%B6%EF%BC%88%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%AC%A1%E6%95%B0%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%E6%88%90%E5%B9%B3%E6%96%B9%E5%85%B3%E7%B3%BB%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20quadratic%28n%29%0A%20%20%20%20print%28%22%E5%B9%B3%E6%96%B9%E9%98%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B9%B3%E6%96%B9%E9%98%B6%EF%BC%88%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%20%20%23%20%E8%AE%A1%E6%95%B0%E5%99%A8%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%20%3D%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%203%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E5%8C%85%E5%90%AB%203%20%E4%B8%AA%E5%8D%95%E5%85%83%E6%93%8D%E4%BD%9C%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%28n,%200,%20-1%29%5D%20%20%23%20%5Bn,%20n-1,%20...,%202,%201%5D%0A%20%20%20%20count%20%3D%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E5%B9%B3%E6%96%B9%E9%98%B6%EF%BC%88%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20exponential%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20base%20%3D%201%0A%20%20%20%20%23%20%E7%BB%86%E8%83%9E%E6%AF%8F%E8%BD%AE%E4%B8%80%E5%88%86%E4%B8%BA%E4%BA%8C%EF%BC%8C%E5%BD%A2%E6%88%90%E6%95%B0%E5%88%97%201,%202,%204,%208,%20...,%202%5E%28n-1%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28base%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%20%20%20%20base%20*%3D%202%0A%20%20%20%20%23%20count%20%3D%201%20%2B%202%20%2B%204%20%2B%208%20%2B%20..%20%2B%202%5E%28n-1%29%20%3D%202%5En%20-%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20exponential%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20exp_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20exp_recur%28n%20-%201%29%20%2B%20exp_recur%28n%20-%201%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%207%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20exp_recur%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20logarithmic%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20while%20n%20%3E%201%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20n%20/%202%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20logarithmic%28n%29%0A%20%20%20%20print%28%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E5%BE%AA%E7%8E%AF%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20return%20log_recur%28n%20/%202%29%20%2B%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20log_recur%28n%29%0A%20%20%20%20print%28%22%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear_log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BA%BF%E6%80%A7%E5%AF%B9%E6%95%B0%E9%98%B6%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%20//%202%29%20%2B%20linear_log_recur%28n%20//%202%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%29%0A%20%20%20%20print%28%22%E7%BA%BF%E6%80%A7%E5%AF%B9%E6%95%B0%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20factorial_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%98%B6%E4%B9%98%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E4%BB%8E%201%20%E4%B8%AA%E5%88%86%E8%A3%82%E5%87%BA%20n%20%E4%B8%AA%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20factorial_recur%28n%20-%201%29%0A%20%20%20%20return%20count%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20print%28%22%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B0%8F%20n%20%3D%22,%20n%29%0A%0A%20%20%20%20count%20%3D%20factorial_recur%28n%29%0A%20%20%20%20print%28%22%E9%98%B6%E4%B9%98%E9%98%B6%EF%BC%88%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0%EF%BC%89%E7%9A%84%E6%93%8D%E4%BD%9C%E6%95%B0%E9%87%8F%20%3D%22,%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md b/zh-hant/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md new file mode 100644 index 000000000..bc3420963 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_numbers%28n%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E7%94%9F%E6%88%90%E4%B8%80%E4%B8%AA%E6%95%B0%E7%BB%84%EF%BC%8C%E5%85%83%E7%B4%A0%E4%B8%BA%3A%201,%202,%20...,%20n%20%EF%BC%8C%E9%A1%BA%E5%BA%8F%E8%A2%AB%E6%89%93%E4%B9%B1%22%22%22%0A%20%20%20%20%23%20%E7%94%9F%E6%88%90%E6%95%B0%E7%BB%84%20nums%20%3D%3A%201,%202,%203,%20...,%20n%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%281,%20n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E9%9A%8F%E6%9C%BA%E6%89%93%E4%B9%B1%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%0A%20%20%20%20random.shuffle%28nums%29%0A%20%20%20%20return%20nums%0A%0Adef%20find_one%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9F%A5%E6%89%BE%E6%95%B0%E7%BB%84%20nums%20%E4%B8%AD%E6%95%B0%E5%AD%97%201%20%E6%89%80%E5%9C%A8%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E5%85%83%E7%B4%A0%201%20%E5%9C%A8%E6%95%B0%E7%BB%84%E5%A4%B4%E9%83%A8%E6%97%B6%EF%BC%8C%E8%BE%BE%E5%88%B0%E6%9C%80%E4%BD%B3%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%20O%281%29%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E5%85%83%E7%B4%A0%201%20%E5%9C%A8%E6%95%B0%E7%BB%84%E5%B0%BE%E9%83%A8%E6%97%B6%EF%BC%8C%E8%BE%BE%E5%88%B0%E6%9C%80%E5%B7%AE%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%20O%28n%29%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2010%0A%20%20%20%20nums%20%3D%20random_numbers%28n%29%0A%20%20%20%20index%20%3D%20find_one%28nums%29%0A%20%20%20%20print%28%22%5Cn%E6%95%B0%E7%BB%84%20%5B%201,%202,%20...,%20n%20%5D%20%E8%A2%AB%E6%89%93%E4%B9%B1%E5%90%8E%20%3D%22,%20nums%29%0A%20%20%20%20print%28%22%E6%95%B0%E5%AD%97%201%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%22,%20index%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md new file mode 100644 index 000000000..193abf62e --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28nums%3A%20list%5Bint%5D,%20target%3A%20int,%20i%3A%20int,%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%9A%E9%97%AE%E9%A2%98%20f%28i,%20j%29%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%EF%BC%8C%E4%BB%A3%E8%A1%A8%E6%97%A0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20i%20%3E%20j%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%0A%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%AD%90%E9%97%AE%E9%A2%98%20f%28m%2B1,%20j%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums,%20target,%20m%20%2B%201,%20j%29%0A%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%AD%90%E9%97%AE%E9%A2%98%20f%28i,%20m-1%29%0A%20%20%20%20%20%20%20%20return%20dfs%28nums,%20target,%20i,%20m%20-%201%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20return%20m%0A%0Adef%20binary_search%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E6%B1%82%E8%A7%A3%E9%97%AE%E9%A2%98%20f%280,%20n-1%29%0A%20%20%20%20return%20dfs%28nums,%20target,%200,%20n%20-%201%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums,%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22,%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_divide_and_conquer/build_tree.md b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/build_tree.md new file mode 100644 index 000000000..6863dee12 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/build_tree.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20dfs%28%0A%20%20%20%20preorder%3A%20list%5Bint%5D,%0A%20%20%20%20inorder_map%3A%20dict%5Bint,%20int%5D,%0A%20%20%20%20i%3A%20int,%0A%20%20%20%20l%3A%20int,%0A%20%20%20%20r%3A%20int,%0A%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%9E%84%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E5%88%86%E6%B2%BB%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%A0%91%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%E6%97%B6%E7%BB%88%E6%AD%A2%0A%20%20%20%20if%20r%20-%20l%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28preorder%5Bi%5D%29%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%20m%20%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%88%92%E5%88%86%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20m%20%3D%20inorder_map%5Bpreorder%5Bi%5D%5D%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%EF%BC%9A%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20dfs%28preorder,%20inorder_map,%20i%20%2B%201,%20l,%20m%20-%201%29%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%EF%BC%9A%E6%9E%84%E5%BB%BA%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.right%20%3D%20dfs%28preorder,%20inorder_map,%20i%20%2B%201%20%2B%20m%20-%20l,%20m%20%2B%201,%20r%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20return%20root%0A%0A%0Adef%20build_tree%28preorder%3A%20list%5Bint%5D,%20inorder%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%9E%84%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E5%AD%98%E5%82%A8%20inorder%20%E5%85%83%E7%B4%A0%E5%88%B0%E7%B4%A2%E5%BC%95%E7%9A%84%E6%98%A0%E5%B0%84%0A%20%20%20%20inorder_map%20%3D%20%7Bval%3A%20i%20for%20i,%20val%20in%20enumerate%28inorder%29%7D%0A%20%20%20%20root%20%3D%20dfs%28preorder,%20inorder_map,%200,%200,%20len%28inorder%29%20-%201%29%0A%20%20%20%20return%20root%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20preorder%20%3D%20%5B3,%209,%202,%201,%207%5D%0A%20%20%20%20inorder%20%3D%20%5B9,%203,%201,%202,%207%5D%0A%20%20%20%20print%28f%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%20%3D%20%7Bpreorder%7D%22%29%0A%20%20%20%20print%28f%22%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%20%3D%20%7Binorder%7D%22%29%0A%20%20%20%20root%20%3D%20build_tree%28preorder,%20inorder%29&cumulative=false&curInstr=21&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_divide_and_conquer/hanota.md b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/hanota.md new file mode 100644 index 000000000..755cb9ebc --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_divide_and_conquer/hanota.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20move%28src%3A%20list%5Bint%5D,%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%22%22%22%0A%20%20%20%20%23%20%E4%BB%8E%20src%20%E9%A1%B6%E9%83%A8%E6%8B%BF%E5%87%BA%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%0A%20%20%20%20pan%20%3D%20src.pop%28%29%0A%20%20%20%20%23%20%E5%B0%86%E5%9C%86%E7%9B%98%E6%94%BE%E5%85%A5%20tar%20%E9%A1%B6%E9%83%A8%0A%20%20%20%20tar.append%28pan%29%0A%0A%0Adef%20dfs%28i%3A%20int,%20src%3A%20list%5Bint%5D,%20buf%3A%20list%5Bint%5D,%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E6%B1%89%E8%AF%BA%E5%A1%94%E9%97%AE%E9%A2%98%20f%28i%29%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%20src%20%E5%8F%AA%E5%89%A9%E4%B8%8B%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E5%B0%86%E5%85%B6%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20if%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20move%28src,%20tar%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%20f%28i-1%29%20%EF%BC%9A%E5%B0%86%20src%20%E9%A1%B6%E9%83%A8%20i-1%20%E4%B8%AA%E5%9C%86%E7%9B%98%E5%80%9F%E5%8A%A9%20tar%20%E7%A7%BB%E5%88%B0%20buf%0A%20%20%20%20dfs%28i%20-%201,%20src,%20tar,%20buf%29%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%20f%281%29%20%EF%BC%9A%E5%B0%86%20src%20%E5%89%A9%E4%BD%99%E4%B8%80%E4%B8%AA%E5%9C%86%E7%9B%98%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20move%28src,%20tar%29%0A%20%20%20%20%23%20%E5%AD%90%E9%97%AE%E9%A2%98%20f%28i-1%29%20%EF%BC%9A%E5%B0%86%20buf%20%E9%A1%B6%E9%83%A8%20i-1%20%E4%B8%AA%E5%9C%86%E7%9B%98%E5%80%9F%E5%8A%A9%20src%20%E7%A7%BB%E5%88%B0%20tar%0A%20%20%20%20dfs%28i%20-%201,%20buf,%20src,%20tar%29%0A%0A%0Adef%20solve_hanota%28A%3A%20list%5Bint%5D,%20B%3A%20list%5Bint%5D,%20C%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%B1%82%E8%A7%A3%E6%B1%89%E8%AF%BA%E5%A1%94%E9%97%AE%E9%A2%98%22%22%22%0A%20%20%20%20n%20%3D%20len%28A%29%0A%20%20%20%20%23%20%E5%B0%86%20A%20%E9%A1%B6%E9%83%A8%20n%20%E4%B8%AA%E5%9C%86%E7%9B%98%E5%80%9F%E5%8A%A9%20B%20%E7%A7%BB%E5%88%B0%20C%0A%20%20%20%20dfs%28n,%20A,%20B,%20C%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%97%E8%A1%A8%E5%B0%BE%E9%83%A8%E6%98%AF%E6%9F%B1%E5%AD%90%E9%A1%B6%E9%83%A8%0A%20%20%20%20A%20%3D%20%5B5,%204,%203,%202,%201%5D%0A%20%20%20%20B%20%3D%20%5B%5D%0A%20%20%20%20C%20%3D%20%5B%5D%0A%20%20%20%20print%28%22%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%E4%B8%8B%EF%BC%9A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29%0A%0A%20%20%20%20solve_hanota%28A,%20B,%20C%29%0A%0A%20%20%20%20print%28%22%E5%9C%86%E7%9B%98%E7%A7%BB%E5%8A%A8%E5%AE%8C%E6%88%90%E5%90%8E%EF%BC%9A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md new file mode 100644 index 000000000..0d1ce488d --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28choices%3A%20list%5Bint%5D,%20state%3A%20int,%20n%3A%20int,%20res%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E6%BA%AF%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%E7%88%AC%E5%88%B0%E7%AC%AC%20n%20%E9%98%B6%E6%97%B6%EF%BC%8C%E6%96%B9%E6%A1%88%E6%95%B0%E9%87%8F%E5%8A%A0%201%0A%20%20%20%20if%20state%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%5B0%5D%20%2B%3D%201%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%89%80%E6%9C%89%E9%80%89%E6%8B%A9%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%89%AA%E6%9E%9D%EF%BC%9A%E4%B8%8D%E5%85%81%E8%AE%B8%E8%B6%8A%E8%BF%87%E7%AC%AC%20n%20%E9%98%B6%0A%20%20%20%20%20%20%20%20if%20state%20%2B%20choice%20%3E%20n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%9D%E8%AF%95%EF%BC%9A%E5%81%9A%E5%87%BA%E9%80%89%E6%8B%A9%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%8A%B6%E6%80%81%0A%20%20%20%20%20%20%20%20backtrack%28choices,%20state%20%2B%20choice,%20n,%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9B%9E%E9%80%80%0A%0A%0Adef%20climbing_stairs_backtrack%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E5%9B%9E%E6%BA%AF%22%22%22%0A%20%20%20%20choices%20%3D%20%5B1,%202%5D%20%20%23%20%E5%8F%AF%E9%80%89%E6%8B%A9%E5%90%91%E4%B8%8A%E7%88%AC%201%20%E9%98%B6%E6%88%96%202%20%E9%98%B6%0A%20%20%20%20state%20%3D%200%20%20%23%20%E4%BB%8E%E7%AC%AC%200%20%E9%98%B6%E5%BC%80%E5%A7%8B%E7%88%AC%0A%20%20%20%20res%20%3D%20%5B0%5D%20%20%23%20%E4%BD%BF%E7%94%A8%20res%5B0%5D%20%E8%AE%B0%E5%BD%95%E6%96%B9%E6%A1%88%E6%95%B0%E9%87%8F%0A%20%20%20%20backtrack%28choices,%20state,%20n,%20res%29%0A%20%20%20%20return%20res%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_backtrack%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md new file mode 100644 index 000000000..221a4c9b9 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_constraint_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%B8%A6%E7%BA%A6%E6%9D%9F%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%203%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%EF%BC%9A%E9%A2%84%E8%AE%BE%E6%9C%80%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D%5B1%5D,%20dp%5B1%5D%5B2%5D%20%3D%201,%200%0A%20%20%20%20dp%5B2%5D%5B1%5D,%20dp%5B2%5D%5B2%5D%20%3D%200,%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E4%BB%8E%E8%BE%83%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BE%83%E5%A4%A7%E5%AD%90%E9%97%AE%E9%A2%98%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B1%5D%20%3D%20dp%5Bi%20-%201%5D%5B2%5D%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B2%5D%20%3D%20dp%5Bi%20-%202%5D%5B1%5D%20%2B%20dp%5Bi%20-%202%5D%5B2%5D%0A%20%20%20%20return%20dp%5Bn%5D%5B1%5D%20%2B%20dp%5Bn%5D%5B2%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_constraint_dp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md new file mode 100644 index 000000000..8092103b9 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E5%B7%B2%E7%9F%A5%20dp%5B1%5D%20%E5%92%8C%20dp%5B2%5D%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%29%20%2B%20dfs%28i%20-%202%29%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20return%20dfs%28n%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md new file mode 100644 index 000000000..40575bde4 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int,%20mem%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E5%B7%B2%E7%9F%A5%20dp%5B1%5D%20%E5%92%8C%20dp%5B2%5D%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20%E8%8B%A5%E5%AD%98%E5%9C%A8%E8%AE%B0%E5%BD%95%20dp%5Bi%5D%20%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%E4%B9%8B%0A%20%20%20%20if%20mem%5Bi%5D%20!%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201,%20mem%29%20%2B%20dfs%28i%20-%202,%20mem%29%0A%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%20dp%5Bi%5D%0A%20%20%20%20mem%5Bi%5D%20%3D%20count%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs_mem%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20mem%5Bi%5D%20%E8%AE%B0%E5%BD%95%E7%88%AC%E5%88%B0%E7%AC%AC%20i%20%E9%98%B6%E7%9A%84%E6%96%B9%E6%A1%88%E6%80%BB%E6%95%B0%EF%BC%8C-1%20%E4%BB%A3%E8%A1%A8%E6%97%A0%E8%AE%B0%E5%BD%95%0A%20%20%20%20mem%20%3D%20%5B-1%5D%20*%20%28n%20%2B%201%29%0A%20%20%20%20return%20dfs%28n,%20mem%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs_mem%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md new file mode 100644 index 000000000..b7741e6aa --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%EF%BC%9A%E9%A2%84%E8%AE%BE%E6%9C%80%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D,%20dp%5B2%5D%20%3D%201,%202%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E4%BB%8E%E8%BE%83%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BE%83%E5%A4%A7%E5%AD%90%E9%97%AE%E9%A2%98%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20dp%5Bi%20-%201%5D%20%2B%20dp%5Bi%20-%202%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_dp_comp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20a,%20b%20%3D%201,%202%0A%20%20%20%20for%20_%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a,%20b%20%3D%20b,%20a%20%2B%20b%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp_comp%28n%29%0A%20%20%20%20print%28f%22%E7%88%AC%20%7Bn%7D%20%E9%98%B6%E6%A5%BC%E6%A2%AF%E5%85%B1%E6%9C%89%20%7Bres%7D%20%E7%A7%8D%E6%96%B9%E6%A1%88%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change.md new file mode 100644 index 000000000..b7000e743 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_dp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Ba%5D%20%3D%20MAX%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%B0%8F%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20min%28dp%5Bi%20-%201%5D%5Ba%5D,%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%20if%20dp%5Bn%5D%5Bamt%5D%20!%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_dp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20coin_change_dp_comp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5BMAX%5D%20*%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%200%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%B0%8F%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20min%28dp%5Ba%5D,%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bamt%5D%20if%20dp%5Bamt%5D%20!%3D%20MAX%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_dp_comp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md new file mode 100644 index 000000000..ce99875ef --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_ii_dp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%28n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B9%8B%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%20%2B%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_ii_dp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%87%BA%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E7%9A%84%E7%A1%AC%E5%B8%81%E7%BB%84%E5%90%88%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20coin_change_ii_dp_comp%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281,%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%A1%AC%E5%B8%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%A1%AC%E5%B8%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B9%8B%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%20%2B%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bamt%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1,%202,%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20coin_change_ii_dp_comp%28coins,%20amt%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%87%BA%E7%9B%AE%E6%A0%87%E9%87%91%E9%A2%9D%E7%9A%84%E7%A1%AC%E5%B8%81%E7%BB%84%E5%90%88%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/edit_distance.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/edit_distance.md new file mode 100644 index 000000000..93ef24eba --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/edit_distance.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20edit_distance_dp%28s%3A%20str,%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E4%B8%A4%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%E6%AD%A4%E4%B8%A4%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E3%80%81%E6%9B%BF%E6%8D%A2%E8%BF%99%E4%B8%89%E7%A7%8D%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D,%20dp%5Bi%20-%201%5D%5Bj%5D,%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s,%20t%29%0A%20%20%20%20print%28f%22%E5%B0%86%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E4%B8%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%BC%96%E8%BE%91%20%7Bres%7D%20%E6%AD%A5%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20edit_distance_dp_comp%28s%3A%20str,%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20%E6%9A%82%E5%AD%98%20dp%5Bi-1,%20j-1%5D%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E4%B8%A4%E5%AD%97%E7%AC%A6%E7%9B%B8%E7%AD%89%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%B7%B3%E8%BF%87%E6%AD%A4%E4%B8%A4%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%3D%20%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E3%80%81%E6%9B%BF%E6%8D%A2%E8%BF%99%E4%B8%89%E7%A7%8D%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E5%B0%91%E7%BC%96%E8%BE%91%E6%AD%A5%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D,%20dp%5Bj%5D,%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%E6%9B%B4%E6%96%B0%E4%B8%BA%E4%B8%8B%E4%B8%80%E8%BD%AE%E7%9A%84%20dp%5Bi-1,%20j-1%5D%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n,%20m%20%3D%20len%28s%29,%20len%28t%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s,%20t%29%0A%20%20%20%20print%28f%22%E5%B0%86%20%7Bs%7D%20%E6%9B%B4%E6%94%B9%E4%B8%BA%20%7Bt%7D%20%E6%9C%80%E5%B0%91%E9%9C%80%E8%A6%81%E7%BC%96%E8%BE%91%20%7Bres%7D%20%E6%AD%A5%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/knapsack.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/knapsack.md new file mode 100644 index 000000000..ef46bf9e9 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/knapsack.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20knapsack_dfs%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20i%3A%20int,%20c%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E9%80%89%E5%AE%8C%E6%89%80%E6%9C%89%E7%89%A9%E5%93%81%E6%88%96%E8%83%8C%E5%8C%85%E6%97%A0%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%E4%BB%B7%E5%80%BC%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E5%8F%AA%E8%83%BD%E9%80%89%E6%8B%A9%E4%B8%8D%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs%28wgt,%20val,%20i%20-%201,%20c%29%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%8D%E6%94%BE%E5%85%A5%E5%92%8C%E6%94%BE%E5%85%A5%E7%89%A9%E5%93%81%20i%20%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC%0A%20%20%20%20no%20%3D%20knapsack_dfs%28wgt,%20val,%20i%20-%201,%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs%28wgt,%20val,%20i%20-%201,%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B8%AD%E4%BB%B7%E5%80%BC%E6%9B%B4%E5%A4%A7%E7%9A%84%E9%82%A3%E4%B8%80%E4%B8%AA%0A%20%20%20%20return%20max%28no,%20yes%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%0A%20%20%20%20res%20%3D%20knapsack_dfs%28wgt,%20val,%20n,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dfs_mem%28%0A%20%20%20%20wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20mem%3A%20list%5Blist%5Bint%5D%5D,%20i%3A%20int,%20c%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E9%80%89%E5%AE%8C%E6%89%80%E6%9C%89%E7%89%A9%E5%93%81%E6%88%96%E8%83%8C%E5%8C%85%E6%97%A0%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%E4%BB%B7%E5%80%BC%200%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E6%9C%89%E8%AE%B0%E5%BD%95%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20if%20mem%5Bi%5D%5Bc%5D%20!%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E5%8F%AA%E8%83%BD%E9%80%89%E6%8B%A9%E4%B8%8D%E6%94%BE%E5%85%A5%E8%83%8C%E5%8C%85%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20i%20-%201,%20c%29%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%8D%E6%94%BE%E5%85%A5%E5%92%8C%E6%94%BE%E5%85%A5%E7%89%A9%E5%93%81%20i%20%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC%0A%20%20%20%20no%20%3D%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20i%20-%201,%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20i%20-%201,%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E5%B9%B6%E8%BF%94%E5%9B%9E%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E4%B8%AD%E4%BB%B7%E5%80%BC%E6%9B%B4%E5%A4%A7%E7%9A%84%E9%82%A3%E4%B8%80%E4%B8%AA%0A%20%20%20%20mem%5Bi%5D%5Bc%5D%20%3D%20max%28no,%20yes%29%0A%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20*%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20res%20%3D%20knapsack_dfs_mem%28wgt,%20val,%20mem,%20n,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281,%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D,%20dp%5Bi%20-%201%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20knapsack_dp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dp_comp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E8%83%8C%E5%8C%85%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%28cap,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D,%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20knapsack_dp_comp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md new file mode 100644 index 000000000..4b4c108ee --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81%EF%BC%9A%E9%A2%84%E8%AE%BE%E6%9C%80%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E7%9A%84%E8%A7%A3%0A%20%20%20%20dp%5B1%5D,%20dp%5B2%5D%20%3D%20cost%5B1%5D,%20cost%5B2%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E4%BB%8E%E8%BE%83%E5%B0%8F%E5%AD%90%E9%97%AE%E9%A2%98%E9%80%90%E6%AD%A5%E6%B1%82%E8%A7%A3%E8%BE%83%E5%A4%A7%E5%AD%90%E9%97%AE%E9%A2%98%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20min%28dp%5Bi%20-%201%5D,%20dp%5Bi%20-%202%5D%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0,%201,%2010,%201,%201,%201,%2010,%201,%201,%2010,%201%5D%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%A5%BC%E6%A2%AF%E7%9A%84%E4%BB%A3%E4%BB%B7%E5%88%97%E8%A1%A8%E4%B8%BA%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp%28cost%29%0A%20%20%20%20print%28f%22%E7%88%AC%E5%AE%8C%E6%A5%BC%E6%A2%AF%E7%9A%84%E6%9C%80%E4%BD%8E%E4%BB%A3%E4%BB%B7%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp_comp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%88%AC%E6%A5%BC%E6%A2%AF%E6%9C%80%E5%B0%8F%E4%BB%A3%E4%BB%B7%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20a,%20b%20%3D%20cost%5B1%5D,%20cost%5B2%5D%0A%20%20%20%20for%20i%20in%20range%283,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a,%20b%20%3D%20b,%20min%28a,%20b%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20b%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0,%201,%2010,%201,%201,%201,%2010,%201,%201,%2010,%201%5D%0A%20%20%20%20print%28f%22%E8%BE%93%E5%85%A5%E6%A5%BC%E6%A2%AF%E7%9A%84%E4%BB%A3%E4%BB%B7%E5%88%97%E8%A1%A8%E4%B8%BA%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp_comp%28cost%29%0A%20%20%20%20print%28f%22%E7%88%AC%E5%AE%8C%E6%A5%BC%E6%A2%AF%E7%9A%84%E6%9C%80%E4%BD%8E%E4%BB%A3%E4%BB%B7%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md new file mode 100644 index 000000000..0b8364cf0 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs%28grid%3A%20list%5Blist%5Bint%5D%5D,%20i%3A%20int,%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E4%B8%BA%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%8D%95%E5%85%83%E6%A0%BC%EF%BC%8C%E5%88%99%E7%BB%88%E6%AD%A2%E6%90%9C%E7%B4%A2%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%A1%8C%E5%88%97%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20%2B%E2%88%9E%20%E4%BB%A3%E4%BB%B7%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i-1,%20j%29%20%E5%92%8C%20%28i,%20j-1%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20up%20%3D%20min_path_sum_dfs%28grid,%20i%20-%201,%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs%28grid,%20i,%20j%20-%201%29%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i,%20j%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20return%20min%28left,%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E6%9A%B4%E5%8A%9B%E6%90%9C%E7%B4%A2%0A%20%20%20%20res%20%3D%20min_path_sum_dfs%28grid,%20n%20-%201,%20m%20-%201%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs_mem%28%0A%20%20%20%20grid%3A%20list%5Blist%5Bint%5D%5D,%20mem%3A%20list%5Blist%5Bint%5D%5D,%20i%3A%20int,%20j%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E8%8B%A5%E4%B8%BA%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%8D%95%E5%85%83%E6%A0%BC%EF%BC%8C%E5%88%99%E7%BB%88%E6%AD%A2%E6%90%9C%E7%B4%A2%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%8B%A5%E8%A1%8C%E5%88%97%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20%2B%E2%88%9E%20%E4%BB%A3%E4%BB%B7%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E8%8B%A5%E5%B7%B2%E6%9C%89%E8%AE%B0%E5%BD%95%EF%BC%8C%E5%88%99%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20if%20mem%5Bi%5D%5Bj%5D%20!%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%20%20%20%20%23%20%E5%B7%A6%E8%BE%B9%E5%92%8C%E4%B8%8A%E8%BE%B9%E5%8D%95%E5%85%83%E6%A0%BC%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20up%20%3D%20min_path_sum_dfs_mem%28grid,%20mem,%20i%20-%201,%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs_mem%28grid,%20mem,%20i,%20j%20-%201%29%0A%20%20%20%20%23%20%E8%AE%B0%E5%BD%95%E5%B9%B6%E8%BF%94%E5%9B%9E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%20%28i,%20j%29%20%E7%9A%84%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E4%BB%A3%E4%BB%B7%0A%20%20%20%20mem%5Bi%5D%5Bj%5D%20%3D%20min%28left,%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%23%20%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20*%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20res%20%3D%20min_path_sum_dfs_mem%28grid,%20mem,%20n%20-%201,%20m%20-%201%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20dp%5B0%5D%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20dp%5B0%5D%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20dp%5Bi%20-%201%5D%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%E5%92%8C%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281,%20n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D,%20dp%5Bi%20-%201%5D%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bn%20-%201%5D%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20min_path_sum_dp%28grid%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp_comp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20m%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E8%A1%8C%0A%20%20%20%20dp%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20dp%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281,%20n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E9%A6%96%E5%88%97%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%3D%20dp%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%EF%BC%9A%E5%85%B6%E4%BD%99%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281,%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D,%20dp%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bm%20-%201%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1,%203,%201,%205%5D,%20%5B2,%202,%204,%202%5D,%20%5B5,%203,%202,%201%5D,%20%5B4,%203,%205,%202%5D%5D%0A%20%20%20%20n,%20m%20%3D%20len%28grid%29,%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20min_path_sum_dp_comp%28grid%29%0A%20%20%20%20print%28f%22%E4%BB%8E%E5%B7%A6%E4%B8%8A%E8%A7%92%E5%88%B0%E5%8F%B3%E4%B8%8B%E8%A7%92%E7%9A%84%E5%81%9A%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md b/zh-hant/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md new file mode 100644 index 000000000..bbd2ed198 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%EF%BC%9A%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20*%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281,%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D,%20dp%5Bi%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1,%202,%203%5D%0A%20%20%20%20val%20%3D%20%5B5,%2011,%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp_comp%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85%EF%BC%9A%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20dp%20%E8%A1%A8%0A%20%20%20%20dp%20%3D%20%5B0%5D%20*%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281,%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AD%A3%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281,%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%88%99%E4%B8%8D%E9%80%89%E7%89%A9%E5%93%81%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%8D%E9%80%89%E5%92%8C%E9%80%89%E7%89%A9%E5%93%81%20i%20%E8%BF%99%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E7%9A%84%E8%BE%83%E5%A4%A7%E5%80%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D,%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1,%202,%203%5D%0A%20%20%20%20val%20%3D%20%5B5,%2011,%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%97%B4%E4%BC%98%E5%8C%96%E5%90%8E%E7%9A%84%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp_comp%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_list.md b/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_list.md new file mode 100644 index 000000000..aa43d5806 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_list.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A1%B6%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BE%93%E5%85%A5%E5%80%BC%E5%88%97%E8%A1%A8%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex,%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%89%80%E6%9C%89%E9%A1%B6%E7%82%B9%E5%92%8C%E8%BE%B9%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D,%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%A1%B6%E7%82%B9%E6%95%B0%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.adj_list%29%0A%0A%20%20%20%20def%20add_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%20vet1%20-%20vet2%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20remove_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%BE%B9%20vet1%20-%20vet2%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.remove%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.remove%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AA%E6%96%B0%E9%93%BE%E8%A1%A8%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20remove_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20not%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%20vet%20%E5%AF%B9%E5%BA%94%E7%9A%84%E9%93%BE%E8%A1%A8%0A%20%20%20%20%20%20%20%20self.adj_list.pop%28vet%29%0A%20%20%20%20%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%85%B6%E4%BB%96%E9%A1%B6%E7%82%B9%E7%9A%84%E9%93%BE%E8%A1%A8%EF%BC%8C%E5%88%A0%E9%99%A4%E6%89%80%E6%9C%89%E5%8C%85%E5%90%AB%20vet%20%E7%9A%84%E8%BE%B9%0A%20%20%20%20%20%20%20%20for%20vertex%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%5Bvertex%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.adj_list%5Bvertex%5D.remove%28vet%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B1,%203,%202,%205,%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B1%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B2%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B2%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B2%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%0A%20%20%20%20graph.add_edge%28v%5B0%5D,%20v%5B2%5D%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%BE%B9%0A%20%20%20%20graph.remove_edge%28v%5B0%5D,%20v%5B1%5D%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%0A%20%20%20%20v5%20%3D%20Vertex%286%29%0A%20%20%20%20graph.add_vertex%28v5%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%0A%20%20%20%20graph.remove_vertex%28v%5B1%5D%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md b/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md new file mode 100644 index 000000000..a053b757e --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20GraphAdjMat%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20vertices%3A%20list%5Bint%5D,%20edges%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.vertices%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.adj_mat%3A%20list%5Blist%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20for%20val%20in%20vertices%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%0A%20%20%20%20%20%20%20%20for%20e%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28e%5B0%5D,%20e%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%A1%B6%E7%82%B9%E6%95%B0%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.vertices%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20n%20%3D%20self.size%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%91%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E6%96%B0%E9%A1%B6%E7%82%B9%E7%9A%84%E5%80%BC%0A%20%20%20%20%20%20%20%20self.vertices.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%B8%80%E8%A1%8C%0A%20%20%20%20%20%20%20%20new_row%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20%20%20%20%20self.adj_mat.append%28new_row%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%B8%80%E5%88%97%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.append%280%29%0A%0A%20%20%20%20def%20remove_vertex%28self,%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20index%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%E4%B8%AD%E7%A7%BB%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20self.vertices.pop%28index%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E8%A1%8C%0A%20%20%20%20%20%20%20%20self.adj_mat.pop%28index%29%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E9%82%BB%E6%8E%A5%E7%9F%A9%E9%98%B5%E4%B8%AD%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%20index%20%E7%9A%84%E5%88%97%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.pop%28index%29%0A%0A%20%20%20%20def%20add_edge%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%E4%B8%8E%E7%9B%B8%E7%AD%89%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20j%20%3E%3D%20self.size%28%29%20or%20i%20%3D%3D%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%201%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%201%0A%0A%20%20%20%20def%20remove_edge%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%B4%A2%E5%BC%95%E8%B6%8A%E7%95%8C%E4%B8%8E%E7%9B%B8%E7%AD%89%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20j%20%3E%3D%20self.size%28%29%20or%20i%20%3D%3D%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%200%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%200%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20vertices%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20edges%20%3D%20%5B%5B0,%201%5D,%20%5B0,%203%5D,%20%5B1,%202%5D,%20%5B2,%203%5D,%20%5B2,%204%5D,%20%5B3,%204%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjMat%28vertices,%20edges%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%BE%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%201,%202%20%E7%9A%84%E7%B4%A2%E5%BC%95%E5%88%86%E5%88%AB%E4%B8%BA%200,%202%0A%20%20%20%20graph.add_edge%280,%202%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%BE%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%201,%203%20%E7%9A%84%E7%B4%A2%E5%BC%95%E5%88%86%E5%88%AB%E4%B8%BA%200,%201%0A%20%20%20%20graph.remove_edge%280,%201%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%0A%20%20%20%20graph.add_vertex%286%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E9%A1%B6%E7%82%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%203%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%201%0A%20%20%20%20graph.remove_vertex%281%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_graph/graph_bfs.md b/zh-hant/codes/pythontutor/chapter_graph/graph_bfs.md new file mode 100644 index 000000000..6edd606e6 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_graph/graph_bfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A1%B6%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BE%93%E5%85%A5%E5%80%BC%E5%88%97%E8%A1%A8%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex,%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D,%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20graph_bfs%28graph%3A%20GraphAdjList,%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E8%BF%87%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E9%98%9F%E5%88%97%E7%94%A8%E4%BA%8E%E5%AE%9E%E7%8E%B0%20BFS%0A%20%20%20%20que%20%3D%20deque%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E4%BB%A5%E9%A1%B6%E7%82%B9%20vet%20%E4%B8%BA%E8%B5%B7%E7%82%B9%EF%BC%8C%E5%BE%AA%E7%8E%AF%E7%9B%B4%E8%87%B3%E8%AE%BF%E9%97%AE%E5%AE%8C%E6%89%80%E6%9C%89%E9%A1%B6%E7%82%B9%0A%20%20%20%20while%20len%28que%29%20%3E%200%3A%0A%20%20%20%20%20%20%20%20vet%20%3D%20que.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E9%A1%B6%E7%82%B9%E5%87%BA%E9%98%9F%0A%20%20%20%20%20%20%20%20res.append%28vet%29%20%20%23%20%E8%AE%B0%E5%BD%95%E8%AE%BF%E9%97%AE%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E8%AF%A5%E9%A1%B6%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20for%20adj_vet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adj_vet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%B7%B3%E8%BF%87%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20que.append%28adj_vet%29%20%20%23%20%E5%8F%AA%E5%85%A5%E9%98%9F%E6%9C%AA%E8%AE%BF%E9%97%AE%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20visited.add%28adj_vet%29%20%20%23%20%E6%A0%87%E8%AE%B0%E8%AF%A5%E9%A1%B6%E7%82%B9%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0,%201,%202,%203,%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B1%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B2%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20graph_bfs%28graph,%20v%5B0%5D%29&cumulative=false&curInstr=131&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_graph/graph_dfs.md b/zh-hant/codes/pythontutor/chapter_graph/graph_dfs.md new file mode 100644 index 000000000..c6ed274c4 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_graph/graph_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A1%B6%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E8%BE%93%E5%85%A5%E5%80%BC%E5%88%97%E8%A1%A8%20vals%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E9%A1%B6%E7%82%B9%E5%88%97%E8%A1%A8%20vets%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%82%BB%E6%8E%A5%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%A0%E5%90%91%E5%9B%BE%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex,%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D,%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self,%20vet1%3A%20Vertex,%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E8%BE%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E9%A1%B6%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20dfs%28graph%3A%20GraphAdjList,%20visited%3A%20set%5BVertex%5D,%20res%3A%20list%5BVertex%5D,%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%E8%BE%85%E5%8A%A9%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20res.append%28vet%29%20%20%23%20%E8%AE%B0%E5%BD%95%E8%AE%BF%E9%97%AE%E9%A1%B6%E7%82%B9%0A%20%20%20%20visited.add%28vet%29%20%20%23%20%E6%A0%87%E8%AE%B0%E8%AF%A5%E9%A1%B6%E7%82%B9%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E8%AF%A5%E9%A1%B6%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20for%20adjVet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20if%20adjVet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%B7%B3%E8%BF%87%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E8%AE%BF%E9%97%AE%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20%20%20%20%20dfs%28graph,%20visited,%20res,%20adjVet%29%0A%0A%0Adef%20graph_dfs%28graph%3A%20GraphAdjList,%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E9%82%BB%E6%8E%A5%E8%A1%A8%E6%9D%A5%E8%A1%A8%E7%A4%BA%E5%9B%BE%EF%BC%8C%E4%BB%A5%E4%BE%BF%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E9%A1%B6%E7%82%B9%E7%9A%84%E6%89%80%E6%9C%89%E9%82%BB%E6%8E%A5%E9%A1%B6%E7%82%B9%0A%20%20%20%20%23%20%E9%A1%B6%E7%82%B9%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E5%B7%B2%E8%A2%AB%E8%AE%BF%E9%97%AE%E8%BF%87%E7%9A%84%E9%A1%B6%E7%82%B9%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%29%0A%20%20%20%20dfs%28graph,%20visited,%20res,%20start_vet%29%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%A0%E5%90%91%E5%9B%BE%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0,%201,%202,%203,%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B1%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D,%20v%5B3%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B2%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D,%20v%5B4%5D%5D,%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%0A%20%20%20%20%23%20%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20graph_dfs%28graph,%20v%5B0%5D%29&cumulative=false&curInstr=130&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_greedy/coin_change_greedy.md b/zh-hant/codes/pythontutor/chapter_greedy/coin_change_greedy.md new file mode 100644 index 000000000..25d8e0ddc --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_greedy/coin_change_greedy.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D,%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%81%87%E8%AE%BE%20coins%20%E5%88%97%E8%A1%A8%E6%9C%89%E5%BA%8F%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E8%BF%9B%E8%A1%8C%E8%B4%AA%E5%BF%83%E9%80%89%E6%8B%A9%EF%BC%8C%E7%9B%B4%E5%88%B0%E6%97%A0%E5%89%A9%E4%BD%99%E9%87%91%E9%A2%9D%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E5%B0%8F%E4%BA%8E%E4%B8%94%E6%9C%80%E6%8E%A5%E8%BF%91%E5%89%A9%E4%BD%99%E9%87%91%E9%A2%9D%E7%9A%84%E7%A1%AC%E5%B8%81%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E9%80%89%E6%8B%A9%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%E8%8B%A5%E6%9C%AA%E6%89%BE%E5%88%B0%E5%8F%AF%E8%A1%8C%E6%96%B9%E6%A1%88%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%EF%BC%9A%E8%83%BD%E5%A4%9F%E4%BF%9D%E8%AF%81%E6%89%BE%E5%88%B0%E5%85%A8%E5%B1%80%E6%9C%80%E4%BC%98%E8%A7%A3%0A%20%20%20%20coins%20%3D%20%5B1,%205,%2010,%2020,%2050,%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins,%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D,%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%20%7Bamt%7D%20%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%EF%BC%9A%E6%97%A0%E6%B3%95%E4%BF%9D%E8%AF%81%E6%89%BE%E5%88%B0%E5%85%A8%E5%B1%80%E6%9C%80%E4%BC%98%E8%A7%A3%0A%20%20%20%20coins%20%3D%20%5B1,%2020,%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins,%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D,%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%E5%87%91%E5%88%B0%20%7Bamt%7D%20%E6%89%80%E9%9C%80%E7%9A%84%E6%9C%80%E5%B0%91%E7%A1%AC%E5%B8%81%E6%95%B0%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E5%AE%9E%E9%99%85%E4%B8%8A%E9%9C%80%E8%A6%81%E7%9A%84%E6%9C%80%E5%B0%91%E6%95%B0%E9%87%8F%E4%B8%BA%203%20%EF%BC%8C%E5%8D%B3%2020%20%2B%2020%20%2B%2020%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_greedy/fractional_knapsack.md b/zh-hant/codes/pythontutor/chapter_greedy/fractional_knapsack.md new file mode 100644 index 000000000..d72831d36 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_greedy/fractional_knapsack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%E7%89%A9%E5%93%81%22%22%22%0A%20%20%20%20def%20__init__%28self,%20w%3A%20int,%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%E7%89%A9%E5%93%81%E9%87%8D%E9%87%8F%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D,%20val%3A%20list%5Bint%5D,%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%88%86%E6%95%B0%E8%83%8C%E5%8C%85%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%88%9B%E5%BB%BA%E7%89%A9%E5%93%81%E5%88%97%E8%A1%A8%EF%BC%8C%E5%8C%85%E5%90%AB%E4%B8%A4%E4%B8%AA%E5%B1%9E%E6%80%A7%EF%BC%9A%E9%87%8D%E9%87%8F%E3%80%81%E4%BB%B7%E5%80%BC%0A%20%20%20%20items%20%3D%20%5BItem%28w,%20v%29%20for%20w,%20v%20in%20zip%28wgt,%20val%29%5D%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E5%8D%95%E4%BD%8D%E4%BB%B7%E5%80%BC%20item.v%20/%20item.w%20%E4%BB%8E%E9%AB%98%E5%88%B0%E4%BD%8E%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20/%20item.w,%20reverse%3DTrue%29%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E8%B4%AA%E5%BF%83%E9%80%89%E6%8B%A9%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%E5%85%85%E8%B6%B3%EF%BC%8C%E5%88%99%E5%B0%86%E5%BD%93%E5%89%8D%E7%89%A9%E5%93%81%E6%95%B4%E4%B8%AA%E8%A3%85%E8%BF%9B%E8%83%8C%E5%8C%85%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%E4%B8%8D%E8%B6%B3%EF%BC%8C%E5%88%99%E5%B0%86%E5%BD%93%E5%89%8D%E7%89%A9%E5%93%81%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86%E8%A3%85%E8%BF%9B%E8%83%8C%E5%8C%85%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20/%20item.w%29%20*%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%B7%B2%E6%97%A0%E5%89%A9%E4%BD%99%E5%AE%B9%E9%87%8F%EF%BC%8C%E5%9B%A0%E6%AD%A4%E8%B7%B3%E5%87%BA%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10,%2020,%2030,%2040,%2050%5D%0A%20%20%20%20val%20%3D%20%5B50,%20120,%20150,%20210,%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt,%20val,%20cap%29%0A%20%20%20%20print%28f%22%E4%B8%8D%E8%B6%85%E8%BF%87%E8%83%8C%E5%8C%85%E5%AE%B9%E9%87%8F%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%A9%E5%93%81%E4%BB%B7%E5%80%BC%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_greedy/max_capacity.md b/zh-hant/codes/pythontutor/chapter_greedy/max_capacity.md new file mode 100644 index 000000000..140b07ce4 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_greedy/max_capacity.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20i,%20j%EF%BC%8C%E4%BD%BF%E5%85%B6%E5%88%86%E5%88%97%E6%95%B0%E7%BB%84%E4%B8%A4%E7%AB%AF%0A%20%20%20%20i,%20j%20%3D%200,%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E4%B8%BA%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E8%B4%AA%E5%BF%83%E9%80%89%E6%8B%A9%EF%BC%8C%E7%9B%B4%E8%87%B3%E4%B8%A4%E6%9D%BF%E7%9B%B8%E9%81%87%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D,%20ht%5Bj%5D%29%20*%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res,%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%91%E5%86%85%E7%A7%BB%E5%8A%A8%E7%9F%AD%E6%9D%BF%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3,%208,%205,%202,%207,%207,%203,%204%5D%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_greedy/max_product_cutting.md b/zh-hant/codes/pythontutor/chapter_greedy/max_product_cutting.md new file mode 100644 index 000000000..c46de9c11 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_greedy/max_product_cutting.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%88%87%E5%88%86%E4%B9%98%E7%A7%AF%EF%BC%9A%E8%B4%AA%E5%BF%83%22%22%22%0A%20%20%20%20%23%20%E5%BD%93%20n%20%3C%3D%203%20%E6%97%B6%EF%BC%8C%E5%BF%85%E9%A1%BB%E5%88%87%E5%88%86%E5%87%BA%E4%B8%80%E4%B8%AA%201%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20*%20%28n%20-%201%29%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E5%9C%B0%E5%88%87%E5%88%86%E5%87%BA%203%20%EF%BC%8Ca%20%E4%B8%BA%203%20%E7%9A%84%E4%B8%AA%E6%95%B0%EF%BC%8Cb%20%E4%B8%BA%E4%BD%99%E6%95%B0%0A%20%20%20%20a,%20b%20%3D%20n%20//%203,%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E4%BD%99%E6%95%B0%E4%B8%BA%201%20%E6%97%B6%EF%BC%8C%E5%B0%86%E4%B8%80%E5%AF%B9%201%20*%203%20%E8%BD%AC%E5%8C%96%E4%B8%BA%202%20*%202%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283,%20a%20-%201%29%29%20*%202%20*%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E4%BD%99%E6%95%B0%E4%B8%BA%202%20%E6%97%B6%EF%BC%8C%E4%B8%8D%E5%81%9A%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283,%20a%29%29%20*%202%0A%20%20%20%20%23%20%E5%BD%93%E4%BD%99%E6%95%B0%E4%B8%BA%200%20%E6%97%B6%EF%BC%8C%E4%B8%8D%E5%81%9A%E5%A4%84%E7%90%86%0A%20%20%20%20return%20int%28math.pow%283,%20a%29%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%88%87%E5%88%86%E4%B9%98%E7%A7%AF%E4%B8%BA%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_hashing/array_hash_map.md b/zh-hant/codes/pythontutor/chapter_hashing/array_hash_map.md new file mode 100644 index 000000000..5ff71db93 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_hashing/array_hash_map.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E9%94%AE%E5%80%BC%E5%AF%B9%22%22%22%0A%20%20%20%20def%20__init__%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0A%0Aclass%20ArrayHashMap%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E7%9A%84%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%EF%BC%8C%E5%8C%85%E5%90%AB%2020%20%E4%B8%AA%E6%A1%B6%0A%20%20%20%20%20%20%20%20self.buckets%3A%20list%5BPair%20%7C%20None%5D%20%3D%20%5BNone%5D%20*%2020%0A%0A%20%20%20%20def%20hash_func%28self,%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%93%88%E5%B8%8C%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20key%20%25%2020%0A%20%20%20%20%20%20%20%20return%20index%0A%0A%20%20%20%20def%20get%28self,%20key%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20pair%3A%20Pair%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20if%20pair%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20pair.val%0A%0A%20%20%20%20def%20put%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key,%20val%29%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20pair%0A%0A%20%20%20%20def%20remove%28self,%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20%23%20%E7%BD%AE%E4%B8%BA%20None%20%EF%BC%8C%E4%BB%A3%E8%A1%A8%E5%88%A0%E9%99%A4%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20None%0A%0A%20%20%20%20def%20entry_set%28self%29%20-%3E%20list%5BPair%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E9%94%AE%E5%80%BC%E5%AF%B9%22%22%22%0A%20%20%20%20%20%20%20%20result%3A%20list%5BPair%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20key_set%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E9%94%AE%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.key%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20value_set%28self%29%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E5%80%BC%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.val%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%89%93%E5%8D%B0%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print%28pair.key,%20%22-%3E%22,%20pair.val%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20ArrayHashMap%28%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20hmap.put%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hmap.put%2815937,%20%22%E5%B0%8F%E5%95%B0%22%29%0A%20%20%20%20hmap.put%2816750,%20%22%E5%B0%8F%E7%AE%97%22%29%0A%20%20%20%20hmap.put%2813276,%20%22%E5%B0%8F%E6%B3%95%22%29%0A%20%20%20%20hmap.put%2810583,%20%22%E5%B0%8F%E9%B8%AD%22%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hmap.get%2815937%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hmap.remove%2810583%29%0A%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20print%28%22%5Cn%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20Key-%3EValue%22%29%0A%20%20%20%20for%20pair%20in%20hmap.entry_set%28%29%3A%0A%20%20%20%20%20%20%20%20print%28pair.key,%20%22-%3E%22,%20pair.val%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_hashing/hash_map_chaining.md b/zh-hant/codes/pythontutor/chapter_hashing/hash_map_chaining.md new file mode 100644 index 000000000..477c4da25 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_hashing/hash_map_chaining.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E9%94%AE%E5%80%BC%E5%AF%B9%22%22%22%0A%20%20%20%20def%20__init__%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20HashMapChaining%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E5%BC%8F%E5%9C%B0%E5%9D%80%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20self.capacity%20%3D%204%0A%20%20%20%20%20%20%20%20self.load_thres%20%3D%202.0%20/%203.0%0A%20%20%20%20%20%20%20%20self.extend_ratio%20%3D%202%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%0A%20%20%20%20def%20hash_func%28self,%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%93%88%E5%B8%8C%E5%87%BD%E6%95%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20key%20%25%20self.capacity%0A%0A%20%20%20%20def%20load_factor%28self%29%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%B4%9F%E8%BD%BD%E5%9B%A0%E5%AD%90%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%20/%20self.capacity%0A%0A%20%20%20%20def%20get%28self,%20key%3A%20int%29%20-%3E%20str%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20pair.val%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20put%28self,%20key%3A%20int,%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.load_factor%28%29%20%3E%20self.load_thres%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend%28%29%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pair.val%20%3D%20val%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key,%20val%29%0A%20%20%20%20%20%20%20%20bucket.append%28pair%29%0A%20%20%20%20%20%20%20%20self.size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self,%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bucket.remove%28pair%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.size%20-%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20def%20extend%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%89%A9%E5%AE%B9%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20buckets%20%3D%20self.buckets%0A%20%20%20%20%20%20%20%20self.capacity%20*%3D%20self.extend_ratio%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.put%28pair.key,%20pair.val%29%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%89%93%E5%8D%B0%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20res.append%28str%28pair.key%29%20%2B%20%22%20-%3E%20%22%20%2B%20pair.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28res%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hashmap%20%3D%20HashMapChaining%28%29%0A%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.put%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hashmap.put%2815937,%20%22%E5%B0%8F%E5%95%B0%22%29%0A%20%20%20%20hashmap.put%2816750,%20%22%E5%B0%8F%E7%AE%97%22%29%0A%20%20%20%20hashmap.put%2813276,%20%22%E5%B0%8F%E6%B3%95%22%29%0A%20%20%20%20hashmap.put%2810583,%20%22%E5%B0%8F%E9%B8%AD%22%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hashmap.get%2813276%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.remove%2812836%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_hashing/simple_hash.md b/zh-hant/codes/pythontutor/chapter_hashing/simple_hash.md new file mode 100644 index 000000000..9345f5f6e --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_hashing/simple_hash.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8A%A0%E6%B3%95%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%B9%98%E6%B3%95%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20*%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%BC%82%E6%88%96%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%97%8B%E8%BD%AC%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%E5%8A%A0%E6%B3%95%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%E4%B9%98%E6%B3%95%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22%E5%BC%82%E6%88%96%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%E6%97%8B%E8%BD%AC%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_heap/my_heap.md b/zh-hant/codes/pythontutor/chapter_heap/my_heap.md new file mode 100644 index 000000000..133a1f4a0 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_heap/my_heap.md @@ -0,0 +1,20 @@ + + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%EF%BC%8C%E6%A0%B9%E6%8D%AE%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%BB%BA%E5%A0%86%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%20%20%20%20%20%20%20%20%23%20%E5%A0%86%E5%8C%96%E9%99%A4%E5%8F%B6%E8%8A%82%E7%82%B9%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.parent%28self.size%28%29%20-%201%29,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.sift_down%28i%29%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D,%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D,%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20sift_down%28self,%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20%20%20%20%20l,%20r,%20ma%20%3D%20self.left%28i%29,%20self.right%28i%29,%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i,%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B1,%202,%203,%204,%205%5D%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + + + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.max_heap%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%20%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9,%208,%206,%206,%207,%205,%202,%201,%204,%203,%206,%202%5D%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20max_heap.peek%28%29%0A%20%20%20%20print%28f%22%5Cn%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E4%B8%BA%20%7Bpeek%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D,%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D,%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20self.max_heap.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%8E%E5%BA%95%E8%87%B3%E9%A1%B6%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_up%28self.size%28%29%20-%201%29%0A%0A%20%20%20%20def%20sift_up%28self,%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E5%BA%95%E8%87%B3%E9%A1%B6%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E8%8A%82%E7%82%B9%20i%20%E7%9A%84%E7%88%B6%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20self.parent%28i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E2%80%9C%E8%B6%8A%E8%BF%87%E6%A0%B9%E8%8A%82%E7%82%B9%E2%80%9D%E6%88%96%E2%80%9C%E8%8A%82%E7%82%B9%E6%97%A0%E9%A1%BB%E4%BF%AE%E5%A4%8D%E2%80%9D%E6%97%B6%EF%BC%8C%E7%BB%93%E6%9D%9F%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20%3C%200%20or%20self.max_heap%5Bi%5D%20%3C%3D%20self.max_heap%5Bp%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i,%20p%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8A%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20p%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9,%208,%206,%206,%207,%205,%202,%201,%204,%203,%206,%202%5D%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20val%20%3D%207%0A%20%20%20%20max_heap.push%28val%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E5%A4%A7%E9%A1%B6%E5%A0%86%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E5%8E%9F%E5%B0%81%E4%B8%8D%E5%8A%A8%E6%B7%BB%E5%8A%A0%E8%BF%9B%E5%A0%86%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%20%20%23%20%E5%90%91%E4%B8%8B%E6%95%B4%E9%99%A4%0A%0A%20%20%20%20def%20swap%28self,%20i%3A%20int,%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D,%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D,%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E7%A9%BA%E5%A4%84%E7%90%86%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E5%A0%86%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%8E%E6%9C%80%E5%8F%B3%E5%8F%B6%E8%8A%82%E7%82%B9%EF%BC%88%E4%BA%A4%E6%8D%A2%E9%A6%96%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20self.swap%280,%20self.size%28%29%20-%201%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20val%20%3D%20self.max_heap.pop%28%29%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_down%280%29%0A%20%20%20%20%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20return%20val%0A%0A%20%20%20%20def%20sift_down%28self,%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20%20%20%20%20l,%20r,%20ma%20%3D%20self.left%28i%29,%20self.right%28i%29,%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i,%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BE%93%E5%85%A5%E6%95%B0%E7%BB%84%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%B7%B2%E7%BB%8F%E6%98%AF%E4%B8%80%E4%B8%AA%E5%90%88%E6%B3%95%E7%9A%84%E5%A0%86%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9,%208,%207,%206,%207,%206,%202,%201,%204,%203,%206,%202,%205%5D%29%0A%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20peek%20%3D%20max_heap.pop%28%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_heap/top_k.md b/zh-hant/codes/pythontutor/chapter_heap/top_k.md new file mode 100644 index 000000000..45adf03e8 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_heap/top_k.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D,%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E5%A0%86%E6%9F%A5%E6%89%BE%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%20k%20%E4%B8%AA%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E5%B0%86%E6%95%B0%E7%BB%84%E7%9A%84%E5%89%8D%20k%20%E4%B8%AA%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap,%20nums%5Bi%5D%29%0A%20%20%20%20%23%20%E4%BB%8E%E7%AC%AC%20k%2B1%20%E4%B8%AA%E5%85%83%E7%B4%A0%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BF%9D%E6%8C%81%E5%A0%86%E7%9A%84%E9%95%BF%E5%BA%A6%E4%B8%BA%20k%0A%20%20%20%20for%20i%20in%20range%28k,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%A4%A7%E4%BA%8E%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%EF%BC%8C%E5%88%99%E5%B0%86%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%E3%80%81%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap,%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%207,%206,%203,%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums,%20k%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_searching/binary_search.md b/zh-hant/codes/pythontutor/chapter_searching/binary_search.md new file mode 100644 index 000000000..f54c96cd5 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_searching/binary_search.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%20%EF%BC%8C%E5%8D%B3%20i,%20j%20%E5%88%86%E5%88%AB%E6%8C%87%E5%90%91%E6%95%B0%E7%BB%84%E9%A6%96%E5%85%83%E7%B4%A0%E3%80%81%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%EF%BC%8C%E5%BD%93%E6%90%9C%E7%B4%A2%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%E6%97%B6%E8%B7%B3%E5%87%BA%EF%BC%88%E5%BD%93%20i%20%3E%20j%20%E6%97%B6%E4%B8%BA%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%90%86%E8%AE%BA%E4%B8%8A%20Python%20%E7%9A%84%E6%95%B0%E5%AD%97%E5%8F%AF%E4%BB%A5%E6%97%A0%E9%99%90%E5%A4%A7%EF%BC%88%E5%8F%96%E5%86%B3%E4%BA%8E%E5%86%85%E5%AD%98%E5%A4%A7%E5%B0%8F%EF%BC%89%EF%BC%8C%E6%97%A0%E9%A1%BB%E8%80%83%E8%99%91%E5%A4%A7%E6%95%B0%E8%B6%8A%E7%95%8C%E9%97%AE%E9%A2%98%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20return%20-1%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums,%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22,%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_lcro%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%B7%A6%E9%97%AD%E5%8F%B3%E5%BC%80%E5%8C%BA%E9%97%B4%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E9%97%AD%E5%8F%B3%E5%BC%80%E5%8C%BA%E9%97%B4%20%5B0,%20n%29%20%EF%BC%8C%E5%8D%B3%20i,%20j%20%E5%88%86%E5%88%AB%E6%8C%87%E5%90%91%E6%95%B0%E7%BB%84%E9%A6%96%E5%85%83%E7%B4%A0%E3%80%81%E5%B0%BE%E5%85%83%E7%B4%A0%2B1%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%0A%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%EF%BC%8C%E5%BD%93%E6%90%9C%E7%B4%A2%E5%8C%BA%E9%97%B4%E4%B8%BA%E7%A9%BA%E6%97%B6%E8%B7%B3%E5%87%BA%EF%BC%88%E5%BD%93%20i%20%3D%20j%20%E6%97%B6%E4%B8%BA%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%29%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20%20%23%20%E6%AD%A4%E6%83%85%E5%86%B5%E8%AF%B4%E6%98%8E%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m%29%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%85%B6%E7%B4%A2%E5%BC%95%0A%20%20%20%20return%20-1%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%EF%BC%88%E5%B7%A6%E9%97%AD%E5%8F%B3%E5%BC%80%E5%8C%BA%E9%97%B4%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search_lcro%28nums,%20target%29%0A%20%20%20%20print%28%22%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0%206%20%E7%9A%84%E7%B4%A2%E5%BC%95%20%3D%20%22,%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_searching/binary_search_edge.md b/zh-hant/codes/pythontutor/chapter_searching/binary_search_edge.md new file mode 100644 index 000000000..b3b1eb2f3 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_searching/binary_search_edge.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_left_edge%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%9C%80%E5%B7%A6%E4%B8%80%E4%B8%AA%20target%22%22%22%0A%20%20%20%20%23%20%E7%AD%89%E4%BB%B7%E4%BA%8E%E6%9F%A5%E6%89%BE%20target%20%E7%9A%84%E6%8F%92%E5%85%A5%E7%82%B9%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums,%20target%29%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20i%20%3D%3D%20len%28nums%29%20or%20nums%5Bi%5D%20!%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%B4%A2%E5%BC%95%20i%0A%20%20%20%20return%20i%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%206,%206,%206,%206,%2010,%2012,%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%B7%A6%E8%BE%B9%E7%95%8C%E5%92%8C%E5%8F%B3%E8%BE%B9%E7%95%8C%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_left_edge%28nums,%20target%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%B7%A6%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_right_edge%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%9C%80%E5%8F%B3%E4%B8%80%E4%B8%AA%20target%22%22%22%0A%20%20%20%20%23%20%E8%BD%AC%E5%8C%96%E4%B8%BA%E6%9F%A5%E6%89%BE%E6%9C%80%E5%B7%A6%E4%B8%80%E4%B8%AA%20target%20%2B%201%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums,%20target%20%2B%201%29%0A%20%20%20%20%23%20j%20%E6%8C%87%E5%90%91%E6%9C%80%E5%8F%B3%E4%B8%80%E4%B8%AA%20target%20%EF%BC%8Ci%20%E6%8C%87%E5%90%91%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%20-1%0A%20%20%20%20if%20j%20%3D%3D%20-1%20or%20nums%5Bj%5D%20!%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20return%20j%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%206,%206,%206,%206,%2010,%2012,%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%B7%A6%E8%BE%B9%E7%95%8C%E5%92%8C%E5%8F%B3%E8%BE%B9%E7%95%8C%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_right_edge%28nums,%20target%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%8F%B3%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_searching/binary_search_insertion.md b/zh-hant/codes/pythontutor/chapter_searching/binary_search_insertion.md new file mode 100644 index 000000000..64b53e186 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_searching/binary_search_insertion.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion_simple%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E6%97%A0%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20m%0A%20%20%20%20%23%20%E6%9C%AA%E6%89%BE%E5%88%B0%20target%20%EF%BC%8C%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%97%A0%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%208,%2012,%2015,%2023,%2026,%2031,%2035%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion_simple%28nums,%20target%29%0A%20%20%20%20print%28f%22%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E6%8F%92%E5%85%A5%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%EF%BC%88%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%EF%BC%89%22%22%22%0A%20%20%20%20i,%20j%20%3D%200,%20len%28nums%29%20-%201%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E9%97%AD%E5%8C%BA%E9%97%B4%20%5B0,%20n-1%5D%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%E7%B4%A2%E5%BC%95%20m%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bm%2B1,%20j%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%20target%20%E7%9A%84%E5%85%83%E7%B4%A0%E5%9C%A8%E5%8C%BA%E9%97%B4%20%5Bi,%20m-1%5D%20%E4%B8%AD%0A%20%20%20%20%23%20%E8%BF%94%E5%9B%9E%E6%8F%92%E5%85%A5%E7%82%B9%20i%0A%20%20%20%20return%20i%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20nums%20%3D%20%5B1,%203,%206,%206,%206,%206,%206,%2010,%2012,%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E6%8F%92%E5%85%A5%E7%82%B9%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion%28nums,%20target%29%0A%20%20%20%20print%28f%22%E5%85%83%E7%B4%A0%20%7Btarget%7D%20%E7%9A%84%E6%8F%92%E5%85%A5%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%E4%B8%BA%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_searching/two_sum.md b/zh-hant/codes/pythontutor/chapter_searching/two_sum.md new file mode 100644 index 000000000..3aca0bc0d --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_searching/two_sum.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20two_sum_brute_force%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%E4%B8%80%EF%BC%9A%E6%9A%B4%E5%8A%9B%E6%9E%9A%E4%B8%BE%22%22%22%0A%20%20%20%20%23%20%E4%B8%A4%E5%B1%82%E5%BE%AA%E7%8E%AF%EF%BC%8C%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E4%B8%BA%20O%28n%5E2%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bi,%20j%5D%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2,%207,%2011,%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_brute_force%28nums,%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20two_sum_hash_table%28nums%3A%20list%5Bint%5D,%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%E4%BA%8C%EF%BC%9A%E8%BE%85%E5%8A%A9%E5%93%88%E5%B8%8C%E8%A1%A8%22%22%22%0A%20%20%20%20%23%20%E8%BE%85%E5%8A%A9%E5%93%88%E5%B8%8C%E8%A1%A8%EF%BC%8C%E7%A9%BA%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E4%B8%BA%20O%28n%29%0A%20%20%20%20dic%20%3D%20%7B%7D%0A%20%20%20%20%23%20%E5%8D%95%E5%B1%82%E5%BE%AA%E7%8E%AF%EF%BC%8C%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E4%B8%BA%20O%28n%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20nums%5Bi%5D%20in%20dic%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bdic%5Btarget%20-%20nums%5Bi%5D%5D,%20i%5D%0A%20%20%20%20%20%20%20%20dic%5Bnums%5Bi%5D%5D%20%3D%20i%0A%20%20%20%20return%20%5B%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2,%207,%2011,%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_hash_table%28nums,%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/bubble_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/bubble_sort.md new file mode 100644 index 000000000..e254840ae --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/bubble_sort.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D,%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D,%20nums%5Bj%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%87%E5%BF%97%E4%BC%98%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%87%E5%BF%97%E4%BD%8D%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i%5D%20%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%E8%87%B3%E8%AF%A5%E5%8C%BA%E9%97%B4%E7%9A%84%E6%9C%80%E5%8F%B3%E7%AB%AF%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%20nums%5Bj%5D%20%E4%B8%8E%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D,%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D,%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%E8%AE%B0%E5%BD%95%E4%BA%A4%E6%8D%A2%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%E6%AD%A4%E8%BD%AE%E2%80%9C%E5%86%92%E6%B3%A1%E2%80%9D%E6%9C%AA%E4%BA%A4%E6%8D%A2%E4%BB%BB%E4%BD%95%E5%85%83%E7%B4%A0%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%B7%B3%E5%87%BA%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/bucket_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/bucket_sort.md new file mode 100644 index 000000000..e3589ddc1 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/bucket_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%E6%A1%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%20k%20%3D%20n/2%20%E4%B8%AA%E6%A1%B6%EF%BC%8C%E9%A2%84%E6%9C%9F%E5%90%91%E6%AF%8F%E4%B8%AA%E6%A1%B6%E5%88%86%E9%85%8D%202%20%E4%B8%AA%E5%85%83%E7%B4%A0%0A%20%20%20%20k%20%3D%20len%28nums%29%20//%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%E5%B0%86%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E5%88%86%E9%85%8D%E5%88%B0%E5%90%84%E4%B8%AA%E6%A1%B6%E4%B8%AD%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E8%8C%83%E5%9B%B4%E4%B8%BA%20%5B0,%201%29%EF%BC%8C%E4%BD%BF%E7%94%A8%20num%20*%20k%20%E6%98%A0%E5%B0%84%E5%88%B0%E7%B4%A2%E5%BC%95%E8%8C%83%E5%9B%B4%20%5B0,%20k-1%5D%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20*%20k%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%20num%20%E6%B7%BB%E5%8A%A0%E8%BF%9B%E6%A1%B6%20i%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%AF%B9%E5%90%84%E4%B8%AA%E6%A1%B6%E6%89%A7%E8%A1%8C%E6%8E%92%E5%BA%8F%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%86%85%E7%BD%AE%E6%8E%92%E5%BA%8F%E5%87%BD%E6%95%B0%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E6%9B%BF%E6%8D%A2%E6%88%90%E5%85%B6%E4%BB%96%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E9%81%8D%E5%8E%86%E6%A1%B6%E5%90%88%E5%B9%B6%E7%BB%93%E6%9E%9C%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%AE%BE%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE%E4%B8%BA%E6%B5%AE%E7%82%B9%E6%95%B0%EF%BC%8C%E8%8C%83%E5%9B%B4%E4%B8%BA%20%5B0,%201%29%0A%20%20%20%20nums%20%3D%20%5B0.49,%200.96,%200.82,%200.09,%200.57,%200.43,%200.91,%200.75,%200.15,%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E6%A1%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/counting_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/counting_sort.md new file mode 100644 index 000000000..34dfc56af --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/counting_sort.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%AE%80%E5%8D%95%E5%AE%9E%E7%8E%B0%EF%BC%8C%E6%97%A0%E6%B3%95%E7%94%A8%E4%BA%8E%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%0A%20%20%20%20%23%201.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m,%20num%29%0A%20%20%20%20%23%202.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E9%81%8D%E5%8E%86%20counter%20%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%200,%201,%202,%200,%204,%200,%202,%202,%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%EF%BC%88%E6%97%A0%E6%B3%95%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E6%95%B4%E5%AE%9E%E7%8E%B0%EF%BC%8C%E5%8F%AF%E6%8E%92%E5%BA%8F%E5%AF%B9%E8%B1%A1%EF%BC%8C%E5%B9%B6%E4%B8%94%E6%98%AF%E7%A8%B3%E5%AE%9A%E6%8E%92%E5%BA%8F%0A%20%20%20%20%23%201.%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%20m%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E7%BB%9F%E8%AE%A1%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E4%BB%A3%E8%A1%A8%20num%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20%E6%B1%82%20counter%20%E7%9A%84%E5%89%8D%E7%BC%80%E5%92%8C%EF%BC%8C%E5%B0%86%E2%80%9C%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E2%80%9D%E8%BD%AC%E6%8D%A2%E4%B8%BA%E2%80%9C%E5%B0%BE%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20%23%20%E5%8D%B3%20counter%5Bnum%5D-1%20%E6%98%AF%20num%20%E5%9C%A8%20res%20%E4%B8%AD%E6%9C%80%E5%90%8E%E4%B8%80%E6%AC%A1%E5%87%BA%E7%8E%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%20nums%20%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84%20res%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%20res%20%E7%94%A8%E4%BA%8E%E8%AE%B0%E5%BD%95%E7%BB%93%E6%9E%9C%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20%E5%B0%86%20num%20%E6%94%BE%E7%BD%AE%E5%88%B0%E5%AF%B9%E5%BA%94%E7%B4%A2%E5%BC%95%E5%A4%84%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E4%BB%A4%E5%89%8D%E7%BC%80%E5%92%8C%E8%87%AA%E5%87%8F%201%20%EF%BC%8C%E5%BE%97%E5%88%B0%E4%B8%8B%E6%AC%A1%E6%94%BE%E7%BD%AE%20num%20%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%BB%93%E6%9E%9C%E6%95%B0%E7%BB%84%20res%20%E8%A6%86%E7%9B%96%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1,%200,%201,%202,%200,%204,%200,%202,%202,%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/heap_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/heap_sort.md new file mode 100644 index 000000000..243f681d0 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/heap_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D,%20n%3A%20int,%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E7%9A%84%E9%95%BF%E5%BA%A6%E4%B8%BA%20n%20%EF%BC%8C%E4%BB%8E%E8%8A%82%E7%82%B9%20i%20%E5%BC%80%E5%A7%8B%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E5%A0%86%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%8A%82%E7%82%B9%20i,%20l,%20r%20%E4%B8%AD%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E8%8A%82%E7%82%B9%EF%BC%8C%E8%AE%B0%E4%B8%BA%20ma%0A%20%20%20%20%20%20%20%20l%20%3D%202%20*%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20*%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E8%8A%82%E7%82%B9%20i%20%E6%9C%80%E5%A4%A7%E6%88%96%E7%B4%A2%E5%BC%95%20l,%20r%20%E8%B6%8A%E7%95%8C%EF%BC%8C%E5%88%99%E6%97%A0%E9%A1%BB%E7%BB%A7%E7%BB%AD%E5%A0%86%E5%8C%96%EF%BC%8C%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E4%B8%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bma%5D%20%3D%20nums%5Bma%5D,%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E5%90%91%E4%B8%8B%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%A0%86%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%BB%BA%E5%A0%86%E6%93%8D%E4%BD%9C%EF%BC%9A%E5%A0%86%E5%8C%96%E9%99%A4%E5%8F%B6%E8%8A%82%E7%82%B9%E4%BB%A5%E5%A4%96%E7%9A%84%E5%85%B6%E4%BB%96%E6%89%80%E6%9C%89%E8%8A%82%E7%82%B9%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20//%202%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums,%20len%28nums%29,%20i%29%0A%20%20%20%20%23%20%E4%BB%8E%E5%A0%86%E4%B8%AD%E6%8F%90%E5%8F%96%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E5%BE%AA%E7%8E%AF%20n-1%20%E8%BD%AE%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201,%200,%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BA%A4%E6%8D%A2%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%8E%E6%9C%80%E5%8F%B3%E5%8F%B6%E8%8A%82%E7%82%B9%EF%BC%88%E4%BA%A4%E6%8D%A2%E9%A6%96%E5%85%83%E7%B4%A0%E4%B8%8E%E5%B0%BE%E5%85%83%E7%B4%A0%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D,%20nums%5Bi%5D%20%3D%20nums%5Bi%5D,%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E4%BB%A5%E6%A0%B9%E8%8A%82%E7%82%B9%E4%B8%BA%E8%B5%B7%E7%82%B9%EF%BC%8C%E4%BB%8E%E9%A1%B6%E8%87%B3%E5%BA%95%E8%BF%9B%E8%A1%8C%E5%A0%86%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums,%20i,%200%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E5%A0%86%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/insertion_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/insertion_sort.md new file mode 100644 index 000000000..51be921b1 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/insertion_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5B0,%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281,%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E5%B0%86%20base%20%E6%8F%92%E5%85%A5%E5%88%B0%E5%B7%B2%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%20%5B0,%20i-1%5D%20%E4%B8%AD%E7%9A%84%E6%AD%A3%E7%A1%AE%E4%BD%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20%E5%B0%86%20nums%5Bj%5D%20%E5%90%91%E5%8F%B3%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20%E5%B0%86%20base%20%E8%B5%8B%E5%80%BC%E5%88%B0%E6%AD%A3%E7%A1%AE%E4%BD%8D%E7%BD%AE%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/merge_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/merge_sort.md new file mode 100644 index 000000000..2c62845d8 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/merge_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20merge%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20mid%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%90%88%E5%B9%B6%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bleft,%20mid%5D,%20%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bmid%2B1,%20right%5D%0A%20%20%20%20%23%20%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%20tmp%20%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%AD%98%E6%94%BE%E5%90%88%E5%B9%B6%E5%90%8E%E7%9A%84%E7%BB%93%E6%9E%9C%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20*%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E8%B5%B7%E5%A7%8B%E7%B4%A2%E5%BC%95%0A%20%20%20%20i,%20j,%20k%20%3D%20left,%20mid%20%2B%201,%200%0A%20%20%20%20%23%20%E5%BD%93%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E9%83%BD%E8%BF%98%E6%9C%89%E5%85%83%E7%B4%A0%E6%97%B6%EF%BC%8C%E8%BF%9B%E8%A1%8C%E6%AF%94%E8%BE%83%E5%B9%B6%E5%B0%86%E8%BE%83%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%86%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%89%A9%E4%BD%99%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%88%B0%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%E4%B8%AD%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B0%86%E4%B8%B4%E6%97%B6%E6%95%B0%E7%BB%84%20tmp%20%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0%E5%A4%8D%E5%88%B6%E5%9B%9E%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%20%E7%9A%84%E5%AF%B9%E5%BA%94%E5%8C%BA%E9%97%B4%0A%20%20%20%20for%20k%20in%20range%280,%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%20%20%23%20%E5%BD%93%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%E9%80%92%E5%BD%92%0A%20%20%20%20%23%20%E5%88%92%E5%88%86%E9%98%B6%E6%AE%B5%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20//%202%20%20%23%20%E8%AE%A1%E7%AE%97%E4%B8%AD%E7%82%B9%0A%20%20%20%20merge_sort%28nums,%20left,%20mid%29%20%20%23%20%E9%80%92%E5%BD%92%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20merge_sort%28nums,%20mid%20%2B%201,%20right%29%20%20%23%20%E9%80%92%E5%BD%92%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%23%20%E5%90%88%E5%B9%B6%E9%98%B6%E6%AE%B5%0A%20%20%20%20merge%28nums,%20left,%20mid,%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7,%203,%202,%206,%200,%201,%205,%204%5D%0A%20%20%20%20merge_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/quick_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/quick_sort.md new file mode 100644 index 000000000..727217026 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/quick_sort.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20partition%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%E9%80%92%E5%BD%92%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%0A%20%20%20%20pivot%20%3D%20partition%28nums,%20left,%20right%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%E3%80%81%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20quick_sort%28nums,%20left,%20pivot%20-%201%29%0A%20%20%20%20quick_sort%28nums,%20pivot%20%2B%201,%20right%29%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20quick_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20median_three%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20mid%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%89%E5%8F%96%E4%B8%89%E4%B8%AA%E5%80%99%E9%80%89%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0%22%22%22%0A%20%20%20%20l,%20m,%20r%20%3D%20nums%5Bleft%5D,%20nums%5Bmid%5D,%20nums%5Bright%5D%0A%20%20%20%20if%20%28l%20%3C%3D%20m%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20m%20%3C%3D%20l%29%3A%0A%20%20%20%20%20%20%20%20return%20mid%20%20%23%20m%20%E5%9C%A8%20l%20%E5%92%8C%20r%20%E4%B9%8B%E9%97%B4%0A%20%20%20%20if%20%28m%20%3C%3D%20l%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20l%20%3C%3D%20m%29%3A%0A%20%20%20%20%20%20%20%20return%20left%20%20%23%20l%20%E5%9C%A8%20m%20%E5%92%8C%20r%20%E4%B9%8B%E9%97%B4%0A%20%20%20%20return%20right%0A%0Adef%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%EF%BC%88%E4%B8%89%E6%95%B0%E5%8F%96%E4%B8%AD%E5%80%BC%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20med%20%3D%20median_three%28nums,%20left,%20%28left%20%2B%20right%29%20//%202,%20right%29%0A%20%20%20%20%23%20%E5%B0%86%E4%B8%AD%E4%BD%8D%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E6%95%B0%E7%BB%84%E6%9C%80%E5%B7%A6%E7%AB%AF%0A%20%20%20%20nums%5Bleft%5D,%20nums%5Bmed%5D%20%3D%20nums%5Bmed%5D,%20nums%5Bleft%5D%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%B8%AD%E4%BD%8D%E5%9F%BA%E5%87%86%E6%95%B0%E4%BC%98%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20partition%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%EF%BC%88%E4%B8%AD%E4%BD%8D%E5%9F%BA%E5%87%86%E6%95%B0%E4%BC%98%E5%8C%96%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%22%22%22%0A%20%20%20%20%23%20%E4%BB%A5%20nums%5Bleft%5D%20%E4%B8%BA%E5%9F%BA%E5%87%86%E6%95%B0%0A%20%20%20%20i,%20j%20%3D%20left,%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E4%BB%8E%E5%8F%B3%E5%90%91%E5%B7%A6%E6%89%BE%E9%A6%96%E4%B8%AA%E5%B0%8F%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E4%BB%8E%E5%B7%A6%E5%90%91%E5%8F%B3%E6%89%BE%E9%A6%96%E4%B8%AA%E5%A4%A7%E4%BA%8E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bj%5D%20%3D%20nums%5Bj%5D,%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%B0%86%E5%9F%BA%E5%87%86%E6%95%B0%E4%BA%A4%E6%8D%A2%E8%87%B3%E4%B8%A4%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E5%88%86%E7%95%8C%E7%BA%BF%0A%20%20%20%20nums%5Bi%5D,%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D,%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E8%BF%94%E5%9B%9E%E5%9F%BA%E5%87%86%E6%95%B0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D,%20left%3A%20int,%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E6%97%B6%E7%BB%88%E6%AD%A2%0A%20%20%20%20while%20left%20%3C%20right%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%93%A8%E5%85%B5%E5%88%92%E5%88%86%E6%93%8D%E4%BD%9C%0A%20%20%20%20%20%20%20%20pivot%20%3D%20partition%28nums,%20left,%20right%29%0A%20%20%20%20%20%20%20%20%23%20%E5%AF%B9%E4%B8%A4%E4%B8%AA%E5%AD%90%E6%95%B0%E7%BB%84%E4%B8%AD%E8%BE%83%E7%9F%AD%E7%9A%84%E9%82%A3%E4%B8%AA%E6%89%A7%E8%A1%8C%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20if%20pivot%20-%20left%20%3C%20right%20-%20pivot%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums,%20left,%20pivot%20-%201%29%20%20%23%20%E9%80%92%E5%BD%92%E6%8E%92%E5%BA%8F%E5%B7%A6%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20pivot%20%2B%201%20%20%23%20%E5%89%A9%E4%BD%99%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bpivot%20%2B%201,%20right%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums,%20pivot%20%2B%201,%20right%29%20%20%23%20%E9%80%92%E5%BD%92%E6%8E%92%E5%BA%8F%E5%8F%B3%E5%AD%90%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20%20%20%20%20right%20%3D%20pivot%20-%201%20%20%23%20%E5%89%A9%E4%BD%99%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bleft,%20pivot%20-%201%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96%EF%BC%89%0A%20%20%20%20nums%20%3D%20%5B2,%204,%201,%200,%203,%205%5D%0A%20%20%20%20quick_sort%28nums,%200,%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%EF%BC%88%E5%B0%BE%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96%EF%BC%89%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/radix_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/radix_sort.md new file mode 100644 index 000000000..98ecd2cd1 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/radix_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20digit%28num%3A%20int,%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%85%83%E7%B4%A0%20num%20%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E5%85%B6%E4%B8%AD%20exp%20%3D%2010%5E%28k-1%29%22%22%22%0A%20%20%20%20%23%20%E4%BC%A0%E5%85%A5%20exp%20%E8%80%8C%E9%9D%9E%20k%20%E5%8F%AF%E4%BB%A5%E9%81%BF%E5%85%8D%E5%9C%A8%E6%AD%A4%E9%87%8D%E5%A4%8D%E6%89%A7%E8%A1%8C%E6%98%82%E8%B4%B5%E7%9A%84%E6%AC%A1%E6%96%B9%E8%AE%A1%E7%AE%97%0A%20%20%20%20return%20%28num%20//%20exp%29%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D,%20exp%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%EF%BC%88%E6%A0%B9%E6%8D%AE%20nums%20%E7%AC%AC%20k%20%E4%BD%8D%E6%8E%92%E5%BA%8F%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%8D%81%E8%BF%9B%E5%88%B6%E7%9A%84%E4%BD%8D%E8%8C%83%E5%9B%B4%E4%B8%BA%200~9%20%EF%BC%8C%E5%9B%A0%E6%AD%A4%E9%9C%80%E8%A6%81%E9%95%BF%E5%BA%A6%E4%B8%BA%2010%20%E7%9A%84%E6%A1%B6%E6%95%B0%E7%BB%84%0A%20%20%20%20counter%20%3D%20%5B0%5D%20*%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E7%BB%9F%E8%AE%A1%200~9%20%E5%90%84%E6%95%B0%E5%AD%97%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D,%20exp%29%20%20%23%20%E8%8E%B7%E5%8F%96%20nums%5Bi%5D%20%E7%AC%AC%20k%20%E4%BD%8D%EF%BC%8C%E8%AE%B0%E4%B8%BA%20d%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%20%20%23%20%E7%BB%9F%E8%AE%A1%E6%95%B0%E5%AD%97%20d%20%E7%9A%84%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%0A%20%20%20%20%23%20%E6%B1%82%E5%89%8D%E7%BC%80%E5%92%8C%EF%BC%8C%E5%B0%86%E2%80%9C%E5%87%BA%E7%8E%B0%E4%B8%AA%E6%95%B0%E2%80%9D%E8%BD%AC%E6%8D%A2%E4%B8%BA%E2%80%9C%E6%95%B0%E7%BB%84%E7%B4%A2%E5%BC%95%E2%80%9D%0A%20%20%20%20for%20i%20in%20range%281,%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E5%80%92%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%8C%E6%A0%B9%E6%8D%AE%E6%A1%B6%E5%86%85%E7%BB%9F%E8%AE%A1%E7%BB%93%E6%9E%9C%EF%BC%8C%E5%B0%86%E5%90%84%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%20res%0A%20%20%20%20res%20%3D%20%5B0%5D%20*%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201,%20-1,%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D,%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%20%20%23%20%E8%8E%B7%E5%8F%96%20d%20%E5%9C%A8%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%20%20%23%20%E5%B0%86%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E5%A1%AB%E5%85%A5%E7%B4%A2%E5%BC%95%20j%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%20%20%23%20%E5%B0%86%20d%20%E7%9A%84%E6%95%B0%E9%87%8F%E5%87%8F%201%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E7%BB%93%E6%9E%9C%E8%A6%86%E7%9B%96%E5%8E%9F%E6%95%B0%E7%BB%84%20nums%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%88%A4%E6%96%AD%E6%9C%80%E5%A4%A7%E4%BD%8D%E6%95%B0%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%20%E6%8C%89%E7%85%A7%E4%BB%8E%E4%BD%8E%E4%BD%8D%E5%88%B0%E9%AB%98%E4%BD%8D%E7%9A%84%E9%A1%BA%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%AF%B9%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%20k%20%E4%BD%8D%E6%89%A7%E8%A1%8C%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%201%20-%3E%20exp%20%3D%201%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%202%20-%3E%20exp%20%3D%2010%0A%20%20%20%20%20%20%20%20%23%20%E5%8D%B3%20exp%20%3D%2010%5E%28k-1%29%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums,%20exp%29%0A%20%20%20%20%20%20%20%20exp%20*%3D%2010%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%0A%20%20%20%20nums%20%3D%20%5B%0A%20%20%20%20%20%20%20%20105,%0A%20%20%20%20%20%20%20%20356,%0A%20%20%20%20%20%20%20%20428,%0A%20%20%20%20%20%20%20%20348,%0A%20%20%20%20%20%20%20%20818,%0A%20%20%20%20%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%22%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_sorting/selection_sort.md b/zh-hant/codes/pythontutor/chapter_sorting/selection_sort.md new file mode 100644 index 000000000..2a9e7531d --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_sorting/selection_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E4%B8%BA%20%5Bi,%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%BE%AA%E7%8E%AF%EF%BC%9A%E6%89%BE%E5%88%B0%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E5%86%85%E7%9A%84%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201,%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%E8%AE%B0%E5%BD%95%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E7%9A%84%E7%B4%A2%E5%BC%95%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%E8%AF%A5%E6%9C%80%E5%B0%8F%E5%85%83%E7%B4%A0%E4%B8%8E%E6%9C%AA%E6%8E%92%E5%BA%8F%E5%8C%BA%E9%97%B4%E7%9A%84%E9%A6%96%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2%0A%20%20%20%20%20%20%20%20nums%5Bi%5D,%20nums%5Bk%5D%20%3D%20nums%5Bk%5D,%20nums%5Bi%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4,%201,%203,%201,%205,%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%E5%AE%8C%E6%88%90%E5%90%8E%20nums%20%3D%22,%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_queue.md b/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_queue.md new file mode 100644 index 000000000..cb987db7f --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_queue.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ArrayQueue%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E7%8E%AF%E5%BD%A2%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E7%9A%84%E9%98%9F%E5%88%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20size%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._nums%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20*%20size%20%20%23%20%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E9%98%9F%E5%88%97%E5%85%83%E7%B4%A0%E7%9A%84%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20self._front%3A%20int%20%3D%200%20%20%23%20%E9%98%9F%E9%A6%96%E6%8C%87%E9%92%88%EF%BC%8C%E6%8C%87%E5%90%91%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%20%20%23%20%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._nums%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._size%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E9%98%9F%E5%88%97%E5%B7%B2%E6%BB%A1%22%29%0A%20%20%20%20%20%20%20%20%23%20%E8%AE%A1%E7%AE%97%E9%98%9F%E5%B0%BE%E6%8C%87%E9%92%88%EF%BC%8C%E6%8C%87%E5%90%91%E9%98%9F%E5%B0%BE%E7%B4%A2%E5%BC%95%20%2B%201%0A%20%20%20%20%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E5%8F%96%E4%BD%99%E6%93%8D%E4%BD%9C%E5%AE%9E%E7%8E%B0%20rear%20%E8%B6%8A%E8%BF%87%E6%95%B0%E7%BB%84%E5%B0%BE%E9%83%A8%E5%90%8E%E5%9B%9E%E5%88%B0%E5%A4%B4%E9%83%A8%0A%20%20%20%20%20%20%20%20rear%3A%20int%20%3D%20%28self._front%20%2B%20self._size%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%B0%86%20num%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20%20%20%20%20self._nums%5Brear%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20num%3A%20int%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20%23%20%E9%98%9F%E9%A6%96%E6%8C%87%E9%92%88%E5%90%91%E5%90%8E%E7%A7%BB%E5%8A%A8%E4%B8%80%E4%BD%8D%EF%BC%8C%E8%8B%A5%E8%B6%8A%E8%BF%87%E5%B0%BE%E9%83%A8%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%E5%88%B0%E6%95%B0%E7%BB%84%E5%A4%B4%E9%83%A8%0A%20%20%20%20%20%20%20%20self._front%20%3D%20%28self._front%20%2B%201%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E9%98%9F%E5%88%97%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._nums%5Bself._front%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%94%E5%9B%9E%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20res%20%3D%20%5B0%5D%20*%20self.size%28%29%0A%20%20%20%20%20%20%20%20j%3A%20int%20%3D%20self._front%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20self._nums%5B%28j%20%25%20self.capacity%28%29%29%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20queue%20%3D%20ArrayQueue%2810%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_stack.md b/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_stack.md new file mode 100644 index 000000000..c28ef2a54 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_stack_and_queue/array_stack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ArrayStack%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E6%95%B0%E7%BB%84%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%A0%88%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._stack%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._stack%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%20%3D%3D%20%5B%5D%0A%0A%20%20%20%20def%20push%28self,%20item%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20self._stack.append%28item%29%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E6%A0%88%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack.pop%28%29%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E6%A0%88%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack%5B-1%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%94%E5%9B%9E%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20stack%20%3D%20ArrayStack%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md b/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md new file mode 100644 index 000000000..340b9f57d --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%0Aclass%20LinkedListQueue%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E9%98%9F%E5%88%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._front%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%A4%B4%E8%8A%82%E7%82%B9%20front%0A%20%20%20%20%20%20%20%20self._rear%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B0%BE%E8%8A%82%E7%82%B9%20rear%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._front%0A%0A%20%20%20%20def%20push%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E8%8A%82%E7%82%B9%E5%90%8E%E6%B7%BB%E5%8A%A0%20num%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28num%29%0A%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E9%98%9F%E5%88%97%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E4%BB%A4%E5%A4%B4%E3%80%81%E5%B0%BE%E8%8A%82%E7%82%B9%E9%83%BD%E6%8C%87%E5%90%91%E8%AF%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20if%20self._front%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._front%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E9%98%9F%E5%88%97%E4%B8%8D%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E5%B0%86%E8%AF%A5%E8%8A%82%E7%82%B9%E6%B7%BB%E5%8A%A0%E5%88%B0%E5%B0%BE%E8%8A%82%E7%82%B9%E5%90%8E%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear.next%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E9%98%9F%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%A4%B4%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20self._front%20%3D%20self._front.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E9%98%9F%E5%88%97%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._front.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BD%AC%E5%8C%96%E4%B8%BA%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20queue%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20temp%20%3D%20self._front%0A%20%20%20%20%20%20%20%20while%20temp%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28temp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20temp.next%0A%20%20%20%20%20%20%20%20return%20queue%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20queue%20%3D%20LinkedListQueue%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20queue%20%3D%22,%20queue.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20queue%20%3D%22,%20queue.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md b/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md new file mode 100644 index 000000000..10ffaaca1 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%0Aclass%20LinkedListStack%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E4%BA%8E%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%A0%88%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._peek%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A4%E6%96%AD%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._peek%0A%0A%20%20%20%20def%20push%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%85%A5%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28val%29%0A%20%20%20%20%20%20%20%20node.next%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%87%BA%E6%A0%88%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20self._peek.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E6%A0%88%E4%B8%BA%E7%A9%BA%22%29%0A%20%20%20%20%20%20%20%20return%20self._peek.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BD%AC%E5%8C%96%E4%B8%BA%E5%88%97%E8%A1%A8%E7%94%A8%E4%BA%8E%E6%89%93%E5%8D%B0%22%22%22%0A%20%20%20%20%20%20%20%20arr%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20node%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20while%20node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20arr.append%28node.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20node.next%0A%20%20%20%20%20%20%20%20arr.reverse%28%29%0A%20%20%20%20%20%20%20%20return%20arr%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20stack%20%3D%20LinkedListStack%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_tree/array_binary_tree.md b/zh-hant/codes/pythontutor/chapter_tree/array_binary_tree.md new file mode 100644 index 000000000..3ebb2f845 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_tree/array_binary_tree.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20ArrayBinaryTree%3A%0A%20%20%20%20%22%22%22%E6%95%B0%E7%BB%84%E8%A1%A8%E7%A4%BA%E4%B8%8B%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91%E7%B1%BB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self,%20arr%3A%20list%5Bint%20%7C%20None%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._tree%20%3D%20list%28arr%29%0A%0A%20%20%20%20def%20size%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%97%E8%A1%A8%E5%AE%B9%E9%87%8F%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._tree%29%0A%0A%20%20%20%20def%20val%28self,%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%B4%A2%E5%BC%95%E4%B8%BA%20i%20%E8%8A%82%E7%82%B9%E7%9A%84%E5%80%BC%22%22%22%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20self._tree%5Bi%5D%0A%0A%20%20%20%20def%20left%28self,%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self,%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20*%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self,%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%8E%B7%E5%8F%96%E7%88%B6%E8%8A%82%E7%82%B9%E7%9A%84%E7%B4%A2%E5%BC%95%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20//%202%0A%0A%20%20%20%20def%20level_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20dfs%28self,%20i%3A%20int,%20order%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22pre%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.left%28i%29,%20order%29%0A%20%20%20%20%20%20%20%20%23%20%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22in%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.right%28i%29,%20order%29%0A%20%20%20%20%20%20%20%20%23%20%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%22post%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%0A%20%20%20%20def%20pre_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280,%20order%3D%22pre%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20in_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280,%20order%3D%22in%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20post_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280,%20order%3D%22post%22%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20arr%20%3D%20%5B1,%202,%203,%204,%20None,%206,%20None%5D%0A%20%20%20%20abt%20%3D%20ArrayBinaryTree%28arr%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E8%8A%82%E7%82%B9%0A%20%20%20%20i%20%3D%201%0A%20%20%20%20l,%20r,%20p%20%3D%20abt.left%28i%29,%20abt.right%28i%29,%20abt.parent%28i%29%0A%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E6%A0%91%0A%20%20%20%20res%20%3D%20abt.level_order%28%29%0A%20%20%20%20res%20%3D%20abt.pre_order%28%29%0A%20%20%20%20res%20%3D%20abt.in_order%28%29%0A%20%20%20%20res%20%3D%20abt.post_order%28%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_tree/binary_search_tree.md b/zh-hant/codes/pythontutor/chapter_tree/binary_search_tree.md new file mode 100644 index 000000000..960c4c2f2 --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_tree/binary_search_tree.md @@ -0,0 +1,14 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%A9%BA%E6%A0%91%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20search%28self,%20num%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20cur%20%3D%20self._root%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%9F%A5%E6%89%BE%EF%BC%8C%E8%B6%8A%E8%BF%87%E5%8F%B6%E8%8A%82%E7%82%B9%E5%90%8E%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20cur.val%20%3E%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%EF%BC%8C%E8%B7%B3%E5%87%BA%E5%BE%AA%E7%8E%AF%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20return%20cur%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E6%A0%91%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%9F%A5%E6%89%BE%EF%BC%8C%E8%B6%8A%E8%BF%87%E5%8F%B6%E8%8A%82%E7%82%B9%E5%90%8E%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E9%87%8D%E5%A4%8D%E8%8A%82%E7%82%B9%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4,%202,%206,%201,%203,%205,%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%0A%20%20%20%20node%20%3D%20bst.search%287%29%0A%20%20%20%20print%28%22%5Cn%E6%9F%A5%E6%89%BE%E5%88%B0%E7%9A%84%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%E4%B8%BA%3A%20%7B%7D%EF%BC%8C%E8%8A%82%E7%82%B9%E5%80%BC%20%3D%20%7B%7D%22.format%28node,%20node.val%29%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E7%A9%BA%E6%A0%91%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E8%8B%A5%E6%A0%91%E4%B8%BA%E7%A9%BA%EF%BC%8C%E5%88%99%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E6%9F%A5%E6%89%BE%EF%BC%8C%E8%B6%8A%E8%BF%87%E5%8F%B6%E8%8A%82%E7%82%B9%E5%90%8E%E8%B7%B3%E5%87%BA%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%89%BE%E5%88%B0%E9%87%8D%E5%A4%8D%E8%8A%82%E7%82%B9%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%8F%B3%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE%E5%9C%A8%20cur%20%E7%9A%84%E5%B7%A6%E5%AD%90%E6%A0%91%E4%B8%AD%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4,%202,%206,%201,%203,%205,%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%0A%20%20%20%20bst.insert%2816%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%22%22%22%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%20%20%20%20def%20remove%28self,%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E6%9F%A5%E6%89%BE%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20cur,%20pre%20%3D%20self._root,%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20if%20cur%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E8%8A%82%E7%82%B9%E6%95%B0%E9%87%8F%20%3D%200%20or%201%0A%20%20%20%20%20%20%20%20if%20cur.left%20is%20None%20or%20cur.right%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BD%93%E5%AD%90%E8%8A%82%E7%82%B9%E6%95%B0%E9%87%8F%20%3D%200%20/%201%20%E6%97%B6%EF%BC%8C%20child%20%3D%20null%20/%20%E8%AF%A5%E5%AD%90%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20child%20%3D%20cur.left%20or%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20!%3D%20self._root%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20pre.left%20%3D%3D%20cur%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20child%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E8%8A%82%E7%82%B9%E6%95%B0%E9%87%8F%20%3D%202%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E4%B8%AD%20cur%20%E7%9A%84%E4%B8%8B%E4%B8%80%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%3A%20TreeNode%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20tmp.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20tmp.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%80%92%E5%BD%92%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20self.remove%28tmp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%94%A8%20tmp%20%E8%A6%86%E7%9B%96%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20cur.val%20%3D%20tmp.val%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4,%202,%206,%201,%203,%205,%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20bst.remove%281%29%20%23%20%E5%BA%A6%E4%B8%BA%200%0A%20%20%20%20bst.remove%282%29%20%23%20%E5%BA%A6%E4%B8%BA%201%0A%20%20%20%20bst.remove%284%29%20%23%20%E5%BA%A6%E4%B8%BA%202&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_tree/binary_tree_bfs.md b/zh-hant/codes/pythontutor/chapter_tree/binary_tree_bfs.md new file mode 100644 index 000000000..e59386e4d --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_tree/binary_tree_bfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%EF%BC%8C%E5%8A%A0%E5%85%A5%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%80%E4%B8%AA%E5%88%97%E8%A1%A8%EF%BC%8C%E7%94%A8%E4%BA%8E%E4%BF%9D%E5%AD%98%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%20%20%23%20%E9%98%9F%E5%88%97%E5%87%BA%E9%98%9F%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%20%20%23%20%E4%BF%9D%E5%AD%98%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%85%A5%E9%98%9F%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%20%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%85%A5%E9%98%9F%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E8%BF%99%E9%87%8C%E5%80%9F%E5%8A%A9%E4%BA%86%E4%B8%80%E4%B8%AA%E4%BB%8E%E6%95%B0%E7%BB%84%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%87%BD%E6%95%B0%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1,%202,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29&cumulative=false&curInstr=127&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/pythontutor/chapter_tree/binary_tree_dfs.md b/zh-hant/codes/pythontutor/chapter_tree/binary_tree_dfs.md new file mode 100644 index 000000000..16128184e --- /dev/null +++ b/zh-hant/codes/pythontutor/chapter_tree/binary_tree_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D,%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%EF%BC%9A%E9%80%92%E5%BD%92%22%22%22%0A%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E8%B6%85%E5%87%BA%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6%EF%BC%8C%E6%88%96%E8%80%85%E5%AF%B9%E5%BA%94%E7%9A%84%E5%85%83%E7%B4%A0%E4%B8%BA%20None%20%EF%BC%8C%E5%88%99%E8%BF%94%E5%9B%9E%20None%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E5%BD%93%E5%89%8D%E8%8A%82%E7%82%B9%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E9%80%92%E5%BD%92%E6%9E%84%E5%BB%BA%E5%B7%A6%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr,%202%20*%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E5%B0%86%E5%88%97%E8%A1%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%A0%91%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr,%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%9A%E6%A0%B9%E8%8A%82%E7%82%B9%20-%3E%20%E5%B7%A6%E5%AD%90%E6%A0%91%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%9A%E5%B7%A6%E5%AD%90%E6%A0%91%20-%3E%20%E6%A0%B9%E8%8A%82%E7%82%B9%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A0%91%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%9A%E5%B7%A6%E5%AD%90%E6%A0%91%20-%3E%20%E5%8F%B3%E5%AD%90%E6%A0%91%20-%3E%20%E6%A0%B9%E8%8A%82%E7%82%B9%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E8%BF%99%E9%87%8C%E5%80%9F%E5%8A%A9%E4%BA%86%E4%B8%80%E4%B8%AA%E4%BB%8E%E6%95%B0%E7%BB%84%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%87%BD%E6%95%B0%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1,%202,%203,%204,%205,%206,%207%5D%29%0A%0A%20%20%20%20%23%20%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29%0A%0A%20%20%20%20%23%20%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29%0A%0A%20%20%20%20%23%20%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E8%8A%82%E7%82%B9%E6%89%93%E5%8D%B0%E5%BA%8F%E5%88%97%20%3D%20%22,%20res%29&cumulative=false&curInstr=129&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/codes/ruby/chapter_array_and_linkedlist/array.rb b/zh-hant/codes/ruby/chapter_array_and_linkedlist/array.rb new file mode 100644 index 000000000..5d0270abf --- /dev/null +++ b/zh-hant/codes/ruby/chapter_array_and_linkedlist/array.rb @@ -0,0 +1,107 @@ +=begin +File: array.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 隨機訪問元素 ### +def random_access(nums) + # 在區間 [0, nums.length) 中隨機抽取一個數字 + random_index = Random.rand(0...nums.length) + + # 獲取並返回隨機元素 + nums[random_index] +end + + +### 擴展陣列長度 ### +# 請注意,Ruby 的 Array 是動態陣列,可以直接擴展 +# 為了方便學習,本函式將 Array 看作長度不可變的陣列 +def extend(nums, enlarge) + # 初始化一個擴展長度後的陣列 + res = Array.new(nums.length + enlarge, 0) + + # 將原陣列中的所有元素複製到新陣列 + for i in 0...nums.length + res[i] = nums[i] + end + + # 返回擴展後的新陣列 + res +end + +### 在陣列的索引 index 處插入元素 num ### +def insert(nums, num, index) + # 把索引 index 以及之後的所有元素向後移動一位 + for i in (nums.length - 1).downto(index + 1) + nums[i] = nums[i - 1] + end + + # 將 num 賦給 index 處的元素 + nums[index] = num +end + + +### 刪除索引 index 處的元素 ### +def remove(nums, index) + # 把索引 index 之後的所有元素向前移動一位 + for i in index...nums.length + nums[i] = nums[i + 1] || 0 + end +end + +### 走訪陣列 ### +def traverse(nums) + count = 0 + + # 透過索引走訪陣列 + for i in 0...nums.length + count += nums[i] + end + + # 直接走訪陣列元素 + for num in nums + count += num + end +end + +### 在陣列中查詢指定元素 ### +def find(nums, target) + for i in 0...nums.length + return i if nums[i] == target + end + + -1 +end + + +### Driver Code ### + +# 初始化陣列 +arr = Array.new(5, 0) +puts "陣列 arr = #{arr}" +nums = [1, 3, 2, 5, 4] +puts "陣列 nums = #{nums}" + +# 隨機訪問 +random_num = random_access(nums) +puts "在 nums 中獲取隨機元素 #{random_num}" + +# 長度擴展 +nums = extend(nums, 3) +puts "將陣列長度擴展至 8 ,得到 nums = #{nums}" + +# 插入元素 +insert(nums, 6, 3) +puts "在索引 3 處插入數字 6 ,得到 nums = #{nums}" + +# 刪除元素 +remove(nums, 2) +puts "刪除索引 2 處的元素,得到 nums = #{nums}" + +# 走訪陣列 +traverse(nums) + +# 查詢元素 +index = find(nums, 3) +puts "在 nums 中查詢元素 3 ,得到索引 = #{index}" diff --git a/zh-hant/codes/ruby/chapter_array_and_linkedlist/linked_list.rb b/zh-hant/codes/ruby/chapter_array_and_linkedlist/linked_list.rb new file mode 100644 index 000000000..9547db9e4 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_array_and_linkedlist/linked_list.rb @@ -0,0 +1,82 @@ +=begin +File: linked_list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' +require_relative '../utils/print_util' + +### 在鏈結串列的節點 n0 之後插入節點 _p ### +# Ruby 的 `p` 是一個內建函式, `P` 是一個常數,所以可以使用 `_p` 代替 +def insert(n0, _p) + n1 = n0.next + _p.next = n1 + n0.next = _p +end + +### 刪除鏈結串列的節點 n0 之後的首個節點 ### +def remove(n0) + return if n0.next.nil? + + # n0 -> remove_node -> n1 + remove_node = n0.next + n1 = remove_node.next + n0.next = n1 +end + +### 訪問鏈結串列中索引為 index 的節點 ### +def access(head, index) + for i in 0...index + return nil if head.nil? + head = head.next + end + + head +end + +### 在鏈結串列中查詢值為 target 的首個節點 ### +def find(head, target) + index = 0 + while head + return index if head.val == target + head = head.next + index += 1 + end + + -1 +end + +### Driver Code ### + +# 初始化鏈結串列 +# 初始化各個節點 +n0 = ListNode.new(1) +n1 = ListNode.new(3) +n2 = ListNode.new(2) +n3 = ListNode.new(5) +n4 = ListNode.new(4) +# 構建節點之間的引用 +n0.next = n1 +n1.next = n2 +n2.next = n3 +n3.next = n4 +puts "初始化的鏈結串列為" +print_linked_list(n0) + +# 插入節點 +insert(n0, ListNode.new(0)) +print_linked_list n0 + +# 刪除節點 +remove(n0) +puts "刪除節點後的鏈結串列為" +print_linked_list(n0) + +# 訪問節點 +node = access(n0, 3) +puts "鏈結串列中索引 3 處的節點的值 = #{node.val}" + +# 查詢節點 +index = find(n0, 2) +puts "鏈結串列中值為 2 的節點的索引 = #{index}" diff --git a/zh-hant/codes/ruby/chapter_array_and_linkedlist/list.rb b/zh-hant/codes/ruby/chapter_array_and_linkedlist/list.rb new file mode 100644 index 000000000..3bffb3016 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_array_and_linkedlist/list.rb @@ -0,0 +1,59 @@ +=begin +File: list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### + +# 初始化串列 +nums = [1, 3, 2, 5, 4] +puts "串列 nums = #{nums}" + +# 訪問元素 +num = nums[1] +puts "訪問索引 1 處的元素,得到 num = #{num}" + +# 更新元素 +nums[1] = 0 +puts "將索引 1 處的元素更新為 0 ,得到 nums = #{nums}" + +# 清空串列 +nums.clear +puts "清空串列後 nums = #{nums}" + +# 在尾部新增元素 +nums << 1 +nums << 3 +nums << 2 +nums << 5 +nums << 4 +puts "新增元素後 nums = #{nums}" + +# 在中間插入元素 +nums.insert(3, 6) +puts "在索引 3 處插入元素 6 ,得到 nums = #{nums}" + +# 刪除元素 +nums.delete_at(3) +puts "刪除索引 3 處的元素,得到 nums = #{nums}" + +# 透過索引走訪串列 +count = 0 +for i in 0...nums.length + count += nums[i] +end + +# 直接走訪串列元素 +count = 0 +nums.each do |x| + count += x +end + +# 拼接兩個串列 +nums1 = [6, 8, 7, 10, 9] +nums += nums1 +puts "將串列 nums1 拼接到 nums 之後,得到 nums = #{nums}" + +nums = nums.sort { |a, b| a <=> b } +puts "排序串列後 nums = #{nums}" diff --git a/zh-hant/codes/ruby/chapter_array_and_linkedlist/my_list.rb b/zh-hant/codes/ruby/chapter_array_and_linkedlist/my_list.rb new file mode 100644 index 000000000..c35d06c1b --- /dev/null +++ b/zh-hant/codes/ruby/chapter_array_and_linkedlist/my_list.rb @@ -0,0 +1,131 @@ +=begin +File: my_list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 串列類別 ### +class MyList + attr_reader :size # 獲取串列長度(當前元素數量) + attr_reader :capacity # 獲取串列容量 + + ### 建構子 ### + def initialize + @capacity = 10 + @size = 0 + @extend_ratio = 2 + @arr = Array.new(capacity) + end + + ### 訪問元素 ### + def get(index) + # 索引如果越界,則丟擲異常,下同 + raise IndexError, "索引越界" if index < 0 || index >= size + @arr[index] + end + + ### 訪問元素 ### + def set(index, num) + raise IndexError, "索引越界" if index < 0 || index >= size + @arr[index] = num + end + + ### 在尾部新增元素 ### + def add(num) + # 元素數量超出容量時,觸發擴容機制 + extend_capacity if size == capacity + @arr[size] = num + + # 更新元素數量 + @size += 1 + end + + ### 在中間插入元素 ### + def insert(index, num) + raise IndexError, "索引越界" if index < 0 || index >= size + + # 元素數量超出容量時,觸發擴容機制 + extend_capacity if size == capacity + + # 將索引 index 以及之後的元素都向後移動一位 + for j in (size - 1).downto(index) + @arr[j + 1] = @arr[j] + end + @arr[index] = num + + # 更新元素數量 + @size += 1 + end + + ### 刪除元素 ### + def remove(index) + raise IndexError, "索引越界" if index < 0 || index >= size + num = @arr[index] + + # 將將索引 index 之後的元素都向前移動一位 + for j in index...size + @arr[j] = @arr[j + 1] + end + + # 更新元素數量 + @size -= 1 + + # 返回被刪除的元素 + num + end + + ### 串列擴容 ### + def extend_capacity + # 新建一個長度為原陣列 extend_ratio 倍的新陣列,並將原陣列複製到新陣列 + arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1)) + # 更新串列容量 + @capacity = arr.length + end + + ### 將串列轉換為陣列 ### + def to_array + sz = size + # 僅轉換有效長度範圍內的串列元素 + arr = Array.new(sz) + for i in 0...sz + arr[i] = get(i) + end + arr + end +end + +### Driver Code ### + +# 初始化串列 +nums = MyList.new + +# 在尾部新增元素 +nums.add(1) +nums.add(3) +nums.add(2) +nums.add(5) +nums.add(4) +puts "串列 nums = #{nums.to_array} ,容量 = #{nums.capacity} ,長度 = #{nums.size}" + +# 在中間插入元素 +nums.insert(3, 6) +puts "在索引 3 處插入數字 6 ,得到 nums = #{nums.to_array}" + +# 刪除元素 +nums.remove(3) +puts "刪除索引 3 的元素,得到 nums = #{nums.to_array}" + +# 訪問元素 +num = nums.get(1) +puts "訪問索引 1 處的元素,得到 num = #{num}" + +# 更新元素 +nums.set(1, 0) +puts "將索引 1 處的元素更新為 0 ,得到 nums = #{nums.to_array}" + +# 測試擴容機制 +for i in 0...10 + # 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i) +end +puts "擴容後的串列 nums = #{nums.to_array} ,容量 = #{nums.capacity} ,長度 = #{nums.size}" diff --git a/zh-hant/codes/ruby/chapter_computational_complexity/iteration.rb b/zh-hant/codes/ruby/chapter_computational_complexity/iteration.rb new file mode 100644 index 000000000..1f85bbe10 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_computational_complexity/iteration.rb @@ -0,0 +1,78 @@ +=begin +File: iteration.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com) +=end + +### for 迴圈 ### +def for_loop(n) + res = 0 + + # 迴圈求和 1, 2, ..., n-1, n + for i in 1..n + res += i + end + + res +end + +### while 迴圈 ### +def while_loop(n) + res = 0 + i = 1 # 初始化條件變數 + + # 迴圈求和 1, 2, ..., n-1, n + while i <= n + res += i + i += 1 # 更新條件變數 + end + + res +end + +### while 迴圈(兩次更新)### +def while_loop_ii(n) + res = 0 + i = 1 # 初始化條件變數 + + # 迴圈求和 1, 4, 10, ... + while i <= n + res += i + # 更新條件變數 + i += 1 + i *= 2 + end + + res +end + +### 雙層 for 迴圈 ### +def nested_for_loop(n) + res = "" + + # 迴圈 i = 1, 2, ..., n-1, n + for i in 1..n + # 迴圈 j = 1, 2, ..., n-1, n + for j in 1..n + res += "(#{i}, #{j}), " + end + end + + res +end + +### Driver Code ### + +n = 5 + +res = for_loop(n) +puts "\nfor 迴圈的求和結果 res = #{res}" + +res = while_loop(n) +puts "\nwhile 迴圈的求和結果 res = #{res}" + +res = while_loop_ii(n) +puts "\nwhile 迴圈(兩次更新)求和結果 res = #{res}" + +res = nested_for_loop(n) +puts "\n雙層 for 迴圈的走訪結果 #{res}" diff --git a/zh-hant/codes/ruby/chapter_computational_complexity/recursion.rb b/zh-hant/codes/ruby/chapter_computational_complexity/recursion.rb new file mode 100644 index 000000000..b95c7c88b --- /dev/null +++ b/zh-hant/codes/ruby/chapter_computational_complexity/recursion.rb @@ -0,0 +1,69 @@ +=begin +File: recursion.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 遞迴 ### +def recur(n) + # 終止條件 + return 1 if n == 1 + # 遞:遞迴呼叫 + res = recur(n - 1) + # 迴:返回結果 + n + res +end + +### 使用迭代模擬遞迴 ### +def for_loop_recur(n) + # 使用一個顯式的堆疊來模擬系統呼叫堆疊 + stack = [] + res = 0 + + # 遞:遞迴呼叫 + for i in n.downto(0) + # 透過“入堆疊操作”模擬“遞” + stack << i + end + # 迴:返回結果 + while !stack.empty? + res += stack.pop + end + + # res = 1+2+3+...+n + res +end + +### 尾遞迴 ### +def tail_recur(n, res) + # 終止條件 + return res if n == 0 + # 尾遞迴呼叫 + tail_recur(n - 1, res + n) +end + +### 費波那契數列:遞迴 ### +def fib(n) + # 終止條件 f(1) = 0, f(2) = 1 + return n - 1 if n == 1 || n == 2 + # 遞迴呼叫 f(n) = f(n-1) + f(n-2) + res = fib(n - 1) + fib(n - 2) + # 返回結果 f(n) + res +end + +### Driver Code ### + +n = 5 + +res = recur(n) +puts "\n遞迴函式的求和結果 res = #{res}" + +res = for_loop_recur(n) +puts "\n使用迭代模擬遞迴求和結果 res = #{res}" + +res = tail_recur(n, 0) +puts "\n尾遞迴函式的求和結果 res = #{res}" + +res = fib(n) +puts "\n費波那契數列的第 #{n} 項為 #{res}" diff --git a/zh-hant/codes/ruby/chapter_computational_complexity/space_complexity.rb b/zh-hant/codes/ruby/chapter_computational_complexity/space_complexity.rb new file mode 100644 index 000000000..e58de7df2 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_computational_complexity/space_complexity.rb @@ -0,0 +1,91 @@ +=begin +File: space_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 函式 ### +def function + # 執行某些操作 + 0 +end + +### 常數階 ### +def constant(n) + # 常數、變數、物件佔用 O(1) 空間 + a = 0 + nums = [0] * 10000 + node = ListNode.new + + # 迴圈中的變數佔用 O(1) 空間 + (0...n).each { c = 0 } + # 迴圈中的函式佔用 O(1) 空間 + (0...n).each { function } +end + +### 線性階 ### +def linear(n) + # 長度為 n 的串列佔用 O(n) 空間 + nums = Array.new(n, 0) + + # 長度為 n 的雜湊表佔用 O(n) 空間 + hmap = {} + for i in 0...n + hmap[i] = i.to_s + end +end + +### 線性階(遞迴實現)### +def linear_recur(n) + puts "遞迴 n = #{n}" + return if n == 1 + linear_recur(n - 1) +end + +### 平方階 ### +def quadratic(n) + # 二維串列佔用 O(n^2) 空間 + Array.new(n) { Array.new(n, 0) } +end + +### 平方階(遞迴實現)### +def quadratic_recur(n) + return 0 unless n > 0 + + # 陣列 nums 長度為 n, n-1, ..., 2, 1 + nums = Array.new(n, 0) + quadratic_recur(n - 1) +end + +### 指數階(建立滿二元樹)### +def build_tree(n) + return if n == 0 + + TreeNode.new.tap do |root| + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + end +end + +### Driver Code ### + +n = 5 + +# 常數階 +constant(n) + +# 線性階 +linear(n) +linear_recur(n) + +# 平方階 +quadratic(n) +quadratic_recur(n) + +# 指數階 +root = build_tree(n) +print_tree(root) diff --git a/zh-hant/codes/ruby/chapter_computational_complexity/time_complexity.rb b/zh-hant/codes/ruby/chapter_computational_complexity/time_complexity.rb new file mode 100644 index 000000000..7b1373532 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_computational_complexity/time_complexity.rb @@ -0,0 +1,164 @@ +=begin +File: time_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 常數階 ### +def constant(n) + count = 0 + size = 100000 + + (0...size).each { count += 1 } + + count +end + +### 線性階 ### +def linear(n) + count = 0 + (0...n).each { count += 1 } + count +end + +### 線性階(走訪陣列)### +def array_traversal(nums) + count = 0 + + # 迴圈次數與陣列長度成正比 + for num in nums + count += 1 + end + + count +end + +### 平方階 ### +def quadratic(n) + count = 0 + + # 迴圈次數與資料大小 n 成平方關係 + for i in 0...n + for j in 0...n + count += 1 + end + end + + count +end + +### 平方階(泡沫排序)### +def bubble_sort(nums) + count = 0 # 計數器 + + # 外迴圈:未排序區間為 [0, i] + for i in (nums.length - 1).downto(0) + # 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0...i + if nums[j] > nums[j + 1] + # 交換 nums[j] 與 nums[j + 1] + tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # 元素交換包含 3 個單元操作 + end + end + end + + count +end + +### 指數階(迴圈實現)### +def exponential(n) + count, base = 0, 1 + + # 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + (0...n).each do + (0...base).each { count += 1 } + base *= 2 + end + + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + count +end + +### 指數階(遞迴實現)### +def exp_recur(n) + return 1 if n == 1 + exp_recur(n - 1) + exp_recur(n - 1) + 1 +end + +### 對數階(迴圈實現)### +def logarithmic(n) + count = 0 + + while n > 1 + n /= 2 + count += 1 + end + + count +end + +### 對數階(遞迴實現)### +def log_recur(n) + return 0 unless n > 1 + log_recur(n / 2) + 1 +end + +### 線性對數階 +def linear_log_recur(n) + return 1 unless n > 1 + + count = linear_log_recur(n / 2) + linear_log_recur(n / 2) + (0...n).each { count += 1 } + + count +end + +### 階乘階(遞迴實現)### +def factorial_recur(n) + return 1 if n == 0 + + count = 0 + # 從 1 個分裂出 n 個 + (0...n).each { count += factorial_recur(n - 1) } + + count +end + +### Driver Code ### + +# 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 +n = 8 +puts "輸入資料大小 n = #{n}" + +count = constant(n) +puts "常數階的操作數量 = #{count}" + +count = linear(n) +puts "線性階的操作數量 = #{count}" +count = array_traversal(Array.new(n, 0)) +puts "線性階(走訪陣列)的操作數量 = #{count}" + +count = quadratic(n) +puts "平方階的操作數量 = #{count}" +nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1] +count = bubble_sort(nums) +puts "平方階(泡沫排序)的操作數量 = #{count}" + +count = exponential(n) +puts "指數階(迴圈實現)的操作數量 = #{count}" +count = exp_recur(n) +puts "指數階(遞迴實現)的操作數量 = #{count}" + +count = logarithmic(n) +puts "對數階(迴圈實現)的操作數量 = #{count}" +count = log_recur(n) +puts "對數階(遞迴實現)的操作數量 = #{count}" + +count = linear_log_recur(n) +puts "線性對數階(遞迴實現)的操作數量 = #{count}" + +count = factorial_recur(n) +puts "階乘階(遞迴實現)的操作數量 = #{count}" diff --git a/zh-hant/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb b/zh-hant/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb new file mode 100644 index 000000000..3e0d9b130 --- /dev/null +++ b/zh-hant/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb @@ -0,0 +1,34 @@ +=begin +File: worst_best_time_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 生成一個陣列,元素為: 1, 2, ..., n ,順序被打亂 ### +def random_number(n) + # 生成陣列 nums =: 1, 2, 3, ..., n + nums = Array.new(n) { |i| i + 1 } + # 隨機打亂陣列元素 + nums.shuffle! +end + +### 查詢陣列 nums 中數字 1 所在索引 ### +def find_one(nums) + for i in 0...nums.length + # 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + # 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + return i if nums[i] == 1 + end + + -1 +end + +### Driver Code ### + +for i in 0...10 + n = 100 + nums = random_number(n) + index = find_one(nums) + puts "\n陣列 [ 1, 2, ..., n ] 被打亂後 = #{nums}" + puts "數字 1 的索引為 #{index}" +end diff --git a/zh-hant/codes/ruby/utils/list_node.rb b/zh-hant/codes/ruby/utils/list_node.rb new file mode 100644 index 000000000..b09d382e8 --- /dev/null +++ b/zh-hant/codes/ruby/utils/list_node.rb @@ -0,0 +1,38 @@ +=begin +File: list_node.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 鏈結串列節點類別 ### +class ListNode + attr_accessor :val # 節點值 + attr_accessor :next # 指向下一節點的引用 + + def initialize(val=0, next_node=nil) + @val = val + @next = next_node + end +end + +### 將串列反序列化為鏈結串列 ### +def arr_to_linked_list(arr) + head = current = ListNode.new(arr[0]) + + for i in 1...arr.length + current.next = ListNode.new(arr[i]) + current = current.next + end + + head +end + +### 將鏈結串列序列化為串列 ### +def linked_list_to_arr(head) + arr = [] + + while head + arr << head.val + head = head.next + end +end diff --git a/zh-hant/codes/ruby/utils/print_util.rb b/zh-hant/codes/ruby/utils/print_util.rb new file mode 100644 index 000000000..04aa4465b --- /dev/null +++ b/zh-hant/codes/ruby/utils/print_util.rb @@ -0,0 +1,58 @@ +=begin +File: print_util.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 列印鏈結串列 ### +def print_linked_list(head) + list = [] + while head + list << head.val + head = head.next + end + puts "#{list.join(" -> ")}" +end + +class Trunk + attr_accessor :prev, :str + + def initialize(prev, str) + @prev = prev + @str = str + end +end + +def show_trunk(p) + return if p.nil? + + show_trunk(p.prev) + print p.str +end + +### 列印二元樹 ### +# This tree printer is borrowed from TECHIE DELIGHT +# https://www.techiedelight.com/c-program-print-binary-tree/ +def print_tree(root, prev=nil, is_right=false) + return if root.nil? + + prev_str = " " + trunk = Trunk.new(prev, prev_str) + print_tree(root.right, trunk, true) + + if prev.nil? + trunk.str = "———" + elsif is_right + trunk.str = "/———" + prev_str = " |" + else + trunk.str = "\\———" + prev.str = prev_str + end + + show_trunk(trunk) + puts " #{root.val}" + prev.str = prev_str if prev + trunk.str = " |" + print_tree(root.left, trunk, false) +end diff --git a/zh-hant/codes/ruby/utils/tree_node.rb b/zh-hant/codes/ruby/utils/tree_node.rb new file mode 100644 index 000000000..61f8eedd3 --- /dev/null +++ b/zh-hant/codes/ruby/utils/tree_node.rb @@ -0,0 +1,18 @@ +=begin +File: tree_node.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 二元樹節點類別 ### +class TreeNode + attr_accessor :val # 節點值 + attr_accessor :height # 節點高度 + attr_accessor :left # 左子節點引用 + attr_accessor :right # 右子節點引用 + + def initialize(val=0) + @val = val + @height = 0 + end +end diff --git a/zh-hant/codes/rust/.gitignore b/zh-hant/codes/rust/.gitignore new file mode 100644 index 000000000..447098846 --- /dev/null +++ b/zh-hant/codes/rust/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock \ No newline at end of file diff --git a/zh-hant/codes/rust/Cargo.toml b/zh-hant/codes/rust/Cargo.toml new file mode 100644 index 000000000..160f9134c --- /dev/null +++ b/zh-hant/codes/rust/Cargo.toml @@ -0,0 +1,413 @@ +[package] +name = "hello-algo-rust" +version = "0.1.0" +edition = "2021" +publish = false + +# Run Command: cargo run --bin time_complexity +[[bin]] +name = "time_complexity" +path = "chapter_computational_complexity/time_complexity.rs" + +# Run Command: cargo run --bin worst_best_time_complexity +[[bin]] +name = "worst_best_time_complexity" +path = "chapter_computational_complexity/worst_best_time_complexity.rs" + +# Run Command: cargo run --bin space_complexity +[[bin]] +name = "space_complexity" +path = "chapter_computational_complexity/space_complexity.rs" + +# Run Command: cargo run --bin iteration +[[bin]] +name = "iteration" +path = "chapter_computational_complexity/iteration.rs" + +# Run Command: cargo run --bin recursion +[[bin]] +name = "recursion" +path = "chapter_computational_complexity/recursion.rs" + +# Run Command: cargo run --bin two_sum +[[bin]] +name = "two_sum" +path = "chapter_searching/two_sum.rs" + +# Run Command: cargo run --bin array +[[bin]] +name = "array" +path = "chapter_array_and_linkedlist/array.rs" + +# Run Command: cargo run --bin linked_list +[[bin]] +name = "linked_list" +path = "chapter_array_and_linkedlist/linked_list.rs" + +# Run Command: cargo run --bin list +[[bin]] +name = "list" +path = "chapter_array_and_linkedlist/list.rs" + +# Run Command: cargo run --bin my_list +[[bin]] +name = "my_list" +path = "chapter_array_and_linkedlist/my_list.rs" + +# Run Command: cargo run --bin stack +[[bin]] +name = "stack" +path = "chapter_stack_and_queue/stack.rs" + +# Run Command: cargo run --bin linkedlist_stack +[[bin]] +name = "linkedlist_stack" +path = "chapter_stack_and_queue/linkedlist_stack.rs" + +# Run Command: cargo run --bin queue +[[bin]] +name = "queue" +path = "chapter_stack_and_queue/queue.rs" + +# Run Command: cargo run --bin linkedlist_queue +[[bin]] +name = "linkedlist_queue" +path = "chapter_stack_and_queue/linkedlist_queue.rs" + +# Run Command: cargo run --bin deque +[[bin]] +name = "deque" +path = "chapter_stack_and_queue/deque.rs" + +# Run Command: cargo run --bin array_deque +[[bin]] +name = "array_deque" +path = "chapter_stack_and_queue/array_deque.rs" + +# Run Command: cargo run --bin linkedlist_deque +[[bin]] +name = "linkedlist_deque" +path = "chapter_stack_and_queue/linkedlist_deque.rs" + +# Run Command: cargo run --bin simple_hash +[[bin]] +name = "simple_hash" +path = "chapter_hashing/simple_hash.rs" + +# Run Command: cargo run --bin hash_map +[[bin]] +name = "hash_map" +path = "chapter_hashing/hash_map.rs" + +# Run Command: cargo run --bin array_hash_map +[[bin]] +name = "array_hash_map" +path = "chapter_hashing/array_hash_map.rs" + +# Run Command: cargo run --bin build_in_hash +[[bin]] +name = "build_in_hash" +path = "chapter_hashing/build_in_hash.rs" + +# Run Command: cargo run --bin hash_map_chaining +[[bin]] +name = "hash_map_chaining" +path = "chapter_hashing/hash_map_chaining.rs" + +# Run Command: cargo run --bin hash_map_open_addressing +[[bin]] +name = "hash_map_open_addressing" +path = "chapter_hashing/hash_map_open_addressing.rs" + +# Run Command: cargo run --bin binary_search +[[bin]] +name = "binary_search" +path = "chapter_searching/binary_search.rs" + +# Run Command: cargo run --bin binary_search_edge +[[bin]] +name = "binary_search_edge" +path = "chapter_searching/binary_search_edge.rs" + +# Run Command: cargo run --bin binary_search_insertion +[[bin]] +name = "binary_search_insertion" +path = "chapter_searching/binary_search_insertion.rs" + +# Run Command: cargo run --bin bubble_sort +[[bin]] +name = "bubble_sort" +path = "chapter_sorting/bubble_sort.rs" + +# Run Command: cargo run --bin insertion_sort +[[bin]] +name = "insertion_sort" +path = "chapter_sorting/insertion_sort.rs" + +# Run Command: cargo run --bin quick_sort +[[bin]] +name = "quick_sort" +path = "chapter_sorting/quick_sort.rs" + +# Run Command: cargo run --bin merge_sort +[[bin]] +name = "merge_sort" +path = "chapter_sorting/merge_sort.rs" + +# Run Command: cargo run --bin selection_sort +[[bin]] +name = "selection_sort" +path = "chapter_sorting/selection_sort.rs" + +# Run Command: cargo run --bin bucket_sort +[[bin]] +name = "bucket_sort" +path = "chapter_sorting/bucket_sort.rs" + +# Run Command: cargo run --bin heap_sort +[[bin]] +name = "heap_sort" +path = "chapter_sorting/heap_sort.rs" + +# Run Command: cargo run --bin counting_sort +[[bin]] +name = "counting_sort" +path = "chapter_sorting/counting_sort.rs" + +# Run Command: cargo run --bin radix_sort +[[bin]] +name = "radix_sort" +path = "chapter_sorting/radix_sort.rs" + +# Run Command: cargo run --bin array_stack +[[bin]] +name = "array_stack" +path = "chapter_stack_and_queue/array_stack.rs" + +# Run Command: cargo run --bin array_queue +[[bin]] +name = "array_queue" +path = "chapter_stack_and_queue/array_queue.rs" + +# Run Command: cargo run --bin array_binary_tree +[[bin]] +name = "array_binary_tree" +path = "chapter_tree/array_binary_tree.rs" + +# Run Command: cargo run --bin avl_tree +[[bin]] +name = "avl_tree" +path = "chapter_tree/avl_tree.rs" + +# Run Command: cargo run --bin binary_search_tree +[[bin]] +name = "binary_search_tree" +path = "chapter_tree/binary_search_tree.rs" + +# Run Command: cargo run --bin binary_tree_bfs +[[bin]] +name = "binary_tree_bfs" +path = "chapter_tree/binary_tree_bfs.rs" + +# Run Command: cargo run --bin binary_tree_dfs +[[bin]] +name = "binary_tree_dfs" +path = "chapter_tree/binary_tree_dfs.rs" + +# Run Command: cargo run --bin binary_tree +[[bin]] +name = "binary_tree" +path = "chapter_tree/binary_tree.rs" + +# Run Command: cargo run --bin heap +[[bin]] +name = "heap" +path = "chapter_heap/heap.rs" + +# Run Command: cargo run --bin my_heap +[[bin]] +name = "my_heap" +path = "chapter_heap/my_heap.rs" + +# Run Command: cargo run --bin top_k +[[bin]] +name = "top_k" +path = "chapter_heap/top_k.rs" + +# Run Command: cargo run --bin graph_adjacency_list +[[bin]] +name = "graph_adjacency_list" +path = "chapter_graph/graph_adjacency_list.rs" + +# Run Command: cargo run --bin graph_adjacency_matrix +[[bin]] +name = "graph_adjacency_matrix" +path = "chapter_graph/graph_adjacency_matrix.rs" + +# Run Command: cargo run --bin graph_bfs +[[bin]] +name = "graph_bfs" +path = "chapter_graph/graph_bfs.rs" + +# Run Command: cargo run --bin graph_dfs +[[bin]] +name = "graph_dfs" +path = "chapter_graph/graph_dfs.rs" + +# Run Command: cargo run --bin linear_search +[[bin]] +name = "linear_search" +path = "chapter_searching/linear_search.rs" + +# Run Command: cargo run --bin hashing_search +[[bin]] +name = "hashing_search" +path = "chapter_searching/hashing_search.rs" + +# Run Command: cargo run --bin climbing_stairs_dfs +[[bin]] +name = "climbing_stairs_dfs" +path = "chapter_dynamic_programming/climbing_stairs_dfs.rs" + +# Run Command: cargo run --bin climbing_stairs_dfs_mem +[[bin]] +name = "climbing_stairs_dfs_mem" +path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.rs" + +# Run Command: cargo run --bin climbing_stairs_dp +[[bin]] +name = "climbing_stairs_dp" +path = "chapter_dynamic_programming/climbing_stairs_dp.rs" + +# Run Command: cargo run --bin min_cost_climbing_stairs_dp +[[bin]] +name = "min_cost_climbing_stairs_dp" +path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs" + +# Run Command: cargo run --bin climbing_stairs_constraint_dp +[[bin]] +name = "climbing_stairs_constraint_dp" +path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.rs" + +# Run Command: cargo run --bin climbing_stairs_backtrack +[[bin]] +name = "climbing_stairs_backtrack" +path = "chapter_dynamic_programming/climbing_stairs_backtrack.rs" + +# Run Command: cargo run --bin subset_sum_i_naive +[[bin]] +name = "subset_sum_i_naive" +path = "chapter_backtracking/subset_sum_i_naive.rs" + +# Run Command: cargo run --bin subset_sum_i +[[bin]] +name = "subset_sum_i" +path = "chapter_backtracking/subset_sum_i.rs" + +# Run Command: cargo run --bin subset_sum_ii +[[bin]] +name = "subset_sum_ii" +path = "chapter_backtracking/subset_sum_ii.rs" + +# Run Command: cargo run --bin coin_change +[[bin]] +name = "coin_change" +path = "chapter_dynamic_programming/coin_change.rs" + +# Run Command: cargo run --bin coin_change_ii +[[bin]] +name = "coin_change_ii" +path = "chapter_dynamic_programming/coin_change_ii.rs" + +# Run Command: cargo run --bin unbounded_knapsack +[[bin]] +name = "unbounded_knapsack" +path = "chapter_dynamic_programming/unbounded_knapsack.rs" + +# Run Command: cargo run --bin knapsack +[[bin]] +name = "knapsack" +path = "chapter_dynamic_programming/knapsack.rs" + +# Run Command: cargo run --bin min_path_sum +[[bin]] +name = "min_path_sum" +path = "chapter_dynamic_programming/min_path_sum.rs" + +# Run Command: cargo run --bin edit_distance +[[bin]] +name = "edit_distance" +path = "chapter_dynamic_programming/edit_distance.rs" + +# Run Command: cargo run --bin n_queens +[[bin]] +name = "n_queens" +path = "chapter_backtracking/n_queens.rs" + +# Run Command: cargo run --bin permutations_i +[[bin]] +name = "permutations_i" +path = "chapter_backtracking/permutations_i.rs" + +# Run Command: cargo run --bin permutations_ii +[[bin]] +name = "permutations_ii" +path = "chapter_backtracking/permutations_ii.rs" + +# Run Command: cargo run --bin preorder_traversal_i_compact +[[bin]] +name = "preorder_traversal_i_compact" +path = "chapter_backtracking/preorder_traversal_i_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_ii_compact +[[bin]] +name = "preorder_traversal_ii_compact" +path = "chapter_backtracking/preorder_traversal_ii_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_iii_compact +[[bin]] +name = "preorder_traversal_iii_compact" +path = "chapter_backtracking/preorder_traversal_iii_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_iii_template +[[bin]] +name = "preorder_traversal_iii_template" +path = "chapter_backtracking/preorder_traversal_iii_template.rs" + +# Run Command: cargo run --bin binary_search_recur +[[bin]] +name = "binary_search_recur" +path = "chapter_divide_and_conquer/binary_search_recur.rs" + +# Run Command: cargo run --bin hanota +[[bin]] +name = "hanota" +path = "chapter_divide_and_conquer/hanota.rs" + +# Run Command: cargo run --bin build_tree +[[bin]] +name = "build_tree" +path = "chapter_divide_and_conquer/build_tree.rs" + +# Run Command: cargo run --bin coin_change_greedy +[[bin]] +name = "coin_change_greedy" +path = "chapter_greedy/coin_change_greedy.rs" + +# Run Command: cargo run --bin fractional_knapsack +[[bin]] +name = "fractional_knapsack" +path = "chapter_greedy/fractional_knapsack.rs" + +# Run Command: cargo run --bin max_capacity +[[bin]] +name = "max_capacity" +path = "chapter_greedy/max_capacity.rs" + +# Run Command: cargo run --bin max_product_cutting +[[bin]] +name = "max_product_cutting" +path = "chapter_greedy/max_product_cutting.rs" + +[dependencies] +rand = "0.8.5" diff --git a/zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs b/zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs new file mode 100644 index 000000000..649e3dedf --- /dev/null +++ b/zh-hant/codes/rust/chapter_array_and_linkedlist/array.rs @@ -0,0 +1,111 @@ +/* + * File: array.rs + * Created Time: 2023-01-15 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use rand::Rng; + +/* 隨機訪問元素 */ +fn random_access(nums: &[i32]) -> i32 { + // 在區間 [0, nums.len()) 中隨機抽取一個數字 + let random_index = rand::thread_rng().gen_range(0..nums.len()); + // 獲取並返回隨機元素 + let random_num = nums[random_index]; + random_num +} + +/* 擴展陣列長度 */ +fn extend(nums: Vec, enlarge: usize) -> Vec { + // 初始化一個擴展長度後的陣列 + let mut res: Vec = vec![0; nums.len() + enlarge]; + // 將原陣列中的所有元素複製到新 + for i in 0..nums.len() { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + res +} + +/* 在陣列的索引 index 處插入元素 num */ +fn insert(nums: &mut Vec, num: i32, index: usize) { + // 把索引 index 以及之後的所有元素向後移動一位 + for i in (index + 1..nums.len()).rev() { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +/* 刪除索引 index 處的元素 */ +fn remove(nums: &mut Vec, index: usize) { + // 把索引 index 之後的所有元素向前移動一位 + for i in index..nums.len() - 1 { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列 */ +fn traverse(nums: &[i32]) { + let mut _count = 0; + // 透過索引走訪陣列 + for i in 0..nums.len() { + _count += nums[i]; + } + // 直接走訪陣列元素 + for num in nums { + _count += num; + } +} + +/* 在陣列中查詢指定元素 */ +fn find(nums: &[i32], target: i32) -> Option { + for i in 0..nums.len() { + if nums[i] == target { + return Some(i); + } + } + None +} + +/* Driver Code */ +fn main() { + /* 初始化陣列 */ + let arr = [0; 5]; + print!("陣列 arr = "); + print_util::print_array(&arr); + // 在 Rust 中,指定長度時([i32; 5])為陣列 + // 由於 Rust 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 + // 為了方便實現擴容 extend() 方法,以下將(Vec) 看作陣列(Array)也是rust一般情況下使用動態陣列的型別 + let nums = vec![1, 3, 2, 5, 4]; + print!("\n陣列 nums = "); + print_util::print_array(&nums); + + // 隨機訪問 + let random_num = random_access(&nums); + println!("\n在 nums 中獲取隨機元素 {}", random_num); + + // 長度擴展 + let mut nums = extend(nums, 3); + print!("將陣列長度擴展至 8 ,得到 nums = "); + print_util::print_array(&nums); + + // 插入元素 + insert(&mut nums, 6, 3); + print!("\n在索引 3 處插入數字 6 ,得到 nums = "); + print_util::print_array(&nums); + + // 刪除元素 + remove(&mut nums, 2); + print!("\n刪除索引 2 處的元素,得到 nums = "); + print_util::print_array(&nums); + + // 走訪陣列 + traverse(&nums); + + // 查詢元素 + let index = find(&nums, 3).unwrap(); + println!("\n在 nums 中查詢元素 3 ,得到索引 = {}", index); +} diff --git a/zh-hant/codes/rust/chapter_array_and_linkedlist/linked_list.rs b/zh-hant/codes/rust/chapter_array_and_linkedlist/linked_list.rs new file mode 100644 index 000000000..d9020ed8c --- /dev/null +++ b/zh-hant/codes/rust/chapter_array_and_linkedlist/linked_list.rs @@ -0,0 +1,92 @@ +/* + * File: linked_list.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use list_node::ListNode; +use std::cell::RefCell; +use std::rc::Rc; + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +#[allow(non_snake_case)] +pub fn insert(n0: &Rc>>, P: Rc>>) { + let n1 = n0.borrow_mut().next.take(); + P.borrow_mut().next = n1; + n0.borrow_mut().next = Some(P); +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +#[allow(non_snake_case)] +pub fn remove(n0: &Rc>>) { + if n0.borrow().next.is_none() { + return; + }; + // n0 -> P -> n1 + let P = n0.borrow_mut().next.take(); + if let Some(node) = P { + let n1 = node.borrow_mut().next.take(); + n0.borrow_mut().next = n1; + } +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +pub fn access(head: Rc>>, index: i32) -> Rc>> { + if index <= 0 { + return head; + }; + if let Some(node) = &head.borrow().next { + return access(node.clone(), index - 1); + } + + return head; +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +pub fn find(head: Rc>>, target: T, index: i32) -> i32 { + if head.borrow().val == target { + return index; + }; + if let Some(node) = &head.borrow_mut().next { + return find(node.clone(), target, index + 1); + } + return -1; +} + +/* Driver Code */ +fn main() { + /* 初始化鏈結串列 */ + // 初始化各個節點 + let n0 = ListNode::new(1); + let n1 = ListNode::new(3); + let n2 = ListNode::new(2); + let n3 = ListNode::new(5); + let n4 = ListNode::new(4); + // 構建節點之間的引用 + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); + print!("初始化的鏈結串列為 "); + print_util::print_linked_list(&n0); + + /* 插入節點 */ + insert(&n0, ListNode::new(0)); + print!("插入節點後的鏈結串列為 "); + print_util::print_linked_list(&n0); + + /* 刪除節點 */ + remove(&n0); + print!("刪除節點後的鏈結串列為 "); + print_util::print_linked_list(&n0); + + /* 訪問節點 */ + let node = access(n0.clone(), 3); + println!("鏈結串列中索引 3 處的節點的值 = {}", node.borrow().val); + + /* 查詢節點 */ + let index = find(n0.clone(), 2, 0); + println!("鏈結串列中值為 2 的節點的索引 = {}", index); +} diff --git a/zh-hant/codes/rust/chapter_array_and_linkedlist/list.rs b/zh-hant/codes/rust/chapter_array_and_linkedlist/list.rs new file mode 100644 index 000000000..dd0d8706b --- /dev/null +++ b/zh-hant/codes/rust/chapter_array_and_linkedlist/list.rs @@ -0,0 +1,72 @@ +/* + * File: list.rs + * Created Time: 2023-01-18 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +/* Driver Code */ +fn main() { + // 初始化串列 + let mut nums: Vec = vec![1, 3, 2, 5, 4]; + print!("串列 nums = "); + print_util::print_array(&nums); + + // 訪問元素 + let num = nums[1]; + println!("\n訪問索引 1 處的元素,得到 num = {num}"); + + // 更新元素 + nums[1] = 0; + print!("將索引 1 處的元素更新為 0 ,得到 nums = "); + print_util::print_array(&nums); + + // 清空串列 + nums.clear(); + print!("\n清空串列後 nums = "); + print_util::print_array(&nums); + + // 在尾部新增元素 + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + print!("\n新增元素後 nums = "); + print_util::print_array(&nums); + + // 在中間插入元素 + nums.insert(3, 6); + print!("\n在索引 3 處插入數字 6 ,得到 nums = "); + print_util::print_array(&nums); + + // 刪除元素 + nums.remove(3); + print!("\n刪除索引 3 處的元素,得到 nums = "); + print_util::print_array(&nums); + + // 透過索引走訪串列 + let mut _count = 0; + for i in 0..nums.len() { + _count += nums[i]; + } + // 直接走訪串列元素 + _count = 0; + for x in &nums { + _count += x; + } + + // 拼接兩個串列 + let mut nums1 = vec![6, 8, 7, 10, 9]; + nums.append(&mut nums1); // append(移動) 之後 nums1 為空! + + // nums.extend(&nums1); // extend(借用) nums1 能繼續使用 + print!("\n將串列 nums1 拼接到 nums 之後,得到 nums = "); + print_util::print_array(&nums); + + // 排序串列 + nums.sort(); + print!("\n排序串列後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_array_and_linkedlist/my_list.rs b/zh-hant/codes/rust/chapter_array_and_linkedlist/my_list.rs new file mode 100644 index 000000000..04a8a6612 --- /dev/null +++ b/zh-hant/codes/rust/chapter_array_and_linkedlist/my_list.rs @@ -0,0 +1,165 @@ +/* + * File: my_list.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +/* 串列類別 */ +#[allow(dead_code)] +struct MyList { + arr: Vec, // 陣列(儲存串列元素) + capacity: usize, // 串列容量 + size: usize, // 串列長度(當前元素數量) + extend_ratio: usize, // 每次串列擴容的倍數 +} + +#[allow(unused, unused_comparisons)] +impl MyList { + /* 建構子 */ + pub fn new(capacity: usize) -> Self { + let mut vec = Vec::new(); + vec.resize(capacity, 0); + Self { + arr: vec, + capacity, + size: 0, + extend_ratio: 2, + } + } + + /* 獲取串列長度(當前元素數量)*/ + pub fn size(&self) -> usize { + return self.size; + } + + /* 獲取串列容量 */ + pub fn capacity(&self) -> usize { + return self.capacity; + } + + /* 訪問元素 */ + pub fn get(&self, index: usize) -> i32 { + // 索引如果越界,則丟擲異常,下同 + if index >= self.size { + panic!("索引越界") + }; + return self.arr[index]; + } + + /* 更新元素 */ + pub fn set(&mut self, index: usize, num: i32) { + if index >= self.size { + panic!("索引越界") + }; + self.arr[index] = num; + } + + /* 在尾部新增元素 */ + pub fn add(&mut self, num: i32) { + // 元素數量超出容量時,觸發擴容機制 + if self.size == self.capacity() { + self.extend_capacity(); + } + self.arr[self.size] = num; + // 更新元素數量 + self.size += 1; + } + + /* 在中間插入元素 */ + pub fn insert(&mut self, index: usize, num: i32) { + if index >= self.size() { + panic!("索引越界") + }; + // 元素數量超出容量時,觸發擴容機制 + if self.size == self.capacity() { + self.extend_capacity(); + } + // 將索引 index 以及之後的元素都向後移動一位 + for j in (index..self.size).rev() { + self.arr[j + 1] = self.arr[j]; + } + self.arr[index] = num; + // 更新元素數量 + self.size += 1; + } + + /* 刪除元素 */ + pub fn remove(&mut self, index: usize) -> i32 { + if index >= self.size() { + panic!("索引越界") + }; + let num = self.arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for j in (index..self.size - 1) { + self.arr[j] = self.arr[j + 1]; + } + // 更新元素數量 + self.size -= 1; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + pub fn extend_capacity(&mut self) { + // 新建一個長度為原陣列 extend_ratio 倍的新陣列,並將原陣列複製到新陣列 + let new_capacity = self.capacity * self.extend_ratio; + self.arr.resize(new_capacity, 0); + // 更新串列容量 + self.capacity = new_capacity; + } + + /* 將串列轉換為陣列 */ + pub fn to_array(&mut self) -> Vec { + // 僅轉換有效長度範圍內的串列元素 + let mut arr = Vec::new(); + for i in 0..self.size { + arr.push(self.get(i)); + } + arr + } +} + +/* Driver Code */ +fn main() { + /* 初始化串列 */ + let mut nums = MyList::new(10); + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print!("串列 nums = "); + print_util::print_array(&nums.to_array()); + print!(" ,容量 = {} ,長度 = {}", nums.capacity(), nums.size()); + + /* 在中間插入元素 */ + nums.insert(3, 6); + print!("\n在索引 3 處插入數字 6 ,得到 nums = "); + print_util::print_array(&nums.to_array()); + + /* 刪除元素 */ + nums.remove(3); + print!("\n刪除索引 3 處的元素,得到 nums = "); + print_util::print_array(&nums.to_array()); + + /* 訪問元素 */ + let num = nums.get(1); + println!("\n訪問索引 1 處的元素,得到 num = {num}"); + + /* 更新元素 */ + nums.set(1, 0); + print!("將索引 1 處的元素更新為 0 ,得到 nums = "); + print_util::print_array(&nums.to_array()); + + /* 測試擴容機制 */ + for i in 0..10 { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i); + } + print!("\n擴容後的串列 nums = "); + print_util::print_array(&nums.to_array()); + print!(" ,容量 = {} ,長度 = {}", nums.capacity(), nums.size()); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/n_queens.rs b/zh-hant/codes/rust/chapter_backtracking/n_queens.rs new file mode 100644 index 000000000..b7708f5bc --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/n_queens.rs @@ -0,0 +1,87 @@ +/* + * File: n_queens.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯演算法:n 皇后 */ +fn backtrack( + row: usize, + n: usize, + state: &mut Vec>, + res: &mut Vec>>, + cols: &mut [bool], + diags1: &mut [bool], + diags2: &mut [bool], +) { + // 當放置完所有行時,記錄解 + if row == n { + let mut copy_state: Vec> = Vec::new(); + for s_row in state.clone() { + copy_state.push(s_row); + } + res.push(copy_state); + return; + } + // 走訪所有列 + for col in 0..n { + // 計算該格子對應的主對角線和次對角線 + let diag1 = row + n - 1 - col; + let diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // 嘗試:將皇后放置在該格子 + state.get_mut(row).unwrap()[col] = "Q".into(); + (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state.get_mut(row).unwrap()[col] = "#".into(); + (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); + } + } +} + +/* 求解 n 皇后 */ +fn n_queens(n: usize) -> Vec>> { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + let mut state: Vec> = Vec::new(); + for _ in 0..n { + let mut row: Vec = Vec::new(); + for _ in 0..n { + row.push("#".into()); + } + state.push(row); + } + let mut cols = vec![false; n]; // 記錄列是否有皇后 + let mut diags1 = vec![false; 2 * n - 1]; // 記錄主對角線上是否有皇后 + let mut diags2 = vec![false; 2 * n - 1]; // 記錄次對角線上是否有皇后 + let mut res: Vec>> = Vec::new(); + + backtrack( + 0, + n, + &mut state, + &mut res, + &mut cols, + &mut diags1, + &mut diags2, + ); + + res +} + +/* Driver Code */ +pub fn main() { + let n: usize = 4; + let res = n_queens(n); + + println!("輸入棋盤長寬為 {n}"); + println!("皇后放置方案共有 {} 種", res.len()); + for state in res.iter() { + println!("--------------------"); + for row in state.iter() { + println!("{:?}", row); + } + } +} diff --git a/zh-hant/codes/rust/chapter_backtracking/permutations_i.rs b/zh-hant/codes/rust/chapter_backtracking/permutations_i.rs new file mode 100644 index 000000000..090f57d95 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/permutations_i.rs @@ -0,0 +1,46 @@ +/* + * File: permutations_i.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯演算法:全排列 I */ +fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { + // 當狀態長度等於元素數量時,記錄解 + if state.len() == choices.len() { + res.push(state); + return; + } + // 走訪所有選擇 + for i in 0..choices.len() { + let choice = choices[i]; + // 剪枝:不允許重複選擇元素 + if !selected[i] { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state.clone(), choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.remove(state.len() - 1); + } + } +} + +/* 全排列 I */ +fn permutations_i(nums: &mut [i32]) -> Vec> { + let mut res = Vec::new(); // 狀態(子集) + backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [1, 2, 3]; + + let res = permutations_i(&mut nums); + + println!("輸入陣列 nums = {:?}", &nums); + println!("所有排列 res = {:?}", &res); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/permutations_ii.rs b/zh-hant/codes/rust/chapter_backtracking/permutations_ii.rs new file mode 100644 index 000000000..d2a97642a --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/permutations_ii.rs @@ -0,0 +1,50 @@ +/* + * File: permutations_ii.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::collections::HashSet; + +/* 回溯演算法:全排列 II */ +fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { + // 當狀態長度等於元素數量時,記錄解 + if state.len() == choices.len() { + res.push(state); + return; + } + // 走訪所有選擇 + let mut duplicated = HashSet::::new(); + for i in 0..choices.len() { + let choice = choices[i]; + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if !selected[i] && !duplicated.contains(&choice) { + // 嘗試:做出選擇,更新狀態 + duplicated.insert(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state.clone(), choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.remove(state.len() - 1); + } + } +} + +/* 全排列 II */ +fn permutations_ii(nums: &mut [i32]) -> Vec> { + let mut res = Vec::new(); + backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [1, 2, 2]; + + let res = permutations_ii(&mut nums); + + println!("輸入陣列 nums = {:?}", &nums); + println!("所有排列 res = {:?}", &res); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs new file mode 100644 index 000000000..40cbb09e9 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs @@ -0,0 +1,43 @@ +/* + * File: preorder_traversal_i_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use std::{cell::RefCell, rc::Rc}; +use tree_node::{vec_to_tree, TreeNode}; + +/* 前序走訪:例題一 */ +fn pre_order(res: &mut Vec>>, root: Option>>) { + if root.is_none() { + return; + } + if let Some(node) = root { + if node.borrow().val == 7 { + // 記錄解 + res.push(node.clone()); + } + pre_order(res, node.borrow().left.clone()); + pre_order(res, node.borrow().right.clone()); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二元樹"); + print_util::print_tree(root.as_ref().unwrap()); + + // 前序走訪 + let mut res = Vec::new(); + pre_order(&mut res, root); + + println!("\n輸出所有值為 7 的節點"); + let mut vals = Vec::new(); + for node in res { + vals.push(node.borrow().val) + } + println!("{:?}", vals); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs new file mode 100644 index 000000000..71447656a --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs @@ -0,0 +1,54 @@ +/* + * File: preorder_traversal_ii_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use std::{cell::RefCell, rc::Rc}; +use tree_node::{vec_to_tree, TreeNode}; + +/* 前序走訪:例題二 */ +fn pre_order( + res: &mut Vec>>>, + path: &mut Vec>>, + root: Option>>, +) { + if root.is_none() { + return; + } + if let Some(node) = root { + // 嘗試 + path.push(node.clone()); + if node.borrow().val == 7 { + // 記錄解 + res.push(path.clone()); + } + pre_order(res, path, node.borrow().left.clone()); + pre_order(res, path, node.borrow().right.clone()); + // 回退 + path.remove(path.len() - 1); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二元樹"); + print_util::print_tree(root.as_ref().unwrap()); + + // 前序走訪 + let mut path = Vec::new(); + let mut res = Vec::new(); + pre_order(&mut res, &mut path, root); + + println!("\n輸出所有根節點到節點 7 的路徑"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs new file mode 100644 index 000000000..b4ed63f6e --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs @@ -0,0 +1,55 @@ +/* + * File: preorder_traversal_iii_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use std::{cell::RefCell, rc::Rc}; +use tree_node::{vec_to_tree, TreeNode}; + +/* 前序走訪:例題三 */ +fn pre_order( + res: &mut Vec>>>, + path: &mut Vec>>, + root: Option>>, +) { + // 剪枝 + if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { + return; + } + if let Some(node) = root { + // 嘗試 + path.push(node.clone()); + if node.borrow().val == 7 { + // 記錄解 + res.push(path.clone()); + } + pre_order(res, path, node.borrow().left.clone()); + pre_order(res, path, node.borrow().right.clone()); + // 回退 + path.remove(path.len() - 1); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二元樹"); + print_util::print_tree(root.as_ref().unwrap()); + + // 前序走訪 + let mut path = Vec::new(); + let mut res = Vec::new(); + pre_order(&mut res, &mut path, root); + + println!("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs new file mode 100644 index 000000000..7c8ecf5ea --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs @@ -0,0 +1,90 @@ +/* + * File: preorder_traversal_iii_template.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use std::{cell::RefCell, rc::Rc}; +use tree_node::{vec_to_tree, TreeNode}; + +/* 判斷當前狀態是否為解 */ +fn is_solution(state: &mut Vec>>) -> bool { + return !state.is_empty() && state.get(state.len() - 1).unwrap().borrow().val == 7; +} + +/* 記錄解 */ +fn record_solution( + state: &mut Vec>>, + res: &mut Vec>>>, +) { + res.push(state.clone()); +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +fn is_valid(_: &mut Vec>>, choice: Rc>) -> bool { + return choice.borrow().val != 3; +} + +/* 更新狀態 */ +fn make_choice(state: &mut Vec>>, choice: Rc>) { + state.push(choice); +} + +/* 恢復狀態 */ +fn undo_choice(state: &mut Vec>>, _: Rc>) { + state.remove(state.len() - 1); +} + +/* 回溯演算法:例題三 */ +fn backtrack( + state: &mut Vec>>, + choices: &mut Vec>>, + res: &mut Vec>>>, +) { + // 檢查是否為解 + if is_solution(state) { + // 記錄解 + record_solution(state, res); + } + // 走訪所有選擇 + for choice in choices { + // 剪枝:檢查選擇是否合法 + if is_valid(state, choice.clone()) { + // 嘗試:做出選擇,更新狀態 + make_choice(state, choice.clone()); + // 進行下一輪選擇 + backtrack( + state, + &mut vec![ + choice.borrow().left.clone().unwrap(), + choice.borrow().right.clone().unwrap(), + ], + res, + ); + // 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice.clone()); + } + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("初始化二元樹"); + print_util::print_tree(root.as_ref().unwrap()); + + // 回溯演算法 + let mut res = Vec::new(); + backtrack(&mut Vec::new(), &mut vec![root.unwrap()], &mut res); + + println!("\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/zh-hant/codes/rust/chapter_backtracking/subset_sum_i.rs b/zh-hant/codes/rust/chapter_backtracking/subset_sum_i.rs new file mode 100644 index 000000000..86c685ec7 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/subset_sum_i.rs @@ -0,0 +1,56 @@ +/* + * File: subset_sum_i.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +fn backtrack( + mut state: Vec, + target: i32, + choices: &[i32], + start: usize, + res: &mut Vec>, +) { + // 子集和等於 target 時,記錄解 + if target == 0 { + res.push(state); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for i in start..choices.len() { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target - choices[i] < 0 { + break; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state.clone(), target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I */ +fn subset_sum_i(nums: &mut [i32], target: i32) -> Vec> { + let state = Vec::new(); // 狀態(子集) + nums.sort(); // 對 nums 進行排序 + let start = 0; // 走訪起始點 + let mut res = Vec::new(); // 結果串列(子集串列) + backtrack(state, target, nums, start, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [3, 4, 5]; + let target = 9; + + let res = subset_sum_i(&mut nums, target); + + println!("輸入陣列 nums = {:?}, target = {}", &nums, target); + println!("所有和等於 {} 的子集 res = {:?}", target, &res); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/subset_sum_i_naive.rs b/zh-hant/codes/rust/chapter_backtracking/subset_sum_i_naive.rs new file mode 100644 index 000000000..6b25bbd41 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/subset_sum_i_naive.rs @@ -0,0 +1,54 @@ +/* + * File: subset_sum_i_naive.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +fn backtrack( + mut state: Vec, + target: i32, + total: i32, + choices: &[i32], + res: &mut Vec>, +) { + // 子集和等於 target 時,記錄解 + if total == target { + res.push(state); + return; + } + // 走訪所有選擇 + for i in 0..choices.len() { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if total + choices[i] > target { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state.clone(), target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I(包含重複子集) */ +fn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec> { + let state = Vec::new(); // 狀態(子集) + let total = 0; // 子集和 + let mut res = Vec::new(); // 結果串列(子集串列) + backtrack(state, target, total, nums, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let nums = [3, 4, 5]; + let target = 9; + + let res = subset_sum_i_naive(&nums, target); + + println!("輸入陣列 nums = {:?}, target = {}", &nums, target); + println!("所有和等於 {} 的子集 res = {:?}", target, &res); + println!("請注意,該方法輸出的結果包含重複集合"); +} diff --git a/zh-hant/codes/rust/chapter_backtracking/subset_sum_ii.rs b/zh-hant/codes/rust/chapter_backtracking/subset_sum_ii.rs new file mode 100644 index 000000000..715922134 --- /dev/null +++ b/zh-hant/codes/rust/chapter_backtracking/subset_sum_ii.rs @@ -0,0 +1,61 @@ +/* + * File: subset_sum_ii.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯演算法:子集和 II */ +fn backtrack( + mut state: Vec, + target: i32, + choices: &[i32], + start: usize, + res: &mut Vec>, +) { + // 子集和等於 target 時,記錄解 + if target == 0 { + res.push(state); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for i in start..choices.len() { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target - choices[i] < 0 { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if i > start && choices[i] == choices[i - 1] { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state.clone(), target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 II */ +fn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec> { + let state = Vec::new(); // 狀態(子集) + nums.sort(); // 對 nums 進行排序 + let start = 0; // 走訪起始點 + let mut res = Vec::new(); // 結果串列(子集串列) + backtrack(state, target, nums, start, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 4, 5]; + let target = 9; + + let res = subset_sum_ii(&mut nums, target); + + println!("輸入陣列 nums = {:?}, target = {}", &nums, target); + println!("所有和等於 {} 的子集 res = {:?}", target, &res); +} diff --git a/zh-hant/codes/rust/chapter_computational_complexity/iteration.rs b/zh-hant/codes/rust/chapter_computational_complexity/iteration.rs new file mode 100644 index 000000000..ed1bbdc86 --- /dev/null +++ b/zh-hant/codes/rust/chapter_computational_complexity/iteration.rs @@ -0,0 +1,74 @@ +/* + * File: iteration.rs + * Created Time: 2023-09-02 + * Author: night-cruise (2586447362@qq.com) + */ + +/* for 迴圈 */ +fn for_loop(n: i32) -> i32 { + let mut res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for i in 1..=n { + res += i; + } + res +} + +/* while 迴圈 */ +fn while_loop(n: i32) -> i32 { + let mut res = 0; + let mut i = 1; // 初始化條件變數 + + // 迴圈求和 1, 2, ..., n-1, n + while i <= n { + res += i; + i += 1; // 更新條件變數 + } + res +} + +/* while 迴圈(兩次更新) */ +fn while_loop_ii(n: i32) -> i32 { + let mut res = 0; + let mut i = 1; // 初始化條件變數 + + // 迴圈求和 1, 4, 10, ... + while i <= n { + res += i; + // 更新條件變數 + i += 1; + i *= 2; + } + res +} + +/* 雙層 for 迴圈 */ +fn nested_for_loop(n: i32) -> String { + let mut res = vec![]; + // 迴圈 i = 1, 2, ..., n-1, n + for i in 1..=n { + // 迴圈 j = 1, 2, ..., n-1, n + for j in 1..=n { + res.push(format!("({}, {}), ", i, j)); + } + } + res.join("") +} + +/* Driver Code */ +fn main() { + let n = 5; + let mut res; + + res = for_loop(n); + println!("\nfor 迴圈的求和結果 res = {res}"); + + res = while_loop(n); + println!("\nwhile 迴圈的求和結果 res = {res}"); + + res = while_loop_ii(n); + println!("\nwhile 迴圈(兩次更新)求和結果 res = {}", res); + + let res = nested_for_loop(n); + println!("\n雙層 for 迴圈的走訪結果 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_computational_complexity/recursion.rs b/zh-hant/codes/rust/chapter_computational_complexity/recursion.rs new file mode 100644 index 000000000..3e590fe82 --- /dev/null +++ b/zh-hant/codes/rust/chapter_computational_complexity/recursion.rs @@ -0,0 +1,76 @@ +/* + * File: recursion.rs + * Created Time: 2023-09-02 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 遞迴 */ +fn recur(n: i32) -> i32 { + // 終止條件 + if n == 1 { + return 1; + } + // 遞:遞迴呼叫 + let res = recur(n - 1); + // 迴:返回結果 + n + res +} + +/* 使用迭代模擬遞迴 */ +fn for_loop_recur(n: i32) -> i32 { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + let mut stack = Vec::new(); + let mut res = 0; + // 遞:遞迴呼叫 + for i in (1..=n).rev() { + // 透過“入堆疊操作”模擬“遞” + stack.push(i); + } + // 迴:返回結果 + while !stack.is_empty() { + // 透過“出堆疊操作”模擬“迴” + res += stack.pop().unwrap(); + } + // res = 1+2+3+...+n + res +} + +/* 尾遞迴 */ +fn tail_recur(n: i32, res: i32) -> i32 { + // 終止條件 + if n == 0 { + return res; + } + // 尾遞迴呼叫 + tail_recur(n - 1, res + n) +} + +/* 費波那契數列:遞迴 */ +fn fib(n: i32) -> i32 { + // 終止條件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1; + } + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + let res = fib(n - 1) + fib(n - 2); + // 返回結果 + res +} + +/* Driver Code */ +fn main() { + let n = 5; + let mut res; + + res = recur(n); + println!("\n遞迴函式的求和結果 res = {res}"); + + res = for_loop_recur(n); + println!("\n使用迭代模擬遞迴求和結果 res = {res}"); + + res = tail_recur(n, 0); + println!("\n尾遞迴函式的求和結果 res = {res}"); + + res = fib(n); + println!("\n費波那契數列的第 {n} 項為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_computational_complexity/space_complexity.rs b/zh-hant/codes/rust/chapter_computational_complexity/space_complexity.rs new file mode 100644 index 000000000..d542f2be2 --- /dev/null +++ b/zh-hant/codes/rust/chapter_computational_complexity/space_complexity.rs @@ -0,0 +1,117 @@ +/* + * File: space_complexity.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use list_node::ListNode; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use tree_node::TreeNode; + +/* 函式 */ +fn function() -> i32 { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +#[allow(unused)] +fn constant(n: i32) { + // 常數、變數、物件佔用 O(1) 空間 + const A: i32 = 0; + let b = 0; + let nums = vec![0; 10000]; + let node = ListNode::new(0); + // 迴圈中的變數佔用 O(1) 空間 + for i in 0..n { + let c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for i in 0..n { + function(); + } +} + +/* 線性階 */ +#[allow(unused)] +fn linear(n: i32) { + // 長度為 n 的陣列佔用 O(n) 空間 + let mut nums = vec![0; n as usize]; + // 長度為 n 的串列佔用 O(n) 空間 + let mut nodes = Vec::new(); + for i in 0..n { + nodes.push(ListNode::new(i)) + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + let mut map = HashMap::new(); + for i in 0..n { + map.insert(i, i.to_string()); + } +} + +/* 線性階(遞迴實現) */ +fn linear_recur(n: i32) { + println!("遞迴 n = {}", n); + if n == 1 { + return; + }; + linear_recur(n - 1); +} + +/* 平方階 */ +#[allow(unused)] +fn quadratic(n: i32) { + // 矩陣佔用 O(n^2) 空間 + let num_matrix = vec![vec![0; n as usize]; n as usize]; + // 二維串列佔用 O(n^2) 空間 + let mut num_list = Vec::new(); + for i in 0..n { + let mut tmp = Vec::new(); + for j in 0..n { + tmp.push(0); + } + num_list.push(tmp); + } +} + +/* 平方階(遞迴實現) */ +fn quadratic_recur(n: i32) -> i32 { + if n <= 0 { + return 0; + }; + // 陣列 nums 長度為 n, n-1, ..., 2, 1 + let nums = vec![0; n as usize]; + println!("遞迴 n = {} 中的 nums 長度 = {}", n, nums.len()); + return quadratic_recur(n - 1); +} + +/* 指數階(建立滿二元樹) */ +fn build_tree(n: i32) -> Option>> { + if n == 0 { + return None; + }; + let root = TreeNode::new(0); + root.borrow_mut().left = build_tree(n - 1); + root.borrow_mut().right = build_tree(n - 1); + return Some(root); +} + +/* Driver Code */ +fn main() { + let n = 5; + // 常數階 + constant(n); + // 線性階 + linear(n); + linear_recur(n); + // 平方階 + quadratic(n); + quadratic_recur(n); + // 指數階 + let root = build_tree(n); + print_util::print_tree(&root.unwrap()); +} diff --git a/zh-hant/codes/rust/chapter_computational_complexity/time_complexity.rs b/zh-hant/codes/rust/chapter_computational_complexity/time_complexity.rs new file mode 100644 index 000000000..809dd0651 --- /dev/null +++ b/zh-hant/codes/rust/chapter_computational_complexity/time_complexity.rs @@ -0,0 +1,170 @@ +/* + * File: time_complexity.rs + * Created Time: 2023-01-10 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +/* 常數階 */ +fn constant(n: i32) -> i32 { + _ = n; + let mut count = 0; + let size = 100_000; + for _ in 0..size { + count += 1; + } + count +} + +/* 線性階 */ +fn linear(n: i32) -> i32 { + let mut count = 0; + for _ in 0..n { + count += 1; + } + count +} + +/* 線性階(走訪陣列) */ +fn array_traversal(nums: &[i32]) -> i32 { + let mut count = 0; + // 迴圈次數與陣列長度成正比 + for _ in nums { + count += 1; + } + count +} + +/* 平方階 */ +fn quadratic(n: i32) -> i32 { + let mut count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for _ in 0..n { + for _ in 0..n { + count += 1; + } + } + count +} + +/* 平方階(泡沫排序) */ +fn bubble_sort(nums: &mut [i32]) -> i32 { + let mut count = 0; // 計數器 + + // 外迴圈:未排序區間為 [0, i] + for i in (1..nums.len()).rev() { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0..i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + count +} + +/* 指數階(迴圈實現) */ +fn exponential(n: i32) -> i32 { + let mut count = 0; + let mut base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for _ in 0..n { + for _ in 0..base { + count += 1 + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + count +} + +/* 指數階(遞迴實現) */ +fn exp_recur(n: i32) -> i32 { + if n == 1 { + return 1; + } + exp_recur(n - 1) + exp_recur(n - 1) + 1 +} + +/* 對數階(迴圈實現) */ +fn logarithmic(mut n: i32) -> i32 { + let mut count = 0; + while n > 1 { + n = n / 2; + count += 1; + } + count +} + +/* 對數階(遞迴實現) */ +fn log_recur(n: i32) -> i32 { + if n <= 1 { + return 0; + } + log_recur(n / 2) + 1 +} + +/* 線性對數階 */ +fn linear_log_recur(n: i32) -> i32 { + if n <= 1 { + return 1; + } + let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2); + for _ in 0..n as i32 { + count += 1; + } + return count; +} + +/* 階乘階(遞迴實現) */ +fn factorial_recur(n: i32) -> i32 { + if n == 0 { + return 1; + } + let mut count = 0; + // 從 1 個分裂出 n 個 + for _ in 0..n { + count += factorial_recur(n - 1); + } + count +} + +/* Driver Code */ +fn main() { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + let n: i32 = 8; + println!("輸入資料大小 n = {}", n); + + let mut count = constant(n); + println!("常數階的操作數量 = {}", count); + + count = linear(n); + println!("線性階的操作數量 = {}", count); + count = array_traversal(&vec![0; n as usize]); + println!("線性階(走訪陣列)的操作數量 = {}", count); + + count = quadratic(n); + println!("平方階的操作數量 = {}", count); + let mut nums = (1..=n).rev().collect::>(); // [n,n-1,...,2,1] + count = bubble_sort(&mut nums); + println!("平方階(泡沫排序)的操作數量 = {}", count); + + count = exponential(n); + println!("指數階(迴圈實現)的操作數量 = {}", count); + count = exp_recur(n); + println!("指數階(遞迴實現)的操作數量 = {}", count); + + count = logarithmic(n); + println!("對數階(迴圈實現)的操作數量 = {}", count); + count = log_recur(n); + println!("對數階(遞迴實現)的操作數量 = {}", count); + + count = linear_log_recur(n); + println!("線性對數階(遞迴實現)的操作數量 = {}", count); + + count = factorial_recur(n); + println!("階乘階(遞迴實現)的操作數量 = {}", count); +} diff --git a/zh-hant/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs b/zh-hant/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs new file mode 100644 index 000000000..f54e7c734 --- /dev/null +++ b/zh-hant/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs @@ -0,0 +1,43 @@ +/* + * File: worst_best_time_complexity.rs + * Created Time: 2023-01-13 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use rand::seq::SliceRandom; +use rand::thread_rng; + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +fn random_numbers(n: i32) -> Vec { + // 生成陣列 nums = { 1, 2, 3, ..., n } + let mut nums = (1..=n).collect::>(); + // 隨機打亂陣列元素 + nums.shuffle(&mut thread_rng()); + nums +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +fn find_one(nums: &[i32]) -> Option { + for i in 0..nums.len() { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if nums[i] == 1 { + return Some(i); + } + } + None +} + +/* Driver Code */ +fn main() { + for _ in 0..10 { + let n = 100; + let nums = random_numbers(n); + let index = find_one(&nums).unwrap(); + print!("\n陣列 [ 1, 2, ..., n ] 被打亂後 = "); + print_util::print_array(&nums); + println!("\n數字 1 的索引為 {}", index); + } +} diff --git a/zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs b/zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs new file mode 100644 index 000000000..85d638ea3 --- /dev/null +++ b/zh-hant/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs @@ -0,0 +1,41 @@ +/* + * File: binary_search_recur.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 二分搜尋:問題 f(i, j) */ +fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { + // 若區間為空,代表無目標元素,則返回 -1 + if i > j { + return -1; + } + let m: i32 = (i + j) / 2; + if nums[m as usize] < target { + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if nums[m as usize] > target { + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } +} + +/* 二分搜尋 */ +fn binary_search(nums: &[i32], target: i32) -> i32 { + let n = nums.len() as i32; + // 求解問題 f(0, n-1) + dfs(nums, target, 0, n - 1) +} + +/* Driver Code */ +pub fn main() { + let target = 6; + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分搜尋(雙閉區間) + let index = binary_search(&nums, target); + println!("目標元素 6 的索引 = {index}"); +} diff --git a/zh-hant/codes/rust/chapter_divide_and_conquer/build_tree.rs b/zh-hant/codes/rust/chapter_divide_and_conquer/build_tree.rs new file mode 100644 index 000000000..96aa1c61b --- /dev/null +++ b/zh-hant/codes/rust/chapter_divide_and_conquer/build_tree.rs @@ -0,0 +1,57 @@ +/* + * File: build_tree.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::collections::HashMap; +use std::{cell::RefCell, rc::Rc}; +include!("../include/include.rs"); +use tree_node::TreeNode; + +/* 構建二元樹:分治 */ +fn dfs( + preorder: &[i32], + inorder_map: &HashMap, + i: i32, + l: i32, + r: i32, +) -> Option>> { + // 子樹區間為空時終止 + if r - l < 0 { + return None; + } + // 初始化根節點 + let root = TreeNode::new(preorder[i as usize]); + // 查詢 m ,從而劃分左右子樹 + let m = inorder_map.get(&preorder[i as usize]).unwrap(); + // 子問題:構建左子樹 + root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r); + // 返回根節點 + Some(root) +} + +/* 構建二元樹 */ +fn build_tree(preorder: &[i32], inorder: &[i32]) -> Option>> { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + let mut inorder_map: HashMap = HashMap::new(); + for i in 0..inorder.len() { + inorder_map.insert(inorder[i], i as i32); + } + let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1); + root +} + +/* Driver Code */ +fn main() { + let preorder = [3, 9, 2, 1, 7]; + let inorder = [9, 3, 1, 2, 7]; + println!("中序走訪 = {:?}", preorder); + println!("前序走訪 = {:?}", inorder); + + let root = build_tree(&preorder, &inorder); + println!("構建的二元樹為:"); + print_util::print_tree(root.as_ref().unwrap()); +} diff --git a/zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs b/zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs new file mode 100644 index 000000000..ccdd77a31 --- /dev/null +++ b/zh-hant/codes/rust/chapter_divide_and_conquer/hanota.rs @@ -0,0 +1,55 @@ +/* + * File: hanota.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +#![allow(non_snake_case)] + +/* 移動一個圓盤 */ +fn move_pan(src: &mut Vec, tar: &mut Vec) { + // 從 src 頂部拿出一個圓盤 + let pan = src.remove(src.len() - 1); + // 將圓盤放入 tar 頂部 + tar.push(pan); +} + +/* 求解河內塔問題 f(i) */ +fn dfs(i: i32, src: &mut Vec, buf: &mut Vec, tar: &mut Vec) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if i == 1 { + move_pan(src, tar); + return; + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move_pan(src, tar); + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解河內塔問題 */ +fn solve_hanota(A: &mut Vec, B: &mut Vec, C: &mut Vec) { + let n = A.len() as i32; + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +pub fn main() { + let mut A = vec![5, 4, 3, 2, 1]; + let mut B = Vec::new(); + let mut C = Vec::new(); + println!("初始狀態下:"); + println!("A = {:?}", A); + println!("B = {:?}", B); + println!("C = {:?}", C); + + solve_hanota(&mut A, &mut B, &mut C); + + println!("圓盤移動完成後:"); + println!("A = {:?}", A); + println!("B = {:?}", B); + println!("C = {:?}", C); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs new file mode 100644 index 000000000..503e9ed7e --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs @@ -0,0 +1,41 @@ +/* + * File: climbing_stairs_backtrack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 回溯 */ +fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) { + // 當爬到第 n 階時,方案數量加 1 + if state == n { + res[0] = res[0] + 1; + } + // 走訪所有選擇 + for &choice in choices { + // 剪枝:不允許越過第 n 階 + if state + choice > n { + continue; + } + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +fn climbing_stairs_backtrack(n: usize) -> i32 { + let choices = vec![1, 2]; // 可選擇向上爬 1 階或 2 階 + let state = 0; // 從第 0 階開始爬 + let mut res = Vec::new(); + res.push(0); // 使用 res[0] 記錄方案數量 + backtrack(&choices, state, n as i32, &mut res); + res[0] +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_backtrack(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs new file mode 100644 index 000000000..139ab1fe8 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs @@ -0,0 +1,33 @@ +/* + * File: climbing_stairs_constraint_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 帶約束爬樓梯:動態規劃 */ +fn climbing_stairs_constraint_dp(n: usize) -> i32 { + if n == 1 || n == 2 { + return 1; + }; + // 初始化 dp 表,用於儲存子問題的解 + let mut dp = vec![vec![-1; 3]; n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3..=n { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + dp[n][1] + dp[n][2] +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_constraint_dp(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs new file mode 100644 index 000000000..06e63e302 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs @@ -0,0 +1,29 @@ +/* + * File: climbing_stairs_dfs.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 搜尋 */ +fn dfs(i: usize) -> i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i as i32; + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i - 1) + dfs(i - 2); + count +} + +/* 爬樓梯:搜尋 */ +fn climbing_stairs_dfs(n: usize) -> i32 { + dfs(n) +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dfs(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs new file mode 100644 index 000000000..33ad73fde --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs @@ -0,0 +1,37 @@ +/* + * File: climbing_stairs_dfs_mem.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 記憶化搜尋 */ +fn dfs(i: usize, mem: &mut [i32]) -> i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i as i32; + } + // 若存在記錄 dp[i] ,則直接返回之 + if mem[i] != -1 { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + count +} + +/* 爬樓梯:記憶化搜尋 */ +fn climbing_stairs_dfs_mem(n: usize) -> i32 { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + let mut mem = vec![-1; n + 1]; + dfs(n, &mut mem) +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dfs_mem(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs new file mode 100644 index 000000000..2966b21c6 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs @@ -0,0 +1,48 @@ +/* + * File: climbing_stairs_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 爬樓梯:動態規劃 */ +fn climbing_stairs_dp(n: usize) -> i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if n == 1 || n == 2 { + return n as i32; + } + // 初始化 dp 表,用於儲存子問題的解 + let mut dp = vec![-1; n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3..=n { + dp[i] = dp[i - 1] + dp[i - 2]; + } + dp[n] +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +fn climbing_stairs_dp_comp(n: usize) -> i32 { + if n == 1 || n == 2 { + return n as i32; + } + let (mut a, mut b) = (1, 2); + for _ in 3..=n { + let tmp = b; + b = a + b; + a = tmp; + } + b +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dp(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); + + let res = climbing_stairs_dp_comp(n); + println!("爬 {n} 階樓梯共有 {res} 種方案"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/coin_change.rs b/zh-hant/codes/rust/chapter_dynamic_programming/coin_change.rs new file mode 100644 index 000000000..dc787677f --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/coin_change.rs @@ -0,0 +1,75 @@ +/* + * File: coin_change.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 零錢兌換:動態規劃 */ +fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + let max = amt + 1; + // 初始化 dp 表 + let mut dp = vec![vec![0; amt + 1]; n + 1]; + // 狀態轉移:首行首列 + for a in 1..=amt { + dp[0][a] = max; + } + // 狀態轉移:其餘行和列 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); + } + } + } + if dp[n][amt] != max { + return dp[n][amt] as i32; + } else { + -1 + } +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + let max = amt + 1; + // 初始化 dp 表 + let mut dp = vec![0; amt + 1]; + dp.fill(max); + dp[0] = 0; + // 狀態轉移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); + } + } + } + if dp[amt] != max { + return dp[amt] as i32; + } else { + -1 + } +} + +/* Driver Code */ +pub fn main() { + let coins = [1, 2, 5]; + let amt: usize = 4; + + // 動態規劃 + let res = coin_change_dp(&coins, amt); + println!("湊到目標金額所需的最少硬幣數量為 {res}"); + + // 空間最佳化後的動態規劃 + let res = coin_change_dp_comp(&coins, amt); + println!("湊到目標金額所需的最少硬幣數量為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/coin_change_ii.rs b/zh-hant/codes/rust/chapter_dynamic_programming/coin_change_ii.rs new file mode 100644 index 000000000..3868934e2 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/coin_change_ii.rs @@ -0,0 +1,64 @@ +/* + * File: coin_change_ii.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 零錢兌換 II:動態規劃 */ +fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + // 初始化 dp 表 + let mut dp = vec![vec![0; amt + 1]; n + 1]; + // 初始化首列 + for i in 0..=n { + dp[i][0] = 1; + } + // 狀態轉移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; + } + } + } + dp[n][amt] +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + // 初始化 dp 表 + let mut dp = vec![0; amt + 1]; + dp[0] = 1; + // 狀態轉移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; + } + } + } + dp[amt] +} + +/* Driver Code */ +pub fn main() { + let coins = [1, 2, 5]; + let amt: usize = 5; + + // 動態規劃 + let res = coin_change_ii_dp(&coins, amt); + println!("湊出目標金額的硬幣組合數量為 {res}"); + + // 空間最佳化後的動態規劃 + let res = coin_change_ii_dp_comp(&coins, amt); + println!("湊出目標金額的硬幣組合數量為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/edit_distance.rs b/zh-hant/codes/rust/chapter_dynamic_programming/edit_distance.rs new file mode 100644 index 000000000..d037328f5 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/edit_distance.rs @@ -0,0 +1,145 @@ +/* + * File: edit_distance.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 編輯距離:暴力搜尋 */ +fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 { + // 若 s 和 t 都為空,則返回 0 + if i == 0 && j == 0 { + return 0; + } + // 若 s 為空,則返回 t 長度 + if i == 0 { + return j as i32; + } + // 若 t 為空,則返回 s 長度 + if j == 0 { + return i as i32; + } + // 若兩字元相等,則直接跳過此兩字元 + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + return edit_distance_dfs(s, t, i - 1, j - 1); + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + let insert = edit_distance_dfs(s, t, i, j - 1); + let delete = edit_distance_dfs(s, t, i - 1, j); + let replace = edit_distance_dfs(s, t, i - 1, j - 1); + // 返回最少編輯步數 + std::cmp::min(std::cmp::min(insert, delete), replace) + 1 +} + +/* 編輯距離:記憶化搜尋 */ +fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec>, i: usize, j: usize) -> i32 { + // 若 s 和 t 都為空,則返回 0 + if i == 0 && j == 0 { + return 0; + } + // 若 s 為空,則返回 t 長度 + if i == 0 { + return j as i32; + } + // 若 t 為空,則返回 s 長度 + if j == 0 { + return i as i32; + } + // 若已有記錄,則直接返回之 + if mem[i][j] != -1 { + return mem[i][j]; + } + // 若兩字元相等,則直接跳過此兩字元 + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1); + let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j); + let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1; + mem[i][j] +} + +/* 編輯距離:動態規劃 */ +fn edit_distance_dp(s: &str, t: &str) -> i32 { + let (n, m) = (s.len(), t.len()); + let mut dp = vec![vec![0; m + 1]; n + 1]; + // 狀態轉移:首行首列 + for i in 1..=n { + dp[i][0] = i as i32; + } + for j in 1..m { + dp[0][j] = j as i32; + } + // 狀態轉移:其餘行和列 + for i in 1..=n { + for j in 1..=m { + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = + std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + dp[n][m] +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +fn edit_distance_dp_comp(s: &str, t: &str) -> i32 { + let (n, m) = (s.len(), t.len()); + let mut dp = vec![0; m + 1]; + // 狀態轉移:首行 + for j in 1..m { + dp[j] = j as i32; + } + // 狀態轉移:其餘行 + for i in 1..=n { + // 狀態轉移:首列 + let mut leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i as i32; + // 狀態轉移:其餘列 + for j in 1..=m { + let temp = dp[j]; + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + dp[m] +} + +/* Driver Code */ +pub fn main() { + let s = "bag"; + let t = "pack"; + let (n, m) = (s.len(), t.len()); + + // 暴力搜尋 + let res = edit_distance_dfs(s, t, n, m); + println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); + + // 記憶搜尋 + let mut mem = vec![vec![0; m + 1]; n + 1]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = edit_distance_dfs_mem(s, t, &mut mem, n, m); + println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); + + // 動態規劃 + let res = edit_distance_dp(s, t); + println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); + + // 空間最佳化後的動態規劃 + let res = edit_distance_dp_comp(s, t); + println!("將 {s} 更改為 {t} 最少需要編輯 {res} 步"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/knapsack.rs b/zh-hant/codes/rust/chapter_dynamic_programming/knapsack.rs new file mode 100644 index 000000000..ba5a6d542 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/knapsack.rs @@ -0,0 +1,113 @@ +/* + * File: knapsack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 0-1 背包:暴力搜尋 */ +fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i - 1] > c as i32 { + return knapsack_dfs(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + let no = knapsack_dfs(wgt, val, i - 1, c); + let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + std::cmp::max(no, yes) +} + +/* 0-1 背包:記憶化搜尋 */ +fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec>, i: usize, c: usize) -> i32 { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0; + } + // 若已有記錄,則直接返回 + if mem[i][c] != -1 { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i - 1] > c as i32 { + return knapsack_dfs_mem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c); + let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = std::cmp::max(no, yes); + mem[i][c] +} + +/* 0-1 背包:動態規劃 */ +fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![vec![0; cap + 1]; n + 1]; + // 狀態轉移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = std::cmp::max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1], + ); + } + } + } + dp[n][cap] +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![0; cap + 1]; + // 狀態轉移 + for i in 1..=n { + // 倒序走訪 + for c in (1..=cap).rev() { + if wgt[i - 1] <= c as i32 { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + dp[cap] +} + +/* Driver Code */ +pub fn main() { + let wgt = [10, 20, 30, 40, 50]; + let val = [50, 120, 150, 210, 240]; + let cap: usize = 50; + let n = wgt.len(); + + // 暴力搜尋 + let res = knapsack_dfs(&wgt, &val, n, cap); + println!("不超過背包容量的最大物品價值為 {res}"); + + // 記憶搜尋 + let mut mem = vec![vec![0; cap + 1]; n + 1]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap); + println!("不超過背包容量的最大物品價值為 {res}"); + + // 動態規劃 + let res = knapsack_dp(&wgt, &val, cap); + println!("不超過背包容量的最大物品價值為 {res}"); + + // 空間最佳化後的動態規劃 + let res = knapsack_dp_comp(&wgt, &val, cap); + println!("不超過背包容量的最大物品價值為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs b/zh-hant/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs new file mode 100644 index 000000000..da54f7a95 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs @@ -0,0 +1,52 @@ +/* + * File: min_cost_climbing_stairs_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::cmp; + +/* 爬樓梯最小代價:動態規劃 */ +fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 { + let n = cost.len() - 1; + if n == 1 || n == 2 { + return cost[n]; + } + // 初始化 dp 表,用於儲存子問題的解 + let mut dp = vec![-1; n + 1]; + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3..=n { + dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i]; + } + dp[n] +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 { + let n = cost.len() - 1; + if n == 1 || n == 2 { + return cost[n]; + }; + let (mut a, mut b) = (cost[1], cost[2]); + for i in 3..=n { + let tmp = b; + b = cmp::min(a, tmp) + cost[i]; + a = tmp; + } + b +} + +/* Driver Code */ +pub fn main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + println!("輸入樓梯的代價串列為 {:?}", &cost); + + let res = min_cost_climbing_stairs_dp(&cost); + println!("爬完樓梯的最低代價為 {res}"); + + let res = min_cost_climbing_stairs_dp_comp(&cost); + println!("爬完樓梯的最低代價為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/min_path_sum.rs b/zh-hant/codes/rust/chapter_dynamic_programming/min_path_sum.rs new file mode 100644 index 000000000..11c4c3afc --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/min_path_sum.rs @@ -0,0 +1,120 @@ +/* + * File: min_path_sum.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 最小路徑和:暴力搜尋 */ +fn min_path_sum_dfs(grid: &Vec>, i: i32, j: i32) -> i32 { + // 若為左上角單元格,則終止搜尋 + if i == 0 && j == 0 { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return i32::MAX; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + let up = min_path_sum_dfs(grid, i - 1, j); + let left = min_path_sum_dfs(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + std::cmp::min(left, up) + grid[i as usize][j as usize] +} + +/* 最小路徑和:記憶化搜尋 */ +fn min_path_sum_dfs_mem(grid: &Vec>, mem: &mut Vec>, i: i32, j: i32) -> i32 { + // 若為左上角單元格,則終止搜尋 + if i == 0 && j == 0 { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return i32::MAX; + } + // 若已有記錄,則直接返回 + if mem[i as usize][j as usize] != -1 { + return mem[i as usize][j as usize]; + } + // 左邊和上邊單元格的最小路徑代價 + let up = min_path_sum_dfs_mem(grid, mem, i - 1, j); + let left = min_path_sum_dfs_mem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize]; + mem[i as usize][j as usize] +} + +/* 最小路徑和:動態規劃 */ +fn min_path_sum_dp(grid: &Vec>) -> i32 { + let (n, m) = (grid.len(), grid[0].len()); + // 初始化 dp 表 + let mut dp = vec![vec![0; m]; n]; + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for j in 1..m { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for i in 1..n { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for i in 1..n { + for j in 1..m { + dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + dp[n - 1][m - 1] +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +fn min_path_sum_dp_comp(grid: &Vec>) -> i32 { + let (n, m) = (grid.len(), grid[0].len()); + // 初始化 dp 表 + let mut dp = vec![0; m]; + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for j in 1..m { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for i in 1..n { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for j in 1..m { + dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + dp[m - 1] +} + +/* Driver Code */ +pub fn main() { + let grid = vec![ + vec![1, 3, 1, 5], + vec![2, 2, 4, 2], + vec![5, 3, 2, 1], + vec![4, 3, 5, 2], + ]; + let (n, m) = (grid.len(), grid[0].len()); + + // 暴力搜尋 + let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1); + println!("從左上角到右下角的最小路徑和為 {res}"); + + // 記憶化搜尋 + let mut mem = vec![vec![0; m]; n]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1); + println!("從左上角到右下角的最小路徑和為 {res}"); + + // 動態規劃 + let res = min_path_sum_dp(&grid); + println!("從左上角到右下角的最小路徑和為 {res}"); + + // 空間最佳化後的動態規劃 + let res = min_path_sum_dp_comp(&grid); + println!("從左上角到右下角的最小路徑和為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs b/zh-hant/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs new file mode 100644 index 000000000..fea015914 --- /dev/null +++ b/zh-hant/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs @@ -0,0 +1,60 @@ +/* + * File: unbounded_knapsack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 完全背包:動態規劃 */ +fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![vec![0; cap + 1]; n + 1]; + // 狀態轉移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空間最佳化後的動態規劃 */ +fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // 初始化 dp 表 + let mut dp = vec![0; cap + 1]; + // 狀態轉移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + dp[cap] +} + +/* Driver Code */ +pub fn main() { + let wgt = [1, 2, 3]; + let val = [5, 11, 15]; + let cap: usize = 4; + + // 動態規劃 + let res = unbounded_knapsack_dp(&wgt, &val, cap); + println!("不超過背包容量的最大物品價值為 {res}"); + + // 空間最佳化後的動態規劃 + let res = unbounded_knapsack_dp_comp(&wgt, &val, cap); + println!("不超過背包容量的最大物品價值為 {res}"); +} diff --git a/zh-hant/codes/rust/chapter_graph/graph_adjacency_list.rs b/zh-hant/codes/rust/chapter_graph/graph_adjacency_list.rs new file mode 100644 index 000000000..07b02caa6 --- /dev/null +++ b/zh-hant/codes/rust/chapter_graph/graph_adjacency_list.rs @@ -0,0 +1,142 @@ +/* + * File: graph_adjacency_list.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +include!("../include/vertex.rs"); + +use std::collections::HashMap; + +/* 基於鄰接表實現的無向圖型別 */ +pub struct GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + pub adj_list: HashMap>, +} + +impl GraphAdjList { + /* 建構子 */ + pub fn new(edges: Vec<[Vertex; 2]>) -> Self { + let mut graph = GraphAdjList { + adj_list: HashMap::new(), + }; + // 新增所有頂點和邊 + for edge in edges { + graph.add_vertex(edge[0]); + graph.add_vertex(edge[1]); + graph.add_edge(edge[0], edge[1]); + } + + graph + } + + /* 獲取頂點數量 */ + #[allow(unused)] + pub fn size(&self) -> usize { + self.adj_list.len() + } + + /* 新增邊 */ + pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if !self.adj_list.contains_key(&vet1) || !self.adj_list.contains_key(&vet2) || vet1 == vet2 + { + panic!("value error"); + } + // 新增邊 vet1 - vet2 + self.adj_list.get_mut(&vet1).unwrap().push(vet2); + self.adj_list.get_mut(&vet2).unwrap().push(vet1); + } + + /* 刪除邊 */ + #[allow(unused)] + pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if !self.adj_list.contains_key(&vet1) || !self.adj_list.contains_key(&vet2) || vet1 == vet2 + { + panic!("value error"); + } + // 刪除邊 vet1 - vet2 + self.adj_list + .get_mut(&vet1) + .unwrap() + .retain(|&vet| vet != vet2); + self.adj_list + .get_mut(&vet2) + .unwrap() + .retain(|&vet| vet != vet1); + } + + /* 新增頂點 */ + pub fn add_vertex(&mut self, vet: Vertex) { + if self.adj_list.contains_key(&vet) { + return; + } + // 在鄰接表中新增一個新鏈結串列 + self.adj_list.insert(vet, vec![]); + } + + /* 刪除頂點 */ + #[allow(unused)] + pub fn remove_vertex(&mut self, vet: Vertex) { + if !self.adj_list.contains_key(&vet) { + panic!("value error"); + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + self.adj_list.remove(&vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for list in self.adj_list.values_mut() { + list.retain(|&v| v != vet); + } + } + + /* 列印鄰接表 */ + pub fn print(&self) { + println!("鄰接表 ="); + for (vertex, list) in &self.adj_list { + let list = list.iter().map(|vertex| vertex.val).collect::>(); + println!("{}: {:?},", vertex.val, list); + } + } +} + +/* Driver Code */ +#[allow(unused)] +fn main() { + /* 初始化無向圖 */ + let v = vals_to_vets(vec![1, 3, 2, 5, 4]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ]; + + let mut graph = GraphAdjList::new(edges); + println!("\n初始化後,圖為"); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.add_edge(v[0], v[2]); + println!("\n新增邊 1-2 後,圖為"); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.remove_edge(v[0], v[1]); + println!("\n刪除邊 1-3 後,圖為"); + graph.print(); + + /* 新增頂點 */ + let v5 = Vertex { val: 6 }; + graph.add_vertex(v5); + println!("\n新增頂點 6 後,圖為"); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.remove_vertex(v[1]); + println!("\n刪除頂點 3 後,圖為"); + graph.print(); +} diff --git a/zh-hant/codes/rust/chapter_graph/graph_adjacency_matrix.rs b/zh-hant/codes/rust/chapter_graph/graph_adjacency_matrix.rs new file mode 100644 index 000000000..f95d4f989 --- /dev/null +++ b/zh-hant/codes/rust/chapter_graph/graph_adjacency_matrix.rs @@ -0,0 +1,136 @@ +/* + * File: graph_adjacency_matrix.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 基於鄰接矩陣實現的無向圖型別 */ +pub struct GraphAdjMat { + // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + pub vertices: Vec, + // 鄰接矩陣,行列索引對應“頂點索引” + pub adj_mat: Vec>, +} + +impl GraphAdjMat { + /* 建構子 */ + pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { + let mut graph = GraphAdjMat { + vertices: vec![], + adj_mat: vec![], + }; + // 新增頂點 + for val in vertices { + graph.add_vertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for edge in edges { + graph.add_edge(edge[0], edge[1]) + } + + graph + } + + /* 獲取頂點數量 */ + pub fn size(&self) -> usize { + self.vertices.len() + } + + /* 新增頂點 */ + pub fn add_vertex(&mut self, val: i32) { + let n = self.size(); + // 向頂點串列中新增新頂點的值 + self.vertices.push(val); + // 在鄰接矩陣中新增一行 + self.adj_mat.push(vec![0; n]); + // 在鄰接矩陣中新增一列 + for row in &mut self.adj_mat { + row.push(0); + } + } + + /* 刪除頂點 */ + pub fn remove_vertex(&mut self, index: usize) { + if index >= self.size() { + panic!("index error") + } + // 在頂點串列中移除索引 index 的頂點 + self.vertices.remove(index); + // 在鄰接矩陣中刪除索引 index 的行 + self.adj_mat.remove(index); + // 在鄰接矩陣中刪除索引 index 的列 + for row in &mut self.adj_mat { + row.remove(index); + } + } + + /* 新增邊 */ + pub fn add_edge(&mut self, i: usize, j: usize) { + // 參數 i, j 對應 vertices 元素索引 + // 索引越界與相等處理 + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + self.adj_mat[i][j] = 1; + self.adj_mat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + pub fn remove_edge(&mut self, i: usize, j: usize) { + // 參數 i, j 對應 vertices 元素索引 + // 索引越界與相等處理 + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + self.adj_mat[i][j] = 0; + self.adj_mat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + pub fn print(&self) { + println!("頂點串列 = {:?}", self.vertices); + println!("鄰接矩陣 ="); + println!("["); + for row in &self.adj_mat { + println!(" {:?},", row); + } + println!("]") + } +} + +/* Driver Code */ +fn main() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + let vertices = vec![1, 3, 2, 5, 4]; + let edges = vec![[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]; + let mut graph = GraphAdjMat::new(vertices, edges); + println!("\n初始化後,圖為"); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.add_edge(0, 2); + println!("\n新增邊 1-2 後,圖為"); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.remove_edge(0, 1); + println!("\n刪除邊 1-3 後,圖為"); + graph.print(); + + /* 新增頂點 */ + graph.add_vertex(6); + println!("\n新增頂點 6 後,圖為"); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.remove_vertex(1); + println!("\n刪除頂點 3 後,圖為"); + graph.print(); +} diff --git a/zh-hant/codes/rust/chapter_graph/graph_bfs.rs b/zh-hant/codes/rust/chapter_graph/graph_bfs.rs new file mode 100644 index 000000000..f2ea5b099 --- /dev/null +++ b/zh-hant/codes/rust/chapter_graph/graph_bfs.rs @@ -0,0 +1,70 @@ +/* + * File: graph_bfs.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +mod graph_adjacency_list; + +use graph_adjacency_list::GraphAdjList; +use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; +use std::collections::{HashSet, VecDeque}; + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // 頂點走訪序列 + let mut res = vec![]; + // 雜湊表,用於記錄已被訪問過的頂點 + let mut visited = HashSet::new(); + visited.insert(start_vet); + // 佇列用於實現 BFS + let mut que = VecDeque::new(); + que.push_back(start_vet); + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while !que.is_empty() { + let vet = que.pop_front().unwrap(); // 佇列首頂點出隊 + res.push(vet); // 記錄訪問頂點 + + // 走訪該頂點的所有鄰接頂點 + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // 跳過已被訪問的頂點 + } + que.push_back(adj_vet); // 只入列未訪問的頂點 + visited.insert(adj_vet); // 標記該頂點已被訪問 + } + } + } + // 返回頂點走訪序列 + res +} + +/* Driver Code */ +fn main() { + /* 初始化無向圖 */ + let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let edges = vec![ + [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]], + ]; + let graph = GraphAdjList::new(edges); + println!("\n初始化後,圖為"); + graph.print(); + + /* 廣度優先走訪 */ + let res = graph_bfs(graph, v[0]); + println!("\n廣度優先走訪(BFS)頂點序列為"); + println!("{:?}", vets_to_vals(res)); +} diff --git a/zh-hant/codes/rust/chapter_graph/graph_dfs.rs b/zh-hant/codes/rust/chapter_graph/graph_dfs.rs new file mode 100644 index 000000000..a1bfc4d1c --- /dev/null +++ b/zh-hant/codes/rust/chapter_graph/graph_dfs.rs @@ -0,0 +1,61 @@ +/* + * File: graph_dfs.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +mod graph_adjacency_list; + +use graph_adjacency_list::GraphAdjList; +use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; +use std::collections::HashSet; + +/* 深度優先走訪輔助函式 */ +fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { + res.push(vet); // 記錄訪問頂點 + visited.insert(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adj_vet); + } + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // 頂點走訪序列 + let mut res = vec![]; + // 雜湊表,用於記錄已被訪問過的頂點 + let mut visited = HashSet::new(); + dfs(&graph, &mut visited, &mut res, start_vet); + + res +} + +/* Driver Code */ +fn main() { + /* 初始化無向圖 */ + let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ]; + let graph = GraphAdjList::new(edges); + println!("\n初始化後,圖為"); + graph.print(); + + /* 深度優先走訪 */ + let res = graph_dfs(graph, v[0]); + println!("\n深度優先走訪(DFS)頂點序列為"); + println!("{:?}", vets_to_vals(res)); +} diff --git a/zh-hant/codes/rust/chapter_greedy/coin_change_greedy.rs b/zh-hant/codes/rust/chapter_greedy/coin_change_greedy.rs new file mode 100644 index 000000000..2104eff81 --- /dev/null +++ b/zh-hant/codes/rust/chapter_greedy/coin_change_greedy.rs @@ -0,0 +1,54 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 零錢兌換:貪婪 */ +fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 { + // 假設 coins 串列有序 + let mut i = coins.len() - 1; + let mut count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while amt > 0 { + // 找到小於且最接近剩餘金額的硬幣 + while i > 0 && coins[i] > amt { + i -= 1; + } + // 選擇 coins[i] + amt -= coins[i]; + count += 1; + } + // 若未找到可行方案,則返回 -1 + if amt == 0 { + count + } else { + -1 + } +} + +/* Driver Code */ +fn main() { + // 貪婪:能夠保證找到全域性最優解 + let coins = [1, 5, 10, 20, 50, 100]; + let amt = 186; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("湊到 {} 所需的最少硬幣數量為 {}", amt, res); + + // 貪婪:無法保證找到全域性最優解 + let coins = [1, 20, 50]; + let amt = 60; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("湊到 {} 所需的最少硬幣數量為 {}", amt, res); + println!("實際上需要的最少數量為 3 ,即 20 + 20 + 20"); + + // 貪婪:無法保證找到全域性最優解 + let coins = [1, 49, 50]; + let amt = 98; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("湊到 {} 所需的最少硬幣數量為 {}", amt, res); + println!("實際上需要的最少數量為 2 ,即 49 + 49"); +} diff --git a/zh-hant/codes/rust/chapter_greedy/fractional_knapsack.rs b/zh-hant/codes/rust/chapter_greedy/fractional_knapsack.rs new file mode 100644 index 000000000..471469df7 --- /dev/null +++ b/zh-hant/codes/rust/chapter_greedy/fractional_knapsack.rs @@ -0,0 +1,59 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 物品 */ +struct Item { + w: i32, // 物品重量 + v: i32, // 物品價值 +} + +impl Item { + fn new(w: i32, v: i32) -> Self { + Self { w, v } + } +} + +/* 分數背包:貪婪 */ +fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 { + // 建立物品串列,包含兩個屬性:重量、價值 + let mut items = wgt + .iter() + .zip(val.iter()) + .map(|(&w, &v)| Item::new(w, v)) + .collect::>(); + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort_by(|a, b| { + (b.v as f64 / b.w as f64) + .partial_cmp(&(a.v as f64 / a.w as f64)) + .unwrap() + }); + // 迴圈貪婪選擇 + let mut res = 0.0; + for item in &items { + if item.w <= cap { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v as f64; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += item.v as f64 / item.w as f64 * cap as f64; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + res +} + +/* Driver Code */ +fn main() { + let wgt = [10, 20, 30, 40, 50]; + let val = [50, 120, 150, 210, 240]; + let cap = 50; + + // 貪婪演算法 + let res = fractional_knapsack(&wgt, &val, cap); + println!("不超過背包容量的最大物品價值為 {}", res); +} diff --git a/zh-hant/codes/rust/chapter_greedy/max_capacity.rs b/zh-hant/codes/rust/chapter_greedy/max_capacity.rs new file mode 100644 index 000000000..216a0b066 --- /dev/null +++ b/zh-hant/codes/rust/chapter_greedy/max_capacity.rs @@ -0,0 +1,36 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 最大容量:貪婪 */ +fn max_capacity(ht: &[i32]) -> i32 { + // 初始化 i, j,使其分列陣列兩端 + let mut i = 0; + let mut j = ht.len() - 1; + // 初始最大容量為 0 + let mut res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while i < j { + // 更新最大容量 + let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32; + res = std::cmp::max(res, cap); + // 向內移動短板 + if ht[i] < ht[j] { + i += 1; + } else { + j -= 1; + } + } + res +} + +/* Driver Code */ +fn main() { + let ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 貪婪演算法 + let res = max_capacity(&ht); + println!("最大容量為 {}", res); +} diff --git a/zh-hant/codes/rust/chapter_greedy/max_product_cutting.rs b/zh-hant/codes/rust/chapter_greedy/max_product_cutting.rs new file mode 100644 index 000000000..a31a55a04 --- /dev/null +++ b/zh-hant/codes/rust/chapter_greedy/max_product_cutting.rs @@ -0,0 +1,35 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 最大切分乘積:貪婪 */ +fn max_product_cutting(n: i32) -> i32 { + // 當 n <= 3 時,必須切分出一個 1 + if n <= 3 { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + let a = n / 3; + let b = n % 3; + if b == 1 { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + 3_i32.pow(a as u32 - 1) * 2 * 2 + } else if b == 2 { + // 當餘數為 2 時,不做處理 + 3_i32.pow(a as u32) * 2 + } else { + // 當餘數為 0 時,不做處理 + 3_i32.pow(a as u32) + } +} + +/* Driver Code */ +fn main() { + let n = 58; + + // 貪婪演算法 + let res = max_product_cutting(n); + println!("最大切分乘積為 {}", res); +} diff --git a/zh-hant/codes/rust/chapter_hashing/array_hash_map.rs b/zh-hant/codes/rust/chapter_hashing/array_hash_map.rs new file mode 100644 index 000000000..4b62198c8 --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/array_hash_map.rs @@ -0,0 +1,124 @@ +/** + * File: array_hash_map.rs + * Created Time: 2023-2-18 + * Author: xBLACICEx (xBLACKICEx@outlook.com) + */ + +/* 鍵值對 */ +#[derive(Debug, Clone, PartialEq)] +pub struct Pair { + pub key: i32, + pub val: String, +} +/* 基於陣列實現的雜湊表 */ +pub struct ArrayHashMap { + buckets: Vec>, +} + +impl ArrayHashMap { + pub fn new() -> ArrayHashMap { + // 初始化陣列,包含 100 個桶 + Self { + buckets: vec![None; 100], + } + } + + /* 雜湊函式 */ + fn hash_func(&self, key: i32) -> usize { + key as usize % 100 + } + + /* 查詢操作 */ + pub fn get(&self, key: i32) -> Option<&String> { + let index = self.hash_func(key); + self.buckets[index].as_ref().map(|pair| &pair.val) + } + + /* 新增操作 */ + pub fn put(&mut self, key: i32, val: &str) { + let index = self.hash_func(key); + self.buckets[index] = Some(Pair { + key, + val: val.to_string(), + }); + } + + /* 刪除操作 */ + pub fn remove(&mut self, key: i32) { + let index = self.hash_func(key); + // 置為 None ,代表刪除 + self.buckets[index] = None; + } + + /* 獲取所有鍵值對 */ + pub fn entry_set(&self) -> Vec<&Pair> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref()) + .collect() + } + + /* 獲取所有鍵 */ + pub fn key_set(&self) -> Vec<&i32> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref().map(|pair| &pair.key)) + .collect() + } + + /* 獲取所有值 */ + pub fn value_set(&self) -> Vec<&String> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref().map(|pair| &pair.val)) + .collect() + } + + /* 列印雜湊表 */ + pub fn print(&self) { + for pair in self.entry_set() { + println!("{} -> {}", pair.key, pair.val); + } + } +} + +fn main() { + /* 初始化雜湊表 */ + let mut map = ArrayHashMap::new(); + /*新增操作 */ + // 在雜湊表中新增鍵值對(key, value) + map.put(12836, "小哈"); + map.put(15937, "小囉"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鴨"); + println!("\n新增完成後,雜湊表為\nKey -> Value"); + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(15937).unwrap(); + println!("\n輸入學號 15937 ,查詢到姓名 {}", name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + println!("\n刪除 10583 後,雜湊表為\nKey -> Value"); + map.print(); + + /* 走訪雜湊表 */ + println!("\n走訪鍵值對 Key->Value"); + for pair in map.entry_set() { + println!("{} -> {}", pair.key, pair.val); + } + + println!("\n單獨走訪鍵 Key"); + for key in map.key_set() { + println!("{}", key); + } + + println!("\n單獨走訪值 Value"); + for val in map.value_set() { + println!("{}", val); + } +} diff --git a/zh-hant/codes/rust/chapter_hashing/build_in_hash.rs b/zh-hant/codes/rust/chapter_hashing/build_in_hash.rs new file mode 100644 index 000000000..47e327619 --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/build_in_hash.rs @@ -0,0 +1,50 @@ +/* + * File: build_in_hash.rs + * Created Time: 2023-7-6 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +include!("../include/include.rs"); + +use crate::list_node::ListNode; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +/* Driver Code */ +fn main() { + let num = 3; + let mut num_hasher = DefaultHasher::new(); + num.hash(&mut num_hasher); + let hash_num = num_hasher.finish(); + println!("整數 {} 的雜湊值為 {}", num, hash_num); + + let bol = true; + let mut bol_hasher = DefaultHasher::new(); + bol.hash(&mut bol_hasher); + let hash_bol = bol_hasher.finish(); + println!("布林量 {} 的雜湊值為 {}", bol, hash_bol); + + let dec: f32 = 3.14159; + let mut dec_hasher = DefaultHasher::new(); + dec.to_bits().hash(&mut dec_hasher); + let hash_dec = dec_hasher.finish(); + println!("小數 {} 的雜湊值為 {}", dec, hash_dec); + + let str = "Hello 演算法"; + let mut str_hasher = DefaultHasher::new(); + str.hash(&mut str_hasher); + let hash_str = str_hasher.finish(); + println!("字串 {} 的雜湊值為 {}", str, hash_str); + + let arr = (&12836, &"小哈"); + let mut tup_hasher = DefaultHasher::new(); + arr.hash(&mut tup_hasher); + let hash_tup = tup_hasher.finish(); + println!("元組 {:?} 的雜湊值為 {}", arr, hash_tup); + + let node = ListNode::new(42); + let mut hasher = DefaultHasher::new(); + node.borrow().val.hash(&mut hasher); + let hash = hasher.finish(); + println!("節點物件 {:?} 的雜湊值為{}", node, hash); +} diff --git a/zh-hant/codes/rust/chapter_hashing/hash_map.rs b/zh-hant/codes/rust/chapter_hashing/hash_map.rs new file mode 100644 index 000000000..ad32c45eb --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/hash_map.rs @@ -0,0 +1,48 @@ +/* + * File: hash_map.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use std::collections::HashMap; + +/* Driver Code */ +pub fn main() { + // 初始化雜湊表 + let mut map = HashMap::new(); + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, value) + map.insert(12836, "小哈"); + map.insert(15937, "小囉"); + map.insert(16750, "小算"); + map.insert(13276, "小法"); + map.insert(10583, "小鴨"); + println!("\n新增完成後,雜湊表為\nKey -> Value"); + print_util::print_hash_map(&map); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(&15937).copied().unwrap(); + println!("\n輸入學號 15937 ,查詢到姓名 {name}"); + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, value) + _ = map.remove(&10583); + println!("\n刪除 10583 後,雜湊表為\nKey -> Value"); + print_util::print_hash_map(&map); + + // 走訪雜湊表 + println!("\n走訪鍵值對 Key->Value"); + print_util::print_hash_map(&map); + println!("\n單獨走訪鍵 Key"); + for key in map.keys() { + println!("{key}"); + } + println!("\n單獨走訪值 value"); + for value in map.values() { + println!("{value}"); + } +} diff --git a/zh-hant/codes/rust/chapter_hashing/hash_map_chaining.rs b/zh-hant/codes/rust/chapter_hashing/hash_map_chaining.rs new file mode 100644 index 000000000..c9ed9669b --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/hash_map_chaining.rs @@ -0,0 +1,167 @@ +/* + * File: hash_map_chaining.rs + * Created Time: 2023-07-07 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +#[derive(Clone)] +/* 鍵值對 */ +struct Pair { + key: i32, + val: String, +} + +/* 鏈式位址雜湊表 */ +struct HashMapChaining { + size: i32, + capacity: i32, + load_thres: f32, + extend_ratio: i32, + buckets: Vec>, +} + +impl HashMapChaining { + /* 建構子 */ + fn new() -> Self { + Self { + size: 0, + capacity: 4, + load_thres: 2.0 / 3.0, + extend_ratio: 2, + buckets: vec![vec![]; 4], + } + } + + /* 雜湊函式 */ + fn hash_func(&self, key: i32) -> usize { + key as usize % self.capacity as usize + } + + /* 負載因子 */ + fn load_factor(&self) -> f32 { + self.size as f32 / self.capacity as f32 + } + + /* 刪除操作 */ + fn remove(&mut self, key: i32) -> Option { + let index = self.hash_func(key); + let bucket = &mut self.buckets[index]; + + // 走訪桶,從中刪除鍵值對 + for i in 0..bucket.len() { + if bucket[i].key == key { + let pair = bucket.remove(i); + self.size -= 1; + return Some(pair.val); + } + } + + // 若未找到 key ,則返回 None + None + } + + /* 擴容雜湊表 */ + fn extend(&mut self) { + // 暫存原雜湊表 + let buckets_tmp = std::mem::replace(&mut self.buckets, vec![]); + + // 初始化擴容後的新雜湊表 + self.capacity *= self.extend_ratio; + self.buckets = vec![Vec::new(); self.capacity as usize]; + self.size = 0; + + // 將鍵值對從原雜湊表搬運至新雜湊表 + for bucket in buckets_tmp { + for pair in bucket { + self.put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + fn print(&self) { + for bucket in &self.buckets { + let mut res = Vec::new(); + for pair in bucket { + res.push(format!("{} -> {}", pair.key, pair.val)); + } + println!("{:?}", res); + } + } + + /* 新增操作 */ + fn put(&mut self, key: i32, val: String) { + // 當負載因子超過閾值時,執行擴容 + if self.load_factor() > self.load_thres { + self.extend(); + } + + let index = self.hash_func(key); + let bucket = &mut self.buckets[index]; + + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for pair in bucket { + if pair.key == key { + pair.val = val.clone(); + return; + } + } + let bucket = &mut self.buckets[index]; + + // 若無該 key ,則將鍵值對新增至尾部 + let pair = Pair { + key, + val: val.clone(), + }; + bucket.push(pair); + self.size += 1; + } + + /* 查詢操作 */ + fn get(&self, key: i32) -> Option<&str> { + let index = self.hash_func(key); + let bucket = &self.buckets[index]; + + // 走訪桶,若找到 key ,則返回對應 val + for pair in bucket { + if pair.key == key { + return Some(&pair.val); + } + } + + // 若未找到 key ,則返回 None + None + } +} + +/* Driver Code */ +pub fn main() { + /* 初始化雜湊表 */ + let mut map = HashMapChaining::new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(12836, "小哈".to_string()); + map.put(15937, "小囉".to_string()); + map.put(16750, "小算".to_string()); + map.put(13276, "小法".to_string()); + map.put(10583, "小鴨".to_string()); + println!("\n新增完成後,雜湊表為\nKey -> Value"); + map.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + println!( + "\n輸入學號 13276,查詢到姓名 {}", + match map.get(13276) { + Some(value) => value, + None => "Not a valid Key", + } + ); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(12836); + println!("\n刪除 12836 後,雜湊表為\nKey -> Value"); + map.print(); +} diff --git a/zh-hant/codes/rust/chapter_hashing/hash_map_open_addressing.rs b/zh-hant/codes/rust/chapter_hashing/hash_map_open_addressing.rs new file mode 100644 index 000000000..984c5a5a3 --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/hash_map_open_addressing.rs @@ -0,0 +1,181 @@ +/* + * File: hash_map_open_addressing.rs + * Created Time: 2023-07-16 + * Author: WSL0809 (wslzzy@outlook.com), night-cruise (2586447362@qq.com) + */ +#![allow(non_snake_case)] +#![allow(unused)] + +mod array_hash_map; + +use array_hash_map::Pair; + +/* 開放定址雜湊表 */ +struct HashMapOpenAddressing { + size: usize, // 鍵值對數量 + capacity: usize, // 雜湊表容量 + load_thres: f64, // 觸發擴容的負載因子閾值 + extend_ratio: usize, // 擴容倍數 + buckets: Vec>, // 桶陣列 + TOMBSTONE: Option, // 刪除標記 +} + +impl HashMapOpenAddressing { + /* 建構子 */ + fn new() -> Self { + Self { + size: 0, + capacity: 4, + load_thres: 2.0 / 3.0, + extend_ratio: 2, + buckets: vec![None; 4], + TOMBSTONE: Some(Pair { + key: -1, + val: "-1".to_string(), + }), + } + } + + /* 雜湊函式 */ + fn hash_func(&self, key: i32) -> usize { + (key % self.capacity as i32) as usize + } + + /* 負載因子 */ + fn load_factor(&self) -> f64 { + self.size as f64 / self.capacity as f64 + } + + /* 搜尋 key 對應的桶索引 */ + fn find_bucket(&mut self, key: i32) -> usize { + let mut index = self.hash_func(key); + let mut first_tombstone = -1; + // 線性探查,當遇到空桶時跳出 + while self.buckets[index].is_some() { + // 若遇到 key,返回對應的桶索引 + if self.buckets[index].as_ref().unwrap().key == key { + // 若之前遇到了刪除標記,則將建值對移動至該索引 + if first_tombstone != -1 { + self.buckets[first_tombstone as usize] = self.buckets[index].take(); + self.buckets[index] = self.TOMBSTONE.clone(); + return first_tombstone as usize; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { + first_tombstone = index as i32; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % self.capacity; + } + // 若 key 不存在,則返回新增點的索引 + if first_tombstone == -1 { + index + } else { + first_tombstone as usize + } + } + + /* 查詢操作 */ + fn get(&mut self, key: i32) -> Option<&str> { + // 搜尋 key 對應的桶索引 + let index = self.find_bucket(key); + // 若找到鍵值對,則返回對應 val + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + return self.buckets[index].as_ref().map(|pair| &pair.val as &str); + } + // 若鍵值對不存在,則返回 null + None + } + + /* 新增操作 */ + fn put(&mut self, key: i32, val: String) { + // 當負載因子超過閾值時,執行擴容 + if self.load_factor() > self.load_thres { + self.extend(); + } + // 搜尋 key 對應的桶索引 + let index = self.find_bucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + self.buckets[index].as_mut().unwrap().val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + self.buckets[index] = Some(Pair { key, val }); + self.size += 1; + } + + /* 刪除操作 */ + fn remove(&mut self, key: i32) { + // 搜尋 key 對應的桶索引 + let index = self.find_bucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + self.buckets[index] = self.TOMBSTONE.clone(); + self.size -= 1; + } + } + + /* 擴容雜湊表 */ + fn extend(&mut self) { + // 暫存原雜湊表 + let buckets_tmp = self.buckets.clone(); + // 初始化擴容後的新雜湊表 + self.capacity *= self.extend_ratio; + self.buckets = vec![None; self.capacity]; + self.size = 0; + + // 將鍵值對從原雜湊表搬運至新雜湊表 + for pair in buckets_tmp { + if pair.is_none() || pair == self.TOMBSTONE { + continue; + } + let pair = pair.unwrap(); + + self.put(pair.key, pair.val); + } + } + /* 列印雜湊表 */ + fn print(&self) { + for pair in &self.buckets { + if pair.is_none() { + println!("null"); + } else if pair == &self.TOMBSTONE { + println!("TOMBSTONE"); + } else { + let pair = pair.as_ref().unwrap(); + println!("{} -> {}", pair.key, pair.val); + } + } + } +} + +/* Driver Code */ +fn main() { + /* 初始化雜湊表 */ + let mut hashmap = HashMapOpenAddressing::new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hashmap.put(12836, "小哈".to_string()); + hashmap.put(15937, "小囉".to_string()); + hashmap.put(16750, "小算".to_string()); + hashmap.put(13276, "小法".to_string()); + hashmap.put(10583, "小鴨".to_string()); + + println!("\n新增完成後,雜湊表為\nKey -> Value"); + hashmap.print(); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 val + let name = hashmap.get(13276).unwrap(); + println!("\n輸入學號 13276 ,查詢到姓名 {}", name); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, val) + hashmap.remove(16750); + println!("\n刪除 16750 後,雜湊表為\nKey -> Value"); + hashmap.print(); +} diff --git a/zh-hant/codes/rust/chapter_hashing/simple_hash.rs b/zh-hant/codes/rust/chapter_hashing/simple_hash.rs new file mode 100644 index 000000000..35b27d560 --- /dev/null +++ b/zh-hant/codes/rust/chapter_hashing/simple_hash.rs @@ -0,0 +1,70 @@ +/* + * File: simple_hash.rs + * Created Time: 2023-09-07 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 加法雜湊 */ +fn add_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = (hash + c as i64) % MODULUS; + } + + hash as i32 +} + +/* 乘法雜湊 */ +fn mul_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = (31 * hash + c as i64) % MODULUS; + } + + hash as i32 +} + +/* 互斥或雜湊 */ +fn xor_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash ^= c as i64; + } + + (hash & MODULUS) as i32 +} + +/* 旋轉雜湊 */ +fn rot_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS; + } + + hash as i32 +} + +/* Driver Code */ +fn main() { + let key = "Hello 演算法"; + + let hash = add_hash(key); + println!("加法雜湊值為 {hash}"); + + let hash = mul_hash(key); + println!("乘法雜湊值為 {hash}"); + + let hash = xor_hash(key); + println!("互斥或雜湊值為 {hash}"); + + let hash = rot_hash(key); + println!("旋轉雜湊值為 {hash}"); +} diff --git a/zh-hant/codes/rust/chapter_heap/heap.rs b/zh-hant/codes/rust/chapter_heap/heap.rs new file mode 100644 index 000000000..979e83e14 --- /dev/null +++ b/zh-hant/codes/rust/chapter_heap/heap.rs @@ -0,0 +1,73 @@ +/* + * File: heap.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +include!("../include/include.rs"); + +use std::collections::BinaryHeap; + +fn test_push(heap: &mut BinaryHeap, val: i32, flag: i32) { + heap.push(flag * val); // 元素入堆積 + println!("\n元素 {} 入堆積後", val); + print_util::print_heap(heap.iter().map(|&val| flag * val).collect()); +} + +fn test_pop(heap: &mut BinaryHeap, flag: i32) { + let val = heap.pop().unwrap(); + println!("\n堆積頂元素 {} 出堆積後", flag * val); + print_util::print_heap(heap.iter().map(|&val| flag * val).collect()); +} + +/* Driver Code */ +fn main() { + /* 初始化堆積 */ + // 初始化小頂堆積 + #[allow(unused_assignments)] + let mut min_heap = BinaryHeap::new(); + // Rust 的 BinaryHeap 是大頂堆積,當入列時將元素值乘以 -1 將其反轉,當出列時將元素值乘以 -1 將其還原 + let min_heap_flag = -1; + // 初始化大頂堆積 + let mut max_heap = BinaryHeap::new(); + let max_heap_flag = 1; + + println!("\n以下測試樣例為大頂堆積"); + + /* 元素入堆積 */ + test_push(&mut max_heap, 1, max_heap_flag); + test_push(&mut max_heap, 3, max_heap_flag); + test_push(&mut max_heap, 2, max_heap_flag); + test_push(&mut max_heap, 5, max_heap_flag); + test_push(&mut max_heap, 4, max_heap_flag); + + /* 獲取堆積頂元素 */ + let peek = max_heap.peek().unwrap() * max_heap_flag; + println!("\n堆積頂元素為 {}", peek); + + /* 堆積頂元素出堆積 */ + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + test_pop(&mut max_heap, max_heap_flag); + + /* 獲取堆積大小 */ + let size = max_heap.len(); + println!("\n堆積元素數量為 {}", size); + + /* 判斷堆積是否為空 */ + let is_empty = max_heap.is_empty(); + println!("\n堆積是否為空 {}", is_empty); + + /* 輸入串列並建堆積 */ + // 時間複雜度為 O(n) ,而非 O(nlogn) + min_heap = BinaryHeap::from( + vec![1, 3, 2, 5, 4] + .into_iter() + .map(|val| min_heap_flag * val) + .collect::>(), + ); + println!("\n輸入串列並建立小頂堆積後"); + print_util::print_heap(min_heap.iter().map(|&val| min_heap_flag * val).collect()); +} diff --git a/zh-hant/codes/rust/chapter_heap/my_heap.rs b/zh-hant/codes/rust/chapter_heap/my_heap.rs new file mode 100644 index 000000000..989d2754e --- /dev/null +++ b/zh-hant/codes/rust/chapter_heap/my_heap.rs @@ -0,0 +1,165 @@ +/* + * File: my_heap.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +include!("../include/include.rs"); + +/* 大頂堆積 */ +struct MaxHeap { + // 使用 vector 而非陣列,這樣無須考慮擴容問題 + max_heap: Vec, +} + +impl MaxHeap { + /* 建構子,根據輸入串列建堆積 */ + fn new(nums: Vec) -> Self { + // 將串列元素原封不動新增進堆積 + let mut heap = MaxHeap { max_heap: nums }; + // 堆積化除葉節點以外的其他所有節點 + for i in (0..=Self::parent(heap.size() - 1)).rev() { + heap.sift_down(i); + } + heap + } + + /* 獲取左子節點的索引 */ + fn left(i: usize) -> usize { + 2 * i + 1 + } + + /* 獲取右子節點的索引 */ + fn right(i: usize) -> usize { + 2 * i + 2 + } + + /* 獲取父節點的索引 */ + fn parent(i: usize) -> usize { + (i - 1) / 2 // 向下整除 + } + + /* 交換元素 */ + fn swap(&mut self, i: usize, j: usize) { + self.max_heap.swap(i, j); + } + + /* 獲取堆積大小 */ + fn size(&self) -> usize { + self.max_heap.len() + } + + /* 判斷堆積是否為空 */ + fn is_empty(&self) -> bool { + self.max_heap.is_empty() + } + + /* 訪問堆積頂元素 */ + fn peek(&self) -> Option { + self.max_heap.first().copied() + } + + /* 元素入堆積 */ + fn push(&mut self, val: i32) { + // 新增節點 + self.max_heap.push(val); + // 從底至頂堆積化 + self.sift_up(self.size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + fn sift_up(&mut self, mut i: usize) { + loop { + // 節點 i 已經是堆積頂節點了,結束堆積化 + if i == 0 { + break; + } + // 獲取節點 i 的父節點 + let p = Self::parent(i); + // 當“節點無須修復”時,結束堆積化 + if self.max_heap[i] <= self.max_heap[p] { + break; + } + // 交換兩節點 + self.swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + fn pop(&mut self) -> i32 { + // 判空處理 + if self.is_empty() { + panic!("index out of bounds"); + } + // 交換根節點與最右葉節點(交換首元素與尾元素) + self.swap(0, self.size() - 1); + // 刪除節點 + let val = self.max_heap.remove(self.size() - 1); + // 從頂至底堆積化 + self.sift_down(0); + // 返回堆積頂元素 + val + } + + /* 從節點 i 開始,從頂至底堆積化 */ + fn sift_down(&mut self, mut i: usize) { + loop { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let (l, r, mut ma) = (Self::left(i), Self::right(i), i); + if l < self.size() && self.max_heap[l] > self.max_heap[ma] { + ma = l; + } + if r < self.size() && self.max_heap[r] > self.max_heap[ma] { + ma = r; + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if ma == i { + break; + } + // 交換兩節點 + self.swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 列印堆積(二元樹) */ + fn print(&self) { + print_util::print_heap(self.max_heap.clone()); + } +} + +/* Driver Code */ +fn main() { + /* 初始化大頂堆積 */ + let mut max_heap = MaxHeap::new(vec![9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + println!("\n輸入串列並建堆積後"); + max_heap.print(); + + /* 獲取堆積頂元素 */ + let peek = max_heap.peek(); + if let Some(peek) = peek { + println!("\n堆積頂元素為 {}", peek); + } + + /* 元素入堆積 */ + let val = 7; + max_heap.push(val); + println!("\n元素 {} 入堆積後", val); + max_heap.print(); + + /* 堆積頂元素出堆積 */ + let peek = max_heap.pop(); + println!("\n堆積頂元素 {} 出堆積後", peek); + max_heap.print(); + + /* 獲取堆積大小 */ + let size = max_heap.size(); + println!("\n堆積元素數量為 {}", size); + + /* 判斷堆積是否為空 */ + let is_empty = max_heap.is_empty(); + println!("\n堆積是否為空 {}", is_empty); +} diff --git a/zh-hant/codes/rust/chapter_heap/top_k.rs b/zh-hant/codes/rust/chapter_heap/top_k.rs new file mode 100644 index 000000000..d978b7c8a --- /dev/null +++ b/zh-hant/codes/rust/chapter_heap/top_k.rs @@ -0,0 +1,39 @@ +/* + * File: top_k.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +include!("../include/include.rs"); + +use std::cmp::Reverse; +use std::collections::BinaryHeap; + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { + // BinaryHeap 是大頂堆積,使用 Reverse 將元素取反,從而實現小頂堆積 + let mut heap = BinaryHeap::>::new(); + // 將陣列的前 k 個元素入堆積 + for &num in nums.iter().take(k) { + heap.push(Reverse(num)); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for &num in nums.iter().skip(k) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if num > heap.peek().unwrap().0 { + heap.pop(); + heap.push(Reverse(num)); + } + } + heap +} + +/* Driver Code */ +fn main() { + let nums = vec![1, 7, 6, 3, 2]; + let k = 3; + + let res = top_k_heap(nums, k); + println!("最大的 {} 個元素為", k); + print_util::print_heap(res.into_iter().map(|item| item.0).collect()); +} diff --git a/zh-hant/codes/rust/chapter_searching/binary_search.rs b/zh-hant/codes/rust/chapter_searching/binary_search.rs new file mode 100644 index 000000000..eba52b4ee --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/binary_search.rs @@ -0,0 +1,65 @@ +/* + * File: binary_search.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 二分搜尋(雙閉區間) */ +fn binary_search(nums: &[i32], target: i32) -> i32 { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + let mut i = 0; + let mut j = nums.len() as i32 - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while i <= j { + let m = i + (j - i) / 2; // 計算中點索引 m + if nums[m as usize] < target { + // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + } else if nums[m as usize] > target { + // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + } else { + // 找到目標元素,返回其索引 + return m; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 二分搜尋(左閉右開區間) */ +fn binary_search_lcro(nums: &[i32], target: i32) -> i32 { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + let mut i = 0; + let mut j = nums.len() as i32; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while i < j { + let m = i + (j - i) / 2; // 計算中點索引 m + if nums[m as usize] < target { + // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + } else if nums[m as usize] > target { + // 此情況說明 target 在區間 [i, m) 中 + j = m; + } else { + // 找到目標元素,返回其索引 + return m; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* Driver Code */ +pub fn main() { + let target = 6; + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分搜尋(雙閉區間) + let mut index = binary_search(&nums, target); + println!("目標元素 6 的索引 = {index}"); + + // 二分搜尋(左閉右開區間) + index = binary_search_lcro(&nums, target); + println!("目標元素 6 的索引 = {index}"); +} diff --git a/zh-hant/codes/rust/chapter_searching/binary_search_edge.rs b/zh-hant/codes/rust/chapter_searching/binary_search_edge.rs new file mode 100644 index 000000000..19a496dd1 --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/binary_search_edge.rs @@ -0,0 +1,50 @@ +/* + * File: binary_search_edge.rs + * Created Time: 2023-08-30 + * Author: night-cruise (2586447362@qq.com) + */ + +mod binary_search_insertion; + +use binary_search_insertion::binary_search_insertion; + +/* 二分搜尋最左一個 target */ +fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 { + // 等價於查詢 target 的插入點 + let i = binary_search_insertion(nums, target); + // 未找到 target ,返回 -1 + if i == nums.len() as i32 || nums[i as usize] != target { + return -1; + } + // 找到 target ,返回索引 i + i +} + +/* 二分搜尋最右一個 target */ +fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 { + // 轉化為查詢最左一個 target + 1 + let i = binary_search_insertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + let j = i - 1; + // 未找到 target ,返回 -1 + if j == -1 || nums[j as usize] != target { + return -1; + } + // 找到 target ,返回索引 j + j +} + +/* Driver Code */ +fn main() { + // 包含重複元素的陣列 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + println!("\n陣列 nums = {:?}", nums); + + // 二分搜尋左邊界和右邊界 + for target in [6, 7] { + let index = binary_search_left_edge(&nums, target); + println!("最左一個元素 {} 的索引為 {}", target, index); + let index = binary_search_right_edge(&nums, target); + println!("最右一個元素 {} 的索引為 {}", target, index); + } +} diff --git a/zh-hant/codes/rust/chapter_searching/binary_search_insertion.rs b/zh-hant/codes/rust/chapter_searching/binary_search_insertion.rs new file mode 100644 index 000000000..3a6fc63f5 --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/binary_search_insertion.rs @@ -0,0 +1,61 @@ +/* + * File: binary_search_insertion.rs + * Created Time: 2023-08-30 + * Author: night-cruise (2586447362@qq.com) + */ +#![allow(unused)] + +/* 二分搜尋插入點(無重複元素) */ +fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 { + let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化雙閉區間 [0, n-1] + while i <= j { + let m = i + (j - i) / 2; // 計算中點索引 m + if nums[m as usize] < target { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if nums[m as usize] > target { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + return m; + } + } + // 未找到 target ,返回插入點 i + i +} + +/* 二分搜尋插入點(存在重複元素) */ +pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 { + let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化雙閉區間 [0, n-1] + while i <= j { + let m = i + (j - i) / 2; // 計算中點索引 m + if nums[m as usize] < target { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if nums[m as usize] > target { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + i +} + +/* Driver Code */ +fn main() { + // 無重複元素的陣列 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + println!("\n陣列 nums = {:?}", nums); + // 二分搜尋插入點 + for target in [6, 9] { + let index = binary_search_insertion_simple(&nums, target); + println!("元素 {} 的插入點的索引為 {}", target, index); + } + + // 包含重複元素的陣列 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + println!("\n陣列 nums = {:?}", nums); + // 二分搜尋插入點 + for target in [2, 6, 20] { + let index = binary_search_insertion(&nums, target); + println!("元素 {} 的插入點的索引為 {}", target, index); + } +} diff --git a/zh-hant/codes/rust/chapter_searching/hashing_search.rs b/zh-hant/codes/rust/chapter_searching/hashing_search.rs new file mode 100644 index 000000000..1f8e6ead4 --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/hashing_search.rs @@ -0,0 +1,52 @@ +/* + * File: hashing_search.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use list_node::ListNode; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +/* 雜湊查詢(陣列) */ +fn hashing_search_array<'a>(map: &'a HashMap, target: i32) -> Option<&'a usize> { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 None + map.get(&target) +} + +/* 雜湊查詢(鏈結串列) */ +fn hashing_search_linked_list( + map: &HashMap>>>, + target: i32, +) -> Option<&Rc>>> { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 None + map.get(&target) +} + +/* Driver Code */ +pub fn main() { + let target = 3; + + /* 雜湊查詢(陣列) */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // 初始化雜湊表 + let mut map = HashMap::new(); + for (i, num) in nums.iter().enumerate() { + map.insert(*num, i); // key: 元素,value: 索引 + } + let index = hashing_search_array(&map, target); + println!("目標元素 3 的索引 = {}", index.unwrap()); + + /* 雜湊查詢(鏈結串列) */ + let head = ListNode::arr_to_linked_list(&nums); + // 初始化雜湊表 + // let mut map1 = HashMap::new(); + let map1 = ListNode::linked_list_to_hashmap(head); + let node = hashing_search_linked_list(&map1, target); + println!("目標節點值 3 的對應節點物件為 {:?}", node); +} diff --git a/zh-hant/codes/rust/chapter_searching/linear_search.rs b/zh-hant/codes/rust/chapter_searching/linear_search.rs new file mode 100644 index 000000000..e731bf1d0 --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/linear_search.rs @@ -0,0 +1,56 @@ +/* + * File: linear_search.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use list_node::ListNode; +use std::cell::RefCell; +use std::rc::Rc; + +/* 線性查詢(陣列) */ +fn linear_search_array(nums: &[i32], target: i32) -> i32 { + // 走訪陣列 + for (i, num) in nums.iter().enumerate() { + // 找到目標元素,返回其索引 + if num == &target { + return i as i32; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 線性查詢(鏈結串列) */ +fn linear_search_linked_list( + head: Rc>>, + target: i32, +) -> Option>>> { + // 找到目標節點,返回之 + if head.borrow().val == target { + return Some(head); + }; + // 找到目標節點,返回之 + if let Some(node) = &head.borrow_mut().next { + return linear_search_linked_list(node.clone(), target); + } + // 未找到目標節點,返回 None + return None; +} + +/* Driver Code */ +pub fn main() { + let target = 3; + + /* 在陣列中執行線性查詢 */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + let index = linear_search_array(&nums, target); + println!("目標元素 3 的索引 = {}", index); + + /* 在鏈結串列中執行線性查詢 */ + let head = ListNode::arr_to_linked_list(&nums); + let node = linear_search_linked_list(head.unwrap(), target); + println!("目標節點值 3 的對應節點物件為 {:?}", node); +} diff --git a/zh-hant/codes/rust/chapter_searching/two_sum.rs b/zh-hant/codes/rust/chapter_searching/two_sum.rs new file mode 100644 index 000000000..3cf044dbe --- /dev/null +++ b/zh-hant/codes/rust/chapter_searching/two_sum.rs @@ -0,0 +1,53 @@ +/* + * File: two_sum.rs + * Created Time: 2023-01-14 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use std::collections::HashMap; + +/* 方法一:暴力列舉 */ +pub fn two_sum_brute_force(nums: &Vec, target: i32) -> Option> { + let size = nums.len(); + // 兩層迴圈,時間複雜度為 O(n^2) + for i in 0..size - 1 { + for j in i + 1..size { + if nums[i] + nums[j] == target { + return Some(vec![i as i32, j as i32]); + } + } + } + None +} + +/* 方法二:輔助雜湊表 */ +pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { + // 輔助雜湊表,空間複雜度為 O(n) + let mut dic = HashMap::new(); + // 單層迴圈,時間複雜度為 O(n) + for (i, num) in nums.iter().enumerate() { + match dic.get(&(target - num)) { + Some(v) => return Some(vec![*v as i32, i as i32]), + None => dic.insert(num, i as i32), + }; + } + None +} + +fn main() { + // ======= Test Case ======= + let nums = vec![2, 7, 11, 15]; + let target = 13; + + // ====== Driver Code ====== + // 方法一 + let res = two_sum_brute_force(&nums, target).unwrap(); + print!("方法一 res = "); + print_util::print_array(&res); + // 方法二 + let res = two_sum_hash_table(&nums, target).unwrap(); + print!("\n方法二 res = "); + print_util::print_array(&res); +} diff --git a/zh-hant/codes/rust/chapter_sorting/bubble_sort.rs b/zh-hant/codes/rust/chapter_sorting/bubble_sort.rs new file mode 100644 index 000000000..576b76baf --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/bubble_sort.rs @@ -0,0 +1,57 @@ +/* + * File: bubble_sort.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +/* 泡沫排序 */ +fn bubble_sort(nums: &mut [i32]) { + // 外迴圈:未排序區間為 [0, i] + for i in (1..nums.len()).rev() { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0..i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* 泡沫排序(標誌最佳化) */ +fn bubble_sort_with_flag(nums: &mut [i32]) { + // 外迴圈:未排序區間為 [0, i] + for i in (1..nums.len()).rev() { + let mut flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0..i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 記錄交換元素 + } + } + if !flag { + break; // 此輪“冒泡”未交換任何元素,直接跳出 + }; + } +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + bubble_sort(&mut nums); + print!("泡沫排序完成後 nums = "); + print_util::print_array(&nums); + + let mut nums1 = [4, 1, 3, 1, 5, 2]; + bubble_sort_with_flag(&mut nums1); + print!("\n泡沫排序完成後 nums1 = "); + print_util::print_array(&nums1); +} diff --git a/zh-hant/codes/rust/chapter_sorting/bucket_sort.rs b/zh-hant/codes/rust/chapter_sorting/bucket_sort.rs new file mode 100644 index 000000000..27c4ef2cd --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/bucket_sort.rs @@ -0,0 +1,43 @@ +/* + * File: bucket_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +include!("../include/include.rs"); + +/* 桶排序 */ +fn bucket_sort(nums: &mut [f64]) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + let k = nums.len() / 2; + let mut buckets = vec![vec![]; k]; + // 1. 將陣列元素分配到各個桶中 + for &mut num in &mut *nums { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + let i = (num * k as f64) as usize; + // 將 num 新增進桶 i + buckets[i].push(num); + } + // 2. 對各個桶執行排序 + for bucket in &mut buckets { + // 使用內建排序函式,也可以替換成其他排序演算法 + bucket.sort_by(|a, b| a.partial_cmp(b).unwrap()); + } + // 3. 走訪桶合併結果 + let mut i = 0; + for bucket in &mut buckets { + for &mut num in bucket { + nums[i] = num; + i += 1; + } + } +} + +/* Driver Code */ +fn main() { + // 設輸入資料為浮點數,範圍為 [0, 1) + let mut nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; + bucket_sort(&mut nums); + print!("桶排序完成後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/counting_sort.rs b/zh-hant/codes/rust/chapter_sorting/counting_sort.rs new file mode 100644 index 000000000..169c30e86 --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/counting_sort.rs @@ -0,0 +1,72 @@ +/* + * File: counting_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +include!("../include/include.rs"); + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +fn counting_sort_naive(nums: &mut [i32]) { + // 1. 統計陣列最大元素 m + let m = *nums.into_iter().max().unwrap(); + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + let mut counter = vec![0; m as usize + 1]; + for &num in &*nums { + counter[num as usize] += 1; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + let mut i = 0; + for num in 0..m + 1 { + for _ in 0..counter[num as usize] { + nums[i] = num; + i += 1; + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +fn counting_sort(nums: &mut [i32]) { + // 1. 統計陣列最大元素 m + let m = *nums.into_iter().max().unwrap(); + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + let mut counter = vec![0; m as usize + 1]; + for &num in &*nums { + counter[num as usize] += 1; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for i in 0..m as usize { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + let n = nums.len(); + let mut res = vec![0; n]; + for i in (0..n).rev() { + let num = nums[i]; + res[counter[num as usize] - 1] = num; // 將 num 放置到對應索引處 + counter[num as usize] -= 1; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for i in 0..n { + nums[i] = res[i]; + } +} + +/* Driver Code */ +fn main() { + let mut nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + counting_sort_naive(&mut nums); + print!("計數排序(無法排序物件)完成後 nums = "); + print_util::print_array(&nums); + + let mut nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + counting_sort(&mut nums1); + print!("\n計數排序完成後 nums1 = "); + print_util::print_array(&nums1); +} diff --git a/zh-hant/codes/rust/chapter_sorting/heap_sort.rs b/zh-hant/codes/rust/chapter_sorting/heap_sort.rs new file mode 100644 index 000000000..83cfe0c6b --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/heap_sort.rs @@ -0,0 +1,58 @@ +/* + * File: heap_sort.rs + * Created Time: 2023-07-04 + * Author: night-cruise (2586447362@qq.com) + */ + +include!("../include/include.rs"); + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { + loop { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let mut ma = i; + if l < n && nums[l] > nums[ma] { + ma = l; + } + if r < n && nums[r] > nums[ma] { + ma = r; + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if ma == i { + break; + } + // 交換兩節點 + let temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +fn heap_sort(nums: &mut [i32]) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for i in (0..=nums.len() / 2 - 1).rev() { + sift_down(nums, nums.len(), i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for i in (1..=nums.len() - 1).rev() { + // 交換根節點與最右葉節點(交換首元素與尾元素) + let tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 以根節點為起點,從頂至底進行堆積化 + sift_down(nums, i, 0); + } +} + +/* Driver Code */ +fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + heap_sort(&mut nums); + print!("堆積排序完成後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/insertion_sort.rs b/zh-hant/codes/rust/chapter_sorting/insertion_sort.rs new file mode 100644 index 000000000..a804c668a --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/insertion_sort.rs @@ -0,0 +1,29 @@ +/* + * File: insertion_sort.rs + * Created Time: 2023-02-13 + * Author: xBLACKICEx (xBLACKICEx@outlook.com) + */ + +include!("../include/include.rs"); + +/* 插入排序 */ +fn insertion_sort(nums: &mut [i32]) { + // 外迴圈:已排序區間為 [0, i-1] + for i in 1..nums.len() { + let (base, mut j) = (nums[i], (i - 1) as i32); + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while j >= 0 && nums[j as usize] > base { + nums[(j + 1) as usize] = nums[j as usize]; // 將 nums[j] 向右移動一位 + j -= 1; + } + nums[(j + 1) as usize] = base; // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + insertion_sort(&mut nums); + print!("插入排序完成後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/merge_sort.rs b/zh-hant/codes/rust/chapter_sorting/merge_sort.rs new file mode 100644 index 000000000..049a0fcf4 --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/merge_sort.rs @@ -0,0 +1,66 @@ +/** + * File: merge_sort.rs + * Created Time: 2023-02-14 + * Author: xBLACKICEx (xBLACKICEx@outlook.com) + */ + +/* 合併左子陣列和右子陣列 */ +fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + let tmp_size = right - left + 1; + let mut tmp = vec![0; tmp_size]; + // 初始化左子陣列和右子陣列的起始索引 + let (mut i, mut j, mut k) = (left, mid + 1, 0); + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while i <= mid && j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i]; + i += 1; + } else { + tmp[k] = nums[j]; + j += 1; + } + k += 1; + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while i <= mid { + tmp[k] = nums[i]; + k += 1; + i += 1; + } + while j <= right { + tmp[k] = nums[j]; + k += 1; + j += 1; + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for k in 0..tmp_size { + nums[left + k] = tmp[k]; + } +} + +/* 合併排序 */ +fn merge_sort(nums: &mut [i32], left: usize, right: usize) { + // 終止條件 + if left >= right { + return; // 當子陣列長度為 1 時終止遞迴 + } + + // 劃分階段 + let mid = (left + right) / 2; // 計算中點 + merge_sort(nums, left, mid); // 遞迴左子陣列 + merge_sort(nums, mid + 1, right); // 遞迴右子陣列 + + // 合併階段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +fn main() { + /* 合併排序 */ + let mut nums = [7, 3, 2, 6, 0, 1, 5, 4]; + let right = nums.len() - 1; + merge_sort(&mut nums, 0, right); + println!("合併排序完成後 nums = {:?}", nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/quick_sort.rs b/zh-hant/codes/rust/chapter_sorting/quick_sort.rs new file mode 100644 index 000000000..eda8c9259 --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/quick_sort.rs @@ -0,0 +1,148 @@ +/** + * File: quick_sort.rs + * Created Time: 2023-02-16 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +/* 快速排序 */ +struct QuickSort; + +impl QuickSort { + /* 哨兵劃分 */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // 以 nums[left] 為基準數 + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 從右向左找首個小於基準數的元素 + } + while i < j && nums[i] <= nums[left] { + i += 1; // 從左向右找首個大於基準數的元素 + } + nums.swap(i, j); // 交換這兩個元素 + } + nums.swap(i, left); // 將基準數交換至兩子陣列的分界線 + i // 返回基準數的索引 + } + + /* 快速排序 */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return; + } + // 哨兵劃分 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 遞迴左子陣列、右子陣列 + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } +} + +/* 快速排序(中位基準數最佳化) */ +struct QuickSortMedian; + +impl QuickSortMedian { + /* 選取三個候選元素的中位數 */ + fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize { + let (l, m, r) = (nums[left], nums[mid], nums[right]); + if (l <= m && m <= r) || (r <= m && m <= l) { + return mid; // m 在 l 和 r 之間 + } + if (m <= l && l <= r) || (r <= l && l <= m) { + return left; // l 在 m 和 r 之間 + } + right + } + + /* 哨兵劃分(三數取中值) */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // 選取三個候選元素的中位數 + let med = Self::median_three(nums, left, (left + right) / 2, right); + // 將中位數交換至陣列最左端 + nums.swap(left, med); + // 以 nums[left] 為基準數 + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 從右向左找首個小於基準數的元素 + } + while i < j && nums[i] <= nums[left] { + i += 1; // 從左向右找首個大於基準數的元素 + } + nums.swap(i, j); // 交換這兩個元素 + } + nums.swap(i, left); // 將基準數交換至兩子陣列的分界線 + i // 返回基準數的索引 + } + + /* 快速排序 */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return; + } + // 哨兵劃分 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 遞迴左子陣列、右子陣列 + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } +} + +/* 快速排序(尾遞迴最佳化) */ +struct QuickSortTailCall; + +impl QuickSortTailCall { + /* 哨兵劃分 */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // 以 nums[left] 為基準數 + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 從右向左找首個小於基準數的元素 + } + while i < j && nums[i] <= nums[left] { + i += 1; // 從左向右找首個大於基準數的元素 + } + nums.swap(i, j); // 交換這兩個元素 + } + nums.swap(i, left); // 將基準數交換至兩子陣列的分界線 + i // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) { + // 子陣列長度為 1 時終止 + while left < right { + // 哨兵劃分操作 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 對兩個子陣列中較短的那個執行快速排序 + if pivot - left < right - pivot { + Self::quick_sort(left, pivot - 1, nums); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + Self::quick_sort(pivot + 1, right, nums); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +fn main() { + /* 快速排序 */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSort::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("快速排序完成後 nums = {:?}", nums); + + /* 快速排序(中位基準數最佳化) */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("快速排序(中位基準數最佳化)完成後 nums = {:?}", nums); + + /* 快速排序(尾遞迴最佳化) */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("快速排序(尾遞迴最佳化)完成後 nums = {:?}", nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/radix_sort.rs b/zh-hant/codes/rust/chapter_sorting/radix_sort.rs new file mode 100644 index 000000000..71689908f --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/radix_sort.rs @@ -0,0 +1,65 @@ +/* + * File: radix_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +include!("../include/include.rs"); + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +fn digit(num: i32, exp: i32) -> usize { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return ((num / exp) % 10) as usize; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +fn counting_sort_digit(nums: &mut [i32], exp: i32) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + let mut counter = [0; 10]; + let n = nums.len(); + // 統計 0~9 各數字的出現次數 + for i in 0..n { + let d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d] += 1; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for i in 1..10 { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + let mut res = vec![0; n]; + for i in (0..n).rev() { + let d = digit(nums[i], exp); + let j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d] -= 1; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for i in 0..n { + nums[i] = res[i]; + } +} + +/* 基數排序 */ +fn radix_sort(nums: &mut [i32]) { + // 獲取陣列的最大元素,用於判斷最大位數 + let m = *nums.into_iter().max().unwrap(); + // 按照從低位到高位的順序走訪 + let mut exp = 1; + while exp <= m { + counting_sort_digit(nums, exp); + exp *= 10; + } +} + +/* Driver Code */ +fn main() { + // 基數排序 + let mut nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, + 63832996, + ]; + radix_sort(&mut nums); + print!("基數排序完成後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_sorting/selection_sort.rs b/zh-hant/codes/rust/chapter_sorting/selection_sort.rs new file mode 100644 index 000000000..70f51dce0 --- /dev/null +++ b/zh-hant/codes/rust/chapter_sorting/selection_sort.rs @@ -0,0 +1,35 @@ +/* + * File: selection_sort.rs + * Created Time: 2023-05-30 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +include!("../include/include.rs"); + +/* 選擇排序 */ +fn selection_sort(nums: &mut [i32]) { + if nums.is_empty() { + return; + } + let n = nums.len(); + // 外迴圈:未排序區間為 [i, n-1] + for i in 0..n - 1 { + // 內迴圈:找到未排序區間內的最小元素 + let mut k = i; + for j in i + 1..n { + if nums[j] < nums[k] { + k = j; // 記錄最小元素的索引 + } + } + // 將該最小元素與未排序區間的首個元素交換 + nums.swap(i, k); + } +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + selection_sort(&mut nums); + print!("\n選擇排序完成後 nums = "); + print_util::print_array(&nums); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/array_deque.rs b/zh-hant/codes/rust/chapter_stack_and_queue/array_deque.rs new file mode 100644 index 000000000..f9308ea7d --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/array_deque.rs @@ -0,0 +1,162 @@ +/* + * File: array_deque.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +/* 基於環形陣列實現的雙向佇列 */ +struct ArrayDeque { + nums: Vec, // 用於儲存雙向佇列元素的陣列 + front: usize, // 佇列首指標,指向佇列首元素 + que_size: usize, // 雙向佇列長度 +} + +impl ArrayDeque { + /* 建構子 */ + pub fn new(capacity: usize) -> Self { + Self { + nums: vec![0; capacity], + front: 0, + que_size: 0, + } + } + + /* 獲取雙向佇列的容量 */ + pub fn capacity(&self) -> usize { + self.nums.len() + } + + /* 獲取雙向佇列的長度 */ + pub fn size(&self) -> usize { + self.que_size + } + + /* 判斷雙向佇列是否為空 */ + pub fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* 計算環形陣列索引 */ + fn index(&self, i: i32) -> usize { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return ((i + self.capacity() as i32) % self.capacity() as i32) as usize; + } + + /* 佇列首入列 */ + pub fn push_first(&mut self, num: i32) { + if self.que_size == self.capacity() { + println!("雙向佇列已滿"); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + self.front = self.index(self.front as i32 - 1); + // 將 num 新增至佇列首 + self.nums[self.front] = num; + self.que_size += 1; + } + + /* 佇列尾入列 */ + pub fn push_last(&mut self, num: i32) { + if self.que_size == self.capacity() { + println!("雙向佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + let rear = self.index(self.front as i32 + self.que_size as i32); + // 將 num 新增至佇列尾 + self.nums[rear] = num; + self.que_size += 1; + } + + /* 佇列首出列 */ + fn pop_first(&mut self) -> i32 { + let num = self.peek_first(); + // 佇列首指標向後移動一位 + self.front = self.index(self.front as i32 + 1); + self.que_size -= 1; + num + } + + /* 佇列尾出列 */ + fn pop_last(&mut self) -> i32 { + let num = self.peek_last(); + self.que_size -= 1; + num + } + + /* 訪問佇列首元素 */ + fn peek_first(&self) -> i32 { + if self.is_empty() { + panic!("雙向佇列為空") + }; + self.nums[self.front] + } + + /* 訪問佇列尾元素 */ + fn peek_last(&self) -> i32 { + if self.is_empty() { + panic!("雙向佇列為空") + }; + // 計算尾元素索引 + let last = self.index(self.front as i32 + self.que_size as i32 - 1); + self.nums[last] + } + + /* 返回陣列用於列印 */ + fn to_array(&self) -> Vec { + // 僅轉換有效長度範圍內的串列元素 + let mut res = vec![0; self.que_size]; + let mut j = self.front; + for i in 0..self.que_size { + res[i] = self.nums[self.index(j as i32)]; + j += 1; + } + res + } +} + +/* Driver Code */ +fn main() { + /* 初始化雙向佇列 */ + let mut deque = ArrayDeque::new(10); + deque.push_last(3); + deque.push_last(2); + deque.push_last(5); + print!("雙向佇列 deque = "); + print_util::print_array(&deque.to_array()); + + /* 訪問元素 */ + let peek_first = deque.peek_first(); + print!("\n佇列首元素 peek_first = {}", peek_first); + let peek_last = deque.peek_last(); + print!("\n佇列尾元素 peek_last = {}", peek_last); + + /* 元素入列 */ + deque.push_last(4); + print!("\n元素 4 佇列尾入列後 deque = "); + print_util::print_array(&deque.to_array()); + deque.push_first(1); + print!("\n元素 1 佇列首入列後 deque = "); + print_util::print_array(&deque.to_array()); + + /* 元素出列 */ + let pop_last = deque.pop_last(); + print!("\n佇列尾出列元素 = {},佇列尾出列後 deque = ", pop_last); + print_util::print_array(&deque.to_array()); + let pop_first = deque.pop_first(); + print!("\n佇列首出列元素 = {},佇列首出列後 deque = ", pop_first); + print_util::print_array(&deque.to_array()); + + /* 獲取雙向佇列的長度 */ + let size = deque.size(); + print!("\n雙向佇列長度 size = {}", size); + + /* 判斷雙向佇列是否為空 */ + let is_empty = deque.is_empty(); + print!("\n雙向佇列是否為空 = {}", is_empty); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/array_queue.rs b/zh-hant/codes/rust/chapter_stack_and_queue/array_queue.rs new file mode 100644 index 000000000..2802f5dad --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/array_queue.rs @@ -0,0 +1,125 @@ +/* + * File: array_queue.rs + * Created Time: 2023-02-06 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +/* 基於環形陣列實現的佇列 */ +struct ArrayQueue { + nums: Vec, // 用於儲存佇列元素的陣列 + front: i32, // 佇列首指標,指向佇列首元素 + que_size: i32, // 佇列長度 + que_capacity: i32, // 佇列容量 +} + +impl ArrayQueue { + /* 建構子 */ + fn new(capacity: i32) -> ArrayQueue { + ArrayQueue { + nums: vec![0; capacity as usize], + front: 0, + que_size: 0, + que_capacity: capacity, + } + } + + /* 獲取佇列的容量 */ + fn capacity(&self) -> i32 { + self.que_capacity + } + + /* 獲取佇列的長度 */ + fn size(&self) -> i32 { + self.que_size + } + + /* 判斷佇列是否為空 */ + fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* 入列 */ + fn push(&mut self, num: i32) { + if self.que_size == self.capacity() { + println!("佇列已滿"); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + let rear = (self.front + self.que_size) % self.que_capacity; + // 將 num 新增至佇列尾 + self.nums[rear as usize] = num; + self.que_size += 1; + } + + /* 出列 */ + fn pop(&mut self) -> i32 { + let num = self.peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + self.front = (self.front + 1) % self.que_capacity; + self.que_size -= 1; + num + } + + /* 訪問佇列首元素 */ + fn peek(&self) -> i32 { + if self.is_empty() { + panic!("index out of bounds"); + } + self.nums[self.front as usize] + } + + /* 返回陣列 */ + fn to_vector(&self) -> Vec { + let cap = self.que_capacity; + let mut j = self.front; + let mut arr = vec![0; self.que_size as usize]; + for i in 0..self.que_size { + arr[i as usize] = self.nums[(j % cap) as usize]; + j += 1; + } + arr + } +} + +/* Driver Code */ +fn main() { + /* 初始化佇列 */ + let capacity = 10; + let mut queue = ArrayQueue::new(capacity); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + println!("佇列 queue = {:?}", queue.to_vector()); + + /* 訪問佇列首元素 */ + let peek = queue.peek(); + println!("佇列首元素 peek = {}", peek); + + /* 元素出列 */ + let pop = queue.pop(); + println!( + "出列元素 pop = {:?},出列後 queue = {:?}", + pop, + queue.to_vector() + ); + + /* 獲取佇列的長度 */ + let size = queue.size(); + println!("佇列長度 size = {}", size); + + /* 判斷佇列是否為空 */ + let is_empty = queue.is_empty(); + println!("佇列是否為空 = {}", is_empty); + + /* 測試環形陣列 */ + for i in 0..10 { + queue.push(i); + queue.pop(); + println!("第 {:?} 輪入列 + 出列後 queue = {:?}", i, queue.to_vector()); + } +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/array_stack.rs b/zh-hant/codes/rust/chapter_stack_and_queue/array_stack.rs new file mode 100644 index 000000000..8eaa0117e --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/array_stack.rs @@ -0,0 +1,86 @@ +/* + * File: array_stack.rs + * Created Time: 2023-02-05 + * Author: WSL0809 (wslzzy@outlook.com), codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +/* 基於陣列實現的堆疊 */ +struct ArrayStack { + stack: Vec, +} + +impl ArrayStack { + /* 初始化堆疊 */ + fn new() -> ArrayStack { + ArrayStack:: { + stack: Vec::::new(), + } + } + + /* 獲取堆疊的長度 */ + fn size(&self) -> usize { + self.stack.len() + } + + /* 判斷堆疊是否為空 */ + fn is_empty(&self) -> bool { + self.size() == 0 + } + + /* 入堆疊 */ + fn push(&mut self, num: T) { + self.stack.push(num); + } + + /* 出堆疊 */ + fn pop(&mut self) -> Option { + self.stack.pop() + } + + /* 訪問堆疊頂元素 */ + fn peek(&self) -> Option<&T> { + if self.is_empty() { + panic!("堆疊為空") + }; + self.stack.last() + } + + /* 返回 &Vec */ + fn to_array(&self) -> &Vec { + &self.stack + } +} + +/* Driver Code */ +fn main() { + // 初始化堆疊 + let mut stack = ArrayStack::::new(); + + // 元素入堆疊 + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("堆疊 stack = "); + print_util::print_array(stack.to_array()); + + //訪問堆疊頂元素 + let peek = stack.peek().unwrap(); + print!("\n堆疊頂元素 peek = {}", peek); + + // 元素出堆疊 + let pop = stack.pop().unwrap(); + print!("\n出堆疊元素 pop = {pop},出堆疊後 stack = "); + print_util::print_array(stack.to_array()); + + // 獲取堆疊的長度 + let size = stack.size(); + print!("\n堆疊的長度 size = {size}"); + + // 判斷是否為空 + let is_empty = stack.is_empty(); + print!("\n堆疊是否為空 = {is_empty}"); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/deque.rs b/zh-hant/codes/rust/chapter_stack_and_queue/deque.rs new file mode 100644 index 000000000..287808cc1 --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/deque.rs @@ -0,0 +1,50 @@ +/* + * File: deque.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +include!("../include/include.rs"); + +use std::collections::VecDeque; + +/* Driver Code */ +pub fn main() { + // 初始化雙向佇列 + let mut deque: VecDeque = VecDeque::new(); + deque.push_back(3); + deque.push_back(2); + deque.push_back(5); + print!("雙向佇列 deque = "); + print_util::print_queue(&deque); + + // 訪問元素 + let peek_first = deque.front().unwrap(); + print!("\n佇列首元素 peekFirst = {peek_first}"); + let peek_last = deque.back().unwrap(); + print!("\n佇列尾元素 peekLast = {peek_last}"); + + /* 元素入列 */ + deque.push_back(4); + print!("\n元素 4 佇列尾入列後 deque = "); + print_util::print_queue(&deque); + deque.push_front(1); + print!("\n元素 1 佇列首入列後 deque = "); + print_util::print_queue(&deque); + + // 元素出列 + let pop_last = deque.pop_back().unwrap(); + print!("\n佇列尾出列元素 = {pop_last},佇列尾出列後 deque = "); + print_util::print_queue(&deque); + let pop_first = deque.pop_front().unwrap(); + print!("\n佇列首出列元素 = {pop_first},佇列首出列後 deque = "); + print_util::print_queue(&deque); + + // 獲取雙向佇列的長度 + let size = deque.len(); + print!("\n雙向佇列長度 size = {size}"); + + // 判斷雙向佇列是否為空 + let is_empty = deque.is_empty(); + print!("\n雙向佇列是否為空 = {is_empty}"); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs new file mode 100644 index 000000000..93e288531 --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs @@ -0,0 +1,214 @@ +/* + * File: linkedlist_deque.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use std::cell::RefCell; +use std::rc::Rc; + +/* 雙向鏈結串列節點 */ +pub struct ListNode { + pub val: T, // 節點值 + pub next: Option>>>, // 後繼節點指標 + pub prev: Option>>>, // 前驅節點指標 +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { + val, + next: None, + prev: None, + })) + } +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +#[allow(dead_code)] +pub struct LinkedListDeque { + front: Option>>>, // 頭節點 front + rear: Option>>>, // 尾節點 rear + que_size: usize, // 雙向佇列的長度 +} + +impl LinkedListDeque { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* 獲取雙向佇列的長度 */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* 判斷雙向佇列是否為空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入列操作 */ + pub fn push(&mut self, num: T, is_front: bool) { + let node = ListNode::new(num); + // 佇列首入列操作 + if is_front { + match self.front.take() { + // 若鏈結串列為空,則令 front 和 rear 都指向 node + None => { + self.rear = Some(node.clone()); + self.front = Some(node); + } + // 將 node 新增至鏈結串列頭部 + Some(old_front) => { + old_front.borrow_mut().prev = Some(node.clone()); + node.borrow_mut().next = Some(old_front); + self.front = Some(node); // 更新頭節點 + } + } + } + // 佇列尾入列操作 + else { + match self.rear.take() { + // 若鏈結串列為空,則令 front 和 rear 都指向 node + None => { + self.front = Some(node.clone()); + self.rear = Some(node); + } + // 將 node 新增至鏈結串列尾部 + Some(old_rear) => { + old_rear.borrow_mut().next = Some(node.clone()); + node.borrow_mut().prev = Some(old_rear); + self.rear = Some(node); // 更新尾節點 + } + } + } + self.que_size += 1; // 更新佇列長度 + } + + /* 佇列首入列 */ + pub fn push_first(&mut self, num: T) { + self.push(num, true); + } + + /* 佇列尾入列 */ + pub fn push_last(&mut self, num: T) { + self.push(num, false); + } + + /* 出列操作 */ + pub fn pop(&mut self, is_front: bool) -> Option { + // 若佇列為空,直接返回 None + if self.is_empty() { + return None; + }; + // 佇列首出列操作 + if is_front { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + new_front.borrow_mut().prev.take(); + self.front = Some(new_front); // 更新頭節點 + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; // 更新佇列長度 + Rc::try_unwrap(old_front).ok().unwrap().into_inner().val + }) + } + // 佇列尾出列操作 + else { + self.rear.take().map(|old_rear| { + match old_rear.borrow_mut().prev.take() { + Some(new_rear) => { + new_rear.borrow_mut().next.take(); + self.rear = Some(new_rear); // 更新尾節點 + } + None => { + self.front.take(); + } + } + self.que_size -= 1; // 更新佇列長度 + Rc::try_unwrap(old_rear).ok().unwrap().into_inner().val + }) + } + } + + /* 佇列首出列 */ + pub fn pop_first(&mut self) -> Option { + return self.pop(true); + } + + /* 佇列尾出列 */ + pub fn pop_last(&mut self) -> Option { + return self.pop(false); + } + + /* 訪問佇列首元素 */ + pub fn peek_first(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* 訪問佇列尾元素 */ + pub fn peek_last(&self) -> Option<&Rc>>> { + self.rear.as_ref() + } + + /* 返回陣列用於列印 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + if let Some(node) = head { + let mut nums = self.to_array(node.borrow().next.as_ref()); + nums.insert(0, node.borrow().val); + return nums; + } + return Vec::new(); + } +} + +/* Driver Code */ +fn main() { + /* 初始化雙向佇列 */ + let mut deque = LinkedListDeque::new(); + deque.push_last(3); + deque.push_last(2); + deque.push_last(5); + print!("雙向佇列 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 訪問元素 */ + let peek_first = deque.peek_first().unwrap().borrow().val; + print!("\n佇列首元素 peek_first = {}", peek_first); + let peek_last = deque.peek_last().unwrap().borrow().val; + print!("\n佇列尾元素 peek_last = {}", peek_last); + + /* 元素入列 */ + deque.push_last(4); + print!("\n元素 4 佇列尾入列後 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + deque.push_first(1); + print!("\n元素 1 佇列首入列後 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 元素出列 */ + let pop_last = deque.pop_last().unwrap(); + print!("\n佇列尾出列元素 = {},佇列尾出列後 deque = ", pop_last); + print_util::print_array(&deque.to_array(deque.peek_first())); + let pop_first = deque.pop_first().unwrap(); + print!("\n佇列首出列元素 = {},佇列首出列後 deque = ", pop_first); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 獲取雙向佇列的長度 */ + let size = deque.size(); + print!("\n雙向佇列長度 size = {}", size); + + /* 判斷雙向佇列是否為空 */ + let is_empty = deque.is_empty(); + print!("\n雙向佇列是否為空 = {}", is_empty); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs new file mode 100644 index 000000000..d12329002 --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs @@ -0,0 +1,121 @@ +/* + * File: linkedlist_queue.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use list_node::ListNode; +use std::cell::RefCell; +use std::rc::Rc; + +/* 基於鏈結串列實現的佇列 */ +#[allow(dead_code)] +pub struct LinkedListQueue { + front: Option>>>, // 頭節點 front + rear: Option>>>, // 尾節點 rear + que_size: usize, // 佇列的長度 +} + +impl LinkedListQueue { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* 獲取佇列的長度 */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* 判斷佇列是否為空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入列 */ + pub fn push(&mut self, num: T) { + // 在尾節點後新增 num + let new_rear = ListNode::new(num); + match self.rear.take() { + // 如果佇列不為空,則將該節點新增到尾節點後 + Some(old_rear) => { + old_rear.borrow_mut().next = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + // 如果佇列為空,則令頭、尾節點都指向該節點 + None => { + self.front = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + } + self.que_size += 1; + } + + /* 出列 */ + pub fn pop(&mut self) -> Option { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + self.front = Some(new_front); + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; + Rc::try_unwrap(old_front).ok().unwrap().into_inner().val + }) + } + + /* 訪問佇列首元素 */ + pub fn peek(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* 將鏈結串列轉化為 Array 並返回 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + if let Some(node) = head { + let mut nums = self.to_array(node.borrow().next.as_ref()); + nums.insert(0, node.borrow().val); + return nums; + } + return Vec::new(); + } +} + +/* Driver Code */ +fn main() { + /* 初始化佇列 */ + let mut queue = LinkedListQueue::new(); + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print!("佇列 queue = "); + print_util::print_array(&queue.to_array(queue.peek())); + + /* 訪問佇列首元素 */ + let peek = queue.peek().unwrap().borrow().val; + print!("\n佇列首元素 peek = {}", peek); + + /* 元素出列 */ + let pop = queue.pop().unwrap(); + print!("\n出列元素 pop = {},出列後 queue = ", pop); + print_util::print_array(&queue.to_array(queue.peek())); + + /* 獲取佇列的長度 */ + let size = queue.size(); + print!("\n佇列長度 size = {}", size); + + /* 判斷佇列是否為空 */ + let is_empty = queue.is_empty(); + print!("\n佇列是否為空 = {}", is_empty); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs new file mode 100644 index 000000000..d5ca545ba --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs @@ -0,0 +1,108 @@ +/* + * File: linkedlist_stack.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +use list_node::ListNode; +use std::cell::RefCell; +use std::rc::Rc; + +/* 基於鏈結串列實現的堆疊 */ +#[allow(dead_code)] +pub struct LinkedListStack { + stack_peek: Option>>>, // 將頭節點作為堆疊頂 + stk_size: usize, // 堆疊的長度 +} + +impl LinkedListStack { + pub fn new() -> Self { + Self { + stack_peek: None, + stk_size: 0, + } + } + + /* 獲取堆疊的長度 */ + pub fn size(&self) -> usize { + return self.stk_size; + } + + /* 判斷堆疊是否為空 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* 入堆疊 */ + pub fn push(&mut self, num: T) { + let node = ListNode::new(num); + node.borrow_mut().next = self.stack_peek.take(); + self.stack_peek = Some(node); + self.stk_size += 1; + } + + /* 出堆疊 */ + pub fn pop(&mut self) -> Option { + self.stack_peek.take().map(|old_head| { + match old_head.borrow_mut().next.take() { + Some(new_head) => { + self.stack_peek = Some(new_head); + } + None => { + self.stack_peek = None; + } + } + self.stk_size -= 1; + Rc::try_unwrap(old_head).ok().unwrap().into_inner().val + }) + } + + /* 訪問堆疊頂元素 */ + pub fn peek(&self) -> Option<&Rc>>> { + self.stack_peek.as_ref() + } + + /* 將 List 轉化為 Array 並返回 */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + if let Some(node) = head { + let mut nums = self.to_array(node.borrow().next.as_ref()); + nums.push(node.borrow().val); + return nums; + } + return Vec::new(); + } +} + +/* Driver Code */ +fn main() { + /* 初始化堆疊 */ + let mut stack = LinkedListStack::new(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("堆疊 stack = "); + print_util::print_array(&stack.to_array(stack.peek())); + + /* 訪問堆疊頂元素 */ + let peek = stack.peek().unwrap().borrow().val; + print!("\n堆疊頂元素 peek = {}", peek); + + /* 元素出堆疊 */ + let pop = stack.pop().unwrap(); + print!("\n出堆疊元素 pop = {},出堆疊後 stack = ", pop); + print_util::print_array(&stack.to_array(stack.peek())); + + /* 獲取堆疊的長度 */ + let size = stack.size(); + print!("\n堆疊的長度 size = {}", size); + + /* 判斷是否為空 */ + let is_empty = stack.is_empty(); + print!("\n堆疊是否為空 = {}", is_empty); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/queue.rs b/zh-hant/codes/rust/chapter_stack_and_queue/queue.rs new file mode 100644 index 000000000..10e0d1b59 --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/queue.rs @@ -0,0 +1,41 @@ +/* + * File: queue.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +include!("../include/include.rs"); + +use std::collections::VecDeque; + +/* Driver Code */ +pub fn main() { + // 初始化佇列 + let mut queue: VecDeque = VecDeque::new(); + + // 元素入列 + queue.push_back(1); + queue.push_back(3); + queue.push_back(2); + queue.push_back(5); + queue.push_back(4); + print!("佇列 queue = "); + print_util::print_queue(&queue); + + // 訪問佇列首元素 + let peek = queue.front().unwrap(); + println!("\n佇列首元素 peek = {peek}"); + + // 元素出列 + let pop = queue.pop_front().unwrap(); + print!("出列元素 pop = {pop},出列後 queue = "); + print_util::print_queue(&queue); + + // 獲取佇列的長度 + let size = queue.len(); + print!("\n佇列長度 size = {size}"); + + // 判斷佇列是否為空 + let is_empty = queue.is_empty(); + print!("\n佇列是否為空 = {is_empty}"); +} diff --git a/zh-hant/codes/rust/chapter_stack_and_queue/stack.rs b/zh-hant/codes/rust/chapter_stack_and_queue/stack.rs new file mode 100644 index 000000000..cc871dbdd --- /dev/null +++ b/zh-hant/codes/rust/chapter_stack_and_queue/stack.rs @@ -0,0 +1,40 @@ +/* + * File: stack.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +include!("../include/include.rs"); + +/* Driver Code */ +pub fn main() { + // 初始化堆疊 + // 在 rust 中,推薦將 Vec 當作堆疊來使用 + let mut stack: Vec = Vec::new(); + + // 元素入堆疊 + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("堆疊 stack = "); + print_util::print_array(&stack); + + // 訪問堆疊頂元素 + let peek = stack.last().unwrap(); + print!("\n堆疊頂元素 peek = {peek}"); + + // 元素出堆疊 + let pop = stack.pop().unwrap(); + print!("\n出堆疊元素 pop = {pop},出堆疊後 stack = "); + print_util::print_array(&stack); + + // 獲取堆疊的長度 + let size = stack.len(); + print!("\n堆疊的長度 size = {size}"); + + // 判斷堆疊是否為空 + let is_empty = stack.is_empty(); + print!("\n堆疊是否為空 = {is_empty}"); +} diff --git a/zh-hant/codes/rust/chapter_tree/array_binary_tree.rs b/zh-hant/codes/rust/chapter_tree/array_binary_tree.rs new file mode 100644 index 000000000..2a60242a3 --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/array_binary_tree.rs @@ -0,0 +1,199 @@ +/* + * File: array_binary_tree.rs + * Created Time: 2023-07-25 + * Author: night-cruise (2586447362@qq.com) + */ + +include!("../include/include.rs"); + +/* 陣列表示下的二元樹類別 */ +struct ArrayBinaryTree { + tree: Vec>, +} + +impl ArrayBinaryTree { + /* 建構子 */ + fn new(arr: Vec>) -> Self { + Self { tree: arr } + } + + /* 串列容量 */ + fn size(&self) -> i32 { + self.tree.len() as i32 + } + + /* 獲取索引為 i 節點的值 */ + fn val(&self, i: i32) -> Option { + // 若索引越界,則返回 None ,代表空位 + if i < 0 || i >= self.size() { + None + } else { + self.tree[i as usize] + } + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + fn left(&self, i: i32) -> i32 { + 2 * i + 1 + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + fn right(&self, i: i32) -> i32 { + 2 * i + 2 + } + + /* 獲取索引為 i 節點的父節點的索引 */ + fn parent(&self, i: i32) -> i32 { + (i - 1) / 2 + } + + /* 層序走訪 */ + fn level_order(&self) -> Vec { + let mut res = vec![]; + // 直接走訪陣列 + for i in 0..self.size() { + if let Some(val) = self.val(i) { + res.push(val) + } + } + res + } + + /* 深度優先走訪 */ + fn dfs(&self, i: i32, order: &str, res: &mut Vec) { + if self.val(i).is_none() { + return; + } + let val = self.val(i).unwrap(); + // 前序走訪 + if order == "pre" { + res.push(val); + } + self.dfs(self.left(i), order, res); + // 中序走訪 + if order == "in" { + res.push(val); + } + self.dfs(self.right(i), order, res); + // 後序走訪 + if order == "post" { + res.push(val); + } + } + + /* 前序走訪 */ + fn pre_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "pre", &mut res); + res + } + + /* 中序走訪 */ + fn in_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "in", &mut res); + res + } + + /* 後序走訪 */ + fn post_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "post", &mut res); + res + } +} + +/* Driver Code */ +fn main() { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let arr = vec![ + Some(1), + Some(2), + Some(3), + Some(4), + None, + Some(6), + Some(7), + Some(8), + Some(9), + None, + None, + Some(12), + None, + None, + Some(15), + ]; + + let root = tree_node::vec_to_tree(arr.clone()).unwrap(); + println!("\n初始化二元樹\n"); + println!("二元樹的陣列表示:"); + println!( + "[{}]", + arr.iter() + .map(|&val| if let Some(val) = val { + format!("{val}") + } else { + "null".to_string() + }) + .collect::>() + .join(", ") + ); + println!("二元樹的鏈結串列表示:"); + print_util::print_tree(&root); + + // 陣列表示下的二元樹類別 + let abt = ArrayBinaryTree::new(arr); + + // 訪問節點 + let i = 1; + let l = abt.left(i); + let r = abt.right(i); + let p = abt.parent(i); + println!( + "\n當前節點的索引為 {} ,值為 {}", + i, + if let Some(val) = abt.val(i) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "其左子節點的索引為 {} ,值為 {}", + l, + if let Some(val) = abt.val(l) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "其右子節點的索引為 {} ,值為 {}", + r, + if let Some(val) = abt.val(r) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "其父節點的索引為 {} ,值為 {}", + p, + if let Some(val) = abt.val(p) { + format!("{val}") + } else { + "null".to_string() + } + ); + + // 走訪樹 + let mut res = abt.level_order(); + println!("\n層序走訪為:{:?}", res); + res = abt.pre_order(); + println!("前序走訪為:{:?}", res); + res = abt.in_order(); + println!("中序走訪為:{:?}", res); + res = abt.post_order(); + println!("後序走訪為:{:?}", res); +} diff --git a/zh-hant/codes/rust/chapter_tree/avl_tree.rs b/zh-hant/codes/rust/chapter_tree/avl_tree.rs new file mode 100644 index 000000000..c414a06e4 --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/avl_tree.rs @@ -0,0 +1,298 @@ +/* + * File: avl_tree.rs + * Created Time: 2023-07-14 + * Author: night-cruise (2586447362@qq.com) + */ + +include!("../include/include.rs"); + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::rc::Rc; +use tree_node::TreeNode; + +type OptionTreeNodeRc = Option>>; + +/* AVL 樹 */ +struct AVLTree { + root: OptionTreeNodeRc, // 根節點 +} + +impl AVLTree { + /* 建構子 */ + fn new() -> Self { + Self { root: None } + } + + /* 獲取節點高度 */ + fn height(node: OptionTreeNodeRc) -> i32 { + // 空節點高度為 -1 ,葉節點高度為 0 + match node { + Some(node) => node.borrow().height, + None => -1, + } + } + + /* 更新節點高度 */ + fn update_height(node: OptionTreeNodeRc) { + if let Some(node) = node { + let left = node.borrow().left.clone(); + let right = node.borrow().right.clone(); + // 節點高度等於最高子樹高度 + 1 + node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; + } + } + + /* 獲取平衡因子 */ + fn balance_factor(node: OptionTreeNodeRc) -> i32 { + match node { + // 空節點平衡因子為 0 + None => 0, + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + Some(node) => { + Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) + } + } + } + + /* 右旋操作 */ + fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().left.clone().unwrap(); + let grand_child = child.borrow().right.clone(); + // 以 child 為原點,將 node 向右旋轉 + child.borrow_mut().right = Some(node.clone()); + node.borrow_mut().left = grand_child; + // 更新節點高度 + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // 返回旋轉後子樹的根節點 + Some(child) + } + None => None, + } + } + + /* 左旋操作 */ + fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().right.clone().unwrap(); + let grand_child = child.borrow().left.clone(); + // 以 child 為原點,將 node 向左旋轉 + child.borrow_mut().left = Some(node.clone()); + node.borrow_mut().right = grand_child; + // 更新節點高度 + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // 返回旋轉後子樹的根節點 + Some(child) + } + None => None, + } + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + // 獲取節點 node 的平衡因子 + let balance_factor = Self::balance_factor(node.clone()); + // 左偏樹 + if balance_factor > 1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().left.clone()) >= 0 { + // 右旋 + Self::right_rotate(Some(node)) + } else { + // 先左旋後右旋 + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::left_rotate(left); + Self::right_rotate(Some(node)) + } + } + // 右偏樹 + else if balance_factor < -1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().right.clone()) <= 0 { + // 左旋 + Self::left_rotate(Some(node)) + } else { + // 先右旋後左旋 + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::right_rotate(right); + Self::left_rotate(Some(node)) + } + } else { + // 平衡樹,無須旋轉,直接返回 + node + } + } + + /* 插入節點 */ + fn insert(&mut self, val: i32) { + self.root = Self::insert_helper(self.root.clone(), val); + } + + /* 遞迴插入節點(輔助方法) */ + fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. 查詢插入位置並插入節點 */ + match { + let node_val = node.borrow().val; + node_val + } + .cmp(&val) + { + Ordering::Greater => { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::insert_helper(left, val); + } + Ordering::Less => { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::insert_helper(right, val); + } + Ordering::Equal => { + return Some(node); // 重複節點不插入,直接返回 + } + } + Self::update_height(Some(node.clone())); // 更新節點高度 + + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = Self::rotate(Some(node)).unwrap(); + // 返回子樹的根節點 + Some(node) + } + None => Some(TreeNode::new(val)), + } + } + + /* 刪除節點 */ + fn remove(&self, val: i32) { + Self::remove_helper(self.root.clone(), val); + } + + /* 遞迴刪除節點(輔助方法) */ + fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. 查詢節點並刪除 */ + if val < node.borrow().val { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::remove_helper(left, val); + } else if val > node.borrow().val { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, val); + } else if node.borrow().left.is_none() || node.borrow().right.is_none() { + let child = if node.borrow().left.is_some() { + node.borrow().left.clone() + } else { + node.borrow().right.clone() + }; + match child { + // 子節點數量 = 0 ,直接刪除 node 並返回 + None => { + return None; + } + // 子節點數量 = 1 ,直接刪除 node + Some(child) => node = child, + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + let mut temp = node.borrow().right.clone().unwrap(); + loop { + let temp_left = temp.borrow().left.clone(); + if temp_left.is_none() { + break; + } + temp = temp_left.unwrap(); + } + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); + node.borrow_mut().val = temp.borrow().val; + } + Self::update_height(Some(node.clone())); // 更新節點高度 + + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = Self::rotate(Some(node)).unwrap(); + // 返回子樹的根節點 + Some(node) + } + None => None, + } + } + + /* 查詢節點 */ + fn search(&self, val: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // 迴圈查詢,越過葉節點後跳出 + while let Some(current) = cur.clone() { + match current.borrow().val.cmp(&val) { + // 目標節點在 cur 的右子樹中 + Ordering::Less => { + cur = current.borrow().right.clone(); + } + // 目標節點在 cur 的左子樹中 + Ordering::Greater => { + cur = current.borrow().left.clone(); + } + // 找到目標節點,跳出迴圈 + Ordering::Equal => { + break; + } + } + } + // 返回目標節點 + cur + } +} + +/* Driver Code */ +fn main() { + fn test_insert(tree: &mut AVLTree, val: i32) { + tree.insert(val); + println!("\n插入節點 {} 後,AVL 樹為", val); + print_util::print_tree(&tree.root.clone().unwrap()); + } + + fn test_remove(tree: &mut AVLTree, val: i32) { + tree.remove(val); + println!("\n刪除節點 {} 後,AVL 樹為", val); + print_util::print_tree(&tree.root.clone().unwrap()); + } + + /* 初始化空 AVL 樹 */ + let mut avl_tree = AVLTree::new(); + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + test_insert(&mut avl_tree, 1); + test_insert(&mut avl_tree, 2); + test_insert(&mut avl_tree, 3); + test_insert(&mut avl_tree, 4); + test_insert(&mut avl_tree, 5); + test_insert(&mut avl_tree, 8); + test_insert(&mut avl_tree, 7); + test_insert(&mut avl_tree, 9); + test_insert(&mut avl_tree, 10); + test_insert(&mut avl_tree, 6); + + /* 插入重複節點 */ + test_insert(&mut avl_tree, 7); + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + test_remove(&mut avl_tree, 8); // 刪除度為 0 的節點 + test_remove(&mut avl_tree, 5); // 刪除度為 1 的節點 + test_remove(&mut avl_tree, 4); // 刪除度為 2 的節點 + + /* 查詢節點 */ + let node = avl_tree.search(7); + if let Some(node) = node { + println!( + "\n查詢到的節點物件為 {:?},節點值 = {}", + &*node.borrow(), + node.borrow().val + ); + } +} diff --git a/zh-hant/codes/rust/chapter_tree/binary_search_tree.rs b/zh-hant/codes/rust/chapter_tree/binary_search_tree.rs new file mode 100644 index 000000000..99bf6ae61 --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/binary_search_tree.rs @@ -0,0 +1,195 @@ +/* + * File: binary_search_tree.rs + * Created Time: 2023-04-20 + * Author: xBLACKICEx (xBLACKICE@outlook.com)、night-cruise (2586447362@qq.com) + */ + +include!("../include/include.rs"); + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::rc::Rc; + +use tree_node::TreeNode; + +type OptionTreeNodeRc = Option>>; + +/* 二元搜尋樹 */ +pub struct BinarySearchTree { + root: OptionTreeNodeRc, +} + +impl BinarySearchTree { + /* 建構子 */ + pub fn new() -> Self { + // 初始化空樹 + Self { root: None } + } + + /* 獲取二元樹根節點 */ + pub fn get_root(&self) -> OptionTreeNodeRc { + self.root.clone() + } + + /* 查詢節點 */ + pub fn search(&self, num: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // 迴圈查詢,越過葉節點後跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 目標節點在 cur 的右子樹中 + Ordering::Greater => cur = node.borrow().right.clone(), + // 目標節點在 cur 的左子樹中 + Ordering::Less => cur = node.borrow().left.clone(), + // 找到目標節點,跳出迴圈 + Ordering::Equal => break, + } + } + + // 返回目標節點 + cur + } + + /* 插入節點 */ + pub fn insert(&mut self, num: i32) { + // 若樹為空,則初始化根節點 + if self.root.is_none() { + self.root = Some(TreeNode::new(num)); + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // 迴圈查詢,越過葉節點後跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 找到重複節點,直接返回 + Ordering::Equal => return, + // 插入位置在 cur 的右子樹中 + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // 插入位置在 cur 的左子樹中 + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // 插入節點 + let pre = pre.unwrap(); + let node = Some(TreeNode::new(num)); + if num > pre.borrow().val { + pre.borrow_mut().right = node; + } else { + pre.borrow_mut().left = node; + } + } + + /* 刪除節點 */ + pub fn remove(&mut self, num: i32) { + // 若樹為空,直接提前返回 + if self.root.is_none() { + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // 迴圈查詢,越過葉節點後跳出 + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 找到待刪除節點,跳出迴圈 + Ordering::Equal => break, + // 待刪除節點在 cur 的右子樹中 + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // 待刪除節點在 cur 的左子樹中 + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // 若無待刪除節點,則直接返回 + if cur.is_none() { + return; + } + let cur = cur.unwrap(); + let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); + match (left_child.clone(), right_child.clone()) { + // 子節點數量 = 0 or 1 + (None, None) | (Some(_), None) | (None, Some(_)) => { + // 當子節點數量 = 0 / 1 時, child = nullptr / 該子節點 + let child = left_child.or(right_child); + let pre = pre.unwrap(); + // 刪除節點 cur + if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { + let left = pre.borrow().left.clone(); + if left.is_some() && Rc::ptr_eq(&left.as_ref().unwrap(), &cur) { + pre.borrow_mut().left = child; + } else { + pre.borrow_mut().right = child; + } + } else { + // 若刪除節點為根節點,則重新指定根節點 + self.root = child; + } + } + // 子節點數量 = 2 + (Some(_), Some(_)) => { + // 獲取中序走訪中 cur 的下一個節點 + let mut tmp = cur.borrow().right.clone(); + while let Some(node) = tmp.clone() { + if node.borrow().left.is_some() { + tmp = node.borrow().left.clone(); + } else { + break; + } + } + let tmpval = tmp.unwrap().borrow().val; + // 遞迴刪除節點 tmp + self.remove(tmpval); + // 用 tmp 覆蓋 cur + cur.borrow_mut().val = tmpval; + } + } + } +} + +/* Driver Code */ +fn main() { + /* 初始化二元搜尋樹 */ + let mut bst = BinarySearchTree::new(); + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + for &num in &nums { + bst.insert(num); + } + println!("\n初始化的二元樹為\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + + /* 查詢結點 */ + let node = bst.search(7); + println!( + "\n查詢到的節點物件為 {:?},節點值 = {}", + node.clone().unwrap(), + node.clone().unwrap().borrow().val + ); + + /* 插入節點 */ + bst.insert(16); + println!("\n插入節點 16 後,二元樹為\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + + /* 刪除節點 */ + bst.remove(1); + println!("\n刪除節點 1 後,二元樹為\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + bst.remove(2); + println!("\n刪除節點 2 後,二元樹為\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + bst.remove(4); + println!("\n刪除節點 4 後,二元樹為\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); +} diff --git a/zh-hant/codes/rust/chapter_tree/binary_tree.rs b/zh-hant/codes/rust/chapter_tree/binary_tree.rs new file mode 100644 index 000000000..f87b7f891 --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/binary_tree.rs @@ -0,0 +1,39 @@ +/** + * File: binary_tree.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ +use std::rc::Rc; +include!("../include/include.rs"); +use tree_node::TreeNode; + +/* Driver Code */ +fn main() { + /* 初始化二元樹 */ + // 初始化節點 + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // 構建節點之間的引用(指標) + n1.borrow_mut().left = Some(Rc::clone(&n2)); + n1.borrow_mut().right = Some(Rc::clone(&n3)); + n2.borrow_mut().left = Some(Rc::clone(&n4)); + n2.borrow_mut().right = Some(Rc::clone(&n5)); + println!("\n初始化二元樹\n"); + print_util::print_tree(&n1); + + // 插入節點與刪除節點 + let p = TreeNode::new(0); + // 在 n1 -> n2 中間插入節點 P + p.borrow_mut().left = Some(Rc::clone(&n2)); + n1.borrow_mut().left = Some(Rc::clone(&p)); + println!("\n插入節點 P 後\n"); + print_util::print_tree(&n1); + // 刪除節點 P + drop(p); + n1.borrow_mut().left = Some(Rc::clone(&n2)); + println!("\n刪除節點 P 後\n"); + print_util::print_tree(&n1); +} diff --git a/zh-hant/codes/rust/chapter_tree/binary_tree_bfs.rs b/zh-hant/codes/rust/chapter_tree/binary_tree_bfs.rs new file mode 100644 index 000000000..d2e6d5e7c --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/binary_tree_bfs.rs @@ -0,0 +1,45 @@ +/* + * File: binary_tree_bfs.rs + * Created Time: 2023-04-07 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +include!("../include/include.rs"); + +use std::collections::VecDeque; +use std::{cell::RefCell, rc::Rc}; +use tree_node::{vec_to_tree, TreeNode}; + +/* 層序走訪 */ +fn level_order(root: &Rc>) -> Vec { + // 初始化佇列,加入根節點 + let mut que = VecDeque::new(); + que.push_back(Rc::clone(&root)); + // 初始化一個串列,用於儲存走訪序列 + let mut vec = Vec::new(); + + while let Some(node) = que.pop_front() { + // 隊列出隊 + vec.push(node.borrow().val); // 儲存節點值 + if let Some(left) = node.borrow().left.as_ref() { + que.push_back(Rc::clone(left)); // 左子節點入列 + } + if let Some(right) = node.borrow().right.as_ref() { + que.push_back(Rc::clone(right)); // 右子節點入列 + }; + } + vec +} + +/* Driver Code */ +fn main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]).unwrap(); + println!("初始化二元樹\n"); + print_util::print_tree(&root); + + /* 層序走訪 */ + let vec = level_order(&root); + print!("\n層序走訪的節點列印序列 = {:?}", vec); +} diff --git a/zh-hant/codes/rust/chapter_tree/binary_tree_dfs.rs b/zh-hant/codes/rust/chapter_tree/binary_tree_dfs.rs new file mode 100644 index 000000000..cef468f12 --- /dev/null +++ b/zh-hant/codes/rust/chapter_tree/binary_tree_dfs.rs @@ -0,0 +1,71 @@ +/* + * File: binary_tree_dfs.rs + * Created Time: 2023-04-06 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +include!("../include/include.rs"); + +use std::cell::RefCell; +use std::rc::Rc; +use tree_node::{vec_to_tree, TreeNode}; + +/* 前序走訪 */ +fn pre_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + if let Some(node) = root { + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + result.push(node.borrow().val); + result.append(&mut pre_order(node.borrow().left.as_ref())); + result.append(&mut pre_order(node.borrow().right.as_ref())); + } + result +} + +/* 中序走訪 */ +fn in_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + if let Some(node) = root { + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + result.append(&mut in_order(node.borrow().left.as_ref())); + result.push(node.borrow().val); + result.append(&mut in_order(node.borrow().right.as_ref())); + } + result +} + +/* 後序走訪 */ +fn post_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + if let Some(node) = root { + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + result.append(&mut post_order(node.borrow().left.as_ref())); + result.append(&mut post_order(node.borrow().right.as_ref())); + result.push(node.borrow().val); + } + result +} + +/* Driver Code */ +fn main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]); + println!("初始化二元樹\n"); + print_util::print_tree(root.as_ref().unwrap()); + + /* 前序走訪 */ + let vec = pre_order(root.as_ref()); + println!("\n前序走訪的節點列印序列 = {:?}", vec); + + /* 中序走訪 */ + let vec = in_order(root.as_ref()); + println!("\n中序走訪的節點列印序列 = {:?}", vec); + + /* 後序走訪 */ + let vec = post_order(root.as_ref()); + print!("\n後序走訪的節點列印序列 = {:?}", vec); +} diff --git a/zh-hant/codes/rust/include/include.rs b/zh-hant/codes/rust/include/include.rs new file mode 100644 index 000000000..a288d8b50 --- /dev/null +++ b/zh-hant/codes/rust/include/include.rs @@ -0,0 +1,10 @@ +/* + * File: include.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) + */ + +pub mod print_util; +pub mod list_node; +pub mod tree_node; +pub mod vertex; \ No newline at end of file diff --git a/zh-hant/codes/rust/include/list_node.rs b/zh-hant/codes/rust/include/list_node.rs new file mode 100644 index 000000000..110382a1a --- /dev/null +++ b/zh-hant/codes/rust/include/list_node.rs @@ -0,0 +1,57 @@ +/* + * File: list_node.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) + */ + +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug)] +pub struct ListNode { + pub val: T, + pub next: Option>>>, +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { val, next: None })) + } + + /* 將陣列反序列化為鏈結串列 */ + pub fn arr_to_linked_list(array: &[T]) -> Option>>> + where + T: Copy + Clone, + { + let mut head = None; + // insert in reverse order + for item in array.iter().rev() { + let node = Rc::new(RefCell::new(ListNode { + val: *item, + next: head.clone(), + })); + head = Some(node); + } + head + } + + /* 將鏈結串列轉化為雜湊表 */ + pub fn linked_list_to_hashmap( + linked_list: Option>>>, + ) -> HashMap>>> + where + T: std::hash::Hash + Eq + Copy + Clone, + { + let mut hashmap = HashMap::new(); + if let Some(node) = linked_list { + let mut current = Some(node.clone()); + while let Some(cur) = current { + let borrow = cur.borrow(); + hashmap.insert(borrow.val.clone(), cur.clone()); + current = borrow.next.clone(); + } + } + hashmap + } +} diff --git a/zh-hant/codes/rust/include/print_util.rs b/zh-hant/codes/rust/include/print_util.rs new file mode 100644 index 000000000..a19a0b4af --- /dev/null +++ b/zh-hant/codes/rust/include/print_util.rs @@ -0,0 +1,103 @@ +/* + * File: print_util.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use std::cell::{Cell, RefCell}; +use std::fmt::Display; +use std::collections::{HashMap, VecDeque}; +use std::rc::Rc; + +use crate::list_node::ListNode; +use crate::tree_node::{TreeNode, vec_to_tree}; + +struct Trunk<'a, 'b> { + prev: Option<&'a Trunk<'a, 'b>>, + str: Cell<&'b str>, +} + +/* 列印陣列 */ +pub fn print_array(nums: &[T]) { + print!("["); + if nums.len() > 0 { + for (i, num) in nums.iter().enumerate() { + print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); + } + } else { + print!("]"); + } +} + +/* 列印雜湊表 */ +pub fn print_hash_map(map: &HashMap) { + for (key, value) in map { + println!("{key} -> {value}"); + } +} + +/* 列印佇列(雙向佇列) */ +pub fn print_queue(queue: &VecDeque) { + print!("["); + let iter = queue.iter(); + for (i, data) in iter.enumerate() { + print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); + } +} + +/* 列印鏈結串列 */ +pub fn print_linked_list(head: &Rc>>) { + print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); + if let Some(node) = &head.borrow().next { + return print_linked_list(node); + } +} + +/* 列印二元樹 */ +pub fn print_tree(root: &Rc>) { + _print_tree(Some(root), None, false); +} + +/* 列印二元樹 */ +fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { + if let Some(node) = root { + let mut prev_str = " "; + let trunk = Trunk { prev, str: Cell::new(prev_str) }; + _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); + + if prev.is_none() { + trunk.str.set("———"); + } else if is_right { + trunk.str.set("/———"); + prev_str = " |"; + } else { + trunk.str.set("\\———"); + prev.as_ref().unwrap().str.set(prev_str); + } + + show_trunks(Some(&trunk)); + println!(" {}", node.borrow().val); + if let Some(prev) = prev { + prev.str.set(prev_str); + } + trunk.str.set(" |"); + + _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); + } +} + +fn show_trunks(trunk: Option<&Trunk>) { + if let Some(trunk) = trunk { + show_trunks(trunk.prev); + print!("{}", trunk.str.get()); + } +} + +/* 列印堆積 */ +pub fn print_heap(heap: Vec) { + println!("堆積的陣列表示:{:?}", heap); + println!("堆積的樹狀表示:"); + if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { + print_tree(&root); + } +} \ No newline at end of file diff --git a/zh-hant/codes/rust/include/tree_node.rs b/zh-hant/codes/rust/include/tree_node.rs new file mode 100644 index 000000000..5fd4294b9 --- /dev/null +++ b/zh-hant/codes/rust/include/tree_node.rs @@ -0,0 +1,93 @@ +/* + * File: tree_node.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) + */ + +use std::cell::RefCell; +use std::rc::Rc; + +/* 二元樹節點型別 */ +#[derive(Debug)] +pub struct TreeNode { + pub val: i32, + pub height: i32, + pub parent: Option>>, + pub left: Option>>, + pub right: Option>>, +} + +impl TreeNode { + /* 建構子 */ + pub fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + parent: None, + left: None, + right: None + })) + } +} + +#[macro_export] +macro_rules! op_vec { + ( $( $x:expr ),* ) => { + vec![ + $( Option::from($x).map(|x| x) ),* + ] + }; +} + +// 序列化編碼規則請參考: +// 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 + +/* 將串列反序列化為二元樹:遞迴 */ +fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { + if i >= arr.len() || arr[i].is_none() { + return None; + } + let root = TreeNode::new(arr[i].unwrap()); + root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); + root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); + Some(root) +} + +/* 將串列反序列化為二元樹 */ +pub fn vec_to_tree(arr: Vec>) -> Option>> { + vec_to_tree_dfs(&arr, 0) +} + +/* 將二元樹序列化為串列:遞迴 */ +fn tree_to_vec_dfs(root: Option>>, i: usize, res: &mut Vec>) { + if root.is_none() { + return; + } + let root = root.unwrap(); + while i >= res.len() { + res.push(None); + } + res[i] = Some(root.borrow().val); + tree_to_vec_dfs(root.borrow().left.clone(), 2 * i + 1, res); + tree_to_vec_dfs(root.borrow().right.clone(), 2 * i + 2, res); +} + +/* 將二元樹序列化為串列 */ +pub fn tree_to_vec(root: Option>>) -> Vec> { + let mut res = vec![]; + tree_to_vec_dfs(root, 0, &mut res); + res +} diff --git a/zh-hant/codes/rust/include/vertex.rs b/zh-hant/codes/rust/include/vertex.rs new file mode 100644 index 000000000..b17bef164 --- /dev/null +++ b/zh-hant/codes/rust/include/vertex.rs @@ -0,0 +1,21 @@ +/* + * File: vertex.rs + * Created Time: 2023-07-13 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 頂點型別 */ +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Vertex { + pub val: i32 +} + +/* 輸入值串列 vals ,返回頂點串列 vets */ +pub fn vals_to_vets(vals: Vec) -> Vec { + vals.into_iter().map(|val| Vertex { val }).collect() +} + +/* 輸入頂點串列 vets ,返回值串列 vals */ +pub fn vets_to_vals(vets: Vec) -> Vec { + vets.into_iter().map(|vet| vet.val).collect() +} \ No newline at end of file diff --git a/zh-hant/codes/swift/.gitignore b/zh-hant/codes/swift/.gitignore new file mode 100644 index 000000000..6295af4cc --- /dev/null +++ b/zh-hant/codes/swift/.gitignore @@ -0,0 +1,130 @@ +# Created by https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager +# Edit at https://www.toptal.com/developers/gitignore?templates=objective-c,swift,swiftpackagemanager + +### Objective-C ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Objective-C Patch ### + +### Swift ### +# Xcode +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + + + + + + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + + +### SwiftPackageManager ### +Packages +xcuserdata +*.xcodeproj + + +# End of https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager diff --git a/zh-hant/codes/swift/Package.resolved b/zh-hant/codes/swift/Package.resolved new file mode 100644 index 000000000..159c83d26 --- /dev/null +++ b/zh-hant/codes/swift/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "branch" : "release/1.1", + "revision" : "4a1d92ba85027010d2c528c05576cde9a362254b" + } + } + ], + "version" : 2 +} diff --git a/zh-hant/codes/swift/Package.swift b/zh-hant/codes/swift/Package.swift new file mode 100644 index 000000000..5326dc138 --- /dev/null +++ b/zh-hant/codes/swift/Package.swift @@ -0,0 +1,206 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "HelloAlgo", + products: [ + // chapter_computational_complexity + .executable(name: "iteration", targets: ["iteration"]), + .executable(name: "recursion", targets: ["recursion"]), + .executable(name: "time_complexity", targets: ["time_complexity"]), + .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), + .executable(name: "space_complexity", targets: ["space_complexity"]), + // chapter_array_and_linkedlist + .executable(name: "array", targets: ["array"]), + .executable(name: "linked_list", targets: ["linked_list"]), + .executable(name: "list", targets: ["list"]), + .executable(name: "my_list", targets: ["my_list"]), + // chapter_stack_and_queue + .executable(name: "stack", targets: ["stack"]), + .executable(name: "linkedlist_stack", targets: ["linkedlist_stack"]), + .executable(name: "array_stack", targets: ["array_stack"]), + .executable(name: "queue", targets: ["queue"]), + .executable(name: "linkedlist_queue", targets: ["linkedlist_queue"]), + .executable(name: "array_queue", targets: ["array_queue"]), + .executable(name: "deque", targets: ["deque"]), + .executable(name: "linkedlist_deque", targets: ["linkedlist_deque"]), + .executable(name: "array_deque", targets: ["array_deque"]), + // chapter_hashing + .executable(name: "hash_map", targets: ["hash_map"]), + .executable(name: "array_hash_map", targets: ["array_hash_map"]), + .executable(name: "hash_map_chaining", targets: ["hash_map_chaining"]), + .executable(name: "hash_map_open_addressing", targets: ["hash_map_open_addressing"]), + .executable(name: "simple_hash", targets: ["simple_hash"]), + .executable(name: "built_in_hash", targets: ["built_in_hash"]), + // chapter_tree + .executable(name: "binary_tree", targets: ["binary_tree"]), + .executable(name: "binary_tree_bfs", targets: ["binary_tree_bfs"]), + .executable(name: "binary_tree_dfs", targets: ["binary_tree_dfs"]), + .executable(name: "array_binary_tree", targets: ["array_binary_tree"]), + .executable(name: "binary_search_tree", targets: ["binary_search_tree"]), + .executable(name: "avl_tree", targets: ["avl_tree"]), + // chapter_heap + .executable(name: "heap", targets: ["heap"]), + .executable(name: "my_heap", targets: ["my_heap"]), + .executable(name: "top_k", targets: ["top_k"]), + // chapter_graph + .executable(name: "graph_adjacency_matrix", targets: ["graph_adjacency_matrix"]), + .executable(name: "graph_adjacency_list", targets: ["graph_adjacency_list"]), + .executable(name: "graph_bfs", targets: ["graph_bfs"]), + .executable(name: "graph_dfs", targets: ["graph_dfs"]), + // chapter_searching + .executable(name: "binary_search", targets: ["binary_search"]), + .executable(name: "binary_search_insertion", targets: ["binary_search_insertion"]), + .executable(name: "binary_search_edge", targets: ["binary_search_edge"]), + .executable(name: "two_sum", targets: ["two_sum"]), + .executable(name: "linear_search", targets: ["linear_search"]), + .executable(name: "hashing_search", targets: ["hashing_search"]), + // chapter_sorting + .executable(name: "selection_sort", targets: ["selection_sort"]), + .executable(name: "bubble_sort", targets: ["bubble_sort"]), + .executable(name: "insertion_sort", targets: ["insertion_sort"]), + .executable(name: "quick_sort", targets: ["quick_sort"]), + .executable(name: "merge_sort", targets: ["merge_sort"]), + .executable(name: "heap_sort", targets: ["heap_sort"]), + .executable(name: "bucket_sort", targets: ["bucket_sort"]), + .executable(name: "counting_sort", targets: ["counting_sort"]), + .executable(name: "radix_sort", targets: ["radix_sort"]), + // chapter_divide_and_conquer + .executable(name: "binary_search_recur", targets: ["binary_search_recur"]), + .executable(name: "build_tree", targets: ["build_tree"]), + .executable(name: "hanota", targets: ["hanota"]), + // chapter_backtracking + .executable(name: "preorder_traversal_i_compact", targets: ["preorder_traversal_i_compact"]), + .executable(name: "preorder_traversal_ii_compact", targets: ["preorder_traversal_ii_compact"]), + .executable(name: "preorder_traversal_iii_compact", targets: ["preorder_traversal_iii_compact"]), + .executable(name: "preorder_traversal_iii_template", targets: ["preorder_traversal_iii_template"]), + .executable(name: "permutations_i", targets: ["permutations_i"]), + .executable(name: "permutations_ii", targets: ["permutations_ii"]), + .executable(name: "subset_sum_i_naive", targets: ["subset_sum_i_naive"]), + .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), + .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), + .executable(name: "n_queens", targets: ["n_queens"]), + // chapter_dynamic_programming + .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), + .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), + .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), + .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), + .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), + .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), + .executable(name: "min_path_sum", targets: ["min_path_sum"]), + .executable(name: "knapsack", targets: ["knapsack"]), + .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), + .executable(name: "coin_change", targets: ["coin_change"]), + .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), + .executable(name: "edit_distance", targets: ["edit_distance"]), + // chapter_greedy + .executable(name: "coin_change_greedy", targets: ["coin_change_greedy"]), + .executable(name: "fractional_knapsack", targets: ["fractional_knapsack"]), + .executable(name: "max_capacity", targets: ["max_capacity"]), + .executable(name: "max_product_cutting", targets: ["max_product_cutting"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-collections", branch: "release/1.1"), + ], + targets: [ + // helper + .target(name: "utils", path: "utils"), + .target(name: "graph_adjacency_list_target", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list_target.swift"], swiftSettings: [.define("TARGET")]), + .target(name: "binary_search_insertion_target", path: "chapter_searching", sources: ["binary_search_insertion_target.swift"], swiftSettings: [.define("TARGET")]), + // chapter_computational_complexity + .executableTarget(name: "iteration", path: "chapter_computational_complexity", sources: ["iteration.swift"]), + .executableTarget(name: "recursion", path: "chapter_computational_complexity", sources: ["recursion.swift"]), + .executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]), + .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), + .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), + // chapter_array_and_linkedlist + .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), + .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), + .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), + .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), + // chapter_stack_and_queue + .executableTarget(name: "stack", path: "chapter_stack_and_queue", sources: ["stack.swift"]), + .executableTarget(name: "linkedlist_stack", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_stack.swift"]), + .executableTarget(name: "array_stack", path: "chapter_stack_and_queue", sources: ["array_stack.swift"]), + .executableTarget(name: "queue", path: "chapter_stack_and_queue", sources: ["queue.swift"]), + .executableTarget(name: "linkedlist_queue", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_queue.swift"]), + .executableTarget(name: "array_queue", path: "chapter_stack_and_queue", sources: ["array_queue.swift"]), + .executableTarget(name: "deque", path: "chapter_stack_and_queue", sources: ["deque.swift"]), + .executableTarget(name: "linkedlist_deque", path: "chapter_stack_and_queue", sources: ["linkedlist_deque.swift"]), + .executableTarget(name: "array_deque", path: "chapter_stack_and_queue", sources: ["array_deque.swift"]), + // chapter_hashing + .executableTarget(name: "hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map.swift"]), + .executableTarget(name: "array_hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["array_hash_map.swift"]), + .executableTarget(name: "hash_map_chaining", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_chaining.swift"]), + .executableTarget(name: "hash_map_open_addressing", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_open_addressing.swift"]), + .executableTarget(name: "simple_hash", path: "chapter_hashing", sources: ["simple_hash.swift"]), + .executableTarget(name: "built_in_hash", dependencies: ["utils"], path: "chapter_hashing", sources: ["built_in_hash.swift"]), + // chapter_tree + .executableTarget(name: "binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree.swift"]), + .executableTarget(name: "binary_tree_bfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_bfs.swift"]), + .executableTarget(name: "binary_tree_dfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_dfs.swift"]), + .executableTarget(name: "array_binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["array_binary_tree.swift"]), + .executableTarget(name: "binary_search_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_search_tree.swift"]), + .executableTarget(name: "avl_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["avl_tree.swift"]), + // chapter_heap + .executableTarget(name: "heap", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["heap.swift"]), + .executableTarget(name: "my_heap", dependencies: ["utils"], path: "chapter_heap", sources: ["my_heap.swift"]), + .executableTarget(name: "top_k", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["top_k.swift"]), + // chapter_graph + .executableTarget(name: "graph_adjacency_matrix", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_matrix.swift"]), + .executableTarget(name: "graph_adjacency_list", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list.swift"]), + .executableTarget(name: "graph_bfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_bfs.swift"]), + .executableTarget(name: "graph_dfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_dfs.swift"]), + // chapter_searching + .executableTarget(name: "binary_search", path: "chapter_searching", sources: ["binary_search.swift"]), + .executableTarget(name: "binary_search_insertion", path: "chapter_searching", sources: ["binary_search_insertion.swift"]), + .executableTarget(name: "binary_search_edge", dependencies: ["binary_search_insertion_target"], path: "chapter_searching", sources: ["binary_search_edge.swift"]), + .executableTarget(name: "two_sum", path: "chapter_searching", sources: ["two_sum.swift"]), + .executableTarget(name: "linear_search", dependencies: ["utils"], path: "chapter_searching", sources: ["linear_search.swift"]), + .executableTarget(name: "hashing_search", dependencies: ["utils"], path: "chapter_searching", sources: ["hashing_search.swift"]), + // chapter_sorting + .executableTarget(name: "selection_sort", path: "chapter_sorting", sources: ["selection_sort.swift"]), + .executableTarget(name: "bubble_sort", path: "chapter_sorting", sources: ["bubble_sort.swift"]), + .executableTarget(name: "insertion_sort", path: "chapter_sorting", sources: ["insertion_sort.swift"]), + .executableTarget(name: "quick_sort", path: "chapter_sorting", sources: ["quick_sort.swift"]), + .executableTarget(name: "merge_sort", path: "chapter_sorting", sources: ["merge_sort.swift"]), + .executableTarget(name: "heap_sort", path: "chapter_sorting", sources: ["heap_sort.swift"]), + .executableTarget(name: "bucket_sort", path: "chapter_sorting", sources: ["bucket_sort.swift"]), + .executableTarget(name: "counting_sort", path: "chapter_sorting", sources: ["counting_sort.swift"]), + .executableTarget(name: "radix_sort", path: "chapter_sorting", sources: ["radix_sort.swift"]), + // chapter_divide_and_conquer + .executableTarget(name: "binary_search_recur", path: "chapter_divide_and_conquer", sources: ["binary_search_recur.swift"]), + .executableTarget(name: "build_tree", dependencies: ["utils"], path: "chapter_divide_and_conquer", sources: ["build_tree.swift"]), + .executableTarget(name: "hanota", path: "chapter_divide_and_conquer", sources: ["hanota.swift"]), + // chapter_backtracking + .executableTarget(name: "preorder_traversal_i_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_i_compact.swift"]), + .executableTarget(name: "preorder_traversal_ii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_ii_compact.swift"]), + .executableTarget(name: "preorder_traversal_iii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_compact.swift"]), + .executableTarget(name: "preorder_traversal_iii_template", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_template.swift"]), + .executableTarget(name: "permutations_i", path: "chapter_backtracking", sources: ["permutations_i.swift"]), + .executableTarget(name: "permutations_ii", path: "chapter_backtracking", sources: ["permutations_ii.swift"]), + .executableTarget(name: "subset_sum_i_naive", path: "chapter_backtracking", sources: ["subset_sum_i_naive.swift"]), + .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), + .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), + .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), + // chapter_dynamic_programming + .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), + .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), + .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), + .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), + .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), + .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), + .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), + .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), + .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), + .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), + .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), + .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), + // chapter_greedy + .executableTarget(name: "coin_change_greedy", path: "chapter_greedy", sources: ["coin_change_greedy.swift"]), + .executableTarget(name: "fractional_knapsack", path: "chapter_greedy", sources: ["fractional_knapsack.swift"]), + .executableTarget(name: "max_capacity", path: "chapter_greedy", sources: ["max_capacity.swift"]), + .executableTarget(name: "max_product_cutting", path: "chapter_greedy", sources: ["max_product_cutting.swift"]), + ] +) diff --git a/zh-hant/codes/swift/chapter_array_and_linkedlist/array.swift b/zh-hant/codes/swift/chapter_array_and_linkedlist/array.swift new file mode 100644 index 000000000..356b11e3a --- /dev/null +++ b/zh-hant/codes/swift/chapter_array_and_linkedlist/array.swift @@ -0,0 +1,107 @@ +/** + * File: array.swift + * Created Time: 2023-01-05 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 隨機訪問元素 */ +func randomAccess(nums: [Int]) -> Int { + // 在區間 [0, nums.count) 中隨機抽取一個數字 + let randomIndex = nums.indices.randomElement()! + // 獲取並返回隨機元素 + let randomNum = nums[randomIndex] + return randomNum +} + +/* 擴展陣列長度 */ +func extend(nums: [Int], enlarge: Int) -> [Int] { + // 初始化一個擴展長度後的陣列 + var res = Array(repeating: 0, count: nums.count + enlarge) + // 將原陣列中的所有元素複製到新陣列 + for i in nums.indices { + res[i] = nums[i] + } + // 返回擴展後的新陣列 + return res +} + +/* 在陣列的索引 index 處插入元素 num */ +func insert(nums: inout [Int], num: Int, index: Int) { + // 把索引 index 以及之後的所有元素向後移動一位 + for i in nums.indices.dropFirst(index).reversed() { + nums[i] = nums[i - 1] + } + // 將 num 賦給 index 處的元素 + nums[index] = num +} + +/* 刪除索引 index 處的元素 */ +func remove(nums: inout [Int], index: Int) { + // 把索引 index 之後的所有元素向前移動一位 + for i in nums.indices.dropFirst(index).dropLast() { + nums[i] = nums[i + 1] + } +} + +/* 走訪陣列 */ +func traverse(nums: [Int]) { + var count = 0 + // 透過索引走訪陣列 + for i in nums.indices { + count += nums[i] + } + // 直接走訪陣列元素 + for num in nums { + count += num + } + // 同時走訪資料索引和元素 + for (i, num) in nums.enumerated() { + count += nums[i] + count += num + } +} + +/* 在陣列中查詢指定元素 */ +func find(nums: [Int], target: Int) -> Int { + for i in nums.indices { + if nums[i] == target { + return i + } + } + return -1 +} + +@main +enum _Array { + /* Driver Code */ + static func main() { + /* 初始化陣列 */ + let arr = Array(repeating: 0, count: 5) + print("陣列 arr = \(arr)") + var nums = [1, 3, 2, 5, 4] + print("陣列 nums = \(nums)") + + /* 隨機訪問 */ + let randomNum = randomAccess(nums: nums) + print("在 nums 中獲取隨機元素 \(randomNum)") + + /* 長度擴展 */ + nums = extend(nums: nums, enlarge: 3) + print("將陣列長度擴展至 8 ,得到 nums = \(nums)") + + /* 插入元素 */ + insert(nums: &nums, num: 6, index: 3) + print("在索引 3 處插入數字 6 ,得到 nums = \(nums)") + + /* 刪除元素 */ + remove(nums: &nums, index: 2) + print("刪除索引 2 處的元素,得到 nums = \(nums)") + + /* 走訪陣列 */ + traverse(nums: nums) + + /* 查詢元素 */ + let index = find(nums: nums, target: 3) + print("在 nums 中查詢元素 3 ,得到索引 = \(index)") + } +} diff --git a/zh-hant/codes/swift/chapter_array_and_linkedlist/linked_list.swift b/zh-hant/codes/swift/chapter_array_and_linkedlist/linked_list.swift new file mode 100644 index 000000000..3b25505f2 --- /dev/null +++ b/zh-hant/codes/swift/chapter_array_and_linkedlist/linked_list.swift @@ -0,0 +1,90 @@ +/** + * File: linked_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +func insert(n0: ListNode, P: ListNode) { + let n1 = n0.next + P.next = n1 + n0.next = P +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +func remove(n0: ListNode) { + if n0.next == nil { + return + } + // n0 -> P -> n1 + let P = n0.next + let n1 = P?.next + n0.next = n1 +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +func access(head: ListNode, index: Int) -> ListNode? { + var head: ListNode? = head + for _ in 0 ..< index { + if head == nil { + return nil + } + head = head?.next + } + return head +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +func find(head: ListNode, target: Int) -> Int { + var head: ListNode? = head + var index = 0 + while head != nil { + if head?.val == target { + return index + } + head = head?.next + index += 1 + } + return -1 +} + +@main +enum LinkedList { + /* Driver Code */ + static func main() { + /* 初始化鏈結串列 */ + // 初始化各個節點 + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // 構建節點之間的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + print("初始化的鏈結串列為") + PrintUtil.printLinkedList(head: n0) + + /* 插入節點 */ + insert(n0: n0, P: ListNode(x: 0)) + print("插入節點後的鏈結串列為") + PrintUtil.printLinkedList(head: n0) + + /* 刪除節點 */ + remove(n0: n0) + print("刪除節點後的鏈結串列為") + PrintUtil.printLinkedList(head: n0) + + /* 訪問節點 */ + let node = access(head: n0, index: 3) + print("鏈結串列中索引 3 處的節點的值 = \(node!.val)") + + /* 查詢節點 */ + let index = find(head: n0, target: 2) + print("鏈結串列中值為 2 的節點的索引 = \(index)") + } +} diff --git a/zh-hant/codes/swift/chapter_array_and_linkedlist/list.swift b/zh-hant/codes/swift/chapter_array_and_linkedlist/list.swift new file mode 100644 index 000000000..f2d1832a5 --- /dev/null +++ b/zh-hant/codes/swift/chapter_array_and_linkedlist/list.swift @@ -0,0 +1,63 @@ +/** + * File: list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum List { + /* Driver Code */ + static func main() { + /* 初始化串列 */ + var nums = [1, 3, 2, 5, 4] + print("串列 nums = \(nums)") + + /* 訪問元素 */ + let num = nums[1] + print("訪問索引 1 處的元素,得到 num = \(num)") + + /* 更新元素 */ + nums[1] = 0 + print("將索引 1 處的元素更新為 0 ,得到 nums = \(nums)") + + /* 清空串列 */ + nums.removeAll() + print("清空串列後 nums = \(nums)") + + /* 在尾部新增元素 */ + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + print("新增元素後 nums = \(nums)") + + /* 在中間插入元素 */ + nums.insert(6, at: 3) + print("在索引 3 處插入數字 6 ,得到 nums = \(nums)") + + /* 刪除元素 */ + nums.remove(at: 3) + print("刪除索引 3 處的元素,得到 nums = \(nums)") + + /* 透過索引走訪串列 */ + var count = 0 + for i in nums.indices { + count += nums[i] + } + /* 直接走訪串列元素 */ + count = 0 + for x in nums { + count += x + } + + /* 拼接兩個串列 */ + let nums1 = [6, 8, 7, 10, 9] + nums.append(contentsOf: nums1) + print("將串列 nums1 拼接到 nums 之後,得到 nums = \(nums)") + + /* 排序串列 */ + nums.sort() + print("排序串列後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_array_and_linkedlist/my_list.swift b/zh-hant/codes/swift/chapter_array_and_linkedlist/my_list.swift new file mode 100644 index 000000000..43f014992 --- /dev/null +++ b/zh-hant/codes/swift/chapter_array_and_linkedlist/my_list.swift @@ -0,0 +1,146 @@ +/** + * File: my_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 串列類別 */ +class MyList { + private var arr: [Int] // 陣列(儲存串列元素) + private var _capacity: Int // 串列容量 + private var _size: Int // 串列長度(當前元素數量) + private let extendRatio: Int // 每次串列擴容的倍數 + + /* 建構子 */ + init() { + _capacity = 10 + _size = 0 + extendRatio = 2 + arr = Array(repeating: 0, count: _capacity) + } + + /* 獲取串列長度(當前元素數量)*/ + func size() -> Int { + _size + } + + /* 獲取串列容量 */ + func capacity() -> Int { + _capacity + } + + /* 訪問元素 */ + func get(index: Int) -> Int { + // 索引如果越界則丟擲錯誤,下同 + if index < 0 || index >= size() { + fatalError("索引越界") + } + return arr[index] + } + + /* 更新元素 */ + func set(index: Int, num: Int) { + if index < 0 || index >= size() { + fatalError("索引越界") + } + arr[index] = num + } + + /* 在尾部新增元素 */ + func add(num: Int) { + // 元素數量超出容量時,觸發擴容機制 + if size() == capacity() { + extendCapacity() + } + arr[size()] = num + // 更新元素數量 + _size += 1 + } + + /* 在中間插入元素 */ + func insert(index: Int, num: Int) { + if index < 0 || index >= size() { + fatalError("索引越界") + } + // 元素數量超出容量時,觸發擴容機制 + if size() == capacity() { + extendCapacity() + } + // 將索引 index 以及之後的元素都向後移動一位 + for j in (index ..< size()).reversed() { + arr[j + 1] = arr[j] + } + arr[index] = num + // 更新元素數量 + _size += 1 + } + + /* 刪除元素 */ + @discardableResult + func remove(index: Int) -> Int { + if index < 0 || index >= size() { + fatalError("索引越界") + } + let num = arr[index] + // 將將索引 index 之後的元素都向前移動一位 + for j in index ..< (size() - 1) { + arr[j] = arr[j + 1] + } + // 更新元素數量 + _size -= 1 + // 返回被刪除的元素 + return num + } + + /* 串列擴容 */ + func extendCapacity() { + // 新建一個長度為原陣列 extendRatio 倍的新陣列,並將原陣列複製到新陣列 + arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1)) + // 更新串列容量 + _capacity = arr.count + } + + /* 將串列轉換為陣列 */ + func toArray() -> [Int] { + Array(arr.prefix(size())) + } +} + +@main +enum _MyList { + /* Driver Code */ + static func main() { + /* 初始化串列 */ + let nums = MyList() + /* 在尾部新增元素 */ + nums.add(num: 1) + nums.add(num: 3) + nums.add(num: 2) + nums.add(num: 5) + nums.add(num: 4) + print("串列 nums = \(nums.toArray()) ,容量 = \(nums.capacity()) ,長度 = \(nums.size())") + + /* 在中間插入元素 */ + nums.insert(index: 3, num: 6) + print("在索引 3 處插入數字 6 ,得到 nums = \(nums.toArray())") + + /* 刪除元素 */ + nums.remove(index: 3) + print("刪除索引 3 處的元素,得到 nums = \(nums.toArray())") + + /* 訪問元素 */ + let num = nums.get(index: 1) + print("訪問索引 1 處的元素,得到 num = \(num)") + + /* 更新元素 */ + nums.set(index: 1, num: 0) + print("將索引 1 處的元素更新為 0 ,得到 nums = \(nums.toArray())") + + /* 測試擴容機制 */ + for i in 0 ..< 10 { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(num: i) + } + print("擴容後的串列 nums = \(nums.toArray()) ,容量 = \(nums.capacity()) ,長度 = \(nums.size())") + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/n_queens.swift b/zh-hant/codes/swift/chapter_backtracking/n_queens.swift new file mode 100644 index 000000000..ea0044fe2 --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/n_queens.swift @@ -0,0 +1,67 @@ +/** + * File: n_queens.swift + * Created Time: 2023-05-14 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:n 皇后 */ +func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { + // 當放置完所有行時,記錄解 + if row == n { + res.append(state) + return + } + // 走訪所有列 + for col in 0 ..< n { + // 計算該格子對應的主對角線和次對角線 + let diag1 = row - col + n - 1 + let diag2 = row + col + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // 嘗試:將皇后放置在該格子 + state[row][col] = "Q" + cols[col] = true + diags1[diag1] = true + diags2[diag2] = true + // 放置下一行 + backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + // 回退:將該格子恢復為空位 + state[row][col] = "#" + cols[col] = false + diags1[diag1] = false + diags2[diag2] = false + } + } +} + +/* 求解 n 皇后 */ +func nQueens(n: Int) -> [[[String]]] { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + var state = Array(repeating: Array(repeating: "#", count: n), count: n) + var cols = Array(repeating: false, count: n) // 記錄列是否有皇后 + var diags1 = Array(repeating: false, count: 2 * n - 1) // 記錄主對角線上是否有皇后 + var diags2 = Array(repeating: false, count: 2 * n - 1) // 記錄次對角線上是否有皇后 + var res: [[[String]]] = [] + + backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + + return res +} + +@main +enum NQueens { + /* Driver Code */ + static func main() { + let n = 4 + let res = nQueens(n: n) + + print("輸入棋盤長寬為 \(n)") + print("皇后放置方案共有 \(res.count) 種") + for state in res { + print("--------------------") + for row in state { + print(row) + } + } + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/permutations_i.swift b/zh-hant/codes/swift/chapter_backtracking/permutations_i.swift new file mode 100644 index 000000000..cb8db59ac --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/permutations_i.swift @@ -0,0 +1,50 @@ +/** + * File: permutations_i.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:全排列 I */ +func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // 當狀態長度等於元素數量時,記錄解 + if state.count == choices.count { + res.append(state) + return + } + // 走訪所有選擇 + for (i, choice) in choices.enumerated() { + // 剪枝:不允許重複選擇元素 + if !selected[i] { + // 嘗試:做出選擇,更新狀態 + selected[i] = true + state.append(choice) + // 進行下一輪選擇 + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.removeLast() + } + } +} + +/* 全排列 I */ +func permutationsI(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res +} + +@main +enum PermutationsI { + /* Driver Code */ + static func main() { + let nums = [1, 2, 3] + + let res = permutationsI(nums: nums) + + print("輸入陣列 nums = \(nums)") + print("所有排列 res = \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/permutations_ii.swift b/zh-hant/codes/swift/chapter_backtracking/permutations_ii.swift new file mode 100644 index 000000000..ae821b44c --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/permutations_ii.swift @@ -0,0 +1,52 @@ +/** + * File: permutations_ii.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:全排列 II */ +func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // 當狀態長度等於元素數量時,記錄解 + if state.count == choices.count { + res.append(state) + return + } + // 走訪所有選擇 + var duplicated: Set = [] + for (i, choice) in choices.enumerated() { + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if !selected[i], !duplicated.contains(choice) { + // 嘗試:做出選擇,更新狀態 + duplicated.insert(choice) // 記錄選擇過的元素值 + selected[i] = true + state.append(choice) + // 進行下一輪選擇 + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false + state.removeLast() + } + } +} + +/* 全排列 II */ +func permutationsII(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res +} + +@main +enum PermutationsII { + /* Driver Code */ + static func main() { + let nums = [1, 2, 3] + + let res = permutationsII(nums: nums) + + print("輸入陣列 nums = \(nums)") + print("所有排列 res = \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift new file mode 100644 index 000000000..4cb11b41d --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var res: [TreeNode] = [] + +/* 前序走訪:例題一 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + if root.val == 7 { + // 記錄解 + res.append(root) + } + preOrder(root: root.left) + preOrder(root: root.right) +} + +@main +enum PreorderTraversalICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + PrintUtil.printTree(root: root) + + // 前序走訪 + res = [] + preOrder(root: root) + + print("\n輸出所有值為 7 的節點") + var vals: [Int] = [] + for node in res { + vals.append(node.val) + } + print(vals) + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift new file mode 100644 index 000000000..3b15cd98a --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var path: [TreeNode] = [] +var res: [[TreeNode]] = [] + +/* 前序走訪:例題二 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 嘗試 + path.append(root) + if root.val == 7 { + // 記錄解 + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // 回退 + path.removeLast() +} + +@main +enum PreorderTraversalIICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + PrintUtil.printTree(root: root) + + // 前序走訪 + path = [] + res = [] + preOrder(root: root) + + print("\n輸出所有根節點到節點 7 的路徑") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift new file mode 100644 index 000000000..686119eab --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var path: [TreeNode] = [] +var res: [[TreeNode]] = [] + +/* 前序走訪:例題三 */ +func preOrder(root: TreeNode?) { + // 剪枝 + guard let root = root, root.val != 3 else { + return + } + // 嘗試 + path.append(root) + if root.val == 7 { + // 記錄解 + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // 回退 + path.removeLast() +} + +@main +enum PreorderTraversalIIICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + PrintUtil.printTree(root: root) + + // 前序走訪 + path = [] + res = [] + preOrder(root: root) + + print("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift new file mode 100644 index 000000000..35dd33b9f --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 判斷當前狀態是否為解 */ +func isSolution(state: [TreeNode]) -> Bool { + !state.isEmpty && state.last!.val == 7 +} + +/* 記錄解 */ +func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { + res.append(state) +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { + choice != nil && choice!.val != 3 +} + +/* 更新狀態 */ +func makeChoice(state: inout [TreeNode], choice: TreeNode) { + state.append(choice) +} + +/* 恢復狀態 */ +func undoChoice(state: inout [TreeNode], choice: TreeNode) { + state.removeLast() +} + +/* 回溯演算法:例題三 */ +func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { + // 檢查是否為解 + if isSolution(state: state) { + recordSolution(state: state, res: &res) + } + // 走訪所有選擇 + for choice in choices { + // 剪枝:檢查選擇是否合法 + if isValid(state: state, choice: choice) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state: &state, choice: choice) + // 進行下一輪選擇 + backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state: &state, choice: choice) + } + } +} + +@main +enum PreorderTraversalIIITemplate { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n初始化二元樹") + PrintUtil.printTree(root: root) + + // 回溯演算法 + var state: [TreeNode] = [] + var res: [[TreeNode]] = [] + backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res) + + print("\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/subset_sum_i.swift b/zh-hant/codes/swift/chapter_backtracking/subset_sum_i.swift new file mode 100644 index 000000000..06b4e7ecd --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/subset_sum_i.swift @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:子集和 I */ +func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // 子集和等於 target 時,記錄解 + if target == 0 { + res.append(state) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for i in choices.indices.dropFirst(start) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target - choices[i] < 0 { + break + } + // 嘗試:做出選擇,更新 target, start + state.append(choices[i]) + // 進行下一輪選擇 + backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast() + } +} + +/* 求解子集和 I */ +func subsetSumI(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 狀態(子集) + let nums = nums.sorted() // 對 nums 進行排序 + let start = 0 // 走訪起始點 + var res: [[Int]] = [] // 結果串列(子集串列) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res +} + +@main +enum SubsetSumI { + /* Driver Code */ + static func main() { + let nums = [3, 4, 5] + let target = 9 + + let res = subsetSumI(nums: nums, target: target) + + print("輸入陣列 nums = \(nums), target = \(target)") + print("所有和等於 \(target) 的子集 res = \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/subset_sum_i_naive.swift b/zh-hant/codes/swift/chapter_backtracking/subset_sum_i_naive.swift new file mode 100644 index 000000000..557e989f7 --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/subset_sum_i_naive.swift @@ -0,0 +1,51 @@ +/** + * File: subset_sum_i_naive.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:子集和 I */ +func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { + // 子集和等於 target 時,記錄解 + if total == target { + res.append(state) + return + } + // 走訪所有選擇 + for i in choices.indices { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if total + choices[i] > target { + continue + } + // 嘗試:做出選擇,更新元素和 total + state.append(choices[i]) + // 進行下一輪選擇 + backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast() + } +} + +/* 求解子集和 I(包含重複子集) */ +func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 狀態(子集) + let total = 0 // 子集和 + var res: [[Int]] = [] // 結果串列(子集串列) + backtrack(state: &state, target: target, total: total, choices: nums, res: &res) + return res +} + +@main +enum SubsetSumINaive { + /* Driver Code */ + static func main() { + let nums = [3, 4, 5] + let target = 9 + + let res = subsetSumINaive(nums: nums, target: target) + + print("輸入陣列 nums = \(nums), target = \(target)") + print("所有和等於 \(target) 的子集 res = \(res)") + print("請注意,該方法輸出的結果包含重複集合") + } +} diff --git a/zh-hant/codes/swift/chapter_backtracking/subset_sum_ii.swift b/zh-hant/codes/swift/chapter_backtracking/subset_sum_ii.swift new file mode 100644 index 000000000..0ee7d991e --- /dev/null +++ b/zh-hant/codes/swift/chapter_backtracking/subset_sum_ii.swift @@ -0,0 +1,58 @@ +/** + * File: subset_sum_ii.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯演算法:子集和 II */ +func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // 子集和等於 target 時,記錄解 + if target == 0 { + res.append(state) + return + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for i in choices.indices.dropFirst(start) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if target - choices[i] < 0 { + break + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if i > start, choices[i] == choices[i - 1] { + continue + } + // 嘗試:做出選擇,更新 target, start + state.append(choices[i]) + // 進行下一輪選擇 + backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + state.removeLast() + } +} + +/* 求解子集和 II */ +func subsetSumII(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 狀態(子集) + let nums = nums.sorted() // 對 nums 進行排序 + let start = 0 // 走訪起始點 + var res: [[Int]] = [] // 結果串列(子集串列) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res +} + +@main +enum SubsetSumII { + /* Driver Code */ + static func main() { + let nums = [4, 4, 5] + let target = 9 + + let res = subsetSumII(nums: nums, target: target) + + print("輸入陣列 nums = \(nums), target = \(target)") + print("所有和等於 \(target) 的子集 res = \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_computational_complexity/iteration.swift b/zh-hant/codes/swift/chapter_computational_complexity/iteration.swift new file mode 100644 index 000000000..0f4d0fcbb --- /dev/null +++ b/zh-hant/codes/swift/chapter_computational_complexity/iteration.swift @@ -0,0 +1,75 @@ +/** + * File: iteration.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* for 迴圈 */ +func forLoop(n: Int) -> Int { + var res = 0 + // 迴圈求和 1, 2, ..., n-1, n + for i in 1 ... n { + res += i + } + return res +} + +/* while 迴圈 */ +func whileLoop(n: Int) -> Int { + var res = 0 + var i = 1 // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while i <= n { + res += i + i += 1 // 更新條件變數 + } + return res +} + +/* while 迴圈(兩次更新) */ +func whileLoopII(n: Int) -> Int { + var res = 0 + var i = 1 // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while i <= n { + res += i + // 更新條件變數 + i += 1 + i *= 2 + } + return res +} + +/* 雙層 for 迴圈 */ +func nestedForLoop(n: Int) -> String { + var res = "" + // 迴圈 i = 1, 2, ..., n-1, n + for i in 1 ... n { + // 迴圈 j = 1, 2, ..., n-1, n + for j in 1 ... n { + res.append("(\(i), \(j)), ") + } + } + return res +} + +@main +enum Iteration { + /* Driver Code */ + static func main() { + let n = 5 + var res = 0 + + res = forLoop(n: n) + print("\nfor 迴圈的求和結果 res = \(res)") + + res = whileLoop(n: n) + print("\nwhile 迴圈的求和結果 res = \(res)") + + res = whileLoopII(n: n) + print("\nwhile 迴圈(兩次更新)求和結果 res = \(res)") + + let resStr = nestedForLoop(n: n) + print("\n雙層 for 迴圈的走訪結果 \(resStr)") + } +} diff --git a/zh-hant/codes/swift/chapter_computational_complexity/recursion.swift b/zh-hant/codes/swift/chapter_computational_complexity/recursion.swift new file mode 100644 index 000000000..62afd9f72 --- /dev/null +++ b/zh-hant/codes/swift/chapter_computational_complexity/recursion.swift @@ -0,0 +1,79 @@ +/** + * File: recursion.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 遞迴 */ +func recur(n: Int) -> Int { + // 終止條件 + if n == 1 { + return 1 + } + // 遞:遞迴呼叫 + let res = recur(n: n - 1) + // 迴:返回結果 + return n + res +} + +/* 使用迭代模擬遞迴 */ +func forLoopRecur(n: Int) -> Int { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + var stack: [Int] = [] + var res = 0 + // 遞:遞迴呼叫 + for i in (1 ... n).reversed() { + // 透過“入堆疊操作”模擬“遞” + stack.append(i) + } + // 迴:返回結果 + while !stack.isEmpty { + // 透過“出堆疊操作”模擬“迴” + res += stack.removeLast() + } + // res = 1+2+3+...+n + return res +} + +/* 尾遞迴 */ +func tailRecur(n: Int, res: Int) -> Int { + // 終止條件 + if n == 0 { + return res + } + // 尾遞迴呼叫 + return tailRecur(n: n - 1, res: res + n) +} + +/* 費波那契數列:遞迴 */ +func fib(n: Int) -> Int { + // 終止條件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + let res = fib(n: n - 1) + fib(n: n - 2) + // 返回結果 f(n) + return res +} + +@main +enum Recursion { + /* Driver Code */ + static func main() { + let n = 5 + var res = 0 + + res = recursion.recur(n: n) + print("\n遞迴函式的求和結果 res = \(res)") + + res = recursion.forLoopRecur(n: n) + print("\n使用迭代模擬遞迴求和結果 res = \(res)") + + res = recursion.tailRecur(n: n, res: 0) + print("\n尾遞迴函式的求和結果 res = \(res)") + + res = recursion.fib(n: n) + print("\n費波那契數列的第 \(n) 項為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_computational_complexity/space_complexity.swift b/zh-hant/codes/swift/chapter_computational_complexity/space_complexity.swift new file mode 100644 index 000000000..99aeb8309 --- /dev/null +++ b/zh-hant/codes/swift/chapter_computational_complexity/space_complexity.swift @@ -0,0 +1,98 @@ +/** + * File: space_complexity.swift + * Created Time: 2023-01-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 函式 */ +@discardableResult +func function() -> Int { + // 執行某些操作 + return 0 +} + +/* 常數階 */ +func constant(n: Int) { + // 常數、變數、物件佔用 O(1) 空間 + let a = 0 + var b = 0 + let nums = Array(repeating: 0, count: 10000) + let node = ListNode(x: 0) + // 迴圈中的變數佔用 O(1) 空間 + for _ in 0 ..< n { + let c = 0 + } + // 迴圈中的函式佔用 O(1) 空間 + for _ in 0 ..< n { + function() + } +} + +/* 線性階 */ +func linear(n: Int) { + // 長度為 n 的陣列佔用 O(n) 空間 + let nums = Array(repeating: 0, count: n) + // 長度為 n 的串列佔用 O(n) 空間 + let nodes = (0 ..< n).map { ListNode(x: $0) } + // 長度為 n 的雜湊表佔用 O(n) 空間 + let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) +} + +/* 線性階(遞迴實現) */ +func linearRecur(n: Int) { + print("遞迴 n = \(n)") + if n == 1 { + return + } + linearRecur(n: n - 1) +} + +/* 平方階 */ +func quadratic(n: Int) { + // 二維串列佔用 O(n^2) 空間 + let numList = Array(repeating: Array(repeating: 0, count: n), count: n) +} + +/* 平方階(遞迴實現) */ +@discardableResult +func quadraticRecur(n: Int) -> Int { + if n <= 0 { + return 0 + } + // 陣列 nums 長度為 n, n-1, ..., 2, 1 + let nums = Array(repeating: 0, count: n) + print("遞迴 n = \(n) 中的 nums 長度 = \(nums.count)") + return quadraticRecur(n: n - 1) +} + +/* 指數階(建立滿二元樹) */ +func buildTree(n: Int) -> TreeNode? { + if n == 0 { + return nil + } + let root = TreeNode(x: 0) + root.left = buildTree(n: n - 1) + root.right = buildTree(n: n - 1) + return root +} + +@main +enum SpaceComplexity { + /* Driver Code */ + static func main() { + let n = 5 + // 常數階 + constant(n: n) + // 線性階 + linear(n: n) + linearRecur(n: n) + // 平方階 + quadratic(n: n) + quadraticRecur(n: n) + // 指數階 + let root = buildTree(n: n) + PrintUtil.printTree(root: root) + } +} diff --git a/zh-hant/codes/swift/chapter_computational_complexity/time_complexity.swift b/zh-hant/codes/swift/chapter_computational_complexity/time_complexity.swift new file mode 100644 index 000000000..68a44b075 --- /dev/null +++ b/zh-hant/codes/swift/chapter_computational_complexity/time_complexity.swift @@ -0,0 +1,172 @@ +/** + * File: time_complexity.swift + * Created Time: 2022-12-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 常數階 */ +func constant(n: Int) -> Int { + var count = 0 + let size = 100_000 + for _ in 0 ..< size { + count += 1 + } + return count +} + +/* 線性階 */ +func linear(n: Int) -> Int { + var count = 0 + for _ in 0 ..< n { + count += 1 + } + return count +} + +/* 線性階(走訪陣列) */ +func arrayTraversal(nums: [Int]) -> Int { + var count = 0 + // 迴圈次數與陣列長度成正比 + for _ in nums { + count += 1 + } + return count +} + +/* 平方階 */ +func quadratic(n: Int) -> Int { + var count = 0 + // 迴圈次數與資料大小 n 成平方關係 + for _ in 0 ..< n { + for _ in 0 ..< n { + count += 1 + } + } + return count +} + +/* 平方階(泡沫排序) */ +func bubbleSort(nums: inout [Int]) -> Int { + var count = 0 // 計數器 + // 外迴圈:未排序區間為 [0, i] + for i in nums.indices.dropFirst().reversed() { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 // 元素交換包含 3 個單元操作 + } + } + } + return count +} + +/* 指數階(迴圈實現) */ +func exponential(n: Int) -> Int { + var count = 0 + var base = 1 + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for _ in 0 ..< n { + for _ in 0 ..< base { + count += 1 + } + base *= 2 + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count +} + +/* 指數階(遞迴實現) */ +func expRecur(n: Int) -> Int { + if n == 1 { + return 1 + } + return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 +} + +/* 對數階(迴圈實現) */ +func logarithmic(n: Int) -> Int { + var count = 0 + var n = n + while n > 1 { + n = n / 2 + count += 1 + } + return count +} + +/* 對數階(遞迴實現) */ +func logRecur(n: Int) -> Int { + if n <= 1 { + return 0 + } + return logRecur(n: n / 2) + 1 +} + +/* 線性對數階 */ +func linearLogRecur(n: Int) -> Int { + if n <= 1 { + return 1 + } + var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) + for _ in stride(from: 0, to: n, by: 1) { + count += 1 + } + return count +} + +/* 階乘階(遞迴實現) */ +func factorialRecur(n: Int) -> Int { + if n == 0 { + return 1 + } + var count = 0 + // 從 1 個分裂出 n 個 + for _ in 0 ..< n { + count += factorialRecur(n: n - 1) + } + return count +} + +@main +enum TimeComplexity { + /* Driver Code */ + static func main() { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + let n = 8 + print("輸入資料大小 n = \(n)") + + var count = constant(n: n) + print("常數階的操作數量 = \(count)") + + count = linear(n: n) + print("線性階的操作數量 = \(count)") + count = arrayTraversal(nums: Array(repeating: 0, count: n)) + print("線性階(走訪陣列)的操作數量 = \(count)") + + count = quadratic(n: n) + print("平方階的操作數量 = \(count)") + var nums = Array(stride(from: n, to: 0, by: -1)) // [n,n-1,...,2,1] + count = bubbleSort(nums: &nums) + print("平方階(泡沫排序)的操作數量 = \(count)") + + count = exponential(n: n) + print("指數階(迴圈實現)的操作數量 = \(count)") + count = expRecur(n: n) + print("指數階(遞迴實現)的操作數量 = \(count)") + + count = logarithmic(n: n) + print("對數階(迴圈實現)的操作數量 = \(count)") + count = logRecur(n: n) + print("對數階(遞迴實現)的操作數量 = \(count)") + + count = linearLogRecur(n: n) + print("線性對數階(遞迴實現)的操作數量 = \(count)") + + count = factorialRecur(n: n) + print("階乘階(遞迴實現)的操作數量 = \(count)") + } +} diff --git a/zh-hant/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift b/zh-hant/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift new file mode 100644 index 000000000..0fc64f530 --- /dev/null +++ b/zh-hant/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift @@ -0,0 +1,40 @@ +/** + * File: worst_best_time_complexity.swift + * Created Time: 2022-12-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +func randomNumbers(n: Int) -> [Int] { + // 生成陣列 nums = { 1, 2, 3, ..., n } + var nums = Array(1 ... n) + // 隨機打亂陣列元素 + nums.shuffle() + return nums +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +func findOne(nums: [Int]) -> Int { + for i in nums.indices { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if nums[i] == 1 { + return i + } + } + return -1 +} + +@main +enum WorstBestTimeComplexity { + /* Driver Code */ + static func main() { + for _ in 0 ..< 10 { + let n = 100 + let nums = randomNumbers(n: n) + let index = findOne(nums: nums) + print("陣列 [ 1, 2, ..., n ] 被打亂後 = \(nums)") + print("數字 1 的索引為 \(index)") + } + } +} diff --git a/zh-hant/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift b/zh-hant/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift new file mode 100644 index 000000000..a1dd27995 --- /dev/null +++ b/zh-hant/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift @@ -0,0 +1,44 @@ +/** + * File: binary_search_recur.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分搜尋:問題 f(i, j) */ +func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { + // 若區間為空,代表無目標元素,則返回 -1 + if i > j { + return -1 + } + // 計算中點索引 m + let m = (i + j) / 2 + if nums[m] < target { + // 遞迴子問題 f(m+1, j) + return dfs(nums: nums, target: target, i: m + 1, j: j) + } else if nums[m] > target { + // 遞迴子問題 f(i, m-1) + return dfs(nums: nums, target: target, i: i, j: m - 1) + } else { + // 找到目標元素,返回其索引 + return m + } +} + +/* 二分搜尋 */ +func binarySearch(nums: [Int], target: Int) -> Int { + // 求解問題 f(0, n-1) + dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) +} + +@main +enum BinarySearchRecur { + /* Driver Code */ + static func main() { + let target = 6 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + // 二分搜尋(雙閉區間) + let index = binarySearch(nums: nums, target: target) + print("目標元素 6 的索引 = \(index)") + } +} diff --git a/zh-hant/codes/swift/chapter_divide_and_conquer/build_tree.swift b/zh-hant/codes/swift/chapter_divide_and_conquer/build_tree.swift new file mode 100644 index 000000000..5908a6f80 --- /dev/null +++ b/zh-hant/codes/swift/chapter_divide_and_conquer/build_tree.swift @@ -0,0 +1,47 @@ +/** + * File: build_tree.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 構建二元樹:分治 */ +func dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { + // 子樹區間為空時終止 + if r - l < 0 { + return nil + } + // 初始化根節點 + let root = TreeNode(x: preorder[i]) + // 查詢 m ,從而劃分左右子樹 + let m = inorderMap[preorder[i]]! + // 子問題:構建左子樹 + root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1) + // 子問題:構建右子樹 + root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r) + // 返回根節點 + return root +} + +/* 構建二元樹 */ +func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } + return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1) +} + +@main +enum BuildTree { + /* Driver Code */ + static func main() { + let preorder = [3, 9, 2, 1, 7] + let inorder = [9, 3, 1, 2, 7] + print("前序走訪 = \(preorder)") + print("中序走訪 = \(inorder)") + + let root = buildTree(preorder: preorder, inorder: inorder) + print("構建的二元樹為:") + PrintUtil.printTree(root: root) + } +} diff --git a/zh-hant/codes/swift/chapter_divide_and_conquer/hanota.swift b/zh-hant/codes/swift/chapter_divide_and_conquer/hanota.swift new file mode 100644 index 000000000..550554b8a --- /dev/null +++ b/zh-hant/codes/swift/chapter_divide_and_conquer/hanota.swift @@ -0,0 +1,58 @@ +/** + * File: hanota.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 移動一個圓盤 */ +func move(src: inout [Int], tar: inout [Int]) { + // 從 src 頂部拿出一個圓盤 + let pan = src.popLast()! + // 將圓盤放入 tar 頂部 + tar.append(pan) +} + +/* 求解河內塔問題 f(i) */ +func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { + // 若 src 只剩下一個圓盤,則直接將其移到 tar + if i == 1 { + move(src: &src, tar: &tar) + return + } + // 子問題 f(i-1) :將 src 頂部 i-1 個圓盤藉助 tar 移到 buf + dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) + // 子問題 f(1) :將 src 剩餘一個圓盤移到 tar + move(src: &src, tar: &tar) + // 子問題 f(i-1) :將 buf 頂部 i-1 個圓盤藉助 src 移到 tar + dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) +} + +/* 求解河內塔問題 */ +func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { + let n = A.count + // 串列尾部是柱子頂部 + // 將 src 頂部 n 個圓盤藉助 B 移到 C + dfs(i: n, src: &A, buf: &B, tar: &C) +} + +@main +enum Hanota { + /* Driver Code */ + static func main() { + // 串列尾部是柱子頂部 + var A = [5, 4, 3, 2, 1] + var B: [Int] = [] + var C: [Int] = [] + print("初始狀態下:") + print("A = \(A)") + print("B = \(B)") + print("C = \(C)") + + solveHanota(A: &A, B: &B, C: &C) + + print("圓盤移動完成後:") + print("A = \(A)") + print("B = \(B)") + print("C = \(C)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift new file mode 100644 index 000000000..4497d519e --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift @@ -0,0 +1,42 @@ +/** + * File: climbing_stairs_backtrack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 回溯 */ +func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { + // 當爬到第 n 階時,方案數量加 1 + if state == n { + res[0] += 1 + } + // 走訪所有選擇 + for choice in choices { + // 剪枝:不允許越過第 n 階 + if state + choice > n { + continue + } + backtrack(choices: choices, state: state + choice, n: n, res: &res) + } +} + +/* 爬樓梯:回溯 */ +func climbingStairsBacktrack(n: Int) -> Int { + let choices = [1, 2] // 可選擇向上爬 1 階或 2 階 + let state = 0 // 從第 0 階開始爬 + var res: [Int] = [] + res.append(0) // 使用 res[0] 記錄方案數量 + backtrack(choices: choices, state: state, n: n, res: &res) + return res[0] +} + +@main +enum ClimbingStairsBacktrack { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsBacktrack(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift new file mode 100644 index 000000000..8f4e6508f --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 帶約束爬樓梯:動態規劃 */ +func climbingStairsConstraintDP(n: Int) -> Int { + if n == 1 || n == 2 { + return 1 + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3 ... n { + 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] +} + +@main +enum ClimbingStairsConstraintDP { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsConstraintDP(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift new file mode 100644 index 000000000..648ed22ea --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 搜尋 */ +func dfs(i: Int) -> Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1) + dfs(i: i - 2) + return count +} + +/* 爬樓梯:搜尋 */ +func climbingStairsDFS(n: Int) -> Int { + dfs(i: n) +} + +@main +enum ClimbingStairsDFS { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFS(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift new file mode 100644 index 000000000..04d274fa4 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dfs_mem.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 記憶化搜尋 */ +func dfs(i: Int, mem: inout [Int]) -> Int { + // 已知 dp[1] 和 dp[2] ,返回之 + if i == 1 || i == 2 { + return i + } + // 若存在記錄 dp[i] ,則直接返回之 + if mem[i] != -1 { + return mem[i] + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) + // 記錄 dp[i] + mem[i] = count + return count +} + +/* 爬樓梯:記憶化搜尋 */ +func climbingStairsDFSMem(n: Int) -> Int { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + var mem = Array(repeating: -1, count: n + 1) + return dfs(i: n, mem: &mem) +} + +@main +enum ClimbingStairsDFSMem { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFSMem(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift new file mode 100644 index 000000000..7ddc627e6 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 爬樓梯:動態規劃 */ +func climbingStairsDP(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = Array(repeating: 0, count: n + 1) + // 初始狀態:預設最小子問題的解 + dp[1] = 1 + dp[2] = 2 + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3 ... n { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +func climbingStairsDPComp(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + var a = 1 + var b = 2 + for _ in 3 ... n { + (a, b) = (b, a + b) + } + return b +} + +@main +enum ClimbingStairsDP { + /* Driver Code */ + static func main() { + let n = 9 + + var res = climbingStairsDP(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + + res = climbingStairsDPComp(n: n) + print("爬 \(n) 階樓梯共有 \(res) 種方案") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/coin_change.swift b/zh-hant/codes/swift/chapter_dynamic_programming/coin_change.swift new file mode 100644 index 000000000..2c4f9ffbc --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/coin_change.swift @@ -0,0 +1,69 @@ +/** + * File: coin_change.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零錢兌換:動態規劃 */ +func coinChangeDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 狀態轉移:首行首列 + for a in 1 ... amt { + dp[0][a] = MAX + } + // 狀態轉移:其餘行和列 + for i in 1 ... n { + for a in 1 ... amt { + 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] != MAX ? dp[n][amt] : -1 +} + +/* 零錢兌換:空間最佳化後的動態規劃 */ +func coinChangeDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // 初始化 dp 表 + var dp = Array(repeating: MAX, count: amt + 1) + dp[0] = 0 + // 狀態轉移 + for i in 1 ... n { + for a in 1 ... amt { + 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] != MAX ? dp[amt] : -1 +} + +@main +enum CoinChange { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 4 + + // 動態規劃 + var res = coinChangeDP(coins: coins, amt: amt) + print("湊到目標金額所需的最少硬幣數量為 \(res)") + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(coins: coins, amt: amt) + print("湊到目標金額所需的最少硬幣數量為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/coin_change_ii.swift b/zh-hant/codes/swift/chapter_dynamic_programming/coin_change_ii.swift new file mode 100644 index 000000000..ff5c3c949 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/coin_change_ii.swift @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零錢兌換 II:動態規劃 */ +func coinChangeIIDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 初始化首列 + for i in 0 ... n { + dp[i][0] = 1 + } + // 狀態轉移 + for i in 1 ... n { + for a in 1 ... amt { + 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] +} + +/* 零錢兌換 II:空間最佳化後的動態規劃 */ +func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: amt + 1) + dp[0] = 1 + // 狀態轉移 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a] + } else { + // 不選和選硬幣 i 這兩種方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] +} + +@main +enum CoinChangeII { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 5 + + // 動態規劃 + var res = coinChangeIIDP(coins: coins, amt: amt) + print("湊出目標金額的硬幣組合數量為 \(res)") + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(coins: coins, amt: amt) + print("湊出目標金額的硬幣組合數量為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/edit_distance.swift b/zh-hant/codes/swift/chapter_dynamic_programming/edit_distance.swift new file mode 100644 index 000000000..7d990e410 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/edit_distance.swift @@ -0,0 +1,147 @@ +/** + * File: edit_distance.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 編輯距離:暴力搜尋 */ +func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { + // 若 s 和 t 都為空,則返回 0 + if i == 0, j == 0 { + return 0 + } + // 若 s 為空,則返回 t 長度 + if i == 0 { + return j + } + // 若 t 為空,則返回 s 長度 + if j == 0 { + return i + } + // 若兩字元相等,則直接跳過此兩字元 + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // 返回最少編輯步數 + return min(min(insert, delete), replace) + 1 +} + +/* 編輯距離:記憶化搜尋 */ +func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { + // 若 s 和 t 都為空,則返回 0 + if i == 0, 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.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // 記錄並返回最少編輯步數 + mem[i][j] = min(min(insert, delete), replace) + 1 + return mem[i][j] +} + +/* 編輯距離:動態規劃 */ +func editDistanceDP(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) + // 狀態轉移:首行首列 + for i in 1 ... n { + dp[i][0] = i + } + for j in 1 ... m { + dp[0][j] = j + } + // 狀態轉移:其餘行和列 + for i in 1 ... n { + for j in 1 ... m { + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1] + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] +} + +/* 編輯距離:空間最佳化後的動態規劃 */ +func editDistanceDPComp(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: 0, count: m + 1) + // 狀態轉移:首行 + for j in 1 ... m { + dp[j] = j + } + // 狀態轉移:其餘行 + for i in 1 ... n { + // 狀態轉移:首列 + var leftup = dp[0] // 暫存 dp[i-1, j-1] + dp[0] = i + // 狀態轉移:其餘列 + for j in 1 ... m { + let temp = dp[j] + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m] +} + +@main +enum EditDistance { + /* Driver Code */ + static func main() { + let s = "bag" + let t = "pack" + let n = s.utf8CString.count + let m = t.utf8CString.count + + // 暴力搜尋 + var res = editDistanceDFS(s: s, t: t, i: n, j: m) + print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") + + // 記憶化搜尋 + var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) + res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) + print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") + + // 動態規劃 + res = editDistanceDP(s: s, t: t) + print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s: s, t: t) + print("將 \(s) 更改為 \(t) 最少需要編輯 \(res) 步") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/knapsack.swift b/zh-hant/codes/swift/chapter_dynamic_programming/knapsack.swift new file mode 100644 index 000000000..d4bd7e1db --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/knapsack.swift @@ -0,0 +1,110 @@ +/** + * File: knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 0-1 背包:暴力搜尋 */ +func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0 + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i - 1] > c { + return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + } + // 計算不放入和放入物品 i 的最大價值 + let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 返回兩種方案中價值更大的那一個 + return max(no, yes) +} + +/* 0-1 背包:記憶化搜尋 */ +func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if i == 0 || c == 0 { + return 0 + } + // 若已有記錄,則直接返回 + if mem[i][c] != -1 { + return mem[i][c] + } + // 若超過背包容量,則只能選擇不放入背包 + if wgt[i - 1] > c { + return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + } + // 計算不放入和放入物品 i 的最大價值 + let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = max(no, yes) + return mem[i][c] +} + +/* 0-1 背包:動態規劃 */ +func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 狀態轉移 + for i in 1 ... n { + for c in 1 ... cap { + 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] +} + +/* 0-1 背包:空間最佳化後的動態規劃 */ +func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: cap + 1) + // 狀態轉移 + for i in 1 ... n { + // 倒序走訪 + for c in (1 ... cap).reversed() { + if wgt[i - 1] <= c { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] +} + +@main +enum Knapsack { + /* Driver Code */ + static func main() { + let wgt = [10, 20, 30, 40, 50] + let val = [50, 120, 150, 210, 240] + let cap = 50 + let n = wgt.count + + // 暴力搜尋 + var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) + print("不超過背包容量的最大物品價值為 \(res)") + + // 記憶化搜尋 + var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) + res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) + print("不超過背包容量的最大物品價值為 \(res)") + + // 動態規劃 + res = knapsackDP(wgt: wgt, val: val, cap: cap) + print("不超過背包容量的最大物品價值為 \(res)") + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(wgt: wgt, val: val, cap: cap) + print("不超過背包容量的最大物品價值為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift b/zh-hant/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift new file mode 100644 index 000000000..a192cbb69 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 爬樓梯最小代價:動態規劃 */ +func minCostClimbingStairsDP(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = Array(repeating: 0, count: n + 1) + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1] + dp[2] = cost[2] + // 狀態轉移:從較小子問題逐步求解較大子問題 + for i in 3 ... n { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] +} + +/* 爬樓梯最小代價:空間最佳化後的動態規劃 */ +func minCostClimbingStairsDPComp(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + var (a, b) = (cost[1], cost[2]) + for i in 3 ... n { + (a, b) = (b, min(a, b) + cost[i]) + } + return b +} + +@main +enum MinCostClimbingStairsDP { + /* Driver Code */ + static func main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print("輸入樓梯的代價串列為 \(cost)") + + var res = minCostClimbingStairsDP(cost: cost) + print("爬完樓梯的最低代價為 \(res)") + + res = minCostClimbingStairsDPComp(cost: cost) + print("爬完樓梯的最低代價為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/min_path_sum.swift b/zh-hant/codes/swift/chapter_dynamic_programming/min_path_sum.swift new file mode 100644 index 000000000..f2621f089 --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/min_path_sum.swift @@ -0,0 +1,123 @@ +/** + * File: min_path_sum.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 最小路徑和:暴力搜尋 */ +func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { + // 若為左上角單元格,則終止搜尋 + if i == 0, j == 0 { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return .max + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + let up = minPathSumDFS(grid: grid, i: i - 1, j: j) + let left = minPathSumDFS(grid: grid, i: i, j: j - 1) + // 返回從左上角到 (i, j) 的最小路徑代價 + return min(left, up) + grid[i][j] +} + +/* 最小路徑和:記憶化搜尋 */ +func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { + // 若為左上角單元格,則終止搜尋 + if i == 0, j == 0 { + return grid[0][0] + } + // 若行列索引越界,則返回 +∞ 代價 + if i < 0 || j < 0 { + return .max + } + // 若已有記錄,則直接返回 + if mem[i][j] != -1 { + return mem[i][j] + } + // 左邊和上邊單元格的最小路徑代價 + let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) + let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] +} + +/* 最小路徑和:動態規劃 */ +func minPathSumDP(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: m), count: n) + dp[0][0] = grid[0][0] + // 狀態轉移:首行 + for j in 1 ..< m { + dp[0][j] = dp[0][j - 1] + grid[0][j] + } + // 狀態轉移:首列 + for i in 1 ..< n { + dp[i][0] = dp[i - 1][0] + grid[i][0] + } + // 狀態轉移:其餘行和列 + for i in 1 ..< n { + for j in 1 ..< m { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + } + } + return dp[n - 1][m - 1] +} + +/* 最小路徑和:空間最佳化後的動態規劃 */ +func minPathSumDPComp(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: m) + // 狀態轉移:首行 + dp[0] = grid[0][0] + for j in 1 ..< m { + dp[j] = dp[j - 1] + grid[0][j] + } + // 狀態轉移:其餘行 + for i in 1 ..< n { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0] + // 狀態轉移:其餘列 + for j in 1 ..< m { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + } + } + return dp[m - 1] +} + +@main +enum MinPathSum { + /* Driver Code */ + static func main() { + let grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ] + let n = grid.count + let m = grid[0].count + + // 暴力搜尋 + var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) + print("從左上角到右下角的做小路徑和為 \(res)") + + // 記憶化搜尋 + var mem = Array(repeating: Array(repeating: -1, count: m), count: n) + res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) + print("從左上角到右下角的做小路徑和為 \(res)") + + // 動態規劃 + res = minPathSumDP(grid: grid) + print("從左上角到右下角的做小路徑和為 \(res)") + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(grid: grid) + print("從左上角到右下角的做小路徑和為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift b/zh-hant/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift new file mode 100644 index 000000000..3e591500c --- /dev/null +++ b/zh-hant/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 完全背包:動態規劃 */ +func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 狀態轉移 + for i in 1 ... n { + for c in 1 ... cap { + 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] +} + +/* 完全背包:空間最佳化後的動態規劃 */ +func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // 初始化 dp 表 + var dp = Array(repeating: 0, count: cap + 1) + // 狀態轉移 + for i in 1 ... n { + for c in 1 ... cap { + 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] +} + +@main +enum UnboundedKnapsack { + /* Driver Code */ + static func main() { + let wgt = [1, 2, 3] + let val = [5, 11, 15] + let cap = 4 + + // 動態規劃 + var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) + print("不超過背包容量的最大物品價值為 \(res)") + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) + print("不超過背包容量的最大物品價值為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_graph/graph_adjacency_list.swift b/zh-hant/codes/swift/chapter_graph/graph_adjacency_list.swift new file mode 100644 index 000000000..65116f7c3 --- /dev/null +++ b/zh-hant/codes/swift/chapter_graph/graph_adjacency_list.swift @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於鄰接表實現的無向圖類別 */ +public class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + public private(set) var adjList: [Vertex: [Vertex]] + + /* 建構子 */ + public init(edges: [[Vertex]]) { + adjList = [:] + // 新增所有頂點和邊 + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* 獲取頂點數量 */ + public func size() -> Int { + adjList.count + } + + /* 新增邊 */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("參數錯誤") + } + // 新增邊 vet1 - vet2 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* 刪除邊 */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("參數錯誤") + } + // 刪除邊 vet1 - vet2 + adjList[vet1]?.removeAll { $0 == vet2 } + adjList[vet2]?.removeAll { $0 == vet1 } + } + + /* 新增頂點 */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // 在鄰接表中新增一個新鏈結串列 + adjList[vet] = [] + } + + /* 刪除頂點 */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("參數錯誤") + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.removeValue(forKey: vet) + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for key in adjList.keys { + adjList[key]?.removeAll { $0 == vet } + } + } + + /* 列印鄰接表 */ + public func print() { + Swift.print("鄰接表 =") + for (vertex, list) in adjList { + let list = list.map { $0.val } + Swift.print("\(vertex.val): \(list),") + } + } +} + +#if !TARGET + +@main +enum GraphAdjacencyList { + /* Driver Code */ + static func main() { + /* 初始化無向圖 */ + let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) + let 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]]] + let graph = GraphAdjList(edges: edges) + print("\n初始化後,圖為") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(vet1: v[0], vet2: v[2]) + print("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(vet1: v[0], vet2: v[1]) + print("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + let v5 = Vertex(val: 6) + graph.addVertex(vet: v5) + print("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(vet: v[1]) + print("\n刪除頂點 3 後,圖為") + graph.print() + } +} + +#endif diff --git a/zh-hant/codes/swift/chapter_graph/graph_adjacency_list_target.swift b/zh-hant/codes/swift/chapter_graph/graph_adjacency_list_target.swift new file mode 100644 index 000000000..65116f7c3 --- /dev/null +++ b/zh-hant/codes/swift/chapter_graph/graph_adjacency_list_target.swift @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於鄰接表實現的無向圖類別 */ +public class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + public private(set) var adjList: [Vertex: [Vertex]] + + /* 建構子 */ + public init(edges: [[Vertex]]) { + adjList = [:] + // 新增所有頂點和邊 + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* 獲取頂點數量 */ + public func size() -> Int { + adjList.count + } + + /* 新增邊 */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("參數錯誤") + } + // 新增邊 vet1 - vet2 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* 刪除邊 */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("參數錯誤") + } + // 刪除邊 vet1 - vet2 + adjList[vet1]?.removeAll { $0 == vet2 } + adjList[vet2]?.removeAll { $0 == vet1 } + } + + /* 新增頂點 */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // 在鄰接表中新增一個新鏈結串列 + adjList[vet] = [] + } + + /* 刪除頂點 */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("參數錯誤") + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + adjList.removeValue(forKey: vet) + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for key in adjList.keys { + adjList[key]?.removeAll { $0 == vet } + } + } + + /* 列印鄰接表 */ + public func print() { + Swift.print("鄰接表 =") + for (vertex, list) in adjList { + let list = list.map { $0.val } + Swift.print("\(vertex.val): \(list),") + } + } +} + +#if !TARGET + +@main +enum GraphAdjacencyList { + /* Driver Code */ + static func main() { + /* 初始化無向圖 */ + let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) + let 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]]] + let graph = GraphAdjList(edges: edges) + print("\n初始化後,圖為") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 即 v[0], v[2] + graph.addEdge(vet1: v[0], vet2: v[2]) + print("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 即 v[0], v[1] + graph.removeEdge(vet1: v[0], vet2: v[1]) + print("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + let v5 = Vertex(val: 6) + graph.addVertex(vet: v5) + print("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 即 v[1] + graph.removeVertex(vet: v[1]) + print("\n刪除頂點 3 後,圖為") + graph.print() + } +} + +#endif diff --git a/zh-hant/codes/swift/chapter_graph/graph_adjacency_matrix.swift b/zh-hant/codes/swift/chapter_graph/graph_adjacency_matrix.swift new file mode 100644 index 000000000..c997da5ad --- /dev/null +++ b/zh-hant/codes/swift/chapter_graph/graph_adjacency_matrix.swift @@ -0,0 +1,130 @@ +/** + * File: graph_adjacency_matrix.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + private var vertices: [Int] // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + private var adjMat: [[Int]] // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + init(vertices: [Int], edges: [[Int]]) { + self.vertices = [] + adjMat = [] + // 新增頂點 + for val in vertices { + addVertex(val: val) + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for e in edges { + addEdge(i: e[0], j: e[1]) + } + } + + /* 獲取頂點數量 */ + func size() -> Int { + vertices.count + } + + /* 新增頂點 */ + func addVertex(val: Int) { + let n = size() + // 向頂點串列中新增新頂點的值 + vertices.append(val) + // 在鄰接矩陣中新增一行 + let newRow = Array(repeating: 0, count: n) + adjMat.append(newRow) + // 在鄰接矩陣中新增一列 + for i in adjMat.indices { + adjMat[i].append(0) + } + } + + /* 刪除頂點 */ + func removeVertex(index: Int) { + if index >= size() { + fatalError("越界") + } + // 在頂點串列中移除索引 index 的頂點 + vertices.remove(at: index) + // 在鄰接矩陣中刪除索引 index 的行 + adjMat.remove(at: index) + // 在鄰接矩陣中刪除索引 index 的列 + for i in adjMat.indices { + adjMat[i].remove(at: index) + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + func addEdge(i: Int, j: Int) { + // 索引越界與相等處理 + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("越界") + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) == (j, i) + adjMat[i][j] = 1 + adjMat[j][i] = 1 + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + func removeEdge(i: Int, j: Int) { + // 索引越界與相等處理 + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("越界") + } + adjMat[i][j] = 0 + adjMat[j][i] = 0 + } + + /* 列印鄰接矩陣 */ + func print() { + Swift.print("頂點串列 = ", terminator: "") + Swift.print(vertices) + Swift.print("鄰接矩陣 =") + PrintUtil.printMatrix(matrix: adjMat) + } +} + +@main +enum GraphAdjacencyMatrix { + /* Driver Code */ + static func main() { + /* 初始化無向圖 */ + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + let vertices = [1, 3, 2, 5, 4] + let edges = [[0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4]] + let graph = GraphAdjMat(vertices: vertices, edges: edges) + print("\n初始化後,圖為") + graph.print() + + /* 新增邊 */ + // 頂點 1, 2 的索引分別為 0, 2 + graph.addEdge(i: 0, j: 2) + print("\n新增邊 1-2 後,圖為") + graph.print() + + /* 刪除邊 */ + // 頂點 1, 3 的索引分別為 0, 1 + graph.removeEdge(i: 0, j: 1) + print("\n刪除邊 1-3 後,圖為") + graph.print() + + /* 新增頂點 */ + graph.addVertex(val: 6) + print("\n新增頂點 6 後,圖為") + graph.print() + + /* 刪除頂點 */ + // 頂點 3 的索引為 1 + graph.removeVertex(index: 1) + print("\n刪除頂點 3 後,圖為") + graph.print() + } +} diff --git a/zh-hant/codes/swift/chapter_graph/graph_bfs.swift b/zh-hant/codes/swift/chapter_graph/graph_bfs.swift new file mode 100644 index 000000000..1358588ad --- /dev/null +++ b/zh-hant/codes/swift/chapter_graph/graph_bfs.swift @@ -0,0 +1,56 @@ +/** + * File: graph_bfs.swift + * Created Time: 2023-02-21 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import graph_adjacency_list_target +import utils + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 頂點走訪序列 + var res: [Vertex] = [] + // 雜湊表,用於記錄已被訪問過的頂點 + var visited: Set = [startVet] + // 佇列用於實現 BFS + var que: [Vertex] = [startVet] + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while !que.isEmpty { + let vet = que.removeFirst() // 佇列首頂點出隊 + res.append(vet) // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳過已被訪問的頂點 + } + que.append(adjVet) // 只入列未訪問的頂點 + visited.insert(adjVet) // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res +} + +@main +enum GraphBFS { + /* Driver Code */ + static func main() { + /* 初始化無向圖 */ + let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + let 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]], + ] + let graph = GraphAdjList(edges: edges) + print("\n初始化後,圖為") + graph.print() + + /* 廣度優先走訪 */ + let res = graphBFS(graph: graph, startVet: v[0]) + print("\n廣度優先走訪(BFS)頂點序列為") + print(Vertex.vetsToVals(vets: res)) + } +} diff --git a/zh-hant/codes/swift/chapter_graph/graph_dfs.swift b/zh-hant/codes/swift/chapter_graph/graph_dfs.swift new file mode 100644 index 000000000..c808d017a --- /dev/null +++ b/zh-hant/codes/swift/chapter_graph/graph_dfs.swift @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.swift + * Created Time: 2023-02-21 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import graph_adjacency_list_target +import utils + +/* 深度優先走訪輔助函式 */ +func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { + res.append(vet) // 記錄訪問頂點 + visited.insert(vet) // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 頂點走訪序列 + var res: [Vertex] = [] + // 雜湊表,用於記錄已被訪問過的頂點 + var visited: Set = [] + dfs(graph: graph, visited: &visited, res: &res, vet: startVet) + return res +} + +@main +enum GraphDFS { + /* Driver Code */ + static func main() { + /* 初始化無向圖 */ + let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6]) + let 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]], + ] + let graph = GraphAdjList(edges: edges) + print("\n初始化後,圖為") + graph.print() + + /* 深度優先走訪 */ + let res = graphDFS(graph: graph, startVet: v[0]) + print("\n深度優先走訪(DFS)頂點序列為") + print(Vertex.vetsToVals(vets: res)) + } +} diff --git a/zh-hant/codes/swift/chapter_greedy/coin_change_greedy.swift b/zh-hant/codes/swift/chapter_greedy/coin_change_greedy.swift new file mode 100644 index 000000000..75580c45f --- /dev/null +++ b/zh-hant/codes/swift/chapter_greedy/coin_change_greedy.swift @@ -0,0 +1,54 @@ +/** + * File: coin_change_greedy.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 零錢兌換:貪婪 */ +func coinChangeGreedy(coins: [Int], amt: Int) -> Int { + // 假設 coins 串列有序 + var i = coins.count - 1 + var count = 0 + var amt = amt + // 迴圈進行貪婪選擇,直到無剩餘金額 + while amt > 0 { + // 找到小於且最接近剩餘金額的硬幣 + while i > 0 && coins[i] > amt { + i -= 1 + } + // 選擇 coins[i] + amt -= coins[i] + count += 1 + } + // 若未找到可行方案,則返回 -1 + return amt == 0 ? count : -1 +} + +@main +enum CoinChangeGreedy { + /* Driver Code */ + static func main() { + // 貪婪:能夠保證找到全域性最優解 + var coins = [1, 5, 10, 20, 50, 100] + var amt = 186 + var res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("湊到 \(amt) 所需的最少硬幣數量為 \(res)") + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 20, 50] + amt = 60 + res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("湊到 \(amt) 所需的最少硬幣數量為 \(res)") + print("實際上需要的最少數量為 3 ,即 20 + 20 + 20") + + // 貪婪:無法保證找到全域性最優解 + coins = [1, 49, 50] + amt = 98 + res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("湊到 \(amt) 所需的最少硬幣數量為 \(res)") + print("實際上需要的最少數量為 2 ,即 49 + 49") + } +} diff --git a/zh-hant/codes/swift/chapter_greedy/fractional_knapsack.swift b/zh-hant/codes/swift/chapter_greedy/fractional_knapsack.swift new file mode 100644 index 000000000..9dd57b707 --- /dev/null +++ b/zh-hant/codes/swift/chapter_greedy/fractional_knapsack.swift @@ -0,0 +1,57 @@ +/** + * File: fractional_knapsack.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 物品 */ +class Item { + var w: Int // 物品重量 + var v: Int // 物品價值 + + init(w: Int, v: Int) { + self.w = w + self.v = v + } +} + +/* 分數背包:貪婪 */ +func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { + // 建立物品串列,包含兩個屬性:重量、價值 + var items = zip(wgt, val).map { Item(w: $0, v: $1) } + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) } + // 迴圈貪婪選擇 + var res = 0.0 + var cap = cap + for item in items { + if item.w <= cap { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += Double(item.v) + cap -= item.w + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += Double(item.v) / Double(item.w) * Double(cap) + // 已無剩餘容量,因此跳出迴圈 + break + } + } + return res +} + +@main +enum FractionalKnapsack { + /* Driver Code */ + static func main() { + // 物品重量 + let wgt = [10, 20, 30, 40, 50] + // 物品價值 + let val = [50, 120, 150, 210, 240] + // 背包容量 + let cap = 50 + + // 貪婪演算法 + let res = fractionalKnapsack(wgt: wgt, val: val, cap: cap) + print("不超過背包容量的最大物品價值為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_greedy/max_capacity.swift b/zh-hant/codes/swift/chapter_greedy/max_capacity.swift new file mode 100644 index 000000000..77169d870 --- /dev/null +++ b/zh-hant/codes/swift/chapter_greedy/max_capacity.swift @@ -0,0 +1,38 @@ +/** + * File: max_capacity.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 最大容量:貪婪 */ +func maxCapacity(ht: [Int]) -> Int { + // 初始化 i, j,使其分列陣列兩端 + var i = ht.startIndex, j = ht.endIndex - 1 + // 初始最大容量為 0 + var res = 0 + // 迴圈貪婪選擇,直至兩板相遇 + while i < j { + // 更新最大容量 + let cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + // 向內移動短板 + if ht[i] < ht[j] { + i += 1 + } else { + j -= 1 + } + } + return res +} + +@main +enum MaxCapacity { + /* Driver Code */ + static func main() { + let ht = [3, 8, 5, 2, 7, 7, 3, 4] + + // 貪婪演算法 + let res = maxCapacity(ht: ht) + print("最大容量為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_greedy/max_product_cutting.swift b/zh-hant/codes/swift/chapter_greedy/max_product_cutting.swift new file mode 100644 index 000000000..69f6d243b --- /dev/null +++ b/zh-hant/codes/swift/chapter_greedy/max_product_cutting.swift @@ -0,0 +1,43 @@ +/** + * File: max_product_cutting.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import Foundation + +func pow(_ x: Int, _ y: Int) -> Int { + Int(Double(truncating: pow(Decimal(x), y) as NSDecimalNumber)) +} + +/* 最大切分乘積:貪婪 */ +func maxProductCutting(n: Int) -> Int { + // 當 n <= 3 時,必須切分出一個 1 + if n <= 3 { + return 1 * (n - 1) + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + let a = n / 3 + let b = n % 3 + if b == 1 { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return pow(3, a - 1) * 2 * 2 + } + if b == 2 { + // 當餘數為 2 時,不做處理 + return pow(3, a) * 2 + } + // 當餘數為 0 時,不做處理 + return pow(3, a) +} + +@main +enum MaxProductCutting { + static func main() { + let n = 58 + + // 貪婪演算法 + let res = maxProductCutting(n: n) + print("最大切分乘積為 \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/array_hash_map.swift b/zh-hant/codes/swift/chapter_hashing/array_hash_map.swift new file mode 100644 index 000000000..61f373ca2 --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/array_hash_map.swift @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.swift + * Created Time: 2023-01-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + private var buckets: [Pair?] + + init() { + // 初始化陣列,包含 100 個桶 + buckets = Array(repeating: nil, count: 100) + } + + /* 雜湊函式 */ + private func hashFunc(key: Int) -> Int { + let index = key % 100 + return index + } + + /* 查詢操作 */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let pair = buckets[index] + return pair?.val + } + + /* 新增操作 */ + func put(key: Int, val: String) { + let pair = Pair(key: key, val: val) + let index = hashFunc(key: key) + buckets[index] = pair + } + + /* 刪除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + // 置為 nil ,代表刪除 + buckets[index] = nil + } + + /* 獲取所有鍵值對 */ + func pairSet() -> [Pair] { + buckets.compactMap { $0 } + } + + /* 獲取所有鍵 */ + func keySet() -> [Int] { + buckets.compactMap { $0?.key } + } + + /* 獲取所有值 */ + func valueSet() -> [String] { + buckets.compactMap { $0?.val } + } + + /* 列印雜湊表 */ + func print() { + for pair in pairSet() { + Swift.print("\(pair.key) -> \(pair.val)") + } + } +} + +@main +enum _ArrayHashMap { + /* Driver Code */ + static func main() { + /* 初始化雜湊表 */ + let map = ArrayHashMap() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(key: 12836, val: "小哈") + map.put(key: 15937, val: "小囉") + map.put(key: 16750, val: "小算") + map.put(key: 13276, val: "小法") + map.put(key: 10583, val: "小鴨") + print("\n新增完成後,雜湊表為\nKey -> Value") + map.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(key: 15937)! + print("\n輸入學號 15937 ,查詢到姓名 \(name)") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(key: 10583) + print("\n刪除 10583 後,雜湊表為\nKey -> Value") + map.print() + + /* 走訪雜湊表 */ + print("\n走訪鍵值對 Key->Value") + for pair in map.pairSet() { + print("\(pair.key) -> \(pair.val)") + } + print("\n單獨走訪鍵 Key") + for key in map.keySet() { + print(key) + } + print("\n單獨走訪值 Value") + for val in map.valueSet() { + print(val) + } + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/built_in_hash.swift b/zh-hant/codes/swift/chapter_hashing/built_in_hash.swift new file mode 100644 index 000000000..5e5871fcc --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/built_in_hash.swift @@ -0,0 +1,37 @@ +/** + * File: built_in_hash.swift + * Created Time: 2023-07-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum BuiltInHash { + /* Driver Code */ + static func main() { + let num = 3 + let hashNum = num.hashValue + print("整數 \(num) 的雜湊值為 \(hashNum)") + + let bol = true + let hashBol = bol.hashValue + print("布林量 \(bol) 的雜湊值為 \(hashBol)") + + let dec = 3.14159 + let hashDec = dec.hashValue + print("小數 \(dec) 的雜湊值為 \(hashDec)") + + let str = "Hello 演算法" + let hashStr = str.hashValue + print("字串 \(str) 的雜湊值為 \(hashStr)") + + let arr = [AnyHashable(12836), AnyHashable("小哈")] + let hashTup = arr.hashValue + print("陣列 \(arr) 的雜湊值為 \(hashTup)") + + let obj = ListNode(x: 0) + let hashObj = obj.hashValue + print("節點物件 \(obj) 的雜湊值為 \(hashObj)") + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/hash_map.swift b/zh-hant/codes/swift/chapter_hashing/hash_map.swift new file mode 100644 index 000000000..822cde8f3 --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/hash_map.swift @@ -0,0 +1,51 @@ +/** + * File: hash_map.swift + * Created Time: 2023-01-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum HashMap { + /* Driver Code */ + static func main() { + /* 初始化雜湊表 */ + var map: [Int: String] = [:] + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈" + map[15937] = "小囉" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鴨" + print("\n新增完成後,雜湊表為\nKey -> Value") + PrintUtil.printHashMap(map: map) + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map[15937]! + print("\n輸入學號 15937 ,查詢到姓名 \(name)") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.removeValue(forKey: 10583) + print("\n刪除 10583 後,雜湊表為\nKey -> Value") + PrintUtil.printHashMap(map: map) + + /* 走訪雜湊表 */ + print("\n走訪鍵值對 Key->Value") + for (key, value) in map { + print("\(key) -> \(value)") + } + print("\n單獨走訪鍵 Key") + for key in map.keys { + print(key) + } + print("\n單獨走訪值 Value") + for value in map.values { + print(value) + } + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/hash_map_chaining.swift b/zh-hant/codes/swift/chapter_hashing/hash_map_chaining.swift new file mode 100644 index 000000000..0e869936b --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/hash_map_chaining.swift @@ -0,0 +1,138 @@ +/** + * File: hash_map_chaining.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + var size: Int // 鍵值對數量 + var capacity: Int // 雜湊表容量 + var loadThres: Double // 觸發擴容的負載因子閾值 + var extendRatio: Int // 擴容倍數 + var buckets: [[Pair]] // 桶陣列 + + /* 建構子 */ + init() { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = Array(repeating: [], count: capacity) + } + + /* 雜湊函式 */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* 負載因子 */ + func loadFactor() -> Double { + Double(size) / Double(capacity) + } + + /* 查詢操作 */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let bucket = buckets[index] + // 走訪桶,若找到 key ,則返回對應 val + for pair in bucket { + if pair.key == key { + return pair.val + } + } + // 若未找到 key ,則返回 nil + return nil + } + + /* 新增操作 */ + func put(key: Int, val: String) { + // 當負載因子超過閾值時,執行擴容 + if loadFactor() > loadThres { + extend() + } + let index = hashFunc(key: key) + let bucket = buckets[index] + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for pair in bucket { + if pair.key == key { + pair.val = val + return + } + } + // 若無該 key ,則將鍵值對新增至尾部 + let pair = Pair(key: key, val: val) + buckets[index].append(pair) + size += 1 + } + + /* 刪除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + let bucket = buckets[index] + // 走訪桶,從中刪除鍵值對 + for (pairIndex, pair) in bucket.enumerated() { + if pair.key == key { + buckets[index].remove(at: pairIndex) + size -= 1 + break + } + } + } + + /* 擴容雜湊表 */ + func extend() { + // 暫存原雜湊表 + let bucketsTmp = buckets + // 初始化擴容後的新雜湊表 + capacity *= extendRatio + buckets = Array(repeating: [], count: capacity) + size = 0 + // 將鍵值對從原雜湊表搬運至新雜湊表 + for bucket in bucketsTmp { + for pair in bucket { + put(key: pair.key, val: pair.val) + } + } + } + + /* 列印雜湊表 */ + func print() { + for bucket in buckets { + let res = bucket.map { "\($0.key) -> \($0.val)" } + Swift.print(res) + } + } +} + +@main +enum _HashMapChaining { + /* Driver Code */ + static func main() { + /* 初始化雜湊表 */ + let map = HashMapChaining() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(key: 12836, val: "小哈") + map.put(key: 15937, val: "小囉") + map.put(key: 16750, val: "小算") + map.put(key: 13276, val: "小法") + map.put(key: 10583, val: "小鴨") + print("\n新增完成後,雜湊表為\nKey -> Value") + map.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(key: 13276) + print("\n輸入學號 13276 ,查詢到姓名 \(name!)") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(key: 12836) + print("\n刪除 12836 後,雜湊表為\nKey -> Value") + map.print() + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/hash_map_open_addressing.swift b/zh-hant/codes/swift/chapter_hashing/hash_map_open_addressing.swift new file mode 100644 index 000000000..390ab6823 --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/hash_map_open_addressing.swift @@ -0,0 +1,164 @@ +/** + * File: hash_map_open_addressing.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + var size: Int // 鍵值對數量 + var capacity: Int // 雜湊表容量 + var loadThres: Double // 觸發擴容的負載因子閾值 + var extendRatio: Int // 擴容倍數 + var buckets: [Pair?] // 桶陣列 + var TOMBSTONE: Pair // 刪除標記 + + /* 建構子 */ + init() { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = Array(repeating: nil, count: capacity) + TOMBSTONE = Pair(key: -1, val: "-1") + } + + /* 雜湊函式 */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* 負載因子 */ + func loadFactor() -> Double { + Double(size) / Double(capacity) + } + + /* 搜尋 key 對應的桶索引 */ + func findBucket(key: Int) -> Int { + var index = hashFunc(key: key) + var firstTombstone = -1 + // 線性探查,當遇到空桶時跳出 + while buckets[index] != nil { + // 若遇到 key ,返回對應的桶索引 + if buckets[index]!.key == key { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if firstTombstone != -1 { + buckets[firstTombstone] = buckets[index] + buckets[index] = TOMBSTONE + return firstTombstone // 返回移動後的桶索引 + } + return index // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if firstTombstone == -1 && buckets[index] == TOMBSTONE { + firstTombstone = index + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % capacity + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone == -1 ? index : firstTombstone + } + + /* 查詢操作 */ + func get(key: Int) -> String? { + // 搜尋 key 對應的桶索引 + let index = findBucket(key: key) + // 若找到鍵值對,則返回對應 val + if buckets[index] != nil, buckets[index] != TOMBSTONE { + return buckets[index]!.val + } + // 若鍵值對不存在,則返回 null + return nil + } + + /* 新增操作 */ + func put(key: Int, val: String) { + // 當負載因子超過閾值時,執行擴容 + if loadFactor() > loadThres { + extend() + } + // 搜尋 key 對應的桶索引 + let index = findBucket(key: key) + // 若找到鍵值對,則覆蓋 val 並返回 + if buckets[index] != nil, buckets[index] != TOMBSTONE { + buckets[index]!.val = val + return + } + // 若鍵值對不存在,則新增該鍵值對 + buckets[index] = Pair(key: key, val: val) + size += 1 + } + + /* 刪除操作 */ + func remove(key: Int) { + // 搜尋 key 對應的桶索引 + let index = findBucket(key: key) + // 若找到鍵值對,則用刪除標記覆蓋它 + if buckets[index] != nil, buckets[index] != TOMBSTONE { + buckets[index] = TOMBSTONE + size -= 1 + } + } + + /* 擴容雜湊表 */ + func extend() { + // 暫存原雜湊表 + let bucketsTmp = buckets + // 初始化擴容後的新雜湊表 + capacity *= extendRatio + buckets = Array(repeating: nil, count: capacity) + size = 0 + // 將鍵值對從原雜湊表搬運至新雜湊表 + for pair in bucketsTmp { + if let pair, pair != TOMBSTONE { + put(key: pair.key, val: pair.val) + } + } + } + + /* 列印雜湊表 */ + func print() { + for pair in buckets { + if pair == nil { + Swift.print("null") + } else if pair == TOMBSTONE { + Swift.print("TOMBSTONE") + } else { + Swift.print("\(pair!.key) -> \(pair!.val)") + } + } + } +} + +@main +enum _HashMapOpenAddressing { + /* Driver Code */ + static func main() { + /* 初始化雜湊表 */ + let map = HashMapOpenAddressing() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.put(key: 12836, val: "小哈") + map.put(key: 15937, val: "小囉") + map.put(key: 16750, val: "小算") + map.put(key: 13276, val: "小法") + map.put(key: 10583, val: "小鴨") + print("\n新增完成後,雜湊表為\nKey -> Value") + map.print() + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(key: 13276) + print("\n輸入學號 13276 ,查詢到姓名 \(name!)") + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(key: 16750) + print("\n刪除 16750 後,雜湊表為\nKey -> Value") + map.print() + } +} diff --git a/zh-hant/codes/swift/chapter_hashing/simple_hash.swift b/zh-hant/codes/swift/chapter_hashing/simple_hash.swift new file mode 100644 index 000000000..0b2188266 --- /dev/null +++ b/zh-hant/codes/swift/chapter_hashing/simple_hash.swift @@ -0,0 +1,73 @@ +/** + * File: simple_hash.swift + * Created Time: 2023-07-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 加法雜湊 */ +func addHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (hash + Int(scalar.value)) % MODULUS + } + } + return hash +} + +/* 乘法雜湊 */ +func mulHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (31 * hash + Int(scalar.value)) % MODULUS + } + } + return hash +} + +/* 互斥或雜湊 */ +func xorHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash ^= Int(scalar.value) + } + } + return hash & MODULUS +} + +/* 旋轉雜湊 */ +func rotHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS + } + } + return hash +} + +@main +enum SimpleHash { + /* Driver Code */ + static func main() { + let key = "Hello 演算法" + + var hash = addHash(key: key) + print("加法雜湊值為 \(hash)") + + hash = mulHash(key: key) + print("乘法雜湊值為 \(hash)") + + hash = xorHash(key: key) + print("互斥或雜湊值為 \(hash)") + + hash = rotHash(key: key) + print("旋轉雜湊值為 \(hash)") + } +} diff --git a/zh-hant/codes/swift/chapter_heap/heap.swift b/zh-hant/codes/swift/chapter_heap/heap.swift new file mode 100644 index 000000000..c24e20253 --- /dev/null +++ b/zh-hant/codes/swift/chapter_heap/heap.swift @@ -0,0 +1,62 @@ +/** + * File: heap.swift + * Created Time: 2024-03-17 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import HeapModule +import utils + +func testPush(heap: inout Heap, val: Int) { + heap.insert(val) + print("\n元素 \(val) 入堆積後\n") + PrintUtil.printHeap(queue: heap.unordered) +} + +func testPop(heap: inout Heap) { + let val = heap.removeMax() + print("\n堆積頂元素 \(val) 出堆積後\n") + PrintUtil.printHeap(queue: heap.unordered) +} + +@main +enum _Heap { + /* Driver Code */ + static func main() { + /* 初始化堆積 */ + // Swift 的 Heap 型別同時支持最大堆積和最小堆積 + var heap = Heap() + + /* 元素入堆積 */ + testPush(heap: &heap, val: 1) + testPush(heap: &heap, val: 3) + testPush(heap: &heap, val: 2) + testPush(heap: &heap, val: 5) + testPush(heap: &heap, val: 4) + + /* 獲取堆積頂元素 */ + let peek = heap.max() + print("\n堆積頂元素為 \(peek!)\n") + + /* 堆積頂元素出堆積 */ + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + + /* 獲取堆積大小 */ + let size = heap.count + print("\n堆積元素數量為 \(size)\n") + + /* 判斷堆積是否為空 */ + let isEmpty = heap.isEmpty + print("\n堆積是否為空 \(isEmpty)\n") + + /* 輸入串列並建堆積 */ + // 時間複雜度為 O(n) ,而非 O(nlogn) + let heap2 = Heap([1, 3, 2, 5, 4]) + print("\n輸入串列並建立堆積後") + PrintUtil.printHeap(queue: heap2.unordered) + } +} diff --git a/zh-hant/codes/swift/chapter_heap/my_heap.swift b/zh-hant/codes/swift/chapter_heap/my_heap.swift new file mode 100644 index 000000000..43f63a618 --- /dev/null +++ b/zh-hant/codes/swift/chapter_heap/my_heap.swift @@ -0,0 +1,163 @@ +/** + * File: my_heap.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 大頂堆積 */ +class MaxHeap { + private var maxHeap: [Int] + + /* 建構子,根據輸入串列建堆積 */ + init(nums: [Int]) { + // 將串列元素原封不動新增進堆積 + maxHeap = nums + // 堆積化除葉節點以外的其他所有節點 + for i in (0 ... parent(i: size() - 1)).reversed() { + siftDown(i: i) + } + } + + /* 獲取左子節點的索引 */ + private func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 獲取右子節點的索引 */ + private func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 獲取父節點的索引 */ + private func parent(i: Int) -> Int { + (i - 1) / 2 // 向下整除 + } + + /* 交換元素 */ + private func swap(i: Int, j: Int) { + maxHeap.swapAt(i, j) + } + + /* 獲取堆積大小 */ + func size() -> Int { + maxHeap.count + } + + /* 判斷堆積是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 訪問堆積頂元素 */ + func peek() -> Int { + maxHeap[0] + } + + /* 元素入堆積 */ + func push(val: Int) { + // 新增節點 + maxHeap.append(val) + // 從底至頂堆積化 + siftUp(i: size() - 1) + } + + /* 從節點 i 開始,從底至頂堆積化 */ + private func siftUp(i: Int) { + var i = i + while true { + // 獲取節點 i 的父節點 + let p = parent(i: i) + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if p < 0 || maxHeap[i] <= maxHeap[p] { + break + } + // 交換兩節點 + swap(i: i, j: p) + // 迴圈向上堆積化 + i = p + } + } + + /* 元素出堆積 */ + func pop() -> Int { + // 判空處理 + if isEmpty() { + fatalError("堆積為空") + } + // 交換根節點與最右葉節點(交換首元素與尾元素) + swap(i: 0, j: size() - 1) + // 刪除節點 + let val = maxHeap.remove(at: size() - 1) + // 從頂至底堆積化 + siftDown(i: 0) + // 返回堆積頂元素 + return val + } + + /* 從節點 i 開始,從頂至底堆積化 */ + private func siftDown(i: Int) { + var i = i + while true { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let l = left(i: i) + let r = right(i: i) + var ma = i + if l < size(), maxHeap[l] > maxHeap[ma] { + ma = l + } + if r < size(), maxHeap[r] > maxHeap[ma] { + ma = r + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if ma == i { + break + } + // 交換兩節點 + swap(i: i, j: ma) + // 迴圈向下堆積化 + i = ma + } + } + + /* 列印堆積(二元樹) */ + func print() { + let queue = maxHeap + PrintUtil.printHeap(queue: queue) + } +} + +@main +enum MyHeap { + /* Driver Code */ + static func main() { + /* 初始化大頂堆積 */ + let maxHeap = MaxHeap(nums: [9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\n輸入串列並建堆積後") + maxHeap.print() + + /* 獲取堆積頂元素 */ + var peek = maxHeap.peek() + print("\n堆積頂元素為 \(peek)") + + /* 元素入堆積 */ + let val = 7 + maxHeap.push(val: val) + print("\n元素 \(val) 入堆積後") + maxHeap.print() + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop() + print("\n堆積頂元素 \(peek) 出堆積後") + maxHeap.print() + + /* 獲取堆積大小 */ + let size = maxHeap.size() + print("\n堆積元素數量為 \(size)") + + /* 判斷堆積是否為空 */ + let isEmpty = maxHeap.isEmpty() + print("\n堆積是否為空 \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_heap/top_k.swift b/zh-hant/codes/swift/chapter_heap/top_k.swift new file mode 100644 index 000000000..36075ec3c --- /dev/null +++ b/zh-hant/codes/swift/chapter_heap/top_k.swift @@ -0,0 +1,36 @@ +/** + * File: top_k.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import HeapModule +import utils + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +func topKHeap(nums: [Int], k: Int) -> [Int] { + // 初始化一個小頂堆積,並將前 k 個元素建堆積 + var heap = Heap(nums.prefix(k)) + // 從第 k+1 個元素開始,保持堆積的長度為 k + for i in nums.indices.dropFirst(k) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if nums[i] > heap.min()! { + _ = heap.removeMin() + heap.insert(nums[i]) + } + } + return heap.unordered +} + +@main +enum TopK { + /* Driver Code */ + static func main() { + let nums = [1, 7, 6, 3, 2] + let k = 3 + + let res = topKHeap(nums: nums, k: k) + print("最大的 \(k) 個元素為") + PrintUtil.printHeap(queue: res) + } +} diff --git a/zh-hant/codes/swift/chapter_searching/binary_search.swift b/zh-hant/codes/swift/chapter_searching/binary_search.swift new file mode 100644 index 000000000..c86860bea --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/binary_search.swift @@ -0,0 +1,62 @@ +/** + * File: binary_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分搜尋(雙閉區間) */ +func binarySearch(nums: [Int], target: Int) -> Int { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + var i = nums.startIndex + var j = nums.endIndex - 1 + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while i <= j { + let m = i + (j - i) / 2 // 計算中點索引 m + if nums[m] < target { // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1 + } else if nums[m] > target { // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1 + } else { // 找到目標元素,返回其索引 + return m + } + } + // 未找到目標元素,返回 -1 + return -1 +} + +/* 二分搜尋(左閉右開區間) */ +func binarySearchLCRO(nums: [Int], target: Int) -> Int { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + var i = nums.startIndex + var j = nums.endIndex + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while i < j { + let m = i + (j - i) / 2 // 計算中點索引 m + if nums[m] < target { // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1 + } else if nums[m] > target { // 此情況說明 target 在區間 [i, m) 中 + j = m + } else { // 找到目標元素,返回其索引 + return m + } + } + // 未找到目標元素,返回 -1 + return -1 +} + +@main +enum BinarySearch { + /* Driver Code */ + static func main() { + let target = 6 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + /* 二分搜尋(雙閉區間) */ + var index = binarySearch(nums: nums, target: target) + print("目標元素 6 的索引 = \(index)") + + /* 二分搜尋(左閉右開區間) */ + index = binarySearchLCRO(nums: nums, target: target) + print("目標元素 6 的索引 = \(index)") + } +} diff --git a/zh-hant/codes/swift/chapter_searching/binary_search_edge.swift b/zh-hant/codes/swift/chapter_searching/binary_search_edge.swift new file mode 100644 index 000000000..11f45fbf3 --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/binary_search_edge.swift @@ -0,0 +1,51 @@ +/** + * File: binary_search_edge.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import binary_search_insertion_target + +/* 二分搜尋最左一個 target */ +func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { + // 等價於查詢 target 的插入點 + let i = binarySearchInsertion(nums: nums, target: target) + // 未找到 target ,返回 -1 + if i == nums.endIndex || nums[i] != target { + return -1 + } + // 找到 target ,返回索引 i + return i +} + +/* 二分搜尋最右一個 target */ +func binarySearchRightEdge(nums: [Int], target: Int) -> Int { + // 轉化為查詢最左一個 target + 1 + let i = binarySearchInsertion(nums: nums, target: target + 1) + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + let j = i - 1 + // 未找到 target ,返回 -1 + if j == -1 || nums[j] != target { + return -1 + } + // 找到 target ,返回索引 j + return j +} + +@main +enum BinarySearchEdge { + /* Driver Code */ + static func main() { + // 包含重複元素的陣列 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n陣列 nums = \(nums)") + + // 二分搜尋左邊界和右邊界 + for target in [6, 7] { + var index = binarySearchLeftEdge(nums: nums, target: target) + print("最左一個元素 \(target) 的索引為 \(index)") + index = binarySearchRightEdge(nums: nums, target: target) + print("最右一個元素 \(target) 的索引為 \(index)") + } + } +} diff --git a/zh-hant/codes/swift/chapter_searching/binary_search_insertion.swift b/zh-hant/codes/swift/chapter_searching/binary_search_insertion.swift new file mode 100644 index 000000000..49f2ce267 --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/binary_search_insertion.swift @@ -0,0 +1,71 @@ +/** + * File: binary_search_insertion.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分搜尋插入點(無重複元素) */ +func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + // 初始化雙閉區間 [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // 計算中點索引 m + if nums[m] < target { + i = m + 1 // target 在區間 [m+1, j] 中 + } else if nums[m] > target { + j = m - 1 // target 在區間 [i, m-1] 中 + } else { + return m // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i +} + +/* 二分搜尋插入點(存在重複元素) */ +public func binarySearchInsertion(nums: [Int], target: Int) -> Int { + // 初始化雙閉區間 [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // 計算中點索引 m + if nums[m] < target { + i = m + 1 // target 在區間 [m+1, j] 中 + } else if nums[m] > target { + j = m - 1 // target 在區間 [i, m-1] 中 + } else { + j = m - 1 // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i +} + +#if !TARGET + +@main +enum BinarySearchInsertion { + /* Driver Code */ + static func main() { + // 無重複元素的陣列 + var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print("\n陣列 nums = \(nums)") + // 二分搜尋插入點 + for target in [6, 9] { + let index = binarySearchInsertionSimple(nums: nums, target: target) + print("元素 \(target) 的插入點的索引為 \(index)") + } + + // 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n陣列 nums = \(nums)") + // 二分搜尋插入點 + for target in [2, 6, 20] { + let index = binarySearchInsertion(nums: nums, target: target) + print("元素 \(target) 的插入點的索引為 \(index)") + } + } +} + +#endif diff --git a/zh-hant/codes/swift/chapter_searching/binary_search_insertion_target.swift b/zh-hant/codes/swift/chapter_searching/binary_search_insertion_target.swift new file mode 100644 index 000000000..49f2ce267 --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/binary_search_insertion_target.swift @@ -0,0 +1,71 @@ +/** + * File: binary_search_insertion.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分搜尋插入點(無重複元素) */ +func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + // 初始化雙閉區間 [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // 計算中點索引 m + if nums[m] < target { + i = m + 1 // target 在區間 [m+1, j] 中 + } else if nums[m] > target { + j = m - 1 // target 在區間 [i, m-1] 中 + } else { + return m // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i +} + +/* 二分搜尋插入點(存在重複元素) */ +public func binarySearchInsertion(nums: [Int], target: Int) -> Int { + // 初始化雙閉區間 [0, n-1] + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // 計算中點索引 m + if nums[m] < target { + i = m + 1 // target 在區間 [m+1, j] 中 + } else if nums[m] > target { + j = m - 1 // target 在區間 [i, m-1] 中 + } else { + j = m - 1 // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i +} + +#if !TARGET + +@main +enum BinarySearchInsertion { + /* Driver Code */ + static func main() { + // 無重複元素的陣列 + var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print("\n陣列 nums = \(nums)") + // 二分搜尋插入點 + for target in [6, 9] { + let index = binarySearchInsertionSimple(nums: nums, target: target) + print("元素 \(target) 的插入點的索引為 \(index)") + } + + // 包含重複元素的陣列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n陣列 nums = \(nums)") + // 二分搜尋插入點 + for target in [2, 6, 20] { + let index = binarySearchInsertion(nums: nums, target: target) + print("元素 \(target) 的插入點的索引為 \(index)") + } + } +} + +#endif diff --git a/zh-hant/codes/swift/chapter_searching/hashing_search.swift b/zh-hant/codes/swift/chapter_searching/hashing_search.swift new file mode 100644 index 000000000..4e14acc41 --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/hashing_search.swift @@ -0,0 +1,50 @@ +/** + * File: hashing_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 雜湊查詢(陣列) */ +func hashingSearchArray(map: [Int: Int], target: Int) -> Int { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map[target, default: -1] +} + +/* 雜湊查詢(鏈結串列) */ +func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map[target] +} + +@main +enum HashingSearch { + /* Driver Code */ + static func main() { + let target = 3 + + /* 雜湊查詢(陣列) */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + // 初始化雜湊表 + var map: [Int: Int] = [:] + for i in nums.indices { + map[nums[i]] = i // key: 元素,value: 索引 + } + let index = hashingSearchArray(map: map, target: target) + print("目標元素 3 的索引 = \(index)") + + /* 雜湊查詢(鏈結串列) */ + var head = ListNode.arrToLinkedList(arr: nums) + // 初始化雜湊表 + var map1: [Int: ListNode] = [:] + while head != nil { + map1[head!.val] = head! // key: 節點值,value: 節點 + head = head?.next + } + let node = hashingSearchLinkedList(map: map1, target: target) + print("目標節點值 3 的對應節點物件為 \(node!)") + } +} diff --git a/zh-hant/codes/swift/chapter_searching/linear_search.swift b/zh-hant/codes/swift/chapter_searching/linear_search.swift new file mode 100644 index 000000000..b018e855d --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/linear_search.swift @@ -0,0 +1,53 @@ +/** + * File: linear_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 線性查詢(陣列) */ +func linearSearchArray(nums: [Int], target: Int) -> Int { + // 走訪陣列 + for i in nums.indices { + // 找到目標元素,返回其索引 + if nums[i] == target { + return i + } + } + // 未找到目標元素,返回 -1 + return -1 +} + +/* 線性查詢(鏈結串列) */ +func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { + var head = head + // 走訪鏈結串列 + while head != nil { + // 找到目標節點,返回之 + if head?.val == target { + return head + } + head = head?.next + } + // 未找到目標節點,返回 null + return nil +} + +@main +enum LinearSearch { + /* Driver Code */ + static func main() { + let target = 3 + + /* 在陣列中執行線性查詢 */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + let index = linearSearchArray(nums: nums, target: target) + print("目標元素 3 的索引 = \(index)") + + /* 在鏈結串列中執行線性查詢 */ + let head = ListNode.arrToLinkedList(arr: nums) + let node = linearSearchLinkedList(head: head, target: target) + print("目標節點值 3 的對應節點物件為 \(node!)") + } +} diff --git a/zh-hant/codes/swift/chapter_searching/two_sum.swift b/zh-hant/codes/swift/chapter_searching/two_sum.swift new file mode 100644 index 000000000..aca95b5dc --- /dev/null +++ b/zh-hant/codes/swift/chapter_searching/two_sum.swift @@ -0,0 +1,49 @@ +/** + * File: two_sum.swift + * Created Time: 2023-01-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 方法一:暴力列舉 */ +func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { + // 兩層迴圈,時間複雜度為 O(n^2) + for i in nums.indices.dropLast() { + for j in nums.indices.dropFirst(i + 1) { + if nums[i] + nums[j] == target { + return [i, j] + } + } + } + return [0] +} + +/* 方法二:輔助雜湊表 */ +func twoSumHashTable(nums: [Int], target: Int) -> [Int] { + // 輔助雜湊表,空間複雜度為 O(n) + var dic: [Int: Int] = [:] + // 單層迴圈,時間複雜度為 O(n) + for i in nums.indices { + if let j = dic[target - nums[i]] { + return [j, i] + } + dic[nums[i]] = i + } + return [0] +} + +@main +enum LeetcodeTwoSum { + /* Driver Code */ + static func main() { + // ======= Test Case ======= + let nums = [2, 7, 11, 15] + let target = 13 + // ====== Driver Code ====== + // 方法一 + var res = twoSumBruteForce(nums: nums, target: target) + print("方法一 res = \(res)") + // 方法二 + res = twoSumHashTable(nums: nums, target: target) + print("方法二 res = \(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/bubble_sort.swift b/zh-hant/codes/swift/chapter_sorting/bubble_sort.swift new file mode 100644 index 000000000..cea8db285 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/bubble_sort.swift @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 泡沫排序 */ +func bubbleSort(nums: inout [Int]) { + // 外迴圈:未排序區間為 [0, i] + for i in nums.indices.dropFirst().reversed() { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + nums.swapAt(j, j + 1) + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +func bubbleSortWithFlag(nums: inout [Int]) { + // 外迴圈:未排序區間為 [0, i] + for i in nums.indices.dropFirst().reversed() { + var flag = false // 初始化標誌位 + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // 交換 nums[j] 與 nums[j + 1] + nums.swapAt(j, j + 1) + flag = true // 記錄交換元素 + } + } + if !flag { // 此輪“冒泡”未交換任何元素,直接跳出 + break + } + } +} + +@main +enum BubbleSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + bubbleSort(nums: &nums) + print("泡沫排序完成後 nums = \(nums)") + + var nums1 = [4, 1, 3, 1, 5, 2] + bubbleSortWithFlag(nums: &nums1) + print("泡沫排序完成後 nums1 = \(nums1)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/bucket_sort.swift b/zh-hant/codes/swift/chapter_sorting/bucket_sort.swift new file mode 100644 index 000000000..93fbffa64 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/bucket_sort.swift @@ -0,0 +1,43 @@ +/** + * File: bucket_sort.swift + * Created Time: 2023-03-27 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 桶排序 */ +func bucketSort(nums: inout [Double]) { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + let k = nums.count / 2 + var buckets = (0 ..< k).map { _ in [Double]() } + // 1. 將陣列元素分配到各個桶中 + for num in nums { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + let i = Int(num * Double(k)) + // 將 num 新增進桶 i + buckets[i].append(num) + } + // 2. 對各個桶執行排序 + for i in buckets.indices { + // 使用內建排序函式,也可以替換成其他排序演算法 + buckets[i].sort() + } + // 3. 走訪桶合併結果 + var i = nums.startIndex + for bucket in buckets { + for num in bucket { + nums[i] = num + i += 1 + } + } +} + +@main +enum BucketSort { + /* Driver Code */ + static func main() { + // 設輸入資料為浮點數,範圍為 [0, 1) + var nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucketSort(nums: &nums) + print("桶排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/counting_sort.swift b/zh-hant/codes/swift/chapter_sorting/counting_sort.swift new file mode 100644 index 000000000..ff28a22ae --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/counting_sort.swift @@ -0,0 +1,70 @@ +/** + * File: counting_sort.swift + * Created Time: 2023-03-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +func countingSortNaive(nums: inout [Int]) { + // 1. 統計陣列最大元素 m + let m = nums.max()! + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + var i = 0 + for num in 0 ..< m + 1 { + for _ in 0 ..< counter[num] { + nums[i] = num + i += 1 + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +func countingSort(nums: inout [Int]) { + // 1. 統計陣列最大元素 m + let m = nums.max()! + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for i in 0 ..< m { + counter[i + 1] += counter[i] + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + var res = Array(repeating: 0, count: nums.count) + for i in nums.indices.reversed() { + let num = nums[i] + res[counter[num] - 1] = num // 將 num 放置到對應索引處 + counter[num] -= 1 // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for i in nums.indices { + nums[i] = res[i] + } +} + +@main +enum CountingSort { + /* Driver Code */ + static func main() { + var nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + countingSortNaive(nums: &nums) + print("計數排序(無法排序物件)完成後 nums = \(nums)") + + var nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + countingSort(nums: &nums1) + print("計數排序完成後 nums1 = \(nums1)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/heap_sort.swift b/zh-hant/codes/swift/chapter_sorting/heap_sort.swift new file mode 100644 index 000000000..fc709e196 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/heap_sort.swift @@ -0,0 +1,55 @@ +/** + * File: heap_sort.swift + * Created Time: 2023-05-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +func siftDown(nums: inout [Int], n: Int, i: Int) { + var i = i + while true { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let l = 2 * i + 1 + let r = 2 * i + 2 + var ma = i + if l < n, nums[l] > nums[ma] { + ma = l + } + if r < n, nums[r] > nums[ma] { + ma = r + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if ma == i { + break + } + // 交換兩節點 + nums.swapAt(i, ma) + // 迴圈向下堆積化 + i = ma + } +} + +/* 堆積排序 */ +func heapSort(nums: inout [Int]) { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { + siftDown(nums: &nums, n: nums.count, i: i) + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for i in nums.indices.dropFirst().reversed() { + // 交換根節點與最右葉節點(交換首元素與尾元素) + nums.swapAt(0, i) + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums: &nums, n: i, i: 0) + } +} + +@main +enum HeapSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + heapSort(nums: &nums) + print("堆積排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/insertion_sort.swift b/zh-hant/codes/swift/chapter_sorting/insertion_sort.swift new file mode 100644 index 000000000..869912f14 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/insertion_sort.swift @@ -0,0 +1,30 @@ +/** + * File: insertion_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 插入排序 */ +func insertionSort(nums: inout [Int]) { + // 外迴圈:已排序區間為 [0, i-1] + for i in nums.indices.dropFirst() { + let base = nums[i] + var j = i - 1 + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while j >= 0, nums[j] > base { + nums[j + 1] = nums[j] // 將 nums[j] 向右移動一位 + j -= 1 + } + nums[j + 1] = base // 將 base 賦值到正確位置 + } +} + +@main +enum InsertionSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + insertionSort(nums: &nums) + print("插入排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/merge_sort.swift b/zh-hant/codes/swift/chapter_sorting/merge_sort.swift new file mode 100644 index 000000000..190b76fa9 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/merge_sort.swift @@ -0,0 +1,65 @@ +/** + * File: merge_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 合併左子陣列和右子陣列 */ +func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + var tmp = Array(repeating: 0, count: right - left + 1) + // 初始化左子陣列和右子陣列的起始索引 + var i = left, j = mid + 1, k = 0 + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while i <= mid, 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 tmp.indices { + nums[left + k] = tmp[k] + } +} + +/* 合併排序 */ +func mergeSort(nums: inout [Int], left: Int, right: Int) { + // 終止條件 + if left >= right { // 當子陣列長度為 1 時終止遞迴 + return + } + // 劃分階段 + let mid = (left + right) / 2 // 計算中點 + mergeSort(nums: &nums, left: left, right: mid) // 遞迴左子陣列 + mergeSort(nums: &nums, left: mid + 1, right: right) // 遞迴右子陣列 + // 合併階段 + merge(nums: &nums, left: left, mid: mid, right: right) +} + +@main +enum MergeSort { + /* Driver Code */ + static func main() { + /* 合併排序 */ + var nums = [7, 3, 2, 6, 0, 1, 5, 4] + mergeSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) + print("合併排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/quick_sort.swift b/zh-hant/codes/swift/chapter_sorting/quick_sort.swift new file mode 100644 index 000000000..088a1d05b --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/quick_sort.swift @@ -0,0 +1,114 @@ +/** + * File: quick_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 快速排序類別 */ +/* 哨兵劃分 */ +func partition(nums: inout [Int], left: Int, right: Int) -> Int { + // 以 nums[left] 為基準數 + var i = left + var j = right + while i < j { + while i < j, nums[j] >= nums[left] { + j -= 1 // 從右向左找首個小於基準數的元素 + } + while i < j, nums[i] <= nums[left] { + i += 1 // 從左向右找首個大於基準數的元素 + } + nums.swapAt(i, j) // 交換這兩個元素 + } + nums.swapAt(i, left) // 將基準數交換至兩子陣列的分界線 + return i // 返回基準數的索引 +} + +/* 快速排序 */ +func quickSort(nums: inout [Int], left: Int, right: Int) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return + } + // 哨兵劃分 + let pivot = partition(nums: &nums, left: left, right: right) + // 遞迴左子陣列、右子陣列 + quickSort(nums: &nums, left: left, right: pivot - 1) + quickSort(nums: &nums, left: pivot + 1, right: right) +} + +/* 快速排序類別(中位基準數最佳化) */ +/* 選取三個候選元素的中位數 */ +func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { + let l = nums[left] + let m = nums[mid] + let r = nums[right] + if (l <= m && m <= r) || (r <= m && m <= l) { + return mid // m 在 l 和 r 之間 + } + if (m <= l && l <= r) || (r <= l && l <= m) { + return left // l 在 m 和 r 之間 + } + return right +} + +/* 哨兵劃分(三數取中值) */ +func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int { + // 選取三個候選元素的中位數 + let med = medianThree(nums: nums, left: left, mid: (left + right) / 2, right: right) + // 將中位數交換至陣列最左端 + nums.swapAt(left, med) + return partition(nums: &nums, left: left, right: right) +} + +/* 快速排序(中位基準數最佳化) */ +func quickSortMedian(nums: inout [Int], left: Int, right: Int) { + // 子陣列長度為 1 時終止遞迴 + if left >= right { + return + } + // 哨兵劃分 + let pivot = partitionMedian(nums: &nums, left: left, right: right) + // 遞迴左子陣列、右子陣列 + quickSortMedian(nums: &nums, left: left, right: pivot - 1) + quickSortMedian(nums: &nums, left: pivot + 1, right: right) +} + +/* 快速排序(尾遞迴最佳化) */ +func quickSortTailCall(nums: inout [Int], left: Int, right: Int) { + var left = left + var right = right + // 子陣列長度為 1 時終止 + while left < right { + // 哨兵劃分操作 + let pivot = partition(nums: &nums, left: left, right: right) + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left) < (right - pivot) { + quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // 遞迴排序左子陣列 + left = pivot + 1 // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // 遞迴排序右子陣列 + right = pivot - 1 // 剩餘未排序區間為 [left, pivot - 1] + } + } +} + +@main +enum QuickSort { + /* Driver Code */ + static func main() { + /* 快速排序 */ + var nums = [2, 4, 1, 0, 3, 5] + quickSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) + print("快速排序完成後 nums = \(nums)") + + /* 快速排序(中位基準數最佳化) */ + var nums1 = [2, 4, 1, 0, 3, 5] + quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1) + print("快速排序(中位基準數最佳化)完成後 nums1 = \(nums1)") + + /* 快速排序(尾遞迴最佳化) */ + var nums2 = [2, 4, 1, 0, 3, 5] + quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1) + print("快速排序(尾遞迴最佳化)完成後 nums2 = \(nums2)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/radix_sort.swift b/zh-hant/codes/swift/chapter_sorting/radix_sort.swift new file mode 100644 index 000000000..15f9ba7f5 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/radix_sort.swift @@ -0,0 +1,79 @@ +/** + * File: radix_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +func digit(num: Int, exp: Int) -> Int { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + (num / exp) % 10 +} + +/* 計數排序(根據 nums 第 k 位排序) */ +func countingSortDigit(nums: inout [Int], exp: Int) { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + var counter = Array(repeating: 0, count: 10) + // 統計 0~9 各數字的出現次數 + for i in nums.indices { + let d = digit(num: nums[i], exp: exp) // 獲取 nums[i] 第 k 位,記為 d + counter[d] += 1 // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for i in 1 ..< 10 { + counter[i] += counter[i - 1] + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + var res = Array(repeating: 0, count: nums.count) + for i in nums.indices.reversed() { + let d = digit(num: nums[i], exp: exp) + let j = counter[d] - 1 // 獲取 d 在陣列中的索引 j + res[j] = nums[i] // 將當前元素填入索引 j + counter[d] -= 1 // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for i in nums.indices { + nums[i] = res[i] + } +} + +/* 基數排序 */ +func radixSort(nums: inout [Int]) { + // 獲取陣列的最大元素,用於判斷最大位數 + var m = Int.min + for num in nums { + if num > m { + m = num + } + } + // 按照從低位到高位的順序走訪 + for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums: &nums, exp: exp) + } +} + +@main +enum RadixSort { + /* Driver Code */ + static func main() { + // 基數排序 + var nums = [ + 10_546_151, + 35_663_510, + 42_865_989, + 34_862_445, + 81_883_077, + 88_906_420, + 72_429_244, + 30_524_779, + 82_060_337, + 63_832_996, + ] + radixSort(nums: &nums) + print("基數排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_sorting/selection_sort.swift b/zh-hant/codes/swift/chapter_sorting/selection_sort.swift new file mode 100644 index 000000000..d65833242 --- /dev/null +++ b/zh-hant/codes/swift/chapter_sorting/selection_sort.swift @@ -0,0 +1,31 @@ +/** + * File: selection_sort.swift + * Created Time: 2023-05-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 選擇排序 */ +func selectionSort(nums: inout [Int]) { + // 外迴圈:未排序區間為 [i, n-1] + for i in nums.indices.dropLast() { + // 內迴圈:找到未排序區間內的最小元素 + var k = i + for j in nums.indices.dropFirst(i + 1) { + if nums[j] < nums[k] { + k = j // 記錄最小元素的索引 + } + } + // 將該最小元素與未排序區間的首個元素交換 + nums.swapAt(i, k) + } +} + +@main +enum SelectionSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + selectionSort(nums: &nums) + print("選擇排序完成後 nums = \(nums)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/array_deque.swift b/zh-hant/codes/swift/chapter_stack_and_queue/array_deque.swift new file mode 100644 index 000000000..5152da047 --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/array_deque.swift @@ -0,0 +1,148 @@ +/** + * File: array_deque.swift + * Created Time: 2023-02-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + private var nums: [Int] // 用於儲存雙向佇列元素的陣列 + private var front: Int // 佇列首指標,指向佇列首元素 + private var _size: Int // 雙向佇列長度 + + /* 建構子 */ + init(capacity: Int) { + nums = Array(repeating: 0, count: capacity) + front = 0 + _size = 0 + } + + /* 獲取雙向佇列的容量 */ + func capacity() -> Int { + nums.count + } + + /* 獲取雙向佇列的長度 */ + func size() -> Int { + _size + } + + /* 判斷雙向佇列是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 計算環形陣列索引 */ + private func index(i: Int) -> Int { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + (i + capacity()) % capacity() + } + + /* 佇列首入列 */ + func pushFirst(num: Int) { + if size() == capacity() { + print("雙向佇列已滿") + return + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + front = index(i: front - 1) + // 將 num 新增至佇列首 + nums[front] = num + _size += 1 + } + + /* 佇列尾入列 */ + func pushLast(num: Int) { + if size() == capacity() { + print("雙向佇列已滿") + return + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + let rear = index(i: front + size()) + // 將 num 新增至佇列尾 + nums[rear] = num + _size += 1 + } + + /* 佇列首出列 */ + func popFirst() -> Int { + let num = peekFirst() + // 佇列首指標向後移動一位 + front = index(i: front + 1) + _size -= 1 + return num + } + + /* 佇列尾出列 */ + func popLast() -> Int { + let num = peekLast() + _size -= 1 + return num + } + + /* 訪問佇列首元素 */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("雙向佇列為空") + } + return nums[front] + } + + /* 訪問佇列尾元素 */ + func peekLast() -> Int { + if isEmpty() { + fatalError("雙向佇列為空") + } + // 計算尾元素索引 + let last = index(i: front + size() - 1) + return nums[last] + } + + /* 返回陣列用於列印 */ + func toArray() -> [Int] { + // 僅轉換有效長度範圍內的串列元素 + (front ..< front + size()).map { nums[index(i: $0)] } + } +} + +@main +enum _ArrayDeque { + /* Driver Code */ + static func main() { + /* 初始化雙向佇列 */ + let deque = ArrayDeque(capacity: 10) + deque.pushLast(num: 3) + deque.pushLast(num: 2) + deque.pushLast(num: 5) + print("雙向佇列 deque = \(deque.toArray())") + + /* 訪問元素 */ + let peekFirst = deque.peekFirst() + print("佇列首元素 peekFirst = \(peekFirst)") + let peekLast = deque.peekLast() + print("佇列尾元素 peekLast = \(peekLast)") + + /* 元素入列 */ + deque.pushLast(num: 4) + print("元素 4 佇列尾入列後 deque = \(deque.toArray())") + deque.pushFirst(num: 1) + print("元素 1 佇列首入列後 deque = \(deque.toArray())") + + /* 元素出列 */ + let popLast = deque.popLast() + print("佇列尾出列元素 = \(popLast),佇列尾出列後 deque = \(deque.toArray())") + let popFirst = deque.popFirst() + print("佇列首出列元素 = \(popFirst),佇列首出列後 deque = \(deque.toArray())") + + /* 獲取雙向佇列的長度 */ + let size = deque.size() + print("雙向佇列長度 size = \(size)") + + /* 判斷雙向佇列是否為空 */ + let isEmpty = deque.isEmpty() + print("雙向佇列是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/array_queue.swift b/zh-hant/codes/swift/chapter_stack_and_queue/array_queue.swift new file mode 100644 index 000000000..c7e86207e --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/array_queue.swift @@ -0,0 +1,113 @@ +/** + * File: array_queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + private var nums: [Int] // 用於儲存佇列元素的陣列 + private var front: Int // 佇列首指標,指向佇列首元素 + private var _size: Int // 佇列長度 + + init(capacity: Int) { + // 初始化陣列 + nums = Array(repeating: 0, count: capacity) + front = 0 + _size = 0 + } + + /* 獲取佇列的容量 */ + func capacity() -> Int { + nums.count + } + + /* 獲取佇列的長度 */ + func size() -> Int { + _size + } + + /* 判斷佇列是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入列 */ + func push(num: Int) { + if size() == capacity() { + print("佇列已滿") + return + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + let rear = (front + size()) % capacity() + // 將 num 新增至佇列尾 + nums[rear] = num + _size += 1 + } + + /* 出列 */ + @discardableResult + func pop() -> Int { + let num = peek() + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + front = (front + 1) % capacity() + _size -= 1 + return num + } + + /* 訪問佇列首元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("佇列為空") + } + return nums[front] + } + + /* 返回陣列 */ + func toArray() -> [Int] { + // 僅轉換有效長度範圍內的串列元素 + (front ..< front + size()).map { nums[$0 % capacity()] } + } +} + +@main +enum _ArrayQueue { + /* Driver Code */ + static func main() { + /* 初始化佇列 */ + let capacity = 10 + let queue = ArrayQueue(capacity: capacity) + + /* 元素入列 */ + queue.push(num: 1) + queue.push(num: 3) + queue.push(num: 2) + queue.push(num: 5) + queue.push(num: 4) + print("佇列 queue = \(queue.toArray())") + + /* 訪問佇列首元素 */ + let peek = queue.peek() + print("佇列首元素 peek = \(peek)") + + /* 元素出列 */ + let pop = queue.pop() + print("出列元素 pop = \(pop),出列後 queue = \(queue.toArray())") + + /* 獲取佇列的長度 */ + let size = queue.size() + print("佇列長度 size = \(size)") + + /* 判斷佇列是否為空 */ + let isEmpty = queue.isEmpty() + print("佇列是否為空 = \(isEmpty)") + + /* 測試環形陣列 */ + for i in 0 ..< 10 { + queue.push(num: i) + queue.pop() + print("第 \(i) 輪入列 + 出列後 queue = \(queue.toArray())") + } + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/array_stack.swift b/zh-hant/codes/swift/chapter_stack_and_queue/array_stack.swift new file mode 100644 index 000000000..5873ef3dc --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/array_stack.swift @@ -0,0 +1,85 @@ +/** + * File: array_stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + private var stack: [Int] + + init() { + // 初始化串列(動態陣列) + stack = [] + } + + /* 獲取堆疊的長度 */ + func size() -> Int { + stack.count + } + + /* 判斷堆疊是否為空 */ + func isEmpty() -> Bool { + stack.isEmpty + } + + /* 入堆疊 */ + func push(num: Int) { + stack.append(num) + } + + /* 出堆疊 */ + @discardableResult + func pop() -> Int { + if isEmpty() { + fatalError("堆疊為空") + } + return stack.removeLast() + } + + /* 訪問堆疊頂元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("堆疊為空") + } + return stack.last! + } + + /* 將 List 轉化為 Array 並返回 */ + func toArray() -> [Int] { + stack + } +} + +@main +enum _ArrayStack { + /* Driver Code */ + static func main() { + /* 初始化堆疊 */ + let stack = ArrayStack() + + /* 元素入堆疊 */ + stack.push(num: 1) + stack.push(num: 3) + stack.push(num: 2) + stack.push(num: 5) + stack.push(num: 4) + print("堆疊 stack = \(stack.toArray())") + + /* 訪問堆疊頂元素 */ + let peek = stack.peek() + print("堆疊頂元素 peek = \(peek)") + + /* 元素出堆疊 */ + let pop = stack.pop() + print("出堆疊元素 pop = \(pop),出堆疊後 stack = \(stack.toArray())") + + /* 獲取堆疊的長度 */ + let size = stack.size() + print("堆疊的長度 size = \(size)") + + /* 判斷是否為空 */ + let isEmpty = stack.isEmpty() + print("堆疊是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/deque.swift b/zh-hant/codes/swift/chapter_stack_and_queue/deque.swift new file mode 100644 index 000000000..fc6f6dcbc --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/deque.swift @@ -0,0 +1,44 @@ +/** + * File: deque.swift + * Created Time: 2023-01-14 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Deque { + /* Driver Code */ + static func main() { + /* 初始化雙向佇列 */ + // Swift 沒有內建的雙向佇列類別,可以把 Array 當作雙向佇列來使用 + var deque: [Int] = [] + + /* 元素入列 */ + deque.append(2) + deque.append(5) + deque.append(4) + deque.insert(3, at: 0) + deque.insert(1, at: 0) + print("雙向佇列 deque = \(deque)") + + /* 訪問元素 */ + let peekFirst = deque.first! + print("佇列首元素 peekFirst = \(peekFirst)") + let peekLast = deque.last! + print("佇列尾元素 peekLast = \(peekLast)") + + /* 元素出列 */ + // 使用 Array 模擬時 popFirst 的複雜度為 O(n) + let popFirst = deque.removeFirst() + print("佇列首出列元素 popFirst = \(popFirst),佇列首出列後 deque = \(deque)") + let popLast = deque.removeLast() + print("佇列尾出列元素 popLast = \(popLast),佇列尾出列後 deque = \(deque)") + + /* 獲取雙向佇列的長度 */ + let size = deque.count + print("雙向佇列長度 size = \(size)") + + /* 判斷雙向佇列是否為空 */ + let isEmpty = deque.isEmpty + print("雙向佇列是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift new file mode 100644 index 000000000..e3055ba31 --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift @@ -0,0 +1,180 @@ +/** + * File: linkedlist_deque.swift + * Created Time: 2023-02-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 雙向鏈結串列節點 */ +class ListNode { + var val: Int // 節點值 + var next: ListNode? // 後繼節點引用 + weak var prev: ListNode? // 前驅節點引用 + + init(val: Int) { + self.val = val + } +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + private var front: ListNode? // 頭節點 front + private var rear: ListNode? // 尾節點 rear + private var _size: Int // 雙向佇列的長度 + + init() { + _size = 0 + } + + /* 獲取雙向佇列的長度 */ + func size() -> Int { + _size + } + + /* 判斷雙向佇列是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入列操作 */ + private func push(num: Int, isFront: Bool) { + let node = ListNode(val: num) + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if isEmpty() { + front = node + rear = node + } + // 佇列首入列操作 + else if isFront { + // 將 node 新增至鏈結串列頭部 + front?.prev = node + node.next = front + front = node // 更新頭節點 + } + // 佇列尾入列操作 + else { + // 將 node 新增至鏈結串列尾部 + rear?.next = node + node.prev = rear + rear = node // 更新尾節點 + } + _size += 1 // 更新佇列長度 + } + + /* 佇列首入列 */ + func pushFirst(num: Int) { + push(num: num, isFront: true) + } + + /* 佇列尾入列 */ + func pushLast(num: Int) { + push(num: num, isFront: false) + } + + /* 出列操作 */ + private func pop(isFront: Bool) -> Int { + if isEmpty() { + fatalError("雙向佇列為空") + } + let val: Int + // 佇列首出列操作 + if isFront { + val = front!.val // 暫存頭節點值 + // 刪除頭節點 + let fNext = front?.next + if fNext != nil { + fNext?.prev = nil + front?.next = nil + } + front = fNext // 更新頭節點 + } + // 佇列尾出列操作 + else { + val = rear!.val // 暫存尾節點值 + // 刪除尾節點 + let rPrev = rear?.prev + if rPrev != nil { + rPrev?.next = nil + rear?.prev = nil + } + rear = rPrev // 更新尾節點 + } + _size -= 1 // 更新佇列長度 + return val + } + + /* 佇列首出列 */ + func popFirst() -> Int { + pop(isFront: true) + } + + /* 佇列尾出列 */ + func popLast() -> Int { + pop(isFront: false) + } + + /* 訪問佇列首元素 */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("雙向佇列為空") + } + return front!.val + } + + /* 訪問佇列尾元素 */ + func peekLast() -> Int { + if isEmpty() { + fatalError("雙向佇列為空") + } + return rear!.val + } + + /* 返回陣列用於列印 */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListDeque { + /* Driver Code */ + static func main() { + /* 初始化雙向佇列 */ + let deque = LinkedListDeque() + deque.pushLast(num: 3) + deque.pushLast(num: 2) + deque.pushLast(num: 5) + print("雙向佇列 deque = \(deque.toArray())") + + /* 訪問元素 */ + let peekFirst = deque.peekFirst() + print("佇列首元素 peekFirst = \(peekFirst)") + let peekLast = deque.peekLast() + print("佇列尾元素 peekLast = \(peekLast)") + + /* 元素入列 */ + deque.pushLast(num: 4) + print("元素 4 佇列尾入列後 deque = \(deque.toArray())") + deque.pushFirst(num: 1) + print("元素 1 佇列首入列後 deque = \(deque.toArray())") + + /* 元素出列 */ + let popLast = deque.popLast() + print("佇列尾出列元素 = \(popLast),佇列尾出列後 deque = \(deque.toArray())") + let popFirst = deque.popFirst() + print("佇列首出列元素 = \(popFirst),佇列首出列後 deque = \(deque.toArray())") + + /* 獲取雙向佇列的長度 */ + let size = deque.size() + print("雙向佇列長度 size = \(size)") + + /* 判斷雙向佇列是否為空 */ + let isEmpty = deque.isEmpty() + print("雙向佇列是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift new file mode 100644 index 000000000..b39c01c67 --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift @@ -0,0 +1,107 @@ +/** + * File: linkedlist_queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + private var front: ListNode? // 頭節點 + private var rear: ListNode? // 尾節點 + private var _size: Int + + init() { + _size = 0 + } + + /* 獲取佇列的長度 */ + func size() -> Int { + _size + } + + /* 判斷佇列是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入列 */ + func push(num: Int) { + // 在尾節點後新增 num + let node = ListNode(x: num) + // 如果佇列為空,則令頭、尾節點都指向該節點 + if front == nil { + front = node + rear = node + } + // 如果佇列不為空,則將該節點新增到尾節點後 + else { + rear?.next = node + rear = node + } + _size += 1 + } + + /* 出列 */ + @discardableResult + func pop() -> Int { + let num = peek() + // 刪除頭節點 + front = front?.next + _size -= 1 + return num + } + + /* 訪問佇列首元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("佇列為空") + } + return front!.val + } + + /* 將鏈結串列轉化為 Array 並返回 */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListQueue { + /* Driver Code */ + static func main() { + /* 初始化佇列 */ + let queue = LinkedListQueue() + + /* 元素入列 */ + queue.push(num: 1) + queue.push(num: 3) + queue.push(num: 2) + queue.push(num: 5) + queue.push(num: 4) + print("佇列 queue = \(queue.toArray())") + + /* 訪問佇列首元素 */ + let peek = queue.peek() + print("佇列首元素 peek = \(peek)") + + /* 元素出列 */ + let pop = queue.pop() + print("出列元素 pop = \(pop),出列後 queue = \(queue.toArray())") + + /* 獲取佇列的長度 */ + let size = queue.size() + print("佇列長度 size = \(size)") + + /* 判斷佇列是否為空 */ + let isEmpty = queue.isEmpty() + print("佇列是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift new file mode 100644 index 000000000..35eb412e2 --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift @@ -0,0 +1,96 @@ +/** + * File: linkedlist_stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + private var _peek: ListNode? // 將頭節點作為堆疊頂 + private var _size: Int // 堆疊的長度 + + init() { + _size = 0 + } + + /* 獲取堆疊的長度 */ + func size() -> Int { + _size + } + + /* 判斷堆疊是否為空 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 入堆疊 */ + func push(num: Int) { + let node = ListNode(x: num) + node.next = _peek + _peek = node + _size += 1 + } + + /* 出堆疊 */ + @discardableResult + func pop() -> Int { + let num = peek() + _peek = _peek?.next + _size -= 1 + return num + } + + /* 訪問堆疊頂元素 */ + func peek() -> Int { + if isEmpty() { + fatalError("堆疊為空") + } + return _peek!.val + } + + /* 將 List 轉化為 Array 並返回 */ + func toArray() -> [Int] { + var node = _peek + var res = Array(repeating: 0, count: size()) + for i in res.indices.reversed() { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListStack { + /* Driver Code */ + static func main() { + /* 初始化堆疊 */ + let stack = LinkedListStack() + + /* 元素入堆疊 */ + stack.push(num: 1) + stack.push(num: 3) + stack.push(num: 2) + stack.push(num: 5) + stack.push(num: 4) + print("堆疊 stack = \(stack.toArray())") + + /* 訪問堆疊頂元素 */ + let peek = stack.peek() + print("堆疊頂元素 peek = \(peek)") + + /* 元素出堆疊 */ + let pop = stack.pop() + print("出堆疊元素 pop = \(pop),出堆疊後 stack = \(stack.toArray())") + + /* 獲取堆疊的長度 */ + let size = stack.size() + print("堆疊的長度 size = \(size)") + + /* 判斷是否為空 */ + let isEmpty = stack.isEmpty() + print("堆疊是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/queue.swift b/zh-hant/codes/swift/chapter_stack_and_queue/queue.swift new file mode 100644 index 000000000..54d9d076a --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/queue.swift @@ -0,0 +1,40 @@ +/** + * File: queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Queue { + /* Driver Code */ + static func main() { + /* 初始化佇列 */ + // Swift 沒有內建的佇列類別,可以把 Array 當作佇列來使用 + var queue: [Int] = [] + + /* 元素入列 */ + queue.append(1) + queue.append(3) + queue.append(2) + queue.append(5) + queue.append(4) + print("佇列 queue = \(queue)") + + /* 訪問佇列首元素 */ + let peek = queue.first! + print("佇列首元素 peek = \(peek)") + + /* 元素出列 */ + // 使用 Array 模擬時 pop 的複雜度為 O(n) + let pool = queue.removeFirst() + print("出列元素 pop = \(pool),出列後 queue = \(queue)") + + /* 獲取佇列的長度 */ + let size = queue.count + print("佇列長度 size = \(size)") + + /* 判斷佇列是否為空 */ + let isEmpty = queue.isEmpty + print("佇列是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_stack_and_queue/stack.swift b/zh-hant/codes/swift/chapter_stack_and_queue/stack.swift new file mode 100644 index 000000000..cb6bd3074 --- /dev/null +++ b/zh-hant/codes/swift/chapter_stack_and_queue/stack.swift @@ -0,0 +1,39 @@ +/** + * File: stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Stack { + /* Driver Code */ + static func main() { + /* 初始化堆疊 */ + // Swift 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 + var stack: [Int] = [] + + /* 元素入堆疊 */ + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + print("堆疊 stack = \(stack)") + + /* 訪問堆疊頂元素 */ + let peek = stack.last! + print("堆疊頂元素 peek = \(peek)") + + /* 元素出堆疊 */ + let pop = stack.removeLast() + print("出堆疊元素 pop = \(pop),出堆疊後 stack = \(stack)") + + /* 獲取堆疊的長度 */ + let size = stack.count + print("堆疊的長度 size = \(size)") + + /* 判斷是否為空 */ + let isEmpty = stack.isEmpty + print("堆疊是否為空 = \(isEmpty)") + } +} diff --git a/zh-hant/codes/swift/chapter_tree/array_binary_tree.swift b/zh-hant/codes/swift/chapter_tree/array_binary_tree.swift new file mode 100644 index 000000000..93ddaba21 --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/array_binary_tree.swift @@ -0,0 +1,141 @@ +/** + * File: array_binary_tree.swift + * Created Time: 2023-07-23 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + private var tree: [Int?] + + /* 建構子 */ + init(arr: [Int?]) { + tree = arr + } + + /* 串列容量 */ + func size() -> Int { + tree.count + } + + /* 獲取索引為 i 節點的值 */ + func val(i: Int) -> Int? { + // 若索引越界,則返回 null ,代表空位 + if i < 0 || i >= size() { + return nil + } + return tree[i] + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 獲取索引為 i 節點的父節點的索引 */ + func parent(i: Int) -> Int { + (i - 1) / 2 + } + + /* 層序走訪 */ + func levelOrder() -> [Int] { + var res: [Int] = [] + // 直接走訪陣列 + for i in 0 ..< size() { + if let val = val(i: i) { + res.append(val) + } + } + return res + } + + /* 深度優先走訪 */ + private func dfs(i: Int, order: String, res: inout [Int]) { + // 若為空位,則返回 + guard let val = val(i: i) else { + return + } + // 前序走訪 + if order == "pre" { + res.append(val) + } + dfs(i: left(i: i), order: order, res: &res) + // 中序走訪 + if order == "in" { + res.append(val) + } + dfs(i: right(i: i), order: order, res: &res) + // 後序走訪 + if order == "post" { + res.append(val) + } + } + + /* 前序走訪 */ + func preOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "pre", res: &res) + return res + } + + /* 中序走訪 */ + func inOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "in", res: &res) + return res + } + + /* 後序走訪 */ + func postOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "post", res: &res) + return res + } +} + +@main +enum _ArrayBinaryTree { + /* Driver Code */ + static func main() { + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + + let root = TreeNode.listToTree(arr: arr) + print("\n初始化二元樹\n") + print("二元樹的陣列表示:") + print(arr) + print("二元樹的鏈結串列表示:") + PrintUtil.printTree(root: root) + + // 陣列表示下的二元樹類別 + let abt = ArrayBinaryTree(arr: arr) + + // 訪問節點 + let i = 1 + let l = abt.left(i: i) + let r = abt.right(i: i) + let p = abt.parent(i: i) + print("\n當前節點的索引為 \(i) ,值為 \(abt.val(i: i) as Any)") + print("其左子節點的索引為 \(l) ,值為 \(abt.val(i: l) as Any)") + print("其右子節點的索引為 \(r) ,值為 \(abt.val(i: r) as Any)") + print("其父節點的索引為 \(p) ,值為 \(abt.val(i: p) as Any)") + + // 走訪樹 + var res = abt.levelOrder() + print("\n層序走訪為:\(res)") + res = abt.preOrder() + print("前序走訪為:\(res)") + res = abt.inOrder() + print("中序走訪為:\(res)") + res = abt.postOrder() + print("後序走訪為:\(res)") + } +} diff --git a/zh-hant/codes/swift/chapter_tree/avl_tree.swift b/zh-hant/codes/swift/chapter_tree/avl_tree.swift new file mode 100644 index 000000000..b2ac8bf97 --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/avl_tree.swift @@ -0,0 +1,230 @@ +/** + * File: avl_tree.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* AVL 樹 */ +class AVLTree { + fileprivate var root: TreeNode? // 根節點 + + init() {} + + /* 獲取節點高度 */ + func height(node: TreeNode?) -> Int { + // 空節點高度為 -1 ,葉節點高度為 0 + node?.height ?? -1 + } + + /* 更新節點高度 */ + private func updateHeight(node: TreeNode?) { + // 節點高度等於最高子樹高度 + 1 + node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 + } + + /* 獲取平衡因子 */ + func balanceFactor(node: TreeNode?) -> Int { + // 空節點平衡因子為 0 + guard let node = node else { return 0 } + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return height(node: node.left) - height(node: node.right) + } + + /* 右旋操作 */ + private func rightRotate(node: TreeNode?) -> TreeNode? { + let child = node?.left + let grandChild = child?.right + // 以 child 為原點,將 node 向右旋轉 + child?.right = node + node?.left = grandChild + // 更新節點高度 + updateHeight(node: node) + updateHeight(node: child) + // 返回旋轉後子樹的根節點 + return child + } + + /* 左旋操作 */ + private func leftRotate(node: TreeNode?) -> TreeNode? { + let child = node?.right + let grandChild = child?.left + // 以 child 為原點,將 node 向左旋轉 + child?.left = node + node?.right = grandChild + // 更新節點高度 + updateHeight(node: node) + updateHeight(node: child) + // 返回旋轉後子樹的根節點 + return child + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + private func rotate(node: TreeNode?) -> TreeNode? { + // 獲取節點 node 的平衡因子 + let balanceFactor = balanceFactor(node: node) + // 左偏樹 + if balanceFactor > 1 { + if self.balanceFactor(node: node?.left) >= 0 { + // 右旋 + return rightRotate(node: node) + } else { + // 先左旋後右旋 + node?.left = leftRotate(node: node?.left) + return rightRotate(node: node) + } + } + // 右偏樹 + if balanceFactor < -1 { + if self.balanceFactor(node: node?.right) <= 0 { + // 左旋 + return leftRotate(node: node) + } else { + // 先右旋後左旋 + node?.right = rightRotate(node: node?.right) + return leftRotate(node: node) + } + } + // 平衡樹,無須旋轉,直接返回 + return node + } + + /* 插入節點 */ + func insert(val: Int) { + root = insertHelper(node: root, val: val) + } + + /* 遞迴插入節點(輔助方法) */ + private func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return TreeNode(x: val) + } + /* 1. 查詢插入位置並插入節點 */ + if val < node!.val { + node?.left = insertHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = insertHelper(node: node?.right, val: val) + } else { + return node // 重複節點不插入,直接返回 + } + updateHeight(node: node) // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node: node) + // 返回子樹的根節點 + return node + } + + /* 刪除節點 */ + func remove(val: Int) { + root = removeHelper(node: root, val: val) + } + + /* 遞迴刪除節點(輔助方法) */ + private func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return nil + } + /* 1. 查詢節點並刪除 */ + if val < node!.val { + node?.left = removeHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = removeHelper(node: node?.right, val: val) + } else { + if node?.left == nil || node?.right == nil { + let child = node?.left ?? node?.right + // 子節點數量 = 0 ,直接刪除 node 並返回 + if child == nil { + return nil + } + // 子節點數量 = 1 ,直接刪除 node + else { + node = child + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + var temp = node?.right + while temp?.left != nil { + temp = temp?.left + } + node?.right = removeHelper(node: node?.right, val: temp!.val) + node?.val = temp!.val + } + } + updateHeight(node: node) // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = rotate(node: node) + // 返回子樹的根節點 + return node + } + + /* 查詢節點 */ + func search(val: Int) -> TreeNode? { + var cur = root + while cur != nil { + // 目標節點在 cur 的右子樹中 + if cur!.val < val { + cur = cur?.right + } + // 目標節點在 cur 的左子樹中 + else if cur!.val > val { + cur = cur?.left + } + // 找到目標節點,跳出迴圈 + else { + break + } + } + // 返回目標節點 + return cur + } +} + +@main +enum _AVLTree { + static func testInsert(tree: AVLTree, val: Int) { + tree.insert(val: val) + print("\n插入節點 \(val) 後,AVL 樹為") + PrintUtil.printTree(root: tree.root) + } + + static func testRemove(tree: AVLTree, val: Int) { + tree.remove(val: val) + print("\n刪除節點 \(val) 後,AVL 樹為") + PrintUtil.printTree(root: tree.root) + } + + /* Driver Code */ + static func main() { + /* 初始化空 AVL 樹 */ + let avlTree = AVLTree() + + /* 插入節點 */ + // 請關注插入節點後,AVL 樹是如何保持平衡的 + testInsert(tree: avlTree, val: 1) + testInsert(tree: avlTree, val: 2) + testInsert(tree: avlTree, val: 3) + testInsert(tree: avlTree, val: 4) + testInsert(tree: avlTree, val: 5) + testInsert(tree: avlTree, val: 8) + testInsert(tree: avlTree, val: 7) + testInsert(tree: avlTree, val: 9) + testInsert(tree: avlTree, val: 10) + testInsert(tree: avlTree, val: 6) + + /* 插入重複節點 */ + testInsert(tree: avlTree, val: 7) + + /* 刪除節點 */ + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(tree: avlTree, val: 8) // 刪除度為 0 的節點 + testRemove(tree: avlTree, val: 5) // 刪除度為 1 的節點 + testRemove(tree: avlTree, val: 4) // 刪除度為 2 的節點 + + /* 查詢節點 */ + let node = avlTree.search(val: 7) + print("\n查詢到的節點物件為 \(node!),節點值 = \(node!.val)") + } +} diff --git a/zh-hant/codes/swift/chapter_tree/binary_search_tree.swift b/zh-hant/codes/swift/chapter_tree/binary_search_tree.swift new file mode 100644 index 000000000..0b54b324c --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/binary_search_tree.swift @@ -0,0 +1,173 @@ +/** + * File: binary_search_tree.swift + * Created Time: 2023-01-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 二元搜尋樹 */ +class BinarySearchTree { + private var root: TreeNode? + + /* 建構子 */ + init() { + // 初始化空樹 + root = nil + } + + /* 獲取二元樹根節點 */ + func getRoot() -> TreeNode? { + root + } + + /* 查詢節點 */ + func search(num: Int) -> TreeNode? { + var cur = root + // 迴圈查詢,越過葉節點後跳出 + while cur != nil { + // 目標節點在 cur 的右子樹中 + if cur!.val < num { + cur = cur?.right + } + // 目標節點在 cur 的左子樹中 + else if cur!.val > num { + cur = cur?.left + } + // 找到目標節點,跳出迴圈 + else { + break + } + } + // 返回目標節點 + return cur + } + + /* 插入節點 */ + func insert(num: Int) { + // 若樹為空,則初始化根節點 + if root == nil { + root = TreeNode(x: num) + return + } + var cur = root + var pre: TreeNode? + // 迴圈查詢,越過葉節點後跳出 + while cur != nil { + // 找到重複節點,直接返回 + if cur!.val == num { + return + } + pre = cur + // 插入位置在 cur 的右子樹中 + if cur!.val < num { + cur = cur?.right + } + // 插入位置在 cur 的左子樹中 + else { + cur = cur?.left + } + } + // 插入節點 + let node = TreeNode(x: num) + if pre!.val < num { + pre?.right = node + } else { + pre?.left = node + } + } + + /* 刪除節點 */ + func remove(num: Int) { + // 若樹為空,直接提前返回 + if root == nil { + return + } + var cur = root + var pre: TreeNode? + // 迴圈查詢,越過葉節點後跳出 + while cur != nil { + // 找到待刪除節點,跳出迴圈 + if cur!.val == num { + break + } + pre = cur + // 待刪除節點在 cur 的右子樹中 + if cur!.val < num { + cur = cur?.right + } + // 待刪除節點在 cur 的左子樹中 + else { + cur = cur?.left + } + } + // 若無待刪除節點,則直接返回 + if cur == nil { + return + } + // 子節點數量 = 0 or 1 + if cur?.left == nil || cur?.right == nil { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + let child = cur?.left ?? cur?.right + // 刪除節點 cur + if cur !== root { + if pre?.left === cur { + pre?.left = child + } else { + pre?.right = child + } + } else { + // 若刪除節點為根節點,則重新指定根節點 + root = child + } + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + var tmp = cur?.right + while tmp?.left != nil { + tmp = tmp?.left + } + // 遞迴刪除節點 tmp + remove(num: tmp!.val) + // 用 tmp 覆蓋 cur + cur?.val = tmp!.val + } + } +} + +@main +enum _BinarySearchTree { + /* Driver Code */ + static func main() { + /* 初始化二元搜尋樹 */ + let bst = BinarySearchTree() + // 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 + let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + for num in nums { + bst.insert(num: num) + } + print("\n初始化的二元樹為\n") + PrintUtil.printTree(root: bst.getRoot()) + + /* 查詢節點 */ + let node = bst.search(num: 7) + print("\n查詢到的節點物件為 \(node!),節點值 = \(node!.val)") + + /* 插入節點 */ + bst.insert(num: 16) + print("\n插入節點 16 後,二元樹為\n") + PrintUtil.printTree(root: bst.getRoot()) + + /* 刪除節點 */ + bst.remove(num: 1) + print("\n刪除節點 1 後,二元樹為\n") + PrintUtil.printTree(root: bst.getRoot()) + bst.remove(num: 2) + print("\n刪除節點 2 後,二元樹為\n") + PrintUtil.printTree(root: bst.getRoot()) + bst.remove(num: 4) + print("\n刪除節點 4 後,二元樹為\n") + PrintUtil.printTree(root: bst.getRoot()) + } +} diff --git a/zh-hant/codes/swift/chapter_tree/binary_tree.swift b/zh-hant/codes/swift/chapter_tree/binary_tree.swift new file mode 100644 index 000000000..65ba07697 --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/binary_tree.swift @@ -0,0 +1,40 @@ +/** + * File: binary_tree.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum BinaryTree { + /* Driver Code */ + static func main() { + /* 初始化二元樹 */ + // 初始化節點 + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + print("\n初始化二元樹\n") + PrintUtil.printTree(root: n1) + + /* 插入與刪除節點 */ + let P = TreeNode(x: 0) + // 在 n1 -> n2 中間插入節點 P + n1.left = P + P.left = n2 + print("\n插入節點 P 後\n") + PrintUtil.printTree(root: n1) + // 刪除節點 P + n1.left = n2 + print("\n刪除節點 P 後\n") + PrintUtil.printTree(root: n1) + } +} diff --git a/zh-hant/codes/swift/chapter_tree/binary_tree_bfs.swift b/zh-hant/codes/swift/chapter_tree/binary_tree_bfs.swift new file mode 100644 index 000000000..ea373bf1e --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/binary_tree_bfs.swift @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 層序走訪 */ +func levelOrder(root: TreeNode) -> [Int] { + // 初始化佇列,加入根節點 + var queue: [TreeNode] = [root] + // 初始化一個串列,用於儲存走訪序列 + var list: [Int] = [] + while !queue.isEmpty { + let node = queue.removeFirst() // 隊列出隊 + list.append(node.val) // 儲存節點值 + if let left = node.left { + queue.append(left) // 左子節點入列 + } + if let right = node.right { + queue.append(right) // 右子節點入列 + } + } + return list +} + +@main +enum BinaryTreeBFS { + /* Driver Code */ + static func main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let node = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! + print("\n初始化二元樹\n") + PrintUtil.printTree(root: node) + + /* 層序走訪 */ + let list = levelOrder(root: node) + print("\n層序走訪的節點列印序列 = \(list)") + } +} diff --git a/zh-hant/codes/swift/chapter_tree/binary_tree_dfs.swift b/zh-hant/codes/swift/chapter_tree/binary_tree_dfs.swift new file mode 100644 index 000000000..342eff2ba --- /dev/null +++ b/zh-hant/codes/swift/chapter_tree/binary_tree_dfs.swift @@ -0,0 +1,70 @@ +/** + * File: binary_tree_dfs.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +// 初始化串列,用於儲存走訪序列 +var list: [Int] = [] + +/* 前序走訪 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.append(root.val) + preOrder(root: root.left) + preOrder(root: root.right) +} + +/* 中序走訪 */ +func inOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root: root.left) + list.append(root.val) + inOrder(root: root.right) +} + +/* 後序走訪 */ +func postOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root: root.left) + postOrder(root: root.right) + list.append(root.val) +} + +@main +enum BinaryTreeDFS { + /* Driver Code */ + static func main() { + /* 初始化二元樹 */ + // 這裡藉助了一個從陣列直接生成二元樹的函式 + let root = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! + print("\n初始化二元樹\n") + PrintUtil.printTree(root: root) + + /* 前序走訪 */ + list.removeAll() + preOrder(root: root) + print("\n前序走訪的節點列印序列 = \(list)") + + /* 中序走訪 */ + list.removeAll() + inOrder(root: root) + print("\n中序走訪的節點列印序列 = \(list)") + + /* 後序走訪 */ + list.removeAll() + postOrder(root: root) + print("\n後序走訪的節點列印序列 = \(list)") + } +} diff --git a/zh-hant/codes/swift/utils/ListNode.swift b/zh-hant/codes/swift/utils/ListNode.swift new file mode 100644 index 000000000..ec4690d5e --- /dev/null +++ b/zh-hant/codes/swift/utils/ListNode.swift @@ -0,0 +1,33 @@ +/** + * File: ListNode.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +public class ListNode: Hashable { + public var val: Int // 節點值 + public var next: ListNode? // 後繼節點引用 + + public init(x: Int) { + val = x + } + + public static func == (lhs: ListNode, rhs: ListNode) -> Bool { + lhs.val == rhs.val && lhs.next.map { ObjectIdentifier($0) } == rhs.next.map { ObjectIdentifier($0) } + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + hasher.combine(next.map { ObjectIdentifier($0) }) + } + + public static func arrToLinkedList(arr: [Int]) -> ListNode? { + let dum = ListNode(x: 0) + var head: ListNode? = dum + for val in arr { + head?.next = ListNode(x: val) + head = head?.next + } + return dum.next + } +} diff --git a/zh-hant/codes/swift/utils/Pair.swift b/zh-hant/codes/swift/utils/Pair.swift new file mode 100644 index 000000000..cfb57c517 --- /dev/null +++ b/zh-hant/codes/swift/utils/Pair.swift @@ -0,0 +1,20 @@ +/** + * File: Pair.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 鍵值對 */ +public class Pair: Equatable { + public var key: Int + public var val: String + + public init(key: Int, val: String) { + self.key = key + self.val = val + } + + public static func == (lhs: Pair, rhs: Pair) -> Bool { + lhs.key == rhs.key && lhs.val == rhs.val + } +} diff --git a/zh-hant/codes/swift/utils/PrintUtil.swift b/zh-hant/codes/swift/utils/PrintUtil.swift new file mode 100644 index 000000000..bfc8911c1 --- /dev/null +++ b/zh-hant/codes/swift/utils/PrintUtil.swift @@ -0,0 +1,93 @@ +/** + * File: PrintUtil.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +public enum PrintUtil { + private class Trunk { + var prev: Trunk? + var str: String + + init(prev: Trunk?, str: String) { + self.prev = prev + self.str = str + } + } + + public static func printLinkedList(head: ListNode) { + var head: ListNode? = head + var list: [String] = [] + while head != nil { + list.append("\(head!.val)") + head = head?.next + } + print(list.joined(separator: " -> ")) + } + + public static func printTree(root: TreeNode?) { + printTree(root: root, prev: nil, isRight: false) + } + + private static func printTree(root: TreeNode?, prev: Trunk?, isRight: Bool) { + if root == nil { + return + } + + var prevStr = " " + let trunk = Trunk(prev: prev, str: prevStr) + + printTree(root: root?.right, prev: trunk, isRight: true) + + if prev == nil { + trunk.str = "———" + } else if isRight { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev?.str = prevStr + } + + showTrunks(p: trunk) + print(" \(root!.val)") + + if prev != nil { + prev?.str = prevStr + } + trunk.str = " |" + + printTree(root: root?.left, prev: trunk, isRight: false) + } + + private static func showTrunks(p: Trunk?) { + if p == nil { + return + } + + showTrunks(p: p?.prev) + print(p!.str, terminator: "") + } + + public static func printHashMap(map: [K: V]) { + for (key, value) in map { + print("\(key) -> \(value)") + } + } + + public static func printHeap(queue: [Int]) { + print("堆積的陣列表示:", terminator: "") + print(queue) + print("堆積的樹狀表示:") + let root = TreeNode.listToTree(arr: queue) + printTree(root: root) + } + + public static func printMatrix(matrix: [[T]]) { + print("[") + for row in matrix { + print(" \(row),") + } + print("]") + } +} diff --git a/zh-hant/codes/swift/utils/TreeNode.swift b/zh-hant/codes/swift/utils/TreeNode.swift new file mode 100644 index 000000000..f62614849 --- /dev/null +++ b/zh-hant/codes/swift/utils/TreeNode.swift @@ -0,0 +1,71 @@ +/** + * File: TreeNode.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二元樹節點類別 */ +public class TreeNode { + public var val: Int // 節點值 + public var height: Int // 節點高度 + public var left: TreeNode? // 左子節點引用 + public var right: TreeNode? // 右子節點引用 + + /* 建構子 */ + public init(x: Int) { + val = x + height = 0 + } + + // 序列化編碼規則請參考: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二元樹的陣列表示: + // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + // 二元樹的鏈結串列表示: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* 將串列反序列化為二元樹:遞迴 */ + private static func listToTreeDFS(arr: [Int?], i: Int) -> TreeNode? { + if i < 0 || i >= arr.count || arr[i] == nil { + return nil + } + let root = TreeNode(x: arr[i]!) + root.left = listToTreeDFS(arr: arr, i: 2 * i + 1) + root.right = listToTreeDFS(arr: arr, i: 2 * i + 2) + return root + } + + /* 將串列反序列化為二元樹 */ + public static func listToTree(arr: [Int?]) -> TreeNode? { + listToTreeDFS(arr: arr, i: 0) + } + + /* 將二元樹序列化為串列:遞迴 */ + private static func treeToListDFS(root: TreeNode?, i: Int, res: inout [Int?]) { + if root == nil { + return + } + while i >= res.count { + res.append(nil) + } + res[i] = root?.val + treeToListDFS(root: root?.left, i: 2 * i + 1, res: &res) + treeToListDFS(root: root?.right, i: 2 * i + 2, res: &res) + } + + /* 將二元樹序列化為串列 */ + public static func treeToList(root: TreeNode?) -> [Int?] { + var res: [Int?] = [] + treeToListDFS(root: root, i: 0, res: &res) + return res + } +} diff --git a/zh-hant/codes/swift/utils/Vertex.swift b/zh-hant/codes/swift/utils/Vertex.swift new file mode 100644 index 000000000..87d0d6525 --- /dev/null +++ b/zh-hant/codes/swift/utils/Vertex.swift @@ -0,0 +1,32 @@ +/** + * File: Vertex.swift + * Created Time: 2023-02-19 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 頂點類別 */ +public class Vertex: Hashable { + public var val: Int + + public init(val: Int) { + self.val = val + } + + public static func == (lhs: Vertex, rhs: Vertex) -> Bool { + lhs.val == rhs.val + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + } + + /* 輸入值串列 vals ,返回頂點串列 vets */ + public static func valsToVets(vals: [Int]) -> [Vertex] { + vals.map { Vertex(val: $0) } + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + public static func vetsToVals(vets: [Vertex]) -> [Int] { + vets.map { $0.val } + } +} diff --git a/zh-hant/codes/typescript/.gitignore b/zh-hant/codes/typescript/.gitignore new file mode 100644 index 000000000..201706994 --- /dev/null +++ b/zh-hant/codes/typescript/.gitignore @@ -0,0 +1,4 @@ +node_modules +out +package.json +package-lock.json diff --git a/zh-hant/codes/typescript/.prettierrc b/zh-hant/codes/typescript/.prettierrc new file mode 100644 index 000000000..3f4aa8cb6 --- /dev/null +++ b/zh-hant/codes/typescript/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true +} diff --git a/zh-hant/codes/typescript/chapter_array_and_linkedlist/array.ts b/zh-hant/codes/typescript/chapter_array_and_linkedlist/array.ts new file mode 100644 index 000000000..47b4c5ce3 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_array_and_linkedlist/array.ts @@ -0,0 +1,101 @@ +/** + * File: array.ts + * Created Time: 2022-12-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 隨機訪問元素 */ +function randomAccess(nums: number[]): number { + // 在區間 [0, nums.length) 中隨機抽取一個數字 + const random_index = Math.floor(Math.random() * nums.length); + // 獲取並返回隨機元素 + const random_num = nums[random_index]; + return random_num; +} + +/* 擴展陣列長度 */ +// 請注意,TypeScript 的 Array 是動態陣列,可以直接擴展 +// 為了方便學習,本函式將 Array 看作長度不可變的陣列 +function extend(nums: number[], enlarge: number): number[] { + // 初始化一個擴展長度後的陣列 + const res = new Array(nums.length + enlarge).fill(0); + // 將原陣列中的所有元素複製到新陣列 + for (let i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 返回擴展後的新陣列 + return res; +} + +/* 在陣列的索引 index 處插入元素 num */ +function insert(nums: number[], num: number, index: number): void { + // 把索引 index 以及之後的所有元素向後移動一位 + for (let i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +/* 刪除索引 index 處的元素 */ +function remove(nums: number[], index: number): void { + // 把索引 index 之後的所有元素向前移動一位 + for (let i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 走訪陣列 */ +function traverse(nums: number[]): void { + let count = 0; + // 透過索引走訪陣列 + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 直接走訪陣列元素 + for (const num of nums) { + count += num; + } +} + +/* 在陣列中查詢指定元素 */ +function find(nums: number[], target: number): number { + for (let i = 0; i < nums.length; i++) { + if (nums[i] === target) { + return i; + } + } + return -1; +} + +/* Driver Code */ +/* 初始化陣列 */ +const arr: number[] = new Array(5).fill(0); +console.log('陣列 arr =', arr); +let nums: number[] = [1, 3, 2, 5, 4]; +console.log('陣列 nums =', nums); + +/* 隨機訪問 */ +let random_num = randomAccess(nums); +console.log('在 nums 中獲取隨機元素', random_num); + +/* 長度擴展 */ +nums = extend(nums, 3); +console.log('將陣列長度擴展至 8 ,得到 nums =', nums); + +/* 插入元素 */ +insert(nums, 6, 3); +console.log('在索引 3 處插入數字 6 ,得到 nums =', nums); + +/* 刪除元素 */ +remove(nums, 2); +console.log('刪除索引 2 處的元素,得到 nums =', nums); + +/* 走訪陣列 */ +traverse(nums); + +/* 查詢元素 */ +let index = find(nums, 3); +console.log('在 nums 中查詢元素 3 ,得到索引 =', index); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_array_and_linkedlist/linked_list.ts b/zh-hant/codes/typescript/chapter_array_and_linkedlist/linked_list.ts new file mode 100644 index 000000000..c68baa053 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_array_and_linkedlist/linked_list.ts @@ -0,0 +1,86 @@ +/** + * File: linked_list.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from '../modules/ListNode'; +import { printLinkedList } from '../modules/PrintUtil'; + +/* 在鏈結串列的節點 n0 之後插入節點 P */ +function insert(n0: ListNode, P: ListNode): void { + const n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* 刪除鏈結串列的節點 n0 之後的首個節點 */ +function remove(n0: ListNode): void { + if (!n0.next) { + return; + } + // n0 -> P -> n1 + const P = n0.next; + const n1 = P.next; + n0.next = n1; +} + +/* 訪問鏈結串列中索引為 index 的節點 */ +function access(head: ListNode | null, index: number): ListNode | null { + for (let i = 0; i < index; i++) { + if (!head) { + return null; + } + head = head.next; + } + return head; +} + +/* 在鏈結串列中查詢值為 target 的首個節點 */ +function find(head: ListNode | null, target: number): number { + let index = 0; + while (head !== null) { + if (head.val === target) { + return index; + } + head = head.next; + index += 1; + } + return -1; +} + +/* Driver Code */ +/* 初始化鏈結串列 */ +// 初始化各個節點 +const n0 = new ListNode(1); +const n1 = new ListNode(3); +const n2 = new ListNode(2); +const n3 = new ListNode(5); +const n4 = new ListNode(4); +// 構建節點之間的引用 +n0.next = n1; +n1.next = n2; +n2.next = n3; +n3.next = n4; +console.log('初始化的鏈結串列為'); +printLinkedList(n0); + +/* 插入節點 */ +insert(n0, new ListNode(0)); +console.log('插入節點後的鏈結串列為'); +printLinkedList(n0); + +/* 刪除節點 */ +remove(n0); +console.log('刪除節點後的鏈結串列為'); +printLinkedList(n0); + +/* 訪問節點 */ +const node = access(n0, 3); +console.log(`鏈結串列中索引 3 處的節點的值 = ${node?.val}`); + +/* 查詢節點 */ +const index = find(n0, 2); +console.log(`鏈結串列中值為 2 的節點的索引 = ${index}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_array_and_linkedlist/list.ts b/zh-hant/codes/typescript/chapter_array_and_linkedlist/list.ts new file mode 100644 index 000000000..dc6ba93f3 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_array_and_linkedlist/list.ts @@ -0,0 +1,59 @@ +/** + * File: list.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 初始化串列 */ +const nums: number[] = [1, 3, 2, 5, 4]; +console.log(`串列 nums = ${nums}`); + +/* 訪問元素 */ +const num: number = nums[1]; +console.log(`訪問索引 1 處的元素,得到 num = ${num}`); + +/* 更新元素 */ +nums[1] = 0; +console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums}`); + +/* 清空串列 */ +nums.length = 0; +console.log(`清空串列後 nums = ${nums}`); + +/* 在尾部新增元素 */ +nums.push(1); +nums.push(3); +nums.push(2); +nums.push(5); +nums.push(4); +console.log(`新增元素後 nums = ${nums}`); + +/* 在中間插入元素 */ +nums.splice(3, 0, 6); +console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums}`); + +/* 刪除元素 */ +nums.splice(3, 1); +console.log(`刪除索引 3 處的元素,得到 nums = ${nums}`); + +/* 透過索引走訪串列 */ +let count = 0; +for (let i = 0; i < nums.length; i++) { + count += nums[i]; +} +/* 直接走訪串列元素 */ +count = 0; +for (const x of nums) { + count += x; +} + +/* 拼接兩個串列 */ +const nums1: number[] = [6, 8, 7, 10, 9]; +nums.push(...nums1); +console.log(`將串列 nums1 拼接到 nums 之後,得到 nums = ${nums}`); + +/* 排序串列 */ +nums.sort((a, b) => a - b); +console.log(`排序串列後 nums = ${nums}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_array_and_linkedlist/my_list.ts b/zh-hant/codes/typescript/chapter_array_and_linkedlist/my_list.ts new file mode 100644 index 000000000..eeaba05fe --- /dev/null +++ b/zh-hant/codes/typescript/chapter_array_and_linkedlist/my_list.ts @@ -0,0 +1,141 @@ +/** + * File: my_list.ts + * Created Time: 2022-12-11 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 串列類別 */ +class MyList { + private arr: Array; // 陣列(儲存串列元素) + private _capacity: number = 10; // 串列容量 + private _size: number = 0; // 串列長度(當前元素數量) + private extendRatio: number = 2; // 每次串列擴容的倍數 + + /* 建構子 */ + constructor() { + this.arr = new Array(this._capacity); + } + + /* 獲取串列長度(當前元素數量)*/ + public size(): number { + return this._size; + } + + /* 獲取串列容量 */ + public capacity(): number { + return this._capacity; + } + + /* 訪問元素 */ + public get(index: number): number { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 || index >= this._size) throw new Error('索引越界'); + return this.arr[index]; + } + + /* 更新元素 */ + public set(index: number, num: number): void { + if (index < 0 || index >= this._size) throw new Error('索引越界'); + this.arr[index] = num; + } + + /* 在尾部新增元素 */ + public add(num: number): void { + // 如果長度等於容量,則需要擴容 + if (this._size === this._capacity) this.extendCapacity(); + // 將新元素新增到串列尾部 + this.arr[this._size] = num; + this._size++; + } + + /* 在中間插入元素 */ + public insert(index: number, num: number): void { + if (index < 0 || index >= this._size) throw new Error('索引越界'); + // 元素數量超出容量時,觸發擴容機制 + if (this._size === this._capacity) { + this.extendCapacity(); + } + // 將索引 index 以及之後的元素都向後移動一位 + for (let j = this._size - 1; j >= index; j--) { + this.arr[j + 1] = this.arr[j]; + } + // 更新元素數量 + this.arr[index] = num; + this._size++; + } + + /* 刪除元素 */ + public remove(index: number): number { + if (index < 0 || index >= this._size) throw new Error('索引越界'); + let num = this.arr[index]; + // 將將索引 index 之後的元素都向前移動一位 + for (let j = index; j < this._size - 1; j++) { + this.arr[j] = this.arr[j + 1]; + } + // 更新元素數量 + this._size--; + // 返回被刪除的元素 + return num; + } + + /* 串列擴容 */ + public extendCapacity(): void { + // 新建一個長度為 size 的陣列,並將原陣列複製到新陣列 + this.arr = this.arr.concat( + new Array(this.capacity() * (this.extendRatio - 1)) + ); + // 更新串列容量 + this._capacity = this.arr.length; + } + + /* 將串列轉換為陣列 */ + public toArray(): number[] { + let size = this.size(); + // 僅轉換有效長度範圍內的串列元素 + const arr = new Array(size); + for (let i = 0; i < size; i++) { + arr[i] = this.get(i); + } + return arr; + } +} + +/* Driver Code */ +/* 初始化串列 */ +const nums = new MyList(); +/* 在尾部新增元素 */ +nums.add(1); +nums.add(3); +nums.add(2); +nums.add(5); +nums.add(4); +console.log( + `串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` +); + +/* 在中間插入元素 */ +nums.insert(3, 6); +console.log(`在索引 3 處插入數字 6 ,得到 nums = ${nums.toArray()}`); + +/* 刪除元素 */ +nums.remove(3); +console.log(`刪除索引 3 處的元素,得到 nums = ${nums.toArray()}`); + +/* 訪問元素 */ +const num = nums.get(1); +console.log(`訪問索引 1 處的元素,得到 num = ${num}`); + +/* 更新元素 */ +nums.set(1, 0); +console.log(`將索引 1 處的元素更新為 0 ,得到 nums = ${nums.toArray()}`); + +/* 測試擴容機制 */ +for (let i = 0; i < 10; i++) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + nums.add(i); +} +console.log( + `擴容後的串列 nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長度 = ${nums.size()}` +); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/n_queens.ts b/zh-hant/codes/typescript/chapter_backtracking/n_queens.ts new file mode 100644 index 000000000..e3d6ef1b2 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/n_queens.ts @@ -0,0 +1,65 @@ +/** + * File: n_queens.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:n 皇后 */ +function backtrack( + row: number, + n: number, + state: string[][], + res: string[][][], + cols: boolean[], + diags1: boolean[], + diags2: boolean[] +): void { + // 當放置完所有行時,記錄解 + if (row === n) { + res.push(state.map((row) => row.slice())); + return; + } + // 走訪所有列 + for (let col = 0; col < n; col++) { + // 計算該格子對應的主對角線和次對角線 + const diag1 = row - col + n - 1; + const diag2 = row + col; + // 剪枝:不允許該格子所在列、主對角線、次對角線上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 嘗試:將皇后放置在該格子 + state[row][col] = 'Q'; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:將該格子恢復為空位 + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +function nQueens(n: number): string[][][] { + // 初始化 n*n 大小的棋盤,其中 'Q' 代表皇后,'#' 代表空位 + const state = Array.from({ length: n }, () => Array(n).fill('#')); + const cols = Array(n).fill(false); // 記錄列是否有皇后 + const diags1 = Array(2 * n - 1).fill(false); // 記錄主對角線上是否有皇后 + const diags2 = Array(2 * n - 1).fill(false); // 記錄次對角線上是否有皇后 + const res: string[][][] = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + return res; +} + +// Driver Code +const n = 4; +const res = nQueens(n); + +console.log(`輸入棋盤長寬為 ${n}`); +console.log(`皇后放置方案共有 ${res.length} 種`); +res.forEach((state) => { + console.log('--------------------'); + state.forEach((row) => console.log(row)); +}); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/permutations_i.ts b/zh-hant/codes/typescript/chapter_backtracking/permutations_i.ts new file mode 100644 index 000000000..990eecd17 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/permutations_i.ts @@ -0,0 +1,49 @@ +/** + * File: permutations_i.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:全排列 I */ +function backtrack( + state: number[], + choices: number[], + selected: boolean[], + res: number[][] +): void { + // 當狀態長度等於元素數量時,記錄解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 走訪所有選擇 + choices.forEach((choice, i) => { + // 剪枝:不允許重複選擇元素 + if (!selected[i]) { + // 嘗試:做出選擇,更新狀態 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 I */ +function permutationsI(nums: number[]): number[][] { + const res: number[][] = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums: number[] = [1, 2, 3]; +const res: number[][] = permutationsI(nums); + +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/permutations_ii.ts b/zh-hant/codes/typescript/chapter_backtracking/permutations_ii.ts new file mode 100644 index 000000000..49ec2ecdc --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/permutations_ii.ts @@ -0,0 +1,51 @@ +/** + * File: permutations_ii.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 回溯演算法:全排列 II */ +function backtrack( + state: number[], + choices: number[], + selected: boolean[], + res: number[][] +): void { + // 當狀態長度等於元素數量時,記錄解 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // 走訪所有選擇 + const duplicated = new Set(); + choices.forEach((choice, i) => { + // 剪枝:不允許重複選擇元素 且 不允許重複選擇相等元素 + if (!selected[i] && !duplicated.has(choice)) { + // 嘗試:做出選擇,更新狀態 + duplicated.add(choice); // 記錄選擇過的元素值 + selected[i] = true; + state.push(choice); + // 進行下一輪選擇 + backtrack(state, choices, selected, res); + // 回退:撤銷選擇,恢復到之前的狀態 + selected[i] = false; + state.pop(); + } + }); +} + +/* 全排列 II */ +function permutationsII(nums: number[]): number[][] { + const res: number[][] = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums: number[] = [1, 2, 2]; +const res: number[][] = permutationsII(nums); + +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}`); +console.log(`所有排列 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts new file mode 100644 index 000000000..7fd306e31 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts @@ -0,0 +1,36 @@ +/** + * File: preorder_traversal_i_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前序走訪:例題一 */ +function preOrder(root: TreeNode | null, res: TreeNode[]): void { + if (root === null) { + return; + } + if (root.val === 7) { + // 記錄解 + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const res: TreeNode[] = []; +preOrder(root, res); + +console.log('\n輸出所有值為 7 的節點'); +console.log(res.map((node) => node.val)); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts new file mode 100644 index 000000000..8ec1f5389 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_ii_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前序走訪:例題二 */ +function preOrder( + root: TreeNode | null, + path: TreeNode[], + res: TreeNode[][] +): void { + if (root === null) { + return; + } + // 嘗試 + path.push(root); + if (root.val === 7) { + // 記錄解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const path: TreeNode[] = []; +const res: TreeNode[][] = []; +preOrder(root, path, res); + +console.log('\n輸出所有根節點到節點 7 的路徑'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts new file mode 100644 index 000000000..2e8acb14e --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts @@ -0,0 +1,48 @@ +/** + * File: preorder_traversal_iii_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前序走訪:例題三 */ +function preOrder( + root: TreeNode | null, + path: TreeNode[], + res: TreeNode[][] +): void { + // 剪枝 + if (root === null || root.val === 3) { + return; + } + // 嘗試 + path.push(root); + if (root.val === 7) { + // 記錄解 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // 回退 + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 前序走訪 +const path: TreeNode[] = []; +const res: TreeNode[][] = []; +preOrder(root, path, res); + +console.log('\n輸出所有根節點到節點 7 的路徑,路徑中不包含值為 3 的節點'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts new file mode 100644 index 000000000..4aa8a96b9 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts @@ -0,0 +1,75 @@ +/** + * File: preorder_traversal_iii_template.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 判斷當前狀態是否為解 */ +function isSolution(state: TreeNode[]): boolean { + return state && state[state.length - 1]?.val === 7; +} + +/* 記錄解 */ +function recordSolution(state: TreeNode[], res: TreeNode[][]): void { + res.push([...state]); +} + +/* 判斷在當前狀態下,該選擇是否合法 */ +function isValid(state: TreeNode[], choice: TreeNode): boolean { + return choice !== null && choice.val !== 3; +} + +/* 更新狀態 */ +function makeChoice(state: TreeNode[], choice: TreeNode): void { + state.push(choice); +} + +/* 恢復狀態 */ +function undoChoice(state: TreeNode[]): void { + state.pop(); +} + +/* 回溯演算法:例題三 */ +function backtrack( + state: TreeNode[], + choices: TreeNode[], + res: TreeNode[][] +): void { + // 檢查是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + } + // 走訪所有選擇 + for (const choice of choices) { + // 剪枝:檢查選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + // 進行下一輪選擇 + backtrack(state, [choice.left, choice.right], res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state); + } + } +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹'); +printTree(root); + +// 回溯演算法 +const res: TreeNode[][] = []; +backtrack([], [root], res); + +console.log('\n輸出所有根節點到節點 7 的路徑,要求路徑中不包含值為 3 的節點'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i.ts b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i.ts new file mode 100644 index 000000000..761cd8df3 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i.ts @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +function backtrack( + state: number[], + target: number, + choices: number[], + start: number, + res: number[][] +): void { + // 子集和等於 target 時,記錄解 + if (target === 0) { + res.push([...state]); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I */ +function subsetSumI(nums: number[], target: number): number[][] { + const state = []; // 狀態(子集) + nums.sort((a, b) => a - b); // 對 nums 進行排序 + const start = 0; // 走訪起始點 + const res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumI(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); + +export { }; diff --git a/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts new file mode 100644 index 000000000..86f112d14 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts @@ -0,0 +1,52 @@ +/** + * File: subset_sum_i_naive.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 I */ +function backtrack( + state: number[], + target: number, + total: number, + choices: number[], + res: number[][] +): void { + // 子集和等於 target 時,記錄解 + if (total === target) { + res.push([...state]); + return; + } + // 走訪所有選擇 + for (let i = 0; i < choices.length; i++) { + // 剪枝:若子集和超過 target ,則跳過該選擇 + if (total + choices[i] > target) { + continue; + } + // 嘗試:做出選擇,更新元素和 total + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 I(包含重複子集) */ +function subsetSumINaive(nums: number[], target: number): number[][] { + const state = []; // 狀態(子集) + const total = 0; // 子集和 + const res = []; // 結果串列(子集串列) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumINaive(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); +console.log('請注意,該方法輸出的結果包含重複集合'); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_backtracking/subset_sum_ii.ts b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_ii.ts new file mode 100644 index 000000000..c5b56fe70 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_backtracking/subset_sum_ii.ts @@ -0,0 +1,59 @@ +/** + * File: subset_sum_ii.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯演算法:子集和 II */ +function backtrack( + state: number[], + target: number, + choices: number[], + start: number, + res: number[][] +): void { + // 子集和等於 target 時,記錄解 + if (target === 0) { + res.push([...state]); + return; + } + // 走訪所有選擇 + // 剪枝二:從 start 開始走訪,避免生成重複子集 + // 剪枝三:從 start 開始走訪,避免重複選擇同一元素 + for (let i = start; i < choices.length; i++) { + // 剪枝一:若子集和超過 target ,則直接結束迴圈 + // 這是因為陣列已排序,後邊元素更大,子集和一定超過 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果該元素與左邊元素相等,說明該搜尋分支重複,直接跳過 + if (i > start && choices[i] === choices[i - 1]) { + continue; + } + // 嘗試:做出選擇,更新 target, start + state.push(choices[i]); + // 進行下一輪選擇 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤銷選擇,恢復到之前的狀態 + state.pop(); + } +} + +/* 求解子集和 II */ +function subsetSumII(nums: number[], target: number): number[][] { + const state = []; // 狀態(子集) + nums.sort((a, b) => a - b); // 對 nums 進行排序 + const start = 0; // 走訪起始點 + const res = []; // 結果串列(子集串列) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [4, 4, 5]; +const target = 9; +const res = subsetSumII(nums, target); +console.log(`輸入陣列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`所有和等於 ${target} 的子集 res = ${JSON.stringify(res)}`); + +export { }; diff --git a/zh-hant/codes/typescript/chapter_computational_complexity/iteration.ts b/zh-hant/codes/typescript/chapter_computational_complexity/iteration.ts new file mode 100644 index 000000000..f6d0d3312 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_computational_complexity/iteration.ts @@ -0,0 +1,72 @@ +/** + * File: iteration.ts + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* for 迴圈 */ +function forLoop(n: number): number { + let res = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while 迴圈 */ +function whileLoop(n: number): number { + let res = 0; + let i = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新條件變數 + } + return res; +} + +/* while 迴圈(兩次更新) */ +function whileLoopII(n: number): number { + let res = 0; + let i = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新條件變數 + i++; + i *= 2; + } + return res; +} + +/* 雙層 for 迴圈 */ +function nestedForLoop(n: number): string { + let res = ''; + // 迴圈 i = 1, 2, ..., n-1, n + for (let i = 1; i <= n; i++) { + // 迴圈 j = 1, 2, ..., n-1, n + for (let j = 1; j <= n; j++) { + res += `(${i}, ${j}), `; + } + } + return res; +} + +/* Driver Code */ +const n = 5; +let res: number; + +res = forLoop(n); +console.log(`for 迴圈的求和結果 res = ${res}`); + +res = whileLoop(n); +console.log(`while 迴圈的求和結果 res = ${res}`); + +res = whileLoopII(n); +console.log(`while 迴圈(兩次更新)求和結果 res = ${res}`); + +const resStr = nestedForLoop(n); +console.log(`雙層 for 迴圈的走訪結果 ${resStr}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_computational_complexity/recursion.ts b/zh-hant/codes/typescript/chapter_computational_complexity/recursion.ts new file mode 100644 index 000000000..af5182e54 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_computational_complexity/recursion.ts @@ -0,0 +1,70 @@ +/** + * File: recursion.ts + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 遞迴 */ +function recur(n: number): number { + // 終止條件 + if (n === 1) return 1; + // 遞:遞迴呼叫 + const res = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +/* 使用迭代模擬遞迴 */ +function forLoopRecur(n: number): number { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + const stack: number[] = []; + let res: number = 0; + // 遞:遞迴呼叫 + for (let i = n; i > 0; i--) { + // 透過“入堆疊操作”模擬“遞” + stack.push(i); + } + // 迴:返回結果 + while (stack.length) { + // 透過“出堆疊操作”模擬“迴” + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾遞迴 */ +function tailRecur(n: number, res: number): number { + // 終止條件 + if (n === 0) return res; + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +/* 費波那契數列:遞迴 */ +function fib(n: number): number { + // 終止條件 f(1) = 0, f(2) = 1 + if (n === 1 || n === 2) return n - 1; + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + const res = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +/* Driver Code */ +const n = 5; +let res: number; + +res = recur(n); +console.log(`遞迴函式的求和結果 res = ${res}`); + +res = forLoopRecur(n); +console.log(`使用迭代模擬遞迴的求和結果 res = ${res}`); + +res = tailRecur(n, 0); +console.log(`尾遞迴函式的求和結果 res = ${res}`); + +res = fib(n); +console.log(`費波那契數列的第 ${n} 項為 ${res}`); + +export { }; diff --git a/zh-hant/codes/typescript/chapter_computational_complexity/space_complexity.ts b/zh-hant/codes/typescript/chapter_computational_complexity/space_complexity.ts new file mode 100644 index 000000000..5282ae565 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_computational_complexity/space_complexity.ts @@ -0,0 +1,103 @@ +/** + * File: space_complexity.ts + * Created Time: 2023-02-05 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from '../modules/ListNode'; +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 函式 */ +function constFunc(): number { + // 執行某些操作 + return 0; +} + +/* 常數階 */ +function constant(n: number): void { + // 常數、變數、物件佔用 O(1) 空間 + const a = 0; + const b = 0; + const nums = new Array(10000); + const node = new ListNode(0); + // 迴圈中的變數佔用 O(1) 空間 + for (let i = 0; i < n; i++) { + const c = 0; + } + // 迴圈中的函式佔用 O(1) 空間 + for (let i = 0; i < n; i++) { + constFunc(); + } +} + +/* 線性階 */ +function linear(n: number): void { + // 長度為 n 的陣列佔用 O(n) 空間 + const nums = new Array(n); + // 長度為 n 的串列佔用 O(n) 空間 + const nodes: ListNode[] = []; + for (let i = 0; i < n; i++) { + nodes.push(new ListNode(i)); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + const map = new Map(); + for (let i = 0; i < n; i++) { + map.set(i, i.toString()); + } +} + +/* 線性階(遞迴實現) */ +function linearRecur(n: number): void { + console.log(`遞迴 n = ${n}`); + if (n === 1) return; + linearRecur(n - 1); +} + +/* 平方階 */ +function quadratic(n: number): void { + // 矩陣佔用 O(n^2) 空間 + const numMatrix = Array(n) + .fill(null) + .map(() => Array(n).fill(null)); + // 二維串列佔用 O(n^2) 空間 + const numList = []; + for (let i = 0; i < n; i++) { + const tmp = []; + for (let j = 0; j < n; j++) { + tmp.push(0); + } + numList.push(tmp); + } +} + +/* 平方階(遞迴實現) */ +function quadraticRecur(n: number): number { + if (n <= 0) return 0; + const nums = new Array(n); + console.log(`遞迴 n = ${n} 中的 nums 長度 = ${nums.length}`); + return quadraticRecur(n - 1); +} + +/* 指數階(建立滿二元樹) */ +function buildTree(n: number): TreeNode | null { + if (n === 0) return null; + const root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +const n = 5; +// 常數階 +constant(n); +// 線性階 +linear(n); +linearRecur(n); +// 平方階 +quadratic(n); +quadraticRecur(n); +// 指數階 +const root = buildTree(n); +printTree(root); diff --git a/zh-hant/codes/typescript/chapter_computational_complexity/time_complexity.ts b/zh-hant/codes/typescript/chapter_computational_complexity/time_complexity.ts new file mode 100644 index 000000000..d10d25e8d --- /dev/null +++ b/zh-hant/codes/typescript/chapter_computational_complexity/time_complexity.ts @@ -0,0 +1,157 @@ +/** + * File: time_complexity.ts + * Created Time: 2023-01-02 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 常數階 */ +function constant(n: number): number { + let count = 0; + const size = 100000; + for (let i = 0; i < size; i++) count++; + return count; +} + +/* 線性階 */ +function linear(n: number): number { + let count = 0; + for (let i = 0; i < n; i++) count++; + return count; +} + +/* 線性階(走訪陣列) */ +function arrayTraversal(nums: number[]): number { + let count = 0; + // 迴圈次數與陣列長度成正比 + for (let i = 0; i < nums.length; i++) { + count++; + } + return count; +} + +/* 平方階 */ +function quadratic(n: number): number { + let count = 0; + // 迴圈次數與資料大小 n 成平方關係 + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方階(泡沫排序) */ +function bubbleSort(nums: number[]): number { + let count = 0; // 計數器 + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +/* 指數階(迴圈實現) */ +function exponential(n: number): number { + let count = 0, + base = 1; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + for (let i = 0; i < n; i++) { + for (let j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指數階(遞迴實現) */ +function expRecur(n: number): number { + if (n === 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 對數階(迴圈實現) */ +function logarithmic(n: number): number { + let count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 對數階(遞迴實現) */ +function logRecur(n: number): number { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +/* 線性對數階 */ +function linearLogRecur(n: number): number { + if (n <= 1) return 1; + let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (let i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乘階(遞迴實現) */ +function factorialRecur(n: number): number { + if (n === 0) return 1; + let count = 0; + // 從 1 個分裂出 n 個 + for (let i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +// 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 +const n = 8; +console.log('輸入資料大小 n = ' + n); + +let count = constant(n); +console.log('常數階的操作數量 = ' + count); + +count = linear(n); +console.log('線性階的操作數量 = ' + count); +count = arrayTraversal(new Array(n)); +console.log('線性階(走訪陣列)的操作數量 = ' + count); + +count = quadratic(n); +console.log('平方階的操作數量 = ' + count); +var nums = new Array(n); +for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] +count = bubbleSort(nums); +console.log('平方階(泡沫排序)的操作數量 = ' + count); + +count = exponential(n); +console.log('指數階(迴圈實現)的操作數量 = ' + count); +count = expRecur(n); +console.log('指數階(遞迴實現)的操作數量 = ' + count); + +count = logarithmic(n); +console.log('對數階(迴圈實現)的操作數量 = ' + count); +count = logRecur(n); +console.log('對數階(遞迴實現)的操作數量 = ' + count); + +count = linearLogRecur(n); +console.log('線性對數階(遞迴實現)的操作數量 = ' + count); + +count = factorialRecur(n); +console.log('階乘階(遞迴實現)的操作數量 = ' + count); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts b/zh-hant/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts new file mode 100644 index 000000000..4ba2038bd --- /dev/null +++ b/zh-hant/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts @@ -0,0 +1,45 @@ +/** + * File: worst_best_time_complexity.ts + * Created Time: 2023-01-05 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 */ +function randomNumbers(n: number): number[] { + const nums = Array(n); + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (let i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 隨機打亂陣列元素 + for (let i = 0; i < n; i++) { + const r = Math.floor(Math.random() * (i + 1)); + const temp = nums[i]; + nums[i] = nums[r]; + nums[r] = temp; + } + return nums; +} + +/* 查詢陣列 nums 中數字 1 所在索引 */ +function findOne(nums: number[]): number { + for (let i = 0; i < nums.length; i++) { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (nums[i] === 1) { + return i; + } + } + return -1; +} + +/* Driver Code */ +for (let i = 0; i < 10; i++) { + const n = 100; + const nums = randomNumbers(n); + const index = findOne(nums); + console.log('\n陣列 [ 1, 2, ..., n ] 被打亂後 = [' + nums.join(', ') + ']'); + console.log('數字 1 的索引為 ' + index); +} + +export {}; diff --git a/zh-hant/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts b/zh-hant/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts new file mode 100644 index 000000000..6ae3755a3 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts @@ -0,0 +1,41 @@ +/** + * File: binary_search_recur.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 二分搜尋:問題 f(i, j) */ +function dfs(nums: number[], target: number, i: number, j: number): number { + // 若區間為空,代表無目標元素,則返回 -1 + if (i > j) { + return -1; + } + // 計算中點索引 m + const m = i + ((j - i) >> 1); + if (nums[m] < target) { + // 遞迴子問題 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 遞迴子問題 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目標元素,返回其索引 + return m; + } +} + +/* 二分搜尋 */ +function binarySearch(nums: number[], target: number): number { + const n = nums.length; + // 求解問題 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +// 二分搜尋(雙閉區間) +const index = binarySearch(nums, target); +console.log(`目標元素 6 的索引 = ${index}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_divide_and_conquer/build_tree.ts b/zh-hant/codes/typescript/chapter_divide_and_conquer/build_tree.ts new file mode 100644 index 000000000..585929d35 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_divide_and_conquer/build_tree.ts @@ -0,0 +1,50 @@ +/** + * File: build_tree.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +import { printTree } from '../modules/PrintUtil'; +import { TreeNode } from '../modules/TreeNode'; + +/* 構建二元樹:分治 */ +function dfs( + preorder: number[], + inorderMap: Map, + i: number, + l: number, + r: number +): TreeNode | null { + // 子樹區間為空時終止 + if (r - l < 0) return null; + // 初始化根節點 + const root: TreeNode = new TreeNode(preorder[i]); + // 查詢 m ,從而劃分左右子樹 + const m = inorderMap.get(preorder[i]); + // 子問題:構建左子樹 + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子問題:構建右子樹 + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根節點 + return root; +} + +/* 構建二元樹 */ +function buildTree(preorder: number[], inorder: number[]): TreeNode | null { + // 初始化雜湊表,儲存 inorder 元素到索引的對映 + let inorderMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + inorderMap.set(inorder[i], i); + } + const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +const preorder = [3, 9, 2, 1, 7]; +const inorder = [9, 3, 1, 2, 7]; +console.log('前序走訪 = ' + JSON.stringify(preorder)); +console.log('中序走訪 = ' + JSON.stringify(inorder)); +const root = buildTree(preorder, inorder); +console.log('構建的二元樹為:'); +printTree(root); diff --git a/zh-hant/codes/typescript/chapter_divide_and_conquer/hanota.ts b/zh-hant/codes/typescript/chapter_divide_and_conquer/hanota.ts new file mode 100644 index 000000000..4b1732d82 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_divide_and_conquer/hanota.ts @@ -0,0 +1,52 @@ +/** + * File: hanota.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 移動一個圓盤 */ +function move(src: number[], tar: number[]): void { + // 從 src 頂部拿出一個圓盤 + const pan = src.pop(); + // 將圓盤放入 tar 頂部 + tar.push(pan); +} + +/* 求解河內塔問題 f(i) */ +function dfs(i: number, src: number[], buf: number[], tar: number[]): void { + // 若 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); +} + +/* 求解河內塔問題 */ +function solveHanota(A: number[], B: number[], C: number[]): void { + const n = A.length; + // 將 A 頂部 n 個圓盤藉助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +// 串列尾部是柱子頂部 +const A = [5, 4, 3, 2, 1]; +const B = []; +const C = []; +console.log('初始狀態下:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); + +solveHanota(A, B, C); + +console.log('圓盤移動完成後:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts new file mode 100644 index 000000000..0017da577 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_backtrack.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 回溯 */ +function backtrack( + choices: number[], + state: number, + n: number, + res: Map<0, any> +): void { + // 當爬到第 n 階時,方案數量加 1 + if (state === n) res.set(0, res.get(0) + 1); + // 走訪所有選擇 + for (const choice of choices) { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) continue; + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬樓梯:回溯 */ +function climbingStairsBacktrack(n: number): number { + const choices = [1, 2]; // 可選擇向上爬 1 階或 2 階 + const state = 0; // 從第 0 階開始爬 + const res = new Map(); + res.set(0, 0); // 使用 res[0] 記錄方案數量 + backtrack(choices, state, n, res); + return res.get(0); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsBacktrack(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts new file mode 100644 index 000000000..9d6b0de54 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_constraint_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 帶約束爬樓梯:動態規劃 */ +function climbingStairsConstraintDP(n: number): number { + if (n === 1 || n === 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + const dp = Array.from({ length: n + 1 }, () => new Array(3)); + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + 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 */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts new file mode 100644 index 000000000..bc8c08b50 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts @@ -0,0 +1,26 @@ +/** + * File: climbing_stairs_dfs.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 搜尋 */ +function dfs(i: number): number { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i === 1 || i === 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬樓梯:搜尋 */ +function climbingStairsDFS(n: number): number { + return dfs(n); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFS(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts new file mode 100644 index 000000000..5fe716ec1 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs_mem.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 記憶化搜尋 */ +function dfs(i: number, mem: number[]): number { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i === 1 || i === 2) return i; + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +/* 爬樓梯:記憶化搜尋 */ +function climbingStairsDFSMem(n: number): number { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + const mem = new Array(n + 1).fill(-1); + return dfs(n, mem); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFSMem(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts new file mode 100644 index 000000000..5a47bf0ef --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts @@ -0,0 +1,42 @@ +/** + * File: climbing_stairs_dp.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 爬樓梯:動態規劃 */ +function climbingStairsDP(n: number): number { + if (n === 1 || n === 2) return n; + // 初始化 dp 表,用於儲存子問題的解 + const dp = new Array(n + 1).fill(-1); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬樓梯:空間最佳化後的動態規劃 */ +function climbingStairsDPComp(n: number): number { + if (n === 1 || n === 2) return n; + let a = 1, + b = 2; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +const n = 9; +let res = climbingStairsDP(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); +res = climbingStairsDPComp(n); +console.log(`爬 ${n} 階樓梯共有 ${res} 種方案`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts new file mode 100644 index 000000000..222ff7ecb --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change.ts @@ -0,0 +1,68 @@ +/** + * File: coin_change.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零錢兌換:動態規劃 */ +function coinChangeDP(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 狀態轉移:首行首列 + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* 零錢兌換:狀態壓縮後的動態規劃 */ +function coinChangeDPComp(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// 動態規劃 +let res = coinChangeDP(coins, amt); +console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); + +// 狀態壓縮後的動態規劃 +res = coinChangeDPComp(coins, amt); +console.log(`湊到目標金額所需的最少硬幣數量為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts new file mode 100644 index 000000000..0c1376918 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 零錢兌換 II:動態規劃 */ +function coinChangeIIDP(coins: Array, amt: number): number { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 初始化首列 + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + 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]; +} + +/* 零錢兌換 II:狀態壓縮後的動態規劃 */ +function coinChangeIIDPComp(coins: Array, amt: number): number { + const n = coins.length; + // 初始化 dp 表 + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + 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 */ +const coins = [1, 2, 5]; +const amt = 5; + +// 動態規劃 +let res = coinChangeIIDP(coins, amt); +console.log(`湊出目標金額的硬幣組合數量為 ${res}`); + +// 狀態壓縮後的動態規劃 +res = coinChangeIIDPComp(coins, amt); +console.log(`湊出目標金額的硬幣組合數量為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts new file mode 100644 index 000000000..de304b070 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/edit_distance.ts @@ -0,0 +1,148 @@ +/** + * File: edit_distance.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 編輯距離:暴力搜尋 */ +function editDistanceDFS(s: string, t: string, i: number, j: number): number { + // 若 s 和 t 都為空,則返回 0 + if (i === 0 && j === 0) return 0; + + // 若 s 為空,則返回 t 長度 + if (i === 0) return j; + + // 若 t 為空,則返回 s 長度 + if (j === 0) return i; + + // 若兩字元相等,則直接跳過此兩字元 + if (s.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return Math.min(insert, del, replace) + 1; +} + +/* 編輯距離:記憶化搜尋 */ +function editDistanceDFSMem( + s: string, + t: string, + mem: Array>, + i: number, + j: number +): number { + // 若 s 和 t 都為空,則返回 0 + if (i === 0 && 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.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = Math.min(insert, del, replace) + 1; + return mem[i][j]; +} + +/* 編輯距離:動態規劃 */ +function editDistanceDP(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => 0) + ); + // 狀態轉移:首行首列 + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = + Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編輯距離:狀態壓縮後的動態規劃 */ +function editDistanceDPComp(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // 狀態轉移:首行 + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // 狀態轉移:其餘行 + for (let i = 1; i <= n; i++) { + // 狀態轉移:首列 + let leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = i; + // 狀態轉移:其餘列 + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// 暴力搜尋 +let res = editDistanceDFS(s, t, n, m); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 記憶化搜尋 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => -1) +); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 動態規劃 +res = editDistanceDP(s, t); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +// 狀態壓縮後的動態規劃 +res = editDistanceDPComp(s, t); +console.log(`將 ${s} 更改為 ${t} 最少需要編輯 ${res} 步`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts new file mode 100644 index 000000000..310c876fa --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/knapsack.ts @@ -0,0 +1,134 @@ +/** + * File: knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 0-1 背包:暴力搜尋 */ +function knapsackDFS( + wgt: Array, + val: Array, + i: number, + c: number +): number { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return Math.max(no, yes); +} + +/* 0-1 背包:記憶化搜尋 */ +function knapsackDFSMem( + wgt: Array, + val: Array, + mem: Array>, + i: number, + c: number +): number { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i === 0 || c === 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] !== -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:動態規劃 */ +function knapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:狀態壓縮後的動態規劃 */ +function knapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array(cap + 1).fill(0); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + // 倒序走訪 + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 暴力搜尋 +let res = knapsackDFS(wgt, val, n, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 記憶化搜尋 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 動態規劃 +res = knapsackDP(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 狀態壓縮後的動態規劃 +res = knapsackDPComp(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts new file mode 100644 index 000000000..66980475d --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 爬樓梯最小代價:動態規劃 */ +function minCostClimbingStairsDP(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // 初始化 dp 表,用於儲存子問題的解 + const dp = new Array(n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬樓梯最小代價:狀態壓縮後的動態規劃 */ +function minCostClimbingStairsDPComp(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log(`輸入樓梯的代價串列為:${cost}`); + +let res = minCostClimbingStairsDP(cost); +console.log(`爬完樓梯的最低代價為:${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`爬完樓梯的最低代價為:${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts new file mode 100644 index 000000000..cb1d7ece5 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/min_path_sum.ts @@ -0,0 +1,132 @@ +/** + * File: min_path_sum.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 最小路徑和:暴力搜尋 */ +function minPathSumDFS( + grid: Array>, + i: number, + j: number +): number { + // 若為左上角單元格,則終止搜尋 + if (i === 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Infinity; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + const up = minPathSumDFS(grid, i - 1, j); + const left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return Math.min(left, up) + grid[i][j]; +} + +/* 最小路徑和:記憶化搜尋 */ +function minPathSumDFSMem( + grid: Array>, + mem: Array>, + i: number, + j: number +): number { + // 若為左上角單元格,則終止搜尋 + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 || j < 0) { + return Infinity; + } + // 若已有記錄,則直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左邊和上邊單元格的最小路徑代價 + const up = minPathSumDFSMem(grid, mem, i - 1, j); + const left = minPathSumDFSMem(grid, mem, i, j - 1); + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小路徑和:動態規劃 */ +function minPathSumDP(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (let i = 1; i < n; i++) { + for (let j: number = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路徑和:狀態壓縮後的動態規劃 */ +function minPathSumDPComp(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // 初始化 dp 表 + const dp = new Array(m); + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (let i = 1; i < n; i++) { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + // 狀態轉移:其餘列 + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// 暴力搜尋 +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 記憶化搜尋 +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 動態規劃 +res = minPathSumDP(grid); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +// 狀態壓縮後的動態規劃 +res = minPathSumDPComp(grid); +console.log(`從左上角到右下角的最小路徑和為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts b/zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts new file mode 100644 index 000000000..2e3b4989e --- /dev/null +++ b/zh-hant/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts @@ -0,0 +1,73 @@ +/** + * File: unbounded_knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 完全背包:動態規劃 */ +function unboundedKnapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:狀態壓縮後的動態規劃 */ +function unboundedKnapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // 初始化 dp 表 + const dp = Array.from({ length: cap + 1 }, () => 0); + // 狀態轉移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// 動態規劃 +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +// 狀態壓縮後的動態規劃 +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_graph/graph_adjacency_list.ts b/zh-hant/codes/typescript/chapter_graph/graph_adjacency_list.ts new file mode 100644 index 000000000..4849143b1 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_graph/graph_adjacency_list.ts @@ -0,0 +1,140 @@ +/** + * File: graph_adjacency_list.ts + * Created Time: 2023-02-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { Vertex } from '../modules/Vertex'; + +/* 基於鄰接表實現的無向圖類別 */ +class GraphAdjList { + // 鄰接表,key:頂點,value:該頂點的所有鄰接頂點 + adjList: Map; + + /* 建構子 */ + constructor(edges: Vertex[][]) { + this.adjList = new Map(); + // 新增所有頂點和邊 + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* 獲取頂點數量 */ + size(): number { + return this.adjList.size; + } + + /* 新增邊 */ + addEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 新增邊 vet1 - vet2 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* 刪除邊 */ + removeEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 刪除邊 vet1 - vet2 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* 新增頂點 */ + addVertex(vet: Vertex): void { + if (this.adjList.has(vet)) return; + // 在鄰接表中新增一個新鏈結串列 + this.adjList.set(vet, []); + } + + /* 刪除頂點 */ + removeVertex(vet: Vertex): void { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // 在鄰接表中刪除頂點 vet 對應的鏈結串列 + this.adjList.delete(vet); + // 走訪其他頂點的鏈結串列,刪除所有包含 vet 的邊 + for (const set of this.adjList.values()) { + const index: number = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* 列印鄰接表 */ + print(): void { + console.log('鄰接表 ='); + for (const [key, value] of this.adjList.entries()) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } +} + +// need to add the package @types/node contains type definitions for Node.js, npm i --save-dev @types/node +if (require.main === module) { + /* Driver Code */ + /* 初始化無向圖 */ + const v0 = new Vertex(1), + v1 = new Vertex(3), + v2 = new Vertex(2), + v3 = new Vertex(5), + v4 = new Vertex(4); + const edges = [ + [v0, v1], + [v1, v2], + [v2, v3], + [v0, v3], + [v2, v4], + [v3, v4], + ]; + const graph = new GraphAdjList(edges); + console.log('\n初始化後,圖為'); + graph.print(); + + /* 新增邊 */ + // 頂點 1, 2 即 v0, v2 + graph.addEdge(v0, v2); + console.log('\n新增邊 1-2 後,圖為'); + graph.print(); + + /* 刪除邊 */ + // 頂點 1, 3 即 v0, v1 + graph.removeEdge(v0, v1); + console.log('\n刪除邊 1-3 後,圖為'); + graph.print(); + + /* 新增頂點 */ + const v5 = new Vertex(6); + graph.addVertex(v5); + console.log('\n新增頂點 6 後,圖為'); + graph.print(); + + /* 刪除頂點 */ + // 頂點 3 即 v1 + graph.removeVertex(v1); + console.log('\n刪除頂點 3 後,圖為'); + graph.print(); +} + +export { GraphAdjList }; diff --git a/zh-hant/codes/typescript/chapter_graph/graph_adjacency_matrix.ts b/zh-hant/codes/typescript/chapter_graph/graph_adjacency_matrix.ts new file mode 100644 index 000000000..e19184881 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_graph/graph_adjacency_matrix.ts @@ -0,0 +1,134 @@ +/** + * File: graph_adjacency_matrix.ts + * Created Time: 2023-02-09 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基於鄰接矩陣實現的無向圖類別 */ +class GraphAdjMat { + vertices: number[]; // 頂點串列,元素代表“頂點值”,索引代表“頂點索引” + adjMat: number[][]; // 鄰接矩陣,行列索引對應“頂點索引” + + /* 建構子 */ + constructor(vertices: number[], edges: number[][]) { + this.vertices = []; + this.adjMat = []; + // 新增頂點 + for (const val of vertices) { + this.addVertex(val); + } + // 新增邊 + // 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* 獲取頂點數量 */ + size(): number { + return this.vertices.length; + } + + /* 新增頂點 */ + addVertex(val: number): void { + const n: number = this.size(); + // 向頂點串列中新增新頂點的值 + this.vertices.push(val); + // 在鄰接矩陣中新增一行 + const newRow: number[] = []; + for (let j: number = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // 在鄰接矩陣中新增一列 + for (const row of this.adjMat) { + row.push(0); + } + } + + /* 刪除頂點 */ + removeVertex(index: number): void { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在頂點串列中移除索引 index 的頂點 + this.vertices.splice(index, 1); + + // 在鄰接矩陣中刪除索引 index 的行 + this.adjMat.splice(index, 1); + // 在鄰接矩陣中刪除索引 index 的列 + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* 新增邊 */ + // 參數 i, j 對應 vertices 元素索引 + addEdge(i: number, j: number): void { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 在無向圖中,鄰接矩陣關於主對角線對稱,即滿足 (i, j) === (j, i) + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* 刪除邊 */ + // 參數 i, j 對應 vertices 元素索引 + removeEdge(i: number, j: number): void { + // 索引越界與相等處理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* 列印鄰接矩陣 */ + print(): void { + console.log('頂點串列 = ', this.vertices); + console.log('鄰接矩陣 =', this.adjMat); + } +} + +/* Driver Code */ +/* 初始化無向圖 */ +// 請注意,edges 元素代表頂點索引,即對應 vertices 元素索引 +const vertices: number[] = [1, 3, 2, 5, 4]; +const edges: number[][] = [ + [0, 1], + [1, 2], + [2, 3], + [0, 3], + [2, 4], + [3, 4], +]; +const graph: GraphAdjMat = new GraphAdjMat(vertices, edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 新增邊 */ +// 頂點 1, 2 的索引分別為 0, 2 +graph.addEdge(0, 2); +console.log('\n新增邊 1-2 後,圖為'); +graph.print(); + +/* 刪除邊 */ +// 頂點 1, 3 的索引分別為 0, 1 +graph.removeEdge(0, 1); +console.log('\n刪除邊 1-3 後,圖為'); +graph.print(); + +/* 新增頂點 */ +graph.addVertex(6); +console.log('\n新增頂點 6 後,圖為'); +graph.print(); + +/* 刪除頂點 */ +// 頂點 3 的索引為 1 +graph.removeVertex(1); +console.log('\n刪除頂點 3 後,圖為'); +graph.print(); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_graph/graph_bfs.ts b/zh-hant/codes/typescript/chapter_graph/graph_bfs.ts new file mode 100644 index 000000000..8095f98c2 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_graph/graph_bfs.ts @@ -0,0 +1,61 @@ +/** + * File: graph_bfs.ts + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { GraphAdjList } from './graph_adjacency_list'; +import { Vertex } from '../modules/Vertex'; + +/* 廣度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // 頂點走訪序列 + const res: Vertex[] = []; + // 雜湊表,用於記錄已被訪問過的頂點 + const visited: Set = new Set(); + visited.add(startVet); + // 佇列用於實現 BFS + const que = [startVet]; + // 以頂點 vet 為起點,迴圈直至訪問完所有頂點 + while (que.length) { + const vet = que.shift(); // 佇列首頂點出隊 + res.push(vet); // 記錄訪問頂點 + // 走訪該頂點的所有鄰接頂點 + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + que.push(adjVet); // 只入列未訪問 + visited.add(adjVet); // 標記該頂點已被訪問 + } + } + // 返回頂點走訪序列 + return res; +} + +/* Driver Code */ +/* 初始化無向圖 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const 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]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 廣度優先走訪 */ +const res = graphBFS(graph, v[0]); +console.log('\n廣度優先走訪(BFS)頂點序列為'); +console.log(Vertex.vetsToVals(res)); diff --git a/zh-hant/codes/typescript/chapter_graph/graph_dfs.ts b/zh-hant/codes/typescript/chapter_graph/graph_dfs.ts new file mode 100644 index 000000000..59f546899 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_graph/graph_dfs.ts @@ -0,0 +1,58 @@ +/** + * File: graph_dfs.ts + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { Vertex } from '../modules/Vertex'; +import { GraphAdjList } from './graph_adjacency_list'; + +/* 深度優先走訪輔助函式 */ +function dfs( + graph: GraphAdjList, + visited: Set, + res: Vertex[], + vet: Vertex +): void { + res.push(vet); // 記錄訪問頂點 + visited.add(vet); // 標記該頂點已被訪問 + // 走訪該頂點的所有鄰接頂點 + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // 跳過已被訪問的頂點 + } + // 遞迴訪問鄰接頂點 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度優先走訪 */ +// 使用鄰接表來表示圖,以便獲取指定頂點的所有鄰接頂點 +function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // 頂點走訪序列 + const res: Vertex[] = []; + // 雜湊表,用於記錄已被訪問過的頂點 + const visited: Set = new Set(); + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +/* 初始化無向圖 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); +const 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]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初始化後,圖為'); +graph.print(); + +/* 深度優先走訪 */ +const res = graphDFS(graph, v[0]); +console.log('\n深度優先走訪(DFS)頂點序列為'); +console.log(Vertex.vetsToVals(res)); diff --git a/zh-hant/codes/typescript/chapter_greedy/coin_change_greedy.ts b/zh-hant/codes/typescript/chapter_greedy/coin_change_greedy.ts new file mode 100644 index 000000000..d8a54c1bd --- /dev/null +++ b/zh-hant/codes/typescript/chapter_greedy/coin_change_greedy.ts @@ -0,0 +1,50 @@ +/** + * File: coin_change_greedy.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 零錢兌換:貪婪 */ +function coinChangeGreedy(coins: number[], amt: number): number { + // 假設 coins 陣列有序 + let i = coins.length - 1; + let count = 0; + // 迴圈進行貪婪選擇,直到無剩餘金額 + while (amt > 0) { + // 找到小於且最接近剩餘金額的硬幣 + while (i > 0 && coins[i] > amt) { + i--; + } + // 選擇 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,則返回 -1 + return amt === 0 ? count : -1; +} + +/* Driver Code */ +// 貪婪:能夠保證找到全域性最優解 +let coins: number[] = [1, 5, 10, 20, 50, 100]; +let amt: number = 186; +let res: number = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); + +// 貪婪:無法保證找到全域性最優解 +coins = [1, 20, 50]; +amt = 60; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); +console.log('實際上需要的最少數量為 3 ,即 20 + 20 + 20'); + +// 貪婪:無法保證找到全域性最優解 +coins = [1, 49, 50]; +amt = 98; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`湊到 ${amt} 所需的最少硬幣數量為 ${res}`); +console.log('實際上需要的最少數量為 2 ,即 49 + 49'); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_greedy/fractional_knapsack.ts b/zh-hant/codes/typescript/chapter_greedy/fractional_knapsack.ts new file mode 100644 index 000000000..fe7f142ca --- /dev/null +++ b/zh-hant/codes/typescript/chapter_greedy/fractional_knapsack.ts @@ -0,0 +1,50 @@ +/** + * File: fractional_knapsack.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 物品 */ +class Item { + w: number; // 物品重量 + v: number; // 物品價值 + + constructor(w: number, v: number) { + this.w = w; + this.v = v; + } +} + +/* 分數背包:貪婪 */ +function fractionalKnapsack(wgt: number[], val: number[], cap: number): number { + // 建立物品串列,包含兩個屬性:重量、價值 + const items: Item[] = wgt.map((w, i) => new Item(w, val[i])); + // 按照單位價值 item.v / item.w 從高到低進行排序 + items.sort((a, b) => b.v / b.w - a.v / a.w); + // 迴圈貪婪選擇 + let res = 0; + for (const item of items) { + if (item.w <= cap) { + // 若剩餘容量充足,則將當前物品整個裝進背包 + res += item.v; + cap -= item.w; + } else { + // 若剩餘容量不足,則將當前物品的一部分裝進背包 + res += (item.v / item.w) * cap; + // 已無剩餘容量,因此跳出迴圈 + break; + } + } + return res; +} + +/* Driver Code */ +const wgt: number[] = [10, 20, 30, 40, 50]; +const val: number[] = [50, 120, 150, 210, 240]; +const cap: number = 50; + +// 貪婪演算法 +const res: number = fractionalKnapsack(wgt, val, cap); +console.log(`不超過背包容量的最大物品價值為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_greedy/max_capacity.ts b/zh-hant/codes/typescript/chapter_greedy/max_capacity.ts new file mode 100644 index 000000000..a835dfee6 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_greedy/max_capacity.ts @@ -0,0 +1,36 @@ +/** + * File: max_capacity.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大容量:貪婪 */ +function maxCapacity(ht: number[]): number { + // 初始化 i, j,使其分列陣列兩端 + let i = 0, + j = ht.length - 1; + // 初始最大容量為 0 + let res = 0; + // 迴圈貪婪選擇,直至兩板相遇 + while (i < j) { + // 更新最大容量 + const cap: number = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 向內移動短板 + if (ht[i] < ht[j]) { + i += 1; + } else { + j -= 1; + } + } + return res; +} + +/* Driver Code */ +const ht: number[] = [3, 8, 5, 2, 7, 7, 3, 4]; + +// 貪婪演算法 +const res: number = maxCapacity(ht); +console.log(`最大容量為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_greedy/max_product_cutting.ts b/zh-hant/codes/typescript/chapter_greedy/max_product_cutting.ts new file mode 100644 index 000000000..97ded2e8b --- /dev/null +++ b/zh-hant/codes/typescript/chapter_greedy/max_product_cutting.ts @@ -0,0 +1,35 @@ +/** + * File: max_product_cutting.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大切分乘積:貪婪 */ +function maxProductCutting(n: number): number { + // 當 n <= 3 時,必須切分出一個 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 貪婪地切分出 3 ,a 為 3 的個數,b 為餘數 + let a: number = Math.floor(n / 3); + let b: number = n % 3; + if (b === 1) { + // 當餘數為 1 時,將一對 1 * 3 轉化為 2 * 2 + return Math.pow(3, a - 1) * 2 * 2; + } + if (b === 2) { + // 當餘數為 2 時,不做處理 + return Math.pow(3, a) * 2; + } + // 當餘數為 0 時,不做處理 + return Math.pow(3, a); +} + +/* Driver Code */ +let n: number = 58; + +// 貪婪演算法 +let res: number = maxProductCutting(n); +console.log(`最大切分乘積為 ${res}`); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_hashing/array_hash_map.ts b/zh-hant/codes/typescript/chapter_hashing/array_hash_map.ts new file mode 100644 index 000000000..018b143bf --- /dev/null +++ b/zh-hant/codes/typescript/chapter_hashing/array_hash_map.ts @@ -0,0 +1,134 @@ +/** + * File: array_hash_map.ts + * Created Time: 2022-12-20 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + public key: number; + public val: string; + + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 基於陣列實現的雜湊表 */ +class ArrayHashMap { + private readonly buckets: (Pair | null)[]; + + constructor() { + // 初始化陣列,包含 100 個桶 + this.buckets = new Array(100).fill(null); + } + + /* 雜湊函式 */ + private hashFunc(key: number): number { + return key % 100; + } + + /* 查詢操作 */ + public get(key: number): string | null { + let index = this.hashFunc(key); + let pair = this.buckets[index]; + if (pair === null) return null; + return pair.val; + } + + /* 新增操作 */ + public set(key: number, val: string) { + let index = this.hashFunc(key); + this.buckets[index] = new Pair(key, val); + } + + /* 刪除操作 */ + public delete(key: number) { + let index = this.hashFunc(key); + // 置為 null ,代表刪除 + this.buckets[index] = null; + } + + /* 獲取所有鍵值對 */ + public entries(): (Pair | null)[] { + let arr: (Pair | null)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i]); + } + } + return arr; + } + + /* 獲取所有鍵 */ + public keys(): (number | undefined)[] { + let arr: (number | undefined)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i].key); + } + } + return arr; + } + + /* 獲取所有值 */ + public values(): (string | undefined)[] { + let arr: (string | undefined)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i].val); + } + } + return arr; + } + + /* 列印雜湊表 */ + public print() { + let pairSet = this.entries(); + for (const pair of pairSet) { + console.info(`${pair.key} -> ${pair.val}`); + } + } +} + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new ArrayHashMap(); +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.set(12836, '小哈'); +map.set(15937, '小囉'); +map.set(16750, '小算'); +map.set(13276, '小法'); +map.set(10583, '小鴨'); +console.info('\n新增完成後,雜湊表為\nKey -> Value'); +map.print(); + +/* 查詢操作 */ +// 向雜湊表中輸入鍵 key ,得到值 value +let name = map.get(15937); +console.info('\n輸入學號 15937 ,查詢到姓名 ' + name); + +/* 刪除操作 */ +// 在雜湊表中刪除鍵值對 (key, value) +map.delete(10583); +console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); +map.print(); + +/* 走訪雜湊表 */ +console.info('\n走訪鍵值對 Key->Value'); +for (const pair of map.entries()) { + if (!pair) continue; + console.info(pair.key + ' -> ' + pair.val); +} +console.info('\n單獨走訪鍵 Key'); +for (const key of map.keys()) { + console.info(key); +} +console.info('\n單獨走訪值 Value'); +for (const val of map.values()) { + console.info(val); +} + +export {}; diff --git a/zh-hant/codes/typescript/chapter_hashing/hash_map.ts b/zh-hant/codes/typescript/chapter_hashing/hash_map.ts new file mode 100644 index 000000000..908451c08 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_hashing/hash_map.ts @@ -0,0 +1,46 @@ +/** + * File: hash_map.ts + * Created Time: 2022-12-20 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new Map(); + +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.set(12836, '小哈'); +map.set(15937, '小囉'); +map.set(16750, '小算'); +map.set(13276, '小法'); +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); +console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); +console.info(map); + +/* 走訪雜湊表 */ +console.info('\n走訪鍵值對 Key->Value'); +for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); +} +console.info('\n單獨走訪鍵 Key'); +for (const k of map.keys()) { + console.info(k); +} +console.info('\n單獨走訪值 Value'); +for (const v of map.values()) { + console.info(v); +} + +export {}; diff --git a/zh-hant/codes/typescript/chapter_hashing/hash_map_chaining.ts b/zh-hant/codes/typescript/chapter_hashing/hash_map_chaining.ts new file mode 100644 index 000000000..a48c985e7 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_hashing/hash_map_chaining.ts @@ -0,0 +1,146 @@ +/** + * File: hash_map_chaining.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + key: number; + val: string; + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 鏈式位址雜湊表 */ +class HashMapChaining { + #size: number; // 鍵值對數量 + #capacity: number; // 雜湊表容量 + #loadThres: number; // 觸發擴容的負載因子閾值 + #extendRatio: number; // 擴容倍數 + #buckets: Pair[][]; // 桶陣列 + + /* 建構子 */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2.0 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* 雜湊函式 */ + #hashFunc(key: number): number { + return key % this.#capacity; + } + + /* 負載因子 */ + #loadFactor(): number { + return this.#size / this.#capacity; + } + + /* 查詢操作 */ + get(key: number): string | null { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 走訪桶,若找到 key ,則返回對應 val + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // 若未找到 key ,則返回 null + return null; + } + + /* 新增操作 */ + put(key: number, val: string): void { + // 當負載因子超過閾值時,執行擴容 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // 若無該 key ,則將鍵值對新增至尾部 + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* 刪除操作 */ + remove(key: number): void { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // 走訪桶,從中刪除鍵值對 + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* 擴容雜湊表 */ + #extend(): void { + // 暫存原雜湊表 + const bucketsTmp = this.#buckets; + // 初始化擴容後的新雜湊表 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + print(): void { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* 初始化雜湊表 */ +const map = new HashMapChaining(); + +/* 新增操作 */ +// 在雜湊表中新增鍵值對 (key, value) +map.put(12836, '小哈'); +map.put(15937, '小囉'); +map.put(16750, '小算'); +map.put(13276, '小法'); +map.put(10583, '小鴨'); +console.log('\n新增完成後,雜湊表為\nKey -> Value'); +map.print(); + +/* 查詢操作 */ +// 向雜湊表中輸入鍵 key ,得到值 value +const name = map.get(13276); +console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); + +/* 刪除操作 */ +// 在雜湊表中刪除鍵值對 (key, value) +map.remove(12836); +console.log('\n刪除 12836 後,雜湊表為\nKey -> Value'); +map.print(); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_hashing/hash_map_open_addressing.ts b/zh-hant/codes/typescript/chapter_hashing/hash_map_open_addressing.ts new file mode 100644 index 000000000..e0bdf778f --- /dev/null +++ b/zh-hant/codes/typescript/chapter_hashing/hash_map_open_addressing.ts @@ -0,0 +1,182 @@ +/** + * File: hash_map_open_addressing.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) + */ + +/* 鍵值對 Number -> String */ +class Pair { + key: number; + val: string; + + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 開放定址雜湊表 */ +class HashMapOpenAddressing { + private size: number; // 鍵值對數量 + private capacity: number; // 雜湊表容量 + private loadThres: number; // 觸發擴容的負載因子閾值 + private extendRatio: number; // 擴容倍數 + private buckets: Array; // 桶陣列 + private TOMBSTONE: Pair; // 刪除標記 + + /* 建構子 */ + constructor() { + this.size = 0; // 鍵值對數量 + this.capacity = 4; // 雜湊表容量 + this.loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 + this.extendRatio = 2; // 擴容倍數 + this.buckets = Array(this.capacity).fill(null); // 桶陣列 + this.TOMBSTONE = new Pair(-1, '-1'); // 刪除標記 + } + + /* 雜湊函式 */ + private hashFunc(key: number): number { + return key % this.capacity; + } + + /* 負載因子 */ + private loadFactor(): number { + return this.size / this.capacity; + } + + /* 搜尋 key 對應的桶索引 */ + private findBucket(key: number): number { + let index = this.hashFunc(key); + let firstTombstone = -1; + // 線性探查,當遇到空桶時跳出 + while (this.buckets[index] !== null) { + // 若遇到 key ,返回對應的桶索引 + if (this.buckets[index]!.key === key) { + // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 + if (firstTombstone !== -1) { + this.buckets[firstTombstone] = this.buckets[index]; + this.buckets[index] = this.TOMBSTONE; + return firstTombstone; // 返回移動後的桶索引 + } + return index; // 返回桶索引 + } + // 記錄遇到的首個刪除標記 + if ( + firstTombstone === -1 && + this.buckets[index] === this.TOMBSTONE + ) { + firstTombstone = index; + } + // 計算桶索引,越過尾部則返回頭部 + index = (index + 1) % this.capacity; + } + // 若 key 不存在,則返回新增點的索引 + return firstTombstone === -1 ? index : firstTombstone; + } + + /* 查詢操作 */ + get(key: number): string | null { + // 搜尋 key 對應的桶索引 + const index = this.findBucket(key); + // 若找到鍵值對,則返回對應 val + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + return this.buckets[index]!.val; + } + // 若鍵值對不存在,則返回 null + return null; + } + + /* 新增操作 */ + put(key: number, val: string): void { + // 當負載因子超過閾值時,執行擴容 + if (this.loadFactor() > this.loadThres) { + this.extend(); + } + // 搜尋 key 對應的桶索引 + const index = this.findBucket(key); + // 若找到鍵值對,則覆蓋 val 並返回 + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index]!.val = val; + return; + } + // 若鍵值對不存在,則新增該鍵值對 + this.buckets[index] = new Pair(key, val); + this.size++; + } + + /* 刪除操作 */ + remove(key: number): void { + // 搜尋 key 對應的桶索引 + const index = this.findBucket(key); + // 若找到鍵值對,則用刪除標記覆蓋它 + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index] = this.TOMBSTONE; + this.size--; + } + } + + /* 擴容雜湊表 */ + private extend(): void { + // 暫存原雜湊表 + const bucketsTmp = this.buckets; + // 初始化擴容後的新雜湊表 + this.capacity *= this.extendRatio; + this.buckets = Array(this.capacity).fill(null); + this.size = 0; + // 將鍵值對從原雜湊表搬運至新雜湊表 + for (const pair of bucketsTmp) { + if (pair !== null && pair !== this.TOMBSTONE) { + this.put(pair.key, pair.val); + } + } + } + + /* 列印雜湊表 */ + print(): void { + for (const pair of this.buckets) { + if (pair === null) { + console.log('null'); + } else if (pair === this.TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); + } + } + } +} + +/* Driver Code */ +// 初始化雜湊表 +const hashmap = new HashMapOpenAddressing(); + +// 新增操作 +// 在雜湊表中新增鍵值對 (key, val) +hashmap.put(12836, '小哈'); +hashmap.put(15937, '小囉'); +hashmap.put(16750, '小算'); +hashmap.put(13276, '小法'); +hashmap.put(10583, '小鴨'); +console.log('\n新增完成後,雜湊表為\nKey -> Value'); +hashmap.print(); + +// 查詢操作 +// 向雜湊表中輸入鍵 key ,得到值 val +const name = hashmap.get(13276); +console.log('\n輸入學號 13276 ,查詢到姓名 ' + name); + +// 刪除操作 +// 在雜湊表中刪除鍵值對 (key, val) +hashmap.remove(16750); +console.log('\n刪除 16750 後,雜湊表為\nKey -> Value'); +hashmap.print(); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_hashing/simple_hash.ts b/zh-hant/codes/typescript/chapter_hashing/simple_hash.ts new file mode 100644 index 000000000..06f89a833 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_hashing/simple_hash.ts @@ -0,0 +1,60 @@ +/** + * File: simple_hash.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 加法雜湊 */ +function addHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 乘法雜湊 */ +function mulHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 互斥或雜湊 */ +function xorHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash & MODULUS; +} + +/* 旋轉雜湊 */ +function rotHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello 演算法'; + +let hash = addHash(key); +console.log('加法雜湊值為 ' + hash); + +hash = mulHash(key); +console.log('乘法雜湊值為 ' + hash); + +hash = xorHash(key); +console.log('互斥或雜湊值為 ' + hash); + +hash = rotHash(key); +console.log('旋轉雜湊值為 ' + hash); diff --git a/zh-hant/codes/typescript/chapter_heap/my_heap.ts b/zh-hant/codes/typescript/chapter_heap/my_heap.ts new file mode 100644 index 000000000..cdaabad4f --- /dev/null +++ b/zh-hant/codes/typescript/chapter_heap/my_heap.ts @@ -0,0 +1,155 @@ +/** + * File: my_heap.ts + * Created Time: 2023-02-07 + * Author: Justin (xiefahit@gmail.com) + */ + +import { printHeap } from '../modules/PrintUtil'; + +/* 最大堆積類別 */ +class MaxHeap { + private maxHeap: number[]; + /* 建構子,建立空堆積或根據輸入串列建堆積 */ + constructor(nums?: number[]) { + // 將串列元素原封不動新增進堆積 + this.maxHeap = nums === undefined ? [] : [...nums]; + // 堆積化除葉節點以外的其他所有節點 + for (let i = this.parent(this.size() - 1); i >= 0; i--) { + this.siftDown(i); + } + } + + /* 獲取左子節點的索引 */ + private left(i: number): number { + return 2 * i + 1; + } + + /* 獲取右子節點的索引 */ + private right(i: number): number { + return 2 * i + 2; + } + + /* 獲取父節點的索引 */ + private parent(i: number): number { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 交換元素 */ + private swap(i: number, j: number): void { + const tmp = this.maxHeap[i]; + this.maxHeap[i] = this.maxHeap[j]; + this.maxHeap[j] = tmp; + } + + /* 獲取堆積大小 */ + public size(): number { + return this.maxHeap.length; + } + + /* 判斷堆積是否為空 */ + public isEmpty(): boolean { + return this.size() === 0; + } + + /* 訪問堆積頂元素 */ + public peek(): number { + return this.maxHeap[0]; + } + + /* 元素入堆積 */ + public push(val: number): void { + // 新增節點 + this.maxHeap.push(val); + // 從底至頂堆積化 + this.siftUp(this.size() - 1); + } + + /* 從節點 i 開始,從底至頂堆積化 */ + private siftUp(i: number): void { + while (true) { + // 獲取節點 i 的父節點 + const p = this.parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; + // 交換兩節點 + this.swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + /* 元素出堆積 */ + public pop(): number { + // 判空處理 + if (this.isEmpty()) throw new RangeError('Heap is empty.'); + // 交換根節點與最右葉節點(交換首元素與尾元素) + this.swap(0, this.size() - 1); + // 刪除節點 + const val = this.maxHeap.pop(); + // 從頂至底堆積化 + this.siftDown(0); + // 返回堆積頂元素 + return val; + } + + /* 從節點 i 開始,從頂至底堆積化 */ + private siftDown(i: number): void { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + const l = this.left(i), + r = this.right(i); + let ma = i; + if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; + if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma === i) break; + // 交換兩節點 + this.swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + /* 列印堆積(二元樹) */ + public print(): void { + printHeap(this.maxHeap); + } + + /* 取出堆積中元素 */ + public getMaxHeap(): number[] { + return this.maxHeap; + } +} + +/* Driver Code */ +if (require.main === module) { + /* 初始化大頂堆積 */ + const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + console.log('\n輸入串列並建堆積後'); + maxHeap.print(); + + /* 獲取堆積頂元素 */ + let peek = maxHeap.peek(); + console.log(`\n堆積頂元素為 ${peek}`); + + /* 元素入堆積 */ + const val = 7; + maxHeap.push(val); + console.log(`\n元素 ${val} 入堆積後`); + maxHeap.print(); + + /* 堆積頂元素出堆積 */ + peek = maxHeap.pop(); + console.log(`\n堆積頂元素 ${peek} 出堆積後`); + maxHeap.print(); + + /* 獲取堆積大小 */ + const size = maxHeap.size(); + console.log(`\n堆積元素數量為 ${size}`); + + /* 判斷堆積是否為空 */ + const isEmpty = maxHeap.isEmpty(); + console.log(`\n堆積是否為空 ${isEmpty}`); +} + +export { MaxHeap }; diff --git a/zh-hant/codes/typescript/chapter_heap/top_k.ts b/zh-hant/codes/typescript/chapter_heap/top_k.ts new file mode 100644 index 000000000..5aa3ea491 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_heap/top_k.ts @@ -0,0 +1,58 @@ +/** + * File: top_k.ts + * Created Time: 2023-08-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { MaxHeap } from './my_heap'; + +/* 元素入堆積 */ +function pushMinHeap(maxHeap: MaxHeap, val: number): void { + // 元素取反 + maxHeap.push(-val); +} + +/* 元素出堆積 */ +function popMinHeap(maxHeap: MaxHeap): number { + // 元素取反 + return -maxHeap.pop(); +} + +/* 訪問堆積頂元素 */ +function peekMinHeap(maxHeap: MaxHeap): number { + // 元素取反 + return -maxHeap.peek(); +} + +/* 取出堆積中元素 */ +function getMinHeap(maxHeap: MaxHeap): number[] { + // 元素取反 + return maxHeap.getMaxHeap().map((num: number) => -num); +} + +/* 基於堆積查詢陣列中最大的 k 個元素 */ +function topKHeap(nums: number[], k: number): number[] { + // 初始化小頂堆積 + // 請注意:我們將堆積中所有元素取反,從而用大頂堆積來模擬小頂堆積 + const maxHeap = new MaxHeap([]); + // 將陣列的前 k 個元素入堆積 + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // 從第 k+1 個元素開始,保持堆積的長度為 k + for (let i = k; i < nums.length; i++) { + // 若當前元素大於堆積頂元素,則將堆積頂元素出堆積、當前元素入堆積 + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // 返回堆積中元素 + return getMinHeap(maxHeap); +} + +/* Driver Code */ +const nums = [1, 7, 6, 3, 2]; +const k = 3; +const res = topKHeap(nums, k); +console.log(`最大的 ${k} 個元素為`, res); diff --git a/zh-hant/codes/typescript/chapter_searching/binary_search.ts b/zh-hant/codes/typescript/chapter_searching/binary_search.ts new file mode 100644 index 000000000..83f37b08e --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/binary_search.ts @@ -0,0 +1,65 @@ +/** + * File: binary_search.ts + * Created Time: 2022-12-27 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* 二分搜尋(雙閉區間) */ +function binarySearch(nums: number[], target: number): number { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + let i = 0, + j = nums.length - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + // 計算中點索引 m + const m = Math.floor(i + (j - i) / 2); + if (nums[m] < target) { + // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + } else if (nums[m] > target) { + // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + } else { + // 找到目標元素,返回其索引 + return m; + } + } + return -1; // 未找到目標元素,返回 -1 +} + +/* 二分搜尋(左閉右開區間) */ +function binarySearchLCRO(nums: number[], target: number): number { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + let i = 0, + j = nums.length; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i < j) { + // 計算中點索引 m + const m = Math.floor(i + (j - i) / 2); + if (nums[m] < target) { + // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + } else if (nums[m] > target) { + // 此情況說明 target 在區間 [i, m) 中 + j = m; + } else { + // 找到目標元素,返回其索引 + return m; + } + } + return -1; // 未找到目標元素,返回 -1 +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + +/* 二分搜尋(雙閉區間) */ +let index = binarySearch(nums, target); +console.info('目標元素 6 的索引 = %d', index); + +/* 二分搜尋(左閉右開區間) */ +index = binarySearchLCRO(nums, target); +console.info('目標元素 6 的索引 = %d', index); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_searching/binary_search_edge.ts b/zh-hant/codes/typescript/chapter_searching/binary_search_edge.ts new file mode 100644 index 000000000..a8b24f3bd --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/binary_search_edge.ts @@ -0,0 +1,46 @@ +/** + * File: binary_search_edge.ts + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ +import { binarySearchInsertion } from './binary_search_insertion'; + +/* 二分搜尋最左一個 target */ +function binarySearchLeftEdge(nums: Array, target: number): number { + // 等價於查詢 target 的插入點 + const i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i === nums.length || nums[i] !== target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分搜尋最右一個 target */ +function binarySearchRightEdge(nums: Array, target: number): number { + // 轉化為查詢最左一個 target + 1 + const i = binarySearchInsertion(nums, target + 1); + // j 指向最右一個 target ,i 指向首個大於 target 的元素 + const j = i - 1; + // 未找到 target ,返回 -1 + if (j === -1 || nums[j] !== target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +// 包含重複元素的陣列 +let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋左邊界和右邊界 +for (const target of [6, 7]) { + let index = binarySearchLeftEdge(nums, target); + console.log('最左一個元素 ' + target + ' 的索引為 ' + index); + index = binarySearchRightEdge(nums, target); + console.log('最右一個元素 ' + target + ' 的索引為 ' + index); +} + +export {}; diff --git a/zh-hant/codes/typescript/chapter_searching/binary_search_insertion.ts b/zh-hant/codes/typescript/chapter_searching/binary_search_insertion.ts new file mode 100644 index 000000000..e42bedc25 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/binary_search_insertion.ts @@ -0,0 +1,65 @@ +/** + * File: binary_search_insertion.ts + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 二分搜尋插入點(無重複元素) */ +function binarySearchInsertionSimple( + nums: Array, + target: number +): number { + let i = 0, + j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入點 m + } + } + // 未找到 target ,返回插入點 i + return i; +} + +/* 二分搜尋插入點(存在重複元素) */ +function binarySearchInsertion(nums: Array, target: number): number { + let i = 0, + j = nums.length - 1; // 初始化雙閉區間 [0, n-1] + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 計算中點索引 m, 使用 Math.floor() 向下取整 + if (nums[m] < target) { + i = m + 1; // target 在區間 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在區間 [i, m-1] 中 + } else { + j = m - 1; // 首個小於 target 的元素在區間 [i, m-1] 中 + } + } + // 返回插入點 i + return i; +} + +/* Driver Code */ +// 無重複元素的陣列 +let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋插入點 +for (const target of [6, 9]) { + const index = binarySearchInsertionSimple(nums, target); + console.log('元素 ' + target + ' 的插入點的索引為 ' + index); +} + +// 包含重複元素的陣列 +nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n陣列 nums = ' + nums); +// 二分搜尋插入點 +for (const target of [2, 6, 20]) { + const index = binarySearchInsertion(nums, target); + console.log('元素 ' + target + ' 的插入點的索引為 ' + index); +} + +export { binarySearchInsertion }; diff --git a/zh-hant/codes/typescript/chapter_searching/hashing_search.ts b/zh-hant/codes/typescript/chapter_searching/hashing_search.ts new file mode 100644 index 000000000..da7bd521b --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/hashing_search.ts @@ -0,0 +1,50 @@ +/** + * File: hashing_search.ts + * Created Time: 2022-12-29 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { ListNode, arrToLinkedList } from '../modules/ListNode'; + +/* 雜湊查詢(陣列) */ +function hashingSearchArray(map: Map, target: number): number { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + return map.has(target) ? (map.get(target) as number) : -1; +} + +/* 雜湊查詢(鏈結串列) */ +function hashingSearchLinkedList( + map: Map, + target: number +): ListNode | null { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + return map.has(target) ? (map.get(target) as ListNode) : null; +} + +/* Driver Code */ +const target = 3; + +/* 雜湊查詢(陣列) */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +// 初始化雜湊表 +const map = new Map(); +for (let i = 0; i < nums.length; i++) { + map.set(nums[i], i); // key: 元素,value: 索引 +} +const index = hashingSearchArray(map, target); +console.log('目標元素 3 的索引 = ' + index); + +/* 雜湊查詢(鏈結串列) */ +let head = arrToLinkedList(nums); +// 初始化雜湊表 +const map1 = new Map(); +while (head != null) { + map1.set(head.val, head); // key: 節點值,value: 節點 + head = head.next; +} +const node = hashingSearchLinkedList(map1, target); +console.log('目標節點值 3 的對應節點物件為', node); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_searching/linear_search.ts b/zh-hant/codes/typescript/chapter_searching/linear_search.ts new file mode 100644 index 000000000..9e2ec49f2 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/linear_search.ts @@ -0,0 +1,52 @@ +/** + * File: linear_search.ts + * Created Time: 2023-01-07 + * Author: Daniel (better.sunjian@gmail.com) + */ + +import { ListNode, arrToLinkedList } from '../modules/ListNode'; + +/* 線性查詢(陣列)*/ +function linearSearchArray(nums: number[], target: number): number { + // 走訪陣列 + for (let i = 0; i < nums.length; i++) { + // 找到目標元素,返回其索引 + if (nums[i] === target) { + return i; + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +/* 線性查詢(鏈結串列)*/ +function linearSearchLinkedList( + head: ListNode | null, + target: number +): ListNode | null { + // 走訪鏈結串列 + while (head) { + // 找到目標節點,返回之 + if (head.val === target) { + return head; + } + head = head.next; + } + // 未找到目標節點,返回 null + return null; +} + +/* Driver Code */ +const target = 3; + +/* 在陣列中執行線性查詢 */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +const index = linearSearchArray(nums, target); +console.log('目標元素 3 的索引 =', index); + +/* 在鏈結串列中執行線性查詢 */ +const head = arrToLinkedList(nums); +const node = linearSearchLinkedList(head, target); +console.log('目標節點值 3 的對應節點物件為', node); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_searching/two_sum.ts b/zh-hant/codes/typescript/chapter_searching/two_sum.ts new file mode 100644 index 000000000..74670f565 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_searching/two_sum.ts @@ -0,0 +1,49 @@ +/** + * File: two_sum.ts + * Created Time: 2022-12-15 + * Author: gyt95 (gytkwan@gmail.com) + */ + +/* 方法一:暴力列舉 */ +function twoSumBruteForce(nums: number[], target: number): number[] { + const n = nums.length; + // 兩層迴圈,時間複雜度為 O(n^2) + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + if (nums[i] + nums[j] === target) { + return [i, j]; + } + } + } + return []; +} + +/* 方法二:輔助雜湊表 */ +function twoSumHashTable(nums: number[], target: number): number[] { + // 輔助雜湊表,空間複雜度為 O(n) + let m: Map = new Map(); + // 單層迴圈,時間複雜度為 O(n) + for (let i = 0; i < nums.length; i++) { + let index = m.get(target - nums[i]); + if (index !== undefined) { + return [index, i]; + } else { + m.set(nums[i], i); + } + } + return []; +} + +/* Driver Code */ +// 方法一 +const nums = [2, 7, 11, 15], + target = 13; + +let res = twoSumBruteForce(nums, target); +console.log('方法一 res = ', res); + +// 方法二 +res = twoSumHashTable(nums, target); +console.log('方法二 res = ', res); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/bubble_sort.ts b/zh-hant/codes/typescript/chapter_sorting/bubble_sort.ts new file mode 100644 index 000000000..ef775d3bb --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/bubble_sort.ts @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 泡沫排序 */ +function bubbleSort(nums: number[]): void { + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* 泡沫排序(標誌最佳化)*/ +function bubbleSortWithFlag(nums: number[]): void { + // 外迴圈:未排序區間為 [0, i] + for (let i = nums.length - 1; i > 0; i--) { + let flag = false; // 初始化標誌位 + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 記錄交換元素 + } + } + if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +bubbleSort(nums); +console.log('泡沫排序完成後 nums =', nums); + +const nums1 = [4, 1, 3, 1, 5, 2]; +bubbleSortWithFlag(nums1); +console.log('泡沫排序完成後 nums =', nums1); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/bucket_sort.ts b/zh-hant/codes/typescript/chapter_sorting/bucket_sort.ts new file mode 100644 index 000000000..c00f77fe4 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/bucket_sort.ts @@ -0,0 +1,41 @@ +/** + * File: bucket_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 桶排序 */ +function bucketSort(nums: number[]): void { + // 初始化 k = n/2 個桶,預期向每個桶分配 2 個元素 + const k = nums.length / 2; + const buckets: number[][] = []; + for (let i = 0; i < k; i++) { + buckets.push([]); + } + // 1. 將陣列元素分配到各個桶中 + for (const num of nums) { + // 輸入資料範圍為 [0, 1),使用 num * k 對映到索引範圍 [0, k-1] + const i = Math.floor(num * k); + // 將 num 新增進桶 i + buckets[i].push(num); + } + // 2. 對各個桶執行排序 + for (const bucket of buckets) { + // 使用內建排序函式,也可以替換成其他排序演算法 + bucket.sort((a, b) => a - b); + } + // 3. 走訪桶合併結果 + let i = 0; + for (const bucket of buckets) { + for (const num of bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; +bucketSort(nums); +console.log('桶排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/counting_sort.ts b/zh-hant/codes/typescript/chapter_sorting/counting_sort.ts new file mode 100644 index 000000000..db62710be --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/counting_sort.ts @@ -0,0 +1,73 @@ +/** + * File: counting_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 計數排序 */ +// 簡單實現,無法用於排序物件 +function countingSortNaive(nums: number[]): void { + // 1. 統計陣列最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + const counter: number[] = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 走訪 counter ,將各元素填入原陣列 nums + let i = 0; + for (let num = 0; num < m + 1; num++) { + for (let j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* 計數排序 */ +// 完整實現,可排序物件,並且是穩定排序 +function countingSort(nums: number[]): void { + // 1. 統計陣列最大元素 m + let m = 0; + for (const num of nums) { + m = Math.max(m, num); + } + // 2. 統計各數字的出現次數 + // counter[num] 代表 num 的出現次數 + const counter: number[] = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. 求 counter 的前綴和,將“出現次數”轉換為“尾索引” + // 即 counter[num]-1 是 num 在 res 中最後一次出現的索引 + for (let i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序走訪 nums ,將各元素填入結果陣列 res + // 初始化陣列 res 用於記錄結果 + const n = nums.length; + const res: number[] = new Array(n); + for (let i = n - 1; i >= 0; i--) { + const num = nums[i]; + res[counter[num] - 1] = num; // 將 num 放置到對應索引處 + counter[num]--; // 令前綴和自減 1 ,得到下次放置 num 的索引 + } + // 使用結果陣列 res 覆蓋原陣列 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Driver Code */ +const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSortNaive(nums); +console.log('計數排序(無法排序物件)完成後 nums =', nums); + +const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSort(nums1); +console.log('計數排序完成後 nums1 =', nums1); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/heap_sort.ts b/zh-hant/codes/typescript/chapter_sorting/heap_sort.ts new file mode 100644 index 000000000..97414f529 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/heap_sort.ts @@ -0,0 +1,51 @@ +/** + * File: heap_sort.ts + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 堆積的長度為 n ,從節點 i 開始,從頂至底堆積化 */ +function siftDown(nums: number[], n: number, i: number): void { + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + let l = 2 * i + 1; + let r = 2 * i + 2; + let ma = i; + if (l < n && nums[l] > nums[ma]) { + ma = l; + } + if (r < n && nums[r] > nums[ma]) { + ma = r; + } + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma === i) { + break; + } + // 交換兩節點 + [nums[i], nums[ma]] = [nums[ma], nums[i]]; + // 迴圈向下堆積化 + i = ma; + } +} + +/* 堆積排序 */ +function heapSort(nums: number[]): void { + // 建堆積操作:堆積化除葉節點以外的其他所有節點 + for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // 從堆積中提取最大元素,迴圈 n-1 輪 + for (let i = nums.length - 1; i > 0; i--) { + // 交換根節點與最右葉節點(交換首元素與尾元素) + [nums[0], nums[i]] = [nums[i], nums[0]]; + // 以根節點為起點,從頂至底進行堆積化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +const nums: number[] = [4, 1, 3, 1, 5, 2]; +heapSort(nums); +console.log('堆積排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/insertion_sort.ts b/zh-hant/codes/typescript/chapter_sorting/insertion_sort.ts new file mode 100644 index 000000000..23081e29a --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/insertion_sort.ts @@ -0,0 +1,27 @@ +/** + * File: insertion_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 插入排序 */ +function insertionSort(nums: number[]): void { + // 外迴圈:已排序區間為 [0, i-1] + for (let i = 1; i < nums.length; i++) { + const base = nums[i]; + let j = i - 1; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 將 nums[j] 向右移動一位 + j--; + } + nums[j + 1] = base; // 將 base 賦值到正確位置 + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +insertionSort(nums); +console.log('插入排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/merge_sort.ts b/zh-hant/codes/typescript/chapter_sorting/merge_sort.ts new file mode 100644 index 000000000..cc28d2b44 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/merge_sort.ts @@ -0,0 +1,54 @@ +/** + * File: merge_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 合併左子陣列和右子陣列 */ +function merge(nums: number[], left: number, mid: number, right: number): void { + // 左子陣列區間為 [left, mid], 右子陣列區間為 [mid+1, right] + // 建立一個臨時陣列 tmp ,用於存放合併後的結果 + const tmp = new Array(right - left + 1); + // 初始化左子陣列和右子陣列的起始索引 + let i = left, + j = mid + 1, + k = 0; + // 當左右子陣列都還有元素時,進行比較並將較小的元素複製到臨時陣列中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 將臨時陣列 tmp 中的元素複製回原陣列 nums 的對應區間 + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* 合併排序 */ +function mergeSort(nums: number[], left: number, right: number): void { + // 終止條件 + if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + let mid = Math.floor((left + right) / 2); // 計算中點 + mergeSort(nums, left, mid); // 遞迴左子陣列 + mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +const nums = [7, 3, 2, 6, 0, 1, 5, 4]; +mergeSort(nums, 0, nums.length - 1); +console.log('合併排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/quick_sort.ts b/zh-hant/codes/typescript/chapter_sorting/quick_sort.ts new file mode 100644 index 000000000..5fe31d605 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/quick_sort.ts @@ -0,0 +1,180 @@ +/** + * File: quick_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 快速排序類別 */ +class QuickSort { + /* 元素交換 */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + partition(nums: number[], left: number, right: number): number { + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j -= 1; // 從右向左找首個小於基準數的元素 + } + while (i < j && nums[i] <= nums[left]) { + i += 1; // 從左向右找首個大於基準數的元素 + } + // 元素交換 + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + quickSort(nums: number[], left: number, right: number): void { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) { + return; + } + // 哨兵劃分 + const pivot = this.partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(中位基準數最佳化) */ +class QuickSortMedian { + /* 元素交換 */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 選取三個候選元素的中位數 */ + medianThree( + nums: number[], + left: number, + mid: number, + right: number + ): number { + let l = nums[left], + m = nums[mid], + r = nums[right]; + // m 在 l 和 r 之間 + if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; + // l 在 m 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) return left; + return right; + } + + /* 哨兵劃分(三數取中值) */ + partition(nums: number[], left: number, right: number): number { + // 選取三個候選元素的中位數 + let med = this.medianThree( + nums, + left, + Math.floor((left + right) / 2), + right + ); + // 將中位數交換至陣列最左端 + this.swap(nums, left, med); + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // 從右向左找首個小於基準數的元素 + } + while (i < j && nums[i] <= nums[left]) { + i++; // 從左向右找首個大於基準數的元素 + } + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序 */ + quickSort(nums: number[], left: number, right: number): void { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) { + return; + } + // 哨兵劃分 + const pivot = this.partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* 快速排序類別(尾遞迴最佳化) */ +class QuickSortTailCall { + /* 元素交換 */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵劃分 */ + partition(nums: number[], left: number, right: number): number { + // 以 nums[left] 為基準數 + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // 從右向左找首個小於基準數的元素 + } + while (i < j && nums[i] <= nums[left]) { + i++; // 從左向右找首個大於基準數的元素 + } + this.swap(nums, i, j); // 交換這兩個元素 + } + this.swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + /* 快速排序(尾遞迴最佳化) */ + quickSort(nums: number[], left: number, right: number): void { + // 子陣列長度為 1 時終止 + while (left < right) { + // 哨兵劃分操作 + let pivot = this.partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + this.quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + this.quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +/* 快速排序 */ +const nums = [2, 4, 1, 0, 3, 5]; +const quickSort = new QuickSort(); +quickSort.quickSort(nums, 0, nums.length - 1); +console.log('快速排序完成後 nums =', nums); + +/* 快速排序(中位基準數最佳化) */ +const nums1 = [2, 4, 1, 0, 3, 5]; +const quickSortMedian = new QuickSortMedian(); +quickSortMedian.quickSort(nums1, 0, nums1.length - 1); +console.log('快速排序(中位基準數最佳化)完成後 nums =', nums1); + +/* 快速排序(尾遞迴最佳化) */ +const nums2 = [2, 4, 1, 0, 3, 5]; +const quickSortTailCall = new QuickSortTailCall(); +quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); +console.log('快速排序(尾遞迴最佳化)完成後 nums =', nums2); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/radix_sort.ts b/zh-hant/codes/typescript/chapter_sorting/radix_sort.ts new file mode 100644 index 000000000..91ca29dd2 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/radix_sort.ts @@ -0,0 +1,68 @@ +/** + * File: radix_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +function digit(num: number, exp: number): number { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return Math.floor(num / exp) % 10; +} + +/* 計數排序(根據 nums 第 k 位排序) */ +function countingSortDigit(nums: number[], exp: number): void { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + const counter = new Array(10).fill(0); + const n = nums.length; + // 統計 0~9 各數字的出現次數 + for (let i = 0; i < n; i++) { + const d = digit(nums[i], exp); // 獲取 nums[i] 第 k 位,記為 d + counter[d]++; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + for (let i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + const res = new Array(n).fill(0); + for (let i = n - 1; i >= 0; i--) { + const d = digit(nums[i], exp); + const j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d]--; // 將 d 的數量減 1 + } + // 使用結果覆蓋原陣列 nums + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* 基數排序 */ +function radixSort(nums: number[]): void { + // 獲取陣列的最大元素,用於判斷最大位數 + let m = Number.MIN_VALUE; + for (const num of nums) { + if (num > m) { + m = num; + } + } + // 按照從低位到高位的順序走訪 + for (let exp = 1; exp <= m; exp *= 10) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); + } +} + +/* Driver Code */ +const nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, + 30524779, 82060337, 63832996, +]; +radixSort(nums); +console.log('基數排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_sorting/selection_sort.ts b/zh-hant/codes/typescript/chapter_sorting/selection_sort.ts new file mode 100644 index 000000000..438d9a87c --- /dev/null +++ b/zh-hant/codes/typescript/chapter_sorting/selection_sort.ts @@ -0,0 +1,29 @@ +/** + * File: selection_sort.ts + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 選擇排序 */ +function selectionSort(nums: number[]): void { + let n = nums.length; + // 外迴圈:未排序區間為 [i, n-1] + for (let i = 0; i < n - 1; i++) { + // 內迴圈:找到未排序區間內的最小元素 + let k = i; + for (let j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) { + k = j; // 記錄最小元素的索引 + } + } + // 將該最小元素與未排序區間的首個元素交換 + [nums[i], nums[k]] = [nums[k], nums[i]]; + } +} + +/* Driver Code */ +const nums: number[] = [4, 1, 3, 1, 5, 2]; +selectionSort(nums); +console.log('選擇排序完成後 nums =', nums); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/array_deque.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/array_deque.ts new file mode 100644 index 000000000..073cf3754 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/array_deque.ts @@ -0,0 +1,158 @@ +/** + * File: array_deque.ts + * Created Time: 2023-02-28 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 基於環形陣列實現的雙向佇列 */ +class ArrayDeque { + private nums: number[]; // 用於儲存雙向佇列元素的陣列 + private front: number; // 佇列首指標,指向佇列首元素 + private queSize: number; // 雙向佇列長度 + + /* 建構子 */ + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = 0; + this.queSize = 0; + } + + /* 獲取雙向佇列的容量 */ + capacity(): number { + return this.nums.length; + } + + /* 獲取雙向佇列的長度 */ + size(): number { + return this.queSize; + } + + /* 判斷雙向佇列是否為空 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 計算環形陣列索引 */ + index(i: number): number { + // 透過取餘操作實現陣列首尾相連 + // 當 i 越過陣列尾部後,回到頭部 + // 當 i 越過陣列頭部後,回到尾部 + return (i + this.capacity()) % this.capacity(); + } + + /* 佇列首入列 */ + pushFirst(num: number): void { + if (this.queSize === this.capacity()) { + console.log('雙向佇列已滿'); + return; + } + // 佇列首指標向左移動一位 + // 透過取餘操作實現 front 越過陣列頭部後回到尾部 + this.front = this.index(this.front - 1); + // 將 num 新增至佇列首 + this.nums[this.front] = num; + this.queSize++; + } + + /* 佇列尾入列 */ + pushLast(num: number): void { + if (this.queSize === this.capacity()) { + console.log('雙向佇列已滿'); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + const rear: number = this.index(this.front + this.queSize); + // 將 num 新增至佇列尾 + this.nums[rear] = num; + this.queSize++; + } + + /* 佇列首出列 */ + popFirst(): number { + const num: number = this.peekFirst(); + // 佇列首指標向後移動一位 + this.front = this.index(this.front + 1); + this.queSize--; + return num; + } + + /* 佇列尾出列 */ + popLast(): number { + const num: number = this.peekLast(); + this.queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peekFirst(): number { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + return this.nums[this.front]; + } + + /* 訪問佇列尾元素 */ + peekLast(): number { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + // 計算尾元素索引 + const last = this.index(this.front + this.queSize - 1); + return this.nums[last]; + } + + /* 返回陣列用於列印 */ + toArray(): number[] { + // 僅轉換有效長度範圍內的串列元素 + const res: number[] = []; + for (let i = 0, j = this.front; i < this.queSize; i++, j++) { + res[i] = this.nums[this.index(j)]; + } + return res; + } +} + +/* Driver Code */ +/* 初始化雙向佇列 */ +const capacity = 5; +const deque: ArrayDeque = new ArrayDeque(capacity); +deque.pushLast(3); +deque.pushLast(2); +deque.pushLast(5); +console.log('雙向佇列 deque = [' + deque.toArray() + ']'); + +/* 訪問元素 */ +const peekFirst = deque.peekFirst(); +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast = deque.peekLast(); +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素入列 */ +deque.pushLast(4); +console.log('元素 4 佇列尾入列後 deque = [' + deque.toArray() + ']'); +deque.pushFirst(1); +console.log('元素 1 佇列首入列後 deque = [' + deque.toArray() + ']'); + +/* 元素出列 */ +const popLast = deque.popLast(); +console.log( + '佇列尾出列元素 = ' + + popLast + + ',佇列尾出列後 deque = [' + + deque.toArray() + + ']' +); +const popFirst = deque.popFirst(); +console.log( + '佇列首出列元素 = ' + + popFirst + + ',佇列首出列後 deque = [' + + deque.toArray() + + ']' +); + +/* 獲取雙向佇列的長度 */ +const size = deque.size(); +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty = deque.isEmpty(); +console.log('雙向佇列是否為空 = ' + isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/array_queue.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/array_queue.ts new file mode 100644 index 000000000..86c28b4b4 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/array_queue.ts @@ -0,0 +1,109 @@ +/** + * File: array_queue.ts + * Created Time: 2022-12-11 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 基於環形陣列實現的佇列 */ +class ArrayQueue { + private nums: number[]; // 用於儲存佇列元素的陣列 + private front: number; // 佇列首指標,指向佇列首元素 + private queSize: number; // 佇列長度 + + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = this.queSize = 0; + } + + /* 獲取佇列的容量 */ + get capacity(): number { + return this.nums.length; + } + + /* 獲取佇列的長度 */ + get size(): number { + return this.queSize; + } + + /* 判斷佇列是否為空 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 入列 */ + push(num: number): void { + if (this.size === this.capacity) { + console.log('佇列已滿'); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + const rear = (this.front + this.queSize) % this.capacity; + // 將 num 新增至佇列尾 + this.nums[rear] = num; + this.queSize++; + } + + /* 出列 */ + pop(): number { + const num = this.peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + this.front = (this.front + 1) % this.capacity; + this.queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peek(): number { + if (this.isEmpty()) throw new Error('佇列為空'); + return this.nums[this.front]; + } + + /* 返回 Array */ + toArray(): number[] { + // 僅轉換有效長度範圍內的串列元素 + const arr = new Array(this.size); + for (let i = 0, j = this.front; i < this.size; i++, j++) { + arr[i] = this.nums[j % this.capacity]; + } + return arr; + } +} + +/* Driver Code */ +/* 初始化佇列 */ +const capacity = 10; +const queue = new ArrayQueue(capacity); + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue =', queue.toArray()); + +/* 訪問佇列首元素 */ +const peek = queue.peek(); +console.log('佇列首元素 peek = ' + peek); + +/* 元素出列 */ +const pop = queue.pop(); +console.log('出列元素 pop = ' + pop + ',出列後 queue =', queue.toArray()); + +/* 獲取佇列的長度 */ +const size = queue.size; +console.log('佇列長度 size = ' + size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.isEmpty(); +console.log('佇列是否為空 = ' + isEmpty); + +/* 測試環形陣列 */ +for (let i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + console.log('第 ' + i + ' 輪入列 + 出列後 queue =', queue.toArray()); +} + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/array_stack.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/array_stack.ts new file mode 100644 index 000000000..965c2d7c9 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/array_stack.ts @@ -0,0 +1,77 @@ +/** + * File: array_stack.ts + * Created Time: 2022-12-08 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 基於陣列實現的堆疊 */ +class ArrayStack { + private stack: number[]; + constructor() { + this.stack = []; + } + + /* 獲取堆疊的長度 */ + get size(): number { + return this.stack.length; + } + + /* 判斷堆疊是否為空 */ + isEmpty(): boolean { + return this.stack.length === 0; + } + + /* 入堆疊 */ + push(num: number): void { + this.stack.push(num); + } + + /* 出堆疊 */ + pop(): number | undefined { + if (this.isEmpty()) throw new Error('堆疊為空'); + return this.stack.pop(); + } + + /* 訪問堆疊頂元素 */ + top(): number | undefined { + if (this.isEmpty()) throw new Error('堆疊為空'); + return this.stack[this.stack.length - 1]; + } + + /* 返回 Array */ + toArray() { + return this.stack; + } +} + +/* Driver Code */ +/* 初始化堆疊 */ +const stack = new ArrayStack(); + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack = '); +console.log(stack.toArray()); + +/* 訪問堆疊頂元素 */ +const top = stack.top(); +console.log('堆疊頂元素 top = ' + top); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = '); +console.log(stack.toArray()); + +/* 獲取堆疊的長度 */ +const size = stack.size; +console.log('堆疊的長度 size = ' + size); + +/* 判斷是否為空 */ +const isEmpty = stack.isEmpty(); +console.log('堆疊是否為空 = ' + isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/deque.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/deque.ts new file mode 100644 index 000000000..3358c1921 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/deque.ts @@ -0,0 +1,46 @@ +/** + * File: deque.ts + * Created Time: 2023-01-17 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Driver Code */ +/* 初始化雙向佇列 */ +// TypeScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 +const deque: number[] = []; + +/* 元素入列 */ +deque.push(2); +deque.push(5); +deque.push(4); +// 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) +deque.unshift(3); +deque.unshift(1); +console.log('雙向佇列 deque = ', deque); + +/* 訪問元素 */ +const peekFirst: number = deque[0]; +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast: number = deque[deque.length - 1]; +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素出列 */ +// 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) +const popFront: number = deque.shift() as number; +console.log( + '佇列首出列元素 popFront = ' + popFront + ',佇列首出列後 deque = ' + deque +); +const popBack: number = deque.pop() as number; +console.log( + '佇列尾出列元素 popBack = ' + popBack + ',佇列尾出列後 deque = ' + deque +); + +/* 獲取雙向佇列的長度 */ +const size: number = deque.length; +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty: boolean = size === 0; +console.log('雙向佇列是否為空 = ' + isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts new file mode 100644 index 000000000..47973ca95 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.ts + * Created Time: 2023-02-04 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 雙向鏈結串列節點 */ +class ListNode { + prev: ListNode; // 前驅節點引用 (指標) + next: ListNode; // 後繼節點引用 (指標) + val: number; // 節點值 + + constructor(val: number) { + this.val = val; + this.next = null; + this.prev = null; + } +} + +/* 基於雙向鏈結串列實現的雙向佇列 */ +class LinkedListDeque { + private front: ListNode; // 頭節點 front + private rear: ListNode; // 尾節點 rear + private queSize: number; // 雙向佇列的長度 + + constructor() { + this.front = null; + this.rear = null; + this.queSize = 0; + } + + /* 佇列尾入列操作 */ + pushLast(val: number): void { + const node: ListNode = new ListNode(val); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // 將 node 新增至鏈結串列尾部 + this.rear.next = node; + node.prev = this.rear; + this.rear = node; // 更新尾節點 + } + this.queSize++; + } + + /* 佇列首入列操作 */ + pushFirst(val: number): void { + const node: ListNode = new ListNode(val); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // 將 node 新增至鏈結串列頭部 + this.front.prev = node; + node.next = this.front; + this.front = node; // 更新頭節點 + } + this.queSize++; + } + + /* 佇列尾出列操作 */ + popLast(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.rear.val; // 儲存尾節點值 + // 刪除尾節點 + let temp: ListNode = this.rear.prev; + if (temp !== null) { + temp.next = null; + this.rear.prev = null; + } + this.rear = temp; // 更新尾節點 + this.queSize--; + return value; + } + + /* 佇列首出列操作 */ + popFirst(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.front.val; // 儲存尾節點值 + // 刪除頭節點 + let temp: ListNode = this.front.next; + if (temp !== null) { + temp.prev = null; + this.front.next = null; + } + this.front = temp; // 更新頭節點 + this.queSize--; + return value; + } + + /* 訪問佇列尾元素 */ + peekLast(): number { + return this.queSize === 0 ? null : this.rear.val; + } + + /* 訪問佇列首元素 */ + peekFirst(): number { + return this.queSize === 0 ? null : this.front.val; + } + + /* 獲取雙向佇列的長度 */ + size(): number { + return this.queSize; + } + + /* 判斷雙向佇列是否為空 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 列印雙向佇列 */ + print(): void { + const arr: number[] = []; + let temp: ListNode = this.front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } +} + +/* Driver Code */ +/* 初始化雙向佇列 */ +const linkedListDeque: LinkedListDeque = new LinkedListDeque(); +linkedListDeque.pushLast(3); +linkedListDeque.pushLast(2); +linkedListDeque.pushLast(5); +console.log('雙向佇列 linkedListDeque = '); +linkedListDeque.print(); + +/* 訪問元素 */ +const peekFirst: number = linkedListDeque.peekFirst(); +console.log('佇列首元素 peekFirst = ' + peekFirst); +const peekLast: number = linkedListDeque.peekLast(); +console.log('佇列尾元素 peekLast = ' + peekLast); + +/* 元素入列 */ +linkedListDeque.pushLast(4); +console.log('元素 4 佇列尾入列後 linkedListDeque = '); +linkedListDeque.print(); +linkedListDeque.pushFirst(1); +console.log('元素 1 佇列首入列後 linkedListDeque = '); +linkedListDeque.print(); + +/* 元素出列 */ +const popLast: number = linkedListDeque.popLast(); +console.log('佇列尾出列元素 = ' + popLast + ',佇列尾出列後 linkedListDeque = '); +linkedListDeque.print(); +const popFirst: number = linkedListDeque.popFirst(); +console.log('佇列首出列元素 = ' + popFirst + ',佇列首出列後 linkedListDeque = '); +linkedListDeque.print(); + +/* 獲取雙向佇列的長度 */ +const size: number = linkedListDeque.size(); +console.log('雙向佇列長度 size = ' + size); + +/* 判斷雙向佇列是否為空 */ +const isEmpty: boolean = linkedListDeque.isEmpty(); +console.log('雙向佇列是否為空 = ' + isEmpty); diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts new file mode 100644 index 000000000..bf2a2a348 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts @@ -0,0 +1,102 @@ +/** + * File: linkedlist_queue.ts + * Created Time: 2022-12-19 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +import { ListNode } from '../modules/ListNode'; + +/* 基於鏈結串列實現的佇列 */ +class LinkedListQueue { + private front: ListNode | null; // 頭節點 front + private rear: ListNode | null; // 尾節點 rear + private queSize: number = 0; + + constructor() { + this.front = null; + this.rear = null; + } + + /* 獲取佇列的長度 */ + get size(): number { + return this.queSize; + } + + /* 判斷佇列是否為空 */ + isEmpty(): boolean { + return this.size === 0; + } + + /* 入列 */ + push(num: number): void { + // 在尾節點後新增 num + const node = new ListNode(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (!this.front) { + this.front = node; + this.rear = node; + // 如果佇列不為空,則將該節點新增到尾節點後 + } else { + this.rear!.next = node; + this.rear = node; + } + this.queSize++; + } + + /* 出列 */ + pop(): number { + const num = this.peek(); + if (!this.front) throw new Error('佇列為空'); + // 刪除頭節點 + this.front = this.front.next; + this.queSize--; + return num; + } + + /* 訪問佇列首元素 */ + peek(): number { + if (this.size === 0) throw new Error('佇列為空'); + return this.front!.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + toArray(): number[] { + let node = this.front; + const res = new Array(this.size); + for (let i = 0; i < res.length; i++) { + res[i] = node!.val; + node = node!.next; + } + return res; + } +} + +/* Driver Code */ +/* 初始化佇列 */ +const queue = new LinkedListQueue(); + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue = ' + queue.toArray()); + +/* 訪問佇列首元素 */ +const peek = queue.peek(); +console.log('佇列首元素 peek = ' + peek); + +/* 元素出列 */ +const pop = queue.pop(); +console.log('出列元素 pop = ' + pop + ',出列後 queue = ' + queue.toArray()); + +/* 獲取佇列的長度 */ +const size = queue.size; +console.log('佇列長度 size = ' + size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.isEmpty(); +console.log('佇列是否為空 = ' + isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts new file mode 100644 index 000000000..eb9f9d665 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts @@ -0,0 +1,91 @@ +/** + * File: linkedlist_stack.ts + * Created Time: 2022-12-21 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +import { ListNode } from '../modules/ListNode'; + +/* 基於鏈結串列實現的堆疊 */ +class LinkedListStack { + private stackPeek: ListNode | null; // 將頭節點作為堆疊頂 + private stkSize: number = 0; // 堆疊的長度 + + constructor() { + this.stackPeek = null; + } + + /* 獲取堆疊的長度 */ + get size(): number { + return this.stkSize; + } + + /* 判斷堆疊是否為空 */ + isEmpty(): boolean { + return this.size === 0; + } + + /* 入堆疊 */ + push(num: number): void { + const node = new ListNode(num); + node.next = this.stackPeek; + this.stackPeek = node; + this.stkSize++; + } + + /* 出堆疊 */ + pop(): number { + const num = this.peek(); + if (!this.stackPeek) throw new Error('堆疊為空'); + this.stackPeek = this.stackPeek.next; + this.stkSize--; + return num; + } + + /* 訪問堆疊頂元素 */ + peek(): number { + if (!this.stackPeek) throw new Error('堆疊為空'); + return this.stackPeek.val; + } + + /* 將鏈結串列轉化為 Array 並返回 */ + toArray(): number[] { + let node = this.stackPeek; + const res = new Array(this.size); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node!.next; + } + return res; + } +} + +/* Driver Code */ +/* 初始化堆疊 */ +const stack = new LinkedListStack(); + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack = ' + stack.toArray()); + +/* 訪問堆疊頂元素 */ +const peek = stack.peek(); +console.log('堆疊頂元素 peek = ' + peek); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop = ' + pop + ',出堆疊後 stack = ' + stack.toArray()); + +/* 獲取堆疊的長度 */ +const size = stack.size; +console.log('堆疊的長度 size = ' + size); + +/* 判斷是否為空 */ +const isEmpty = stack.isEmpty(); +console.log('堆疊是否為空 = ' + isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/queue.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/queue.ts new file mode 100644 index 000000000..8880dcfd1 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/queue.ts @@ -0,0 +1,37 @@ +/** + * File: queue.ts + * Created Time: 2022-12-05 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* 初始化佇列 */ +// TypeScript 沒有內建的佇列,可以把 Array 當作佇列來使用 +const queue: number[] = []; + +/* 元素入列 */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('佇列 queue =', queue); + +/* 訪問佇列首元素 */ +const peek = queue[0]; +console.log('佇列首元素 peek =', peek); + +/* 元素出列 */ +// 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) +const pop = queue.shift(); +console.log('出列元素 pop =', pop, ',出列後 queue = ', queue); + +/* 獲取佇列的長度 */ +const size = queue.length; +console.log('佇列長度 size =', size); + +/* 判斷佇列是否為空 */ +const isEmpty = queue.length === 0; +console.log('佇列是否為空 = ', isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_stack_and_queue/stack.ts b/zh-hant/codes/typescript/chapter_stack_and_queue/stack.ts new file mode 100644 index 000000000..0008bfc13 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_stack_and_queue/stack.ts @@ -0,0 +1,37 @@ +/** + * File: stack.ts + * Created Time: 2022-12-04 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* 初始化堆疊 */ +// TypeScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 +const stack: number[] = []; + +/* 元素入堆疊 */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('堆疊 stack =', stack); + +/* 訪問堆疊頂元素 */ +const peek = stack[stack.length - 1]; +console.log('堆疊頂元素 peek =', peek); + +/* 元素出堆疊 */ +const pop = stack.pop(); +console.log('出堆疊元素 pop =', pop); +console.log('出堆疊後 stack =', stack); + +/* 獲取堆疊的長度 */ +const size = stack.length; +console.log('堆疊的長度 size =', size); + +/* 判斷是否為空 */ +const isEmpty = stack.length === 0; +console.log('堆疊是否為空 =', isEmpty); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/array_binary_tree.ts b/zh-hant/codes/typescript/chapter_tree/array_binary_tree.ts new file mode 100644 index 000000000..20c9dfb7e --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/array_binary_tree.ts @@ -0,0 +1,151 @@ +/** + * File: array_binary_tree.js + * Created Time: 2023-08-09 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +type Order = 'pre' | 'in' | 'post'; + +/* 陣列表示下的二元樹類別 */ +class ArrayBinaryTree { + #tree: (number | null)[]; + + /* 建構子 */ + constructor(arr: (number | null)[]) { + this.#tree = arr; + } + + /* 串列容量 */ + size(): number { + return this.#tree.length; + } + + /* 獲取索引為 i 節點的值 */ + val(i: number): number | null { + // 若索引越界,則返回 null ,代表空位 + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* 獲取索引為 i 節點的左子節點的索引 */ + left(i: number): number { + return 2 * i + 1; + } + + /* 獲取索引為 i 節點的右子節點的索引 */ + right(i: number): number { + return 2 * i + 2; + } + + /* 獲取索引為 i 節點的父節點的索引 */ + parent(i: number): number { + return Math.floor((i - 1) / 2); // 向下整除 + } + + /* 層序走訪 */ + levelOrder(): number[] { + let res = []; + // 直接走訪陣列 + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* 深度優先走訪 */ + #dfs(i: number, order: Order, res: (number | null)[]): void { + // 若為空位,則返回 + if (this.val(i) === null) return; + // 前序走訪 + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // 中序走訪 + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // 後序走訪 + if (order === 'post') res.push(this.val(i)); + } + + /* 前序走訪 */ + preOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* 中序走訪 */ + inOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* 後序走訪 */ + postOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +// 初始化二元樹 +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const arr = Array.of( + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 +); + +const root = arrToTree(arr); +console.log('\n初始化二元樹\n'); +console.log('二元樹的陣列表示:'); +console.log(arr); +console.log('二元樹的鏈結串列表示:'); +printTree(root); + +// 陣列表示下的二元樹類別 +const abt = new ArrayBinaryTree(arr); + +// 訪問節點 +const i = 1; +const l = abt.left(i); +const r = abt.right(i); +const p = abt.parent(i); +console.log('\n當前節點的索引為 ' + i + ' ,值為 ' + abt.val(i)); +console.log( + '其左子節點的索引為 ' + l + ' ,值為 ' + (l === null ? 'null' : abt.val(l)) +); +console.log( + '其右子節點的索引為 ' + r + ' ,值為 ' + (r === null ? 'null' : abt.val(r)) +); +console.log( + '其父節點的索引為 ' + p + ' ,值為 ' + (p === null ? 'null' : abt.val(p)) +); + +// 走訪樹 +let res = abt.levelOrder(); +console.log('\n層序走訪為:' + res); +res = abt.preOrder(); +console.log('前序走訪為:' + res); +res = abt.inOrder(); +console.log('中序走訪為:' + res); +res = abt.postOrder(); +console.log('後序走訪為:' + res); + +export { }; diff --git a/zh-hant/codes/typescript/chapter_tree/avl_tree.ts b/zh-hant/codes/typescript/chapter_tree/avl_tree.ts new file mode 100644 index 000000000..4c1d8ea8c --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/avl_tree.ts @@ -0,0 +1,222 @@ +/** + * File: avl_tree.ts + * Created Time: 2023-02-06 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* AVL 樹*/ +class AVLTree { + root: TreeNode; + /* 建構子 */ + constructor() { + this.root = null; //根節點 + } + + /* 獲取節點高度 */ + height(node: TreeNode): number { + // 空節點高度為 -1 ,葉節點高度為 0 + return node === null ? -1 : node.height; + } + + /* 更新節點高度 */ + private updateHeight(node: TreeNode): void { + // 節點高度等於最高子樹高度 + 1 + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + + /* 獲取平衡因子 */ + balanceFactor(node: TreeNode): number { + // 空節點平衡因子為 0 + if (node === null) return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return this.height(node.left) - this.height(node.right); + } + + /* 右旋操作 */ + private rightRotate(node: TreeNode): TreeNode { + const child = node.left; + const grandChild = child.right; + // 以 child 為原點,將 node 向右旋轉 + child.right = node; + node.left = grandChild; + // 更新節點高度 + this.updateHeight(node); + this.updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 左旋操作 */ + private leftRotate(node: TreeNode): TreeNode { + const child = node.right; + const grandChild = child.left; + // 以 child 為原點,將 node 向左旋轉 + child.left = node; + node.right = grandChild; + // 更新節點高度 + this.updateHeight(node); + this.updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + /* 執行旋轉操作,使該子樹重新恢復平衡 */ + private rotate(node: TreeNode): TreeNode { + // 獲取節點 node 的平衡因子 + const balanceFactor = this.balanceFactor(node); + // 左偏樹 + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // 右旋 + return this.rightRotate(node); + } else { + // 先左旋後右旋 + node.left = this.leftRotate(node.left); + return this.rightRotate(node); + } + } + // 右偏樹 + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // 左旋 + return this.leftRotate(node); + } else { + // 先右旋後左旋 + node.right = this.rightRotate(node.right); + return this.leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + /* 插入節點 */ + insert(val: number): void { + this.root = this.insertHelper(this.root, val); + } + + /* 遞迴插入節點(輔助方法) */ + private insertHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return new TreeNode(val); + /* 1. 查詢插入位置並插入節點 */ + if (val < node.val) { + node.left = this.insertHelper(node.left, val); + } else if (val > node.val) { + node.right = this.insertHelper(node.right, val); + } else { + return node; // 重複節點不插入,直接返回 + } + this.updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = this.rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 刪除節點 */ + remove(val: number): void { + this.root = this.removeHelper(this.root, val); + } + + /* 遞迴刪除節點(輔助方法) */ + private removeHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return null; + /* 1. 查詢節點並刪除 */ + if (val < node.val) { + node.left = this.removeHelper(node.left, val); + } else if (val > node.val) { + node.right = this.removeHelper(node.right, val); + } else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child === null) { + return null; + } else { + // 子節點數量 = 1 ,直接刪除 node + node = child; + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.updateHeight(node); // 更新節點高度 + /* 2. 執行旋轉操作,使該子樹重新恢復平衡 */ + node = this.rotate(node); + // 返回子樹的根節點 + return node; + } + + /* 查詢節點 */ + search(val: number): TreeNode { + let cur = this.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + if (cur.val < val) { + // 目標節點在 cur 的右子樹中 + cur = cur.right; + } else if (cur.val > val) { + // 目標節點在 cur 的左子樹中 + cur = cur.left; + } else { + // 找到目標節點,跳出迴圈 + break; + } + } + // 返回目標節點 + return cur; + } +} + +function testInsert(tree: AVLTree, val: number): void { + tree.insert(val); + console.log('\n插入節點 ' + val + ' 後,AVL 樹為'); + printTree(tree.root); +} + +function testRemove(tree: AVLTree, val: number): void { + tree.remove(val); + console.log('\n刪除節點 ' + val + ' 後,AVL 樹為'); + printTree(tree.root); +} + +/* Driver Code */ +/* 初始化空 AVL 樹 */ +const avlTree = new AVLTree(); +/* 插入節點 */ +// 請關注插入節點後,AVL 樹是如何保持平衡的 +testInsert(avlTree, 1); +testInsert(avlTree, 2); +testInsert(avlTree, 3); +testInsert(avlTree, 4); +testInsert(avlTree, 5); +testInsert(avlTree, 8); +testInsert(avlTree, 7); +testInsert(avlTree, 9); +testInsert(avlTree, 10); +testInsert(avlTree, 6); + +/* 插入重複節點 */ +testInsert(avlTree, 7); + +/* 刪除節點 */ +// 請關注刪除節點後,AVL 樹是如何保持平衡的 +testRemove(avlTree, 8); // 刪除度為 0 的節點 +testRemove(avlTree, 5); // 刪除度為 1 的節點 +testRemove(avlTree, 4); // 刪除度為 2 的節點 + +/* 查詢節點 */ +const node = avlTree.search(7); +console.log('\n查詢到的節點物件為', node, ',節點值 = ' + node.val); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/binary_search_tree.ts b/zh-hant/codes/typescript/chapter_tree/binary_search_tree.ts new file mode 100644 index 000000000..43c883fe5 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/binary_search_tree.ts @@ -0,0 +1,146 @@ +/** + * File: binary_search_tree.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 二元搜尋樹 */ +class BinarySearchTree { + private root: TreeNode | null; + + /* 建構子 */ + constructor() { + // 初始化空樹 + this.root = null; + } + + /* 獲取二元樹根節點 */ + getRoot(): TreeNode | null { + return this.root; + } + + /* 查詢節點 */ + search(num: number): TreeNode | null { + let cur = this.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 目標節點在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 目標節點在 cur 的左子樹中 + else if (cur.val > num) cur = cur.left; + // 找到目標節點,跳出迴圈 + else break; + } + // 返回目標節點 + return cur; + } + + /* 插入節點 */ + insert(num: number): void { + // 若樹為空,則初始化根節點 + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 找到重複節點,直接返回 + if (cur.val === num) return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 插入位置在 cur 的左子樹中 + else cur = cur.left; + } + // 插入節點 + const node = new TreeNode(num); + if (pre!.val < num) pre!.right = node; + else pre!.left = node; + } + + /* 刪除節點 */ + remove(num: number): void { + // 若樹為空,直接提前返回 + if (this.root === null) return; + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur !== null) { + // 找到待刪除節點,跳出迴圈 + if (cur.val === num) break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.val < num) cur = cur.right; + // 待刪除節點在 cur 的左子樹中 + else cur = cur.left; + } + // 若無待刪除節點,則直接返回 + if (cur === null) return; + // 子節點數量 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + const child: TreeNode | null = + cur.left !== null ? cur.left : cur.right; + // 刪除節點 cur + if (cur !== this.root) { + if (pre!.left === cur) pre!.left = child; + else pre!.right = child; + } else { + // 若刪除節點為根節點,則重新指定根節點 + this.root = child; + } + } + // 子節點數量 = 2 + else { + // 獲取中序走訪中 cur 的下一個節點 + let tmp: TreeNode | null = cur.right; + while (tmp!.left !== null) { + tmp = tmp!.left; + } + // 遞迴刪除節點 tmp + this.remove(tmp!.val); + // 用 tmp 覆蓋 cur + cur.val = tmp!.val; + } + } +} + +/* Driver Code */ +/* 初始化二元搜尋樹 */ +const bst = new BinarySearchTree(); +// 請注意,不同的插入順序會生成不同的二元樹,該序列可以生成一個完美二元樹 +const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; +for (const num of nums) { + bst.insert(num); +} +console.log('\n初始化的二元樹為\n'); +printTree(bst.getRoot()); + +/* 查詢節點 */ +const node = bst.search(7); +console.log( + '\n查詢到的節點物件為 ' + node + ',節點值 = ' + (node ? node.val : 'null') +); + +/* 插入節點 */ +bst.insert(16); +console.log('\n插入節點 16 後,二元樹為\n'); +printTree(bst.getRoot()); + +/* 刪除節點 */ +bst.remove(1); +console.log('\n刪除節點 1 後,二元樹為\n'); +printTree(bst.getRoot()); +bst.remove(2); +console.log('\n刪除節點 2 後,二元樹為\n'); +printTree(bst.getRoot()); +bst.remove(4); +console.log('\n刪除節點 4 後,二元樹為\n'); +printTree(bst.getRoot()); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/binary_tree.ts b/zh-hant/codes/typescript/chapter_tree/binary_tree.ts new file mode 100644 index 000000000..571004966 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/binary_tree.ts @@ -0,0 +1,37 @@ +/** + * File: binary_tree.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 初始化二元樹 */ +// 初始化節點 +let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); +// 構建節點之間的引用(指標) +n1.left = n2; +n1.right = n3; +n2.left = n4; +n2.right = n5; +console.log('\n初始化二元樹\n'); +printTree(n1); + +/* 插入與刪除節點 */ +const P = new TreeNode(0); +// 在 n1 -> n2 中間插入節點 P +n1.left = P; +P.left = n2; +console.log('\n插入節點 P 後\n'); +printTree(n1); +// 刪除節點 P +n1.left = n2; +console.log('\n刪除節點 P 後\n'); +printTree(n1); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/binary_tree_bfs.ts b/zh-hant/codes/typescript/chapter_tree/binary_tree_bfs.ts new file mode 100644 index 000000000..143a80767 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/binary_tree_bfs.ts @@ -0,0 +1,41 @@ +/** + * File: binary_tree_bfs.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 層序走訪 */ +function levelOrder(root: TreeNode | null): number[] { + // 初始化佇列,加入根節點 + const queue = [root]; + // 初始化一個串列,用於儲存走訪序列 + const list: number[] = []; + while (queue.length) { + let node = queue.shift() as TreeNode; // 隊列出隊 + list.push(node.val); // 儲存節點值 + if (node.left) { + queue.push(node.left); // 左子節點入列 + } + if (node.right) { + queue.push(node.right); // 右子節點入列 + } + } + return list; +} + +/* Driver Code */ +/* 初始化二元樹 */ +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹\n'); +printTree(root); + +/* 層序走訪 */ +const list = levelOrder(root); +console.log('\n層序走訪的節點列印序列 = ' + list); + +export {}; diff --git a/zh-hant/codes/typescript/chapter_tree/binary_tree_dfs.ts b/zh-hant/codes/typescript/chapter_tree/binary_tree_dfs.ts new file mode 100644 index 000000000..f830bd5e4 --- /dev/null +++ b/zh-hant/codes/typescript/chapter_tree/binary_tree_dfs.ts @@ -0,0 +1,69 @@ +/** + * File: binary_tree_dfs.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +// 初始化串列,用於儲存走訪序列 +const list: number[] = []; + +/* 前序走訪 */ +function preOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + list.push(root.val); + preOrder(root.left); + preOrder(root.right); +} + +/* 中序走訪 */ +function inOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + inOrder(root.left); + list.push(root.val); + inOrder(root.right); +} + +/* 後序走訪 */ +function postOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + postOrder(root.left); + postOrder(root.right); + list.push(root.val); +} + +/* Driver Code */ +/* 初始化二元樹 */ +// 這裡藉助了一個從陣列直接生成二元樹的函式 +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n初始化二元樹\n'); +printTree(root); + +/* 前序走訪 */ +list.length = 0; +preOrder(root); +console.log('\n前序走訪的節點列印序列 = ' + list); + +/* 中序走訪 */ +list.length = 0; +inOrder(root); +console.log('\n中序走訪的節點列印序列 = ' + list); + +/* 後序走訪 */ +list.length = 0; +postOrder(root); +console.log('\n後序走訪的節點列印序列 = ' + list); + +export {}; diff --git a/zh-hant/codes/typescript/modules/ListNode.ts b/zh-hant/codes/typescript/modules/ListNode.ts new file mode 100644 index 000000000..63c1f57e9 --- /dev/null +++ b/zh-hant/codes/typescript/modules/ListNode.ts @@ -0,0 +1,28 @@ +/** + * File: ListNode.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 鏈結串列節點 */ +class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/* 將陣列反序列化為鏈結串列 */ +function arrToLinkedList(arr: number[]): ListNode | null { + const dum: ListNode = new ListNode(0); + let head = dum; + for (const val of arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; +} + +export { ListNode, arrToLinkedList }; diff --git a/zh-hant/codes/typescript/modules/PrintUtil.ts b/zh-hant/codes/typescript/modules/PrintUtil.ts new file mode 100644 index 000000000..58280781a --- /dev/null +++ b/zh-hant/codes/typescript/modules/PrintUtil.ts @@ -0,0 +1,96 @@ +/** + * File: PrintUtil.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from './ListNode'; +import { TreeNode, arrToTree } from './TreeNode'; + +/* 列印鏈結串列 */ +function printLinkedList(head: ListNode | null): void { + const list: string[] = []; + while (head !== null) { + list.push(head.val.toString()); + head = head.next; + } + console.log(list.join(' -> ')); +} + +class Trunk { + prev: Trunk | null; + str: string; + + constructor(prev: Trunk | null, str: string) { + this.prev = prev; + this.str = str; + } +} + +/** + * 列印二元樹 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +function printTree(root: TreeNode | null) { + printTreeHelper(root, null, false); +} + +/* 列印二元樹 */ +function printTreeHelper( + root: TreeNode | null, + prev: Trunk | null, + isRight: boolean +) { + if (root === null) { + return; + } + + let prev_str = ' '; + const trunk = new Trunk(prev, prev_str); + + printTreeHelper(root.right, trunk, true); + + if (prev === null) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTreeHelper(root.left, trunk, false); +} + +function showTrunks(p: Trunk | null) { + if (p === null) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); + // ts-node to execute, we need to install type definitions for node + // solve: npm i --save-dev @types/node + // restart the vscode +} + +/* 列印堆積 */ +function printHeap(arr: number[]): void { + console.log('堆積的陣列表示:'); + console.log(arr); + console.log('堆積的樹狀表示:'); + const root = arrToTree(arr); + printTree(root); +} + +export { printLinkedList, printTree, printHeap }; diff --git a/zh-hant/codes/typescript/modules/TreeNode.ts b/zh-hant/codes/typescript/modules/TreeNode.ts new file mode 100644 index 000000000..ff004e78e --- /dev/null +++ b/zh-hant/codes/typescript/modules/TreeNode.ts @@ -0,0 +1,37 @@ +/** + * File: TreeNode.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 二元樹節點 */ +class TreeNode { + val: number; // 節點值 + height: number; // 節點高度 + left: TreeNode | null; // 左子節點指標 + 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; + } +} + +/* 將陣列反序列化為二元樹 */ +function arrToTree(arr: (number | null)[], i: number = 0): TreeNode | null { + if (i < 0 || i >= arr.length || arr[i] === null) { + return null; + } + let root = new TreeNode(arr[i]); + root.left = arrToTree(arr, 2 * i + 1); + root.right = arrToTree(arr, 2 * i + 2); + return root; +} + +export { TreeNode, arrToTree }; diff --git a/zh-hant/codes/typescript/modules/Vertex.ts b/zh-hant/codes/typescript/modules/Vertex.ts new file mode 100644 index 000000000..480acf748 --- /dev/null +++ b/zh-hant/codes/typescript/modules/Vertex.ts @@ -0,0 +1,33 @@ +/** + * File: Vertex.ts + * Created Time: 2023-02-15 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 頂點類別 */ +class Vertex { + val: number; + constructor(val: number) { + this.val = val; + } + + /* 輸入值串列 vals ,返回頂點串列 vets */ + public static valsToVets(vals: number[]): Vertex[] { + const vets: Vertex[] = []; + for (let i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 輸入頂點串列 vets ,返回值串列 vals */ + public static vetsToVals(vets: Vertex[]): number[] { + const vals: number[] = []; + for (const vet of vets) { + vals.push(vet.val); + } + return vals; + } +} + +export { Vertex }; diff --git a/zh-hant/codes/typescript/tsconfig.json b/zh-hant/codes/typescript/tsconfig.json new file mode 100644 index 000000000..8c057ea0d --- /dev/null +++ b/zh-hant/codes/typescript/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "CommonJS", + "outDir": "out", + "sourceMap": true + } +} diff --git a/zh-hant/codes/zig/.gitignore b/zh-hant/codes/zig/.gitignore new file mode 100644 index 000000000..8bc911a30 --- /dev/null +++ b/zh-hant/codes/zig/.gitignore @@ -0,0 +1,2 @@ +zig-out/ +zig-cache/ \ No newline at end of file diff --git a/zh-hant/codes/zig/build.zig b/zh-hant/codes/zig/build.zig new file mode 100644 index 000000000..080f84166 --- /dev/null +++ b/zh-hant/codes/zig/build.zig @@ -0,0 +1,221 @@ +// File: build.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// Zig Version: 0.11.0 +// Zig Build Command: zig build -Doptimize=ReleaseSafe +// Zig Run Command: zig build run_* -Doptimize=ReleaseSafe +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const group_name_path = .{ + // Source File: "chapter_computational_complexity/time_complexity.zig" + // Run Command: zig build run_time_complexity -Doptimize=ReleaseSafe + .{ .name = "time_complexity", .path = "chapter_computational_complexity/time_complexity.zig" }, + + // Source File: "chapter_computational_complexity/worst_best_time_complexity.zig" + // Run Command: zig build run_worst_best_time_complexity -Doptimize=ReleaseSafe + .{ .name = "worst_best_time_complexity", .path = "chapter_computational_complexity/worst_best_time_complexity.zig" }, + + // Source File: "chapter_computational_complexity/space_complexity.zig" + // Run Command: zig build run_space_complexity -Doptimize=ReleaseSafe + .{ .name = "space_complexity", .path = "chapter_computational_complexity/space_complexity.zig" }, + + // Source File: "chapter_computational_complexity/iteration.zig" + // Run Command: zig build run_iteration -Doptimize=ReleaseFast + .{ .name = "iteration", .path = "chapter_computational_complexity/iteration.zig" }, + + // Source File: "chapter_computational_complexity/recursion.zig" + // Run Command: zig build run_recursion -Doptimize=ReleaseFast + .{ .name = "recursion", .path = "chapter_computational_complexity/recursion.zig" }, + + // Source File: "chapter_array_and_linkedlist/array.zig" + // Run Command: zig build run_array -Doptimize=ReleaseSafe + .{ .name = "array", .path = "chapter_array_and_linkedlist/array.zig" }, + + // Source File: "chapter_array_and_linkedlist/linked_list.zig" + // Run Command: zig build run_linked_list -Doptimize=ReleaseSafe + .{ .name = "linked_list", .path = "chapter_array_and_linkedlist/linked_list.zig" }, + + // Source File: "chapter_array_and_linkedlist/list.zig" + // Run Command: zig build run_list -Doptimize=ReleaseSafe + .{ .name = "list", .path = "chapter_array_and_linkedlist/list.zig" }, + + // Source File: "chapter_array_and_linkedlist/my_list.zig" + // Run Command: zig build run_my_list -Doptimize=ReleaseSafe + .{ .name = "my_list", .path = "chapter_array_and_linkedlist/my_list.zig" }, + + // Source File: "chapter_stack_and_queue/stack.zig" + // Run Command: zig build run_stack -Doptimize=ReleaseSafe + .{ .name = "stack", .path = "chapter_stack_and_queue/stack.zig" }, + + // Source File: "chapter_stack_and_queue/linkedlist_stack.zig" + // Run Command: zig build run_linkedlist_stack -Doptimize=ReleaseSafe + .{ .name = "linkedlist_stack", .path = "chapter_stack_and_queue/linkedlist_stack.zig" }, + + // Source File: "chapter_stack_and_queue/array_stack.zig" + // Run Command: zig build run_array_stack -Doptimize=ReleaseSafe + .{ .name = "array_stack", .path = "chapter_stack_and_queue/array_stack.zig" }, + + // Source File: "chapter_stack_and_queue/queue.zig" + // Run Command: zig build run_queue -Doptimize=ReleaseSafe + .{ .name = "queue", .path = "chapter_stack_and_queue/queue.zig" }, + + // Source File: "chapter_stack_and_queue/array_queue.zig" + // Run Command: zig build run_array_queue -Doptimize=ReleaseSafe + .{ .name = "array_queue", .path = "chapter_stack_and_queue/array_queue.zig" }, + + // Source File: "chapter_stack_and_queue/linkedlist_queue.zig" + // Run Command: zig build run_linkedlist_queue -Doptimize=ReleaseSafe + .{ .name = "linkedlist_queue", .path = "chapter_stack_and_queue/linkedlist_queue.zig" }, + + // Source File: "chapter_stack_and_queue/deque.zig" + // Run Command: zig build run_deque -Doptimize=ReleaseSafe + .{ .name = "deque", .path = "chapter_stack_and_queue/deque.zig" }, + + // Source File: "chapter_stack_and_queue/linkedlist_deque.zig" + // Run Command: zig build run_linkedlist_deque -Doptimize=ReleaseSafe + .{ .name = "linkedlist_deque", .path = "chapter_stack_and_queue/linkedlist_deque.zig" }, + + // Source File: "chapter_hashing/hash_map.zig" + // Run Command: zig build run_hash_map -Doptimize=ReleaseSafe + .{ .name = "hash_map", .path = "chapter_hashing/hash_map.zig" }, + + // Source File: "chapter_hashing/array_hash_map.zig" + // Run Command: zig build run_array_hash_map -Doptimize=ReleaseSafe + .{ .name = "array_hash_map", .path = "chapter_hashing/array_hash_map.zig" }, + + // Source File: "chapter_tree/binary_tree.zig" + // Run Command: zig build run_binary_tree -Doptimize=ReleaseSafe + .{ .name = "binary_tree", .path = "chapter_tree/binary_tree.zig" }, + + // Source File: "chapter_tree/binary_tree_bfs.zig" + // Run Command: zig build run_binary_tree_bfs -Doptimize=ReleaseSafe + .{ .name = "binary_tree_bfs", .path = "chapter_tree/binary_tree_bfs.zig" }, + + // Source File: "chapter_tree/binary_tree_dfs.zig" + // Run Command: zig build run_binary_tree_dfs -Doptimize=ReleaseSafe + .{ .name = "binary_tree_dfs", .path = "chapter_tree/binary_tree_dfs.zig" }, + + // Source File: "chapter_tree/binary_search_tree.zig" + // Run Command: zig build run_binary_search_tree -Doptimize=ReleaseSafe + .{ .name = "binary_search_tree", .path = "chapter_tree/binary_search_tree.zig" }, + + // Source File: "chapter_tree/avl_tree.zig" + // Run Command: zig build run_avl_tree -Doptimize=ReleaseSafe + .{ .name = "avl_tree", .path = "chapter_tree/avl_tree.zig" }, + + // Source File: "chapter_heap/heap.zig" + // Run Command: zig build run_heap -Doptimize=ReleaseSafe + .{ .name = "heap", .path = "chapter_heap/heap.zig" }, + + // Source File: "chapter_heap/my_heap.zig" + // Run Command: zig build run_my_heap -Doptimize=ReleaseSafe + .{ .name = "my_heap", .path = "chapter_heap/my_heap.zig" }, + + // Source File: "chapter_searching/linear_search.zig" + // Run Command: zig build run_linear_search -Doptimize=ReleaseSafe + .{ .name = "linear_search", .path = "chapter_searching/linear_search.zig" }, + + // Source File: "chapter_searching/binary_search.zig" + // Run Command: zig build run_binary_search -Doptimize=ReleaseSafe + .{ .name = "binary_search", .path = "chapter_searching/binary_search.zig" }, + + // Source File: "chapter_searching/hashing_search.zig" + // Run Command: zig build run_hashing_search -Doptimize=ReleaseSafe + .{ .name = "hashing_search", .path = "chapter_searching/hashing_search.zig" }, + + // Source File: "chapter_searching/two_sum.zig" + // Run Command: zig build run_two_sum -Doptimize=ReleaseSafe + .{ .name = "two_sum", .path = "chapter_searching/two_sum.zig" }, + + // Source File: "chapter_sorting/bubble_sort.zig" + // Run Command: zig build run_bubble_sort -Doptimize=ReleaseSafe + .{ .name = "bubble_sort", .path = "chapter_sorting/bubble_sort.zig" }, + + // Source File: "chapter_sorting/insertion_sort.zig" + // Run Command: zig build run_insertion_sort -Doptimize=ReleaseSafe + .{ .name = "insertion_sort", .path = "chapter_sorting/insertion_sort.zig" }, + + // Source File: "chapter_sorting/quick_sort.zig" + // Run Command: zig build run_quick_sort -Doptimize=ReleaseSafe + .{ .name = "quick_sort", .path = "chapter_sorting/quick_sort.zig" }, + + // Source File: "chapter_sorting/merge_sort.zig" + // Run Command: zig build run_merge_sort -Doptimize=ReleaseSafe + .{ .name = "merge_sort", .path = "chapter_sorting/merge_sort.zig" }, + + // Source File: "chapter_sorting/radix_sort.zig" + // Run Command: zig build run_radix_sort -Doptimize=ReleaseSafe + .{ .name = "radix_sort", .path = "chapter_sorting/radix_sort.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_backtrack.zig" + // Run Command: zig build run_climbing_stairs_backtrack -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_backtrack", .path = "chapter_dynamic_programming/climbing_stairs_backtrack.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_constraint_dp.zig" + // Run Command: zig build run_climbing_stairs_constraint_dp -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_constraint_dp", .path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_dfs_mem.zig" + // Run Command: zig build run_climbing_stairs_dfs_mem -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_dfs_mem", .path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_dfs.zig" + // Run Command: zig build run_climbing_stairs_dfs -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_dfs", .path = "chapter_dynamic_programming/climbing_stairs_dfs.zig" }, + + // Source File: "chapter_dynamic_programming/climbing_stairs_dp.zig" + // Run Command: zig build run_climbing_stairs_dp -Doptimize=ReleaseSafe + .{ .name = "climbing_stairs_dp", .path = "chapter_dynamic_programming/climbing_stairs_dp.zig" }, + + // Source File: "chapter_dynamic_programming/coin_change_ii.zig" + // Run Command: zig build run_coin_change_ii -Doptimize=ReleaseSafe + .{ .name = "coin_change_ii", .path = "chapter_dynamic_programming/coin_change_ii.zig" }, + + // Source File: "chapter_dynamic_programming/coin_change.zig" + // Run Command: zig build run_coin_change -Doptimize=ReleaseSafe + .{ .name = "coin_change", .path = "chapter_dynamic_programming/coin_change.zig" }, + + // Source File: "chapter_dynamic_programming/edit_distance.zig" + // Run Command: zig build run_edit_distance -Doptimize=ReleaseSafe + .{ .name = "edit_distance", .path = "chapter_dynamic_programming/edit_distance.zig" }, + + // Source File: "chapter_dynamic_programming/knapsack.zig" + // Run Command: zig build run_knapsack -Doptimize=ReleaseSafe + .{ .name = "knapsack", .path = "chapter_dynamic_programming/knapsack.zig" }, + + // Source File: "chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig" + // Run Command: zig build run_min_cost_climbing_stairs_dp -Doptimize=ReleaseSafe + .{ .name = "min_cost_climbing_stairs_dp", .path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig" }, + + // Source File: "chapter_dynamic_programming/min_path_sum.zig" + // Run Command: zig build run_min_path_sum -Doptimize=ReleaseSafe + .{ .name = "min_path_sum", .path = "chapter_dynamic_programming/min_path_sum.zig" }, + + // Source File: "chapter_dynamic_programming/unbounded_knapsack.zig" + // Run Command: zig build run_unbounded_knapsack -Doptimize=ReleaseSafe + .{ .name = "unbounded_knapsack", .path = "chapter_dynamic_programming/unbounded_knapsack.zig" }, + }; + + inline for (group_name_path) |name_path| { + const exe = b.addExecutable(.{ + .name = name_path.name, + .root_source_file = .{ .path = name_path.path }, + .target = target, + .optimize = optimize, + }); + exe.addModule("include", b.addModule("", .{ + .source_file = .{ .path = "include/include.zig" }, + })); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + const run_step = b.step("run_" ++ name_path.name, "Run the app"); + run_step.dependOn(&run_cmd.step); + } +} diff --git a/zh-hant/codes/zig/chapter_array_and_linkedlist/array.zig b/zh-hant/codes/zig/chapter_array_and_linkedlist/array.zig new file mode 100644 index 000000000..25f2884dc --- /dev/null +++ b/zh-hant/codes/zig/chapter_array_and_linkedlist/array.zig @@ -0,0 +1,117 @@ +// File: array.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 隨機訪問元素 +pub fn randomAccess(nums: []i32) i32 { + // 在區間 [0, nums.len) 中隨機抽取一個整數 + var randomIndex = std.crypto.random.intRangeLessThan(usize, 0, nums.len); + // 獲取並返回隨機元素 + var randomNum = nums[randomIndex]; + return randomNum; +} + +// 擴展陣列長度 +pub fn extend(mem_allocator: std.mem.Allocator, nums: []i32, enlarge: usize) ![]i32 { + // 初始化一個擴展長度後的陣列 + var res = try mem_allocator.alloc(i32, nums.len + enlarge); + @memset(res, 0); + // 將原陣列中的所有元素複製到新陣列 + std.mem.copy(i32, res, nums); + // 返回擴展後的新陣列 + return res; +} + +// 在陣列的索引 index 處插入元素 num +pub fn insert(nums: []i32, num: i32, index: usize) void { + // 把索引 index 以及之後的所有元素向後移動一位 + var i = nums.len - 1; + while (i > index) : (i -= 1) { + nums[i] = nums[i - 1]; + } + // 將 num 賦給 index 處的元素 + nums[index] = num; +} + +// 刪除索引 index 處的元素 +pub fn remove(nums: []i32, index: usize) void { + // 把索引 index 之後的所有元素向前移動一位 + var i = index; + while (i < nums.len - 1) : (i += 1) { + nums[i] = nums[i + 1]; + } +} + +// 走訪陣列 +pub fn traverse(nums: []i32) void { + var count: i32 = 0; + // 透過索引走訪陣列 + var i: i32 = 0; + while (i < nums.len) : (i += 1) { + count += nums[i]; + } + count = 0; + // 直接走訪陣列元素 + for (nums) |num| { + count += num; + } +} + +// 在陣列中查詢指定元素 +pub fn find(nums: []i32, target: i32) i32 { + for (nums, 0..) |num, i| { + if (num == target) return @intCast(i); + } + return -1; +} + +// Driver Code +pub fn main() !void { + // 初始化記憶體分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化陣列 + var arr = [_]i32{0} ** 5; + std.debug.print("陣列 arr = ", .{}); + inc.PrintUtil.printArray(i32, &arr); + + var array = [_]i32{ 1, 3, 2, 5, 4 }; + var known_at_runtime_zero: usize = 0; + var nums = array[known_at_runtime_zero..]; + std.debug.print("\n陣列 nums = ", .{}); + inc.PrintUtil.printArray(i32, nums); + + // 隨機訪問 + var randomNum = randomAccess(nums); + std.debug.print("\n在 nums 中獲取隨機元素 {}", .{randomNum}); + + // 長度擴展 + nums = try extend(mem_allocator, nums, 3); + std.debug.print("\n將陣列長度擴展至 8 ,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, nums); + + // 插入元素 + insert(nums, 6, 3); + std.debug.print("\n在索引 3 處插入數字 6 ,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, nums); + + // 刪除元素 + remove(nums, 2); + std.debug.print("\n刪除索引 2 處的元素,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, nums); + + // 走訪陣列 + traverse(nums); + + // 查詢元素 + var index = find(nums, 3); + std.debug.print("\n在 nums 中查詢元素 3 ,得到索引 = {}\n", .{index}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_array_and_linkedlist/linked_list.zig b/zh-hant/codes/zig/chapter_array_and_linkedlist/linked_list.zig new file mode 100644 index 000000000..2e255f10b --- /dev/null +++ b/zh-hant/codes/zig/chapter_array_and_linkedlist/linked_list.zig @@ -0,0 +1,84 @@ +// File: linked_list.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 在鏈結串列的節點 n0 之後插入節點 P +pub fn insert(n0: ?*inc.ListNode(i32), P: ?*inc.ListNode(i32)) void { + var n1 = n0.?.next; + P.?.next = n1; + n0.?.next = P; +} + +// 刪除鏈結串列的節點 n0 之後的首個節點 +pub fn remove(n0: ?*inc.ListNode(i32)) void { + if (n0.?.next == null) return; + // n0 -> P -> n1 + var P = n0.?.next; + var n1 = P.?.next; + n0.?.next = n1; +} + +// 訪問鏈結串列中索引為 index 的節點 +pub fn access(node: ?*inc.ListNode(i32), index: i32) ?*inc.ListNode(i32) { + var head = node; + var i: i32 = 0; + while (i < index) : (i += 1) { + head = head.?.next; + if (head == null) return null; + } + return head; +} + +// 在鏈結串列中查詢值為 target 的首個節點 +pub fn find(node: ?*inc.ListNode(i32), target: i32) i32 { + var head = node; + var index: i32 = 0; + while (head != null) { + if (head.?.val == target) return index; + head = head.?.next; + index += 1; + } + return -1; +} + +// Driver Code +pub fn main() !void { + // 初始化鏈結串列 + // 初始化各個節點 + var n0 = inc.ListNode(i32){.val = 1}; + var n1 = inc.ListNode(i32){.val = 3}; + var n2 = inc.ListNode(i32){.val = 2}; + var n3 = inc.ListNode(i32){.val = 5}; + var n4 = inc.ListNode(i32){.val = 4}; + // 構建節點之間的引用 + n0.next = &n1; + n1.next = &n2; + n2.next = &n3; + n3.next = &n4; + std.debug.print("初始化的鏈結串列為", .{}); + try inc.PrintUtil.printLinkedList(i32, &n0); + + // 插入節點 + var tmp = inc.ListNode(i32){.val = 0}; + insert(&n0, &tmp); + std.debug.print("插入節點後的鏈結串列為", .{}); + try inc.PrintUtil.printLinkedList(i32, &n0); + + // 刪除節點 + remove(&n0); + std.debug.print("刪除節點後的鏈結串列為", .{}); + try inc.PrintUtil.printLinkedList(i32, &n0); + + // 訪問節點 + var node = access(&n0, 3); + std.debug.print("鏈結串列中索引 3 處的節點的值 = {}\n", .{node.?.val}); + + // 查詢節點 + var index = find(&n0, 2); + std.debug.print("鏈結串列中值為 2 的節點的索引 = {}\n", .{index}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_array_and_linkedlist/list.zig b/zh-hant/codes/zig/chapter_array_and_linkedlist/list.zig new file mode 100644 index 000000000..6e2b1401e --- /dev/null +++ b/zh-hant/codes/zig/chapter_array_and_linkedlist/list.zig @@ -0,0 +1,78 @@ +// File: list.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化串列 + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + std.debug.print("串列 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 訪問元素 + var num = nums.items[1]; + std.debug.print("\n訪問索引 1 處的元素,得到 num = {}", .{num}); + + // 更新元素 + nums.items[1] = 0; + std.debug.print("\n將索引 1 處的元素更新為 0 ,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 清空串列 + nums.clearRetainingCapacity(); + std.debug.print("\n清空串列後 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 在尾部新增元素 + try nums.append(1); + try nums.append(3); + try nums.append(2); + try nums.append(5); + try nums.append(4); + std.debug.print("\n新增元素後 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 在中間插入元素 + try nums.insert(3, 6); + std.debug.print("\n在索引 3 處插入數字 6 ,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 刪除元素 + _ = nums.orderedRemove(3); + std.debug.print("\n刪除索引 3 處的元素,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 透過索引走訪串列 + var count: i32 = 0; + var i: i32 = 0; + while (i < nums.items.len) : (i += 1) { + count += nums[i]; + } + // 直接走訪串列元素 + count = 0; + for (nums.items) |x| { + count += x; + } + + // 拼接兩個串列 + var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums1.deinit(); + try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); + try nums.insertSlice(nums.items.len, nums1.items); + std.debug.print("\n將串列 nums1 拼接到 nums 之後,得到 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + // 排序串列 + std.mem.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); + std.debug.print("\n排序串列後 nums = ", .{}); + inc.PrintUtil.printList(i32, nums); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_array_and_linkedlist/my_list.zig b/zh-hant/codes/zig/chapter_array_and_linkedlist/my_list.zig new file mode 100644 index 000000000..1e38998db --- /dev/null +++ b/zh-hant/codes/zig/chapter_array_and_linkedlist/my_list.zig @@ -0,0 +1,173 @@ +// File: my_list.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 串列類別 +pub fn MyList(comptime T: type) type { + return struct { + const Self = @This(); + + arr: []T = undefined, // 陣列(儲存串列元素) + arrCapacity: usize = 10, // 串列容量 + numSize: usize = 0, // 串列長度(當前元素數量) + extendRatio: usize = 2, // 每次串列擴容的倍數 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子(分配記憶體+初始化串列) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.arr = try self.mem_allocator.alloc(T, self.arrCapacity); + @memset(self.arr, @as(T, 0)); + } + + // 析構函式(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取串列長度(當前元素數量) + pub fn size(self: *Self) usize { + return self.numSize; + } + + // 獲取串列容量 + pub fn capacity(self: *Self) usize { + return self.arrCapacity; + } + + // 訪問元素 + pub fn get(self: *Self, index: usize) T { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 or index >= self.size()) @panic("索引越界"); + return self.arr[index]; + } + + // 更新元素 + pub fn set(self: *Self, index: usize, num: T) void { + // 索引如果越界,則丟擲異常,下同 + if (index < 0 or index >= self.size()) @panic("索引越界"); + self.arr[index] = num; + } + + // 在尾部新增元素 + pub fn add(self: *Self, num: T) !void { + // 元素數量超出容量時,觸發擴容機制 + if (self.size() == self.capacity()) try self.extendCapacity(); + self.arr[self.size()] = num; + // 更新元素數量 + self.numSize += 1; + } + + // 在中間插入元素 + pub fn insert(self: *Self, index: usize, num: T) !void { + if (index < 0 or index >= self.size()) @panic("索引越界"); + // 元素數量超出容量時,觸發擴容機制 + if (self.size() == self.capacity()) try self.extendCapacity(); + // 將索引 index 以及之後的元素都向後移動一位 + var j = self.size() - 1; + while (j >= index) : (j -= 1) { + self.arr[j + 1] = self.arr[j]; + } + self.arr[index] = num; + // 更新元素數量 + self.numSize += 1; + } + + // 刪除元素 + pub fn remove(self: *Self, index: usize) T { + if (index < 0 or index >= self.size()) @panic("索引越界"); + var num = self.arr[index]; + // 將索引 index 之後的元素都向前移動一位 + var j = index; + while (j < self.size() - 1) : (j += 1) { + self.arr[j] = self.arr[j + 1]; + } + // 更新元素數量 + self.numSize -= 1; + // 返回被刪除的元素 + return num; + } + + // 串列擴容 + pub fn extendCapacity(self: *Self) !void { + // 新建一個長度為 size * extendRatio 的陣列,並將原陣列複製到新陣列 + var newCapacity = self.capacity() * self.extendRatio; + var extend = try self.mem_allocator.alloc(T, newCapacity); + @memset(extend, @as(T, 0)); + // 將原陣列中的所有元素複製到新陣列 + std.mem.copy(T, extend, self.arr); + self.arr = extend; + // 更新串列容量 + self.arrCapacity = newCapacity; + } + + // 將串列轉換為陣列 + pub fn toArray(self: *Self) ![]T { + // 僅轉換有效長度範圍內的串列元素 + var arr = try self.mem_allocator.alloc(T, self.size()); + @memset(arr, @as(T, 0)); + for (arr, 0..) |*num, i| { + num.* = self.get(i); + } + return arr; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化串列 + var nums = MyList(i32){}; + try nums.init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer nums.deinit(); + + // 在尾部新增元素 + try nums.add(1); + try nums.add(3); + try nums.add(2); + try nums.add(5); + try nums.add(4); + std.debug.print("串列 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + std.debug.print(" ,容量 = {} ,長度 = {}", .{nums.capacity(), nums.size()}); + + // 在中間插入元素 + try nums.insert(3, 6); + std.debug.print("\n在索引 3 處插入數字 6 ,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + + // 刪除元素 + _ = nums.remove(3); + std.debug.print("\n刪除索引 3 處的元素,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + + // 訪問元素 + var num = nums.get(1); + std.debug.print("\n訪問索引 1 處的元素,得到 num = {}", .{num}); + + // 更新元素 + nums.set(1, 0); + std.debug.print("\n將索引 1 處的元素更新為 0 ,得到 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + + // 測試擴容機制 + var i: i32 = 0; + while (i < 10) : (i += 1) { + // 在 i = 5 時,串列長度將超出串列容量,此時觸發擴容機制 + try nums.add(i); + } + std.debug.print("\n擴容後的串列 nums = ", .{}); + inc.PrintUtil.printArray(i32, try nums.toArray()); + std.debug.print(" ,容量 = {} ,長度 = {}\n", .{nums.capacity(), nums.size()}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_computational_complexity/iteration.zig b/zh-hant/codes/zig/chapter_computational_complexity/iteration.zig new file mode 100644 index 000000000..b458becca --- /dev/null +++ b/zh-hant/codes/zig/chapter_computational_complexity/iteration.zig @@ -0,0 +1,77 @@ +// File: iteration.zig +// Created Time: 2023-09-27 +// Author: QiLOL (pikaqqpika@gmail.com) + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +// for 迴圈 +fn forLoop(n: usize) i32 { + var res: i32 = 0; + // 迴圈求和 1, 2, ..., n-1, n + for (1..n+1) |i| { + res = res + @as(i32, @intCast(i)); + } + return res; +} + +// while 迴圈 +fn whileLoop(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // 初始化條件變數 + // 迴圈求和 1, 2, ..., n-1, n + while (i <= n) { + res += @intCast(i); + i += 1; + } + return res; +} + +// while 迴圈(兩次更新) +fn whileLoopII(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // 初始化條件變數 + // 迴圈求和 1, 4, 10, ... + while (i <= n) { + res += @intCast(i); + // 更新條件變數 + i += 1; + i *= 2; + } + return res; +} + +// 雙層 for 迴圈 +fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 { + var res = std.ArrayList(u8).init(allocator); + defer res.deinit(); + var buffer: [20]u8 = undefined; + // 迴圈 i = 1, 2, ..., n-1, n + for (1..n+1) |i| { + // 迴圈 j = 1, 2, ..., n-1, n + for (1..n+1) |j| { + var _str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{i, j}); + try res.appendSlice(_str); + } + } + return res.toOwnedSlice(); +} + +// Driver Code +pub fn main() !void { + const n: i32 = 5; + var res: i32 = 0; + + res = forLoop(n); + std.debug.print("\nfor 迴圈的求和結果 res = {}\n", .{res}); + + res = whileLoop(n); + std.debug.print("\nwhile 迴圈的求和結果 res = {}\n", .{res}); + + res = whileLoopII(n); + std.debug.print("\nwhile 迴圈(兩次更新)求和結果 res = {}\n", .{res}); + + const allocator = std.heap.page_allocator; + const resStr = try nestedForLoop(allocator, n); + std.debug.print("\n雙層 for 迴圈的走訪結果 {s}\n", .{resStr}); +} diff --git a/zh-hant/codes/zig/chapter_computational_complexity/recursion.zig b/zh-hant/codes/zig/chapter_computational_complexity/recursion.zig new file mode 100644 index 000000000..de2b1d099 --- /dev/null +++ b/zh-hant/codes/zig/chapter_computational_complexity/recursion.zig @@ -0,0 +1,78 @@ +// File: recursion.zig +// Created Time: 2023-09-27 +// Author: QiLOL (pikaqqpika@gmail.com) + +const std = @import("std"); + +// 遞迴函式 +fn recur(n: i32) i32 { + // 終止條件 + if (n == 1) { + return 1; + } + // 遞:遞迴呼叫 + var res: i32 = recur(n - 1); + // 迴:返回結果 + return n + res; +} + +// 使用迭代模擬遞迴 +fn forLoopRecur(comptime n: i32) i32 { + // 使用一個顯式的堆疊來模擬系統呼叫堆疊 + var stack: [n]i32 = undefined; + var res: i32 = 0; + // 遞:遞迴呼叫 + var i: usize = n; + while (i > 0) { + stack[i - 1] = @intCast(i); + i -= 1; + } + // 迴:返回結果 + var index: usize = n; + while (index > 0) { + index -= 1; + res += stack[index]; + } + // res = 1+2+3+...+n + return res; +} + +// 尾遞迴函式 +fn tailRecur(n: i32, res: i32) i32 { + // 終止條件 + if (n == 0) { + return res; + } + // 尾遞迴呼叫 + return tailRecur(n - 1, res + n); +} + +// 費波那契數列 +fn fib(n: i32) i32 { + // 終止條件 f(1) = 0, f(2) = 1 + if (n == 1 or n == 2) { + return n - 1; + } + // 遞迴呼叫 f(n) = f(n-1) + f(n-2) + var res: i32 = fib(n - 1) + fib(n - 2); + // 返回結果 f(n) + return res; +} + +// Driver Code +pub fn main() !void { + const n: i32 = 5; + var res: i32 = 0; + + res = recur(n); + std.debug.print("\n遞迴函式的求和結果 res = {}\n", .{recur(n)}); + + res = forLoopRecur(n); + std.debug.print("\n使用迭代模擬遞迴的求和結果 res = {}\n", .{forLoopRecur(n)}); + + res = tailRecur(n, 0); + std.debug.print("\n尾遞迴函式的求和結果 res = {}\n", .{tailRecur(n, 0)}); + + res = fib(n); + std.debug.print("\n費波那契數列的第 {} 項為 {}\n", .{n, fib(n)}); +} diff --git a/zh-hant/codes/zig/chapter_computational_complexity/space_complexity.zig b/zh-hant/codes/zig/chapter_computational_complexity/space_complexity.zig new file mode 100644 index 000000000..9205cc759 --- /dev/null +++ b/zh-hant/codes/zig/chapter_computational_complexity/space_complexity.zig @@ -0,0 +1,124 @@ +// File: space_complexity.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 函式 +fn function() i32 { + // 執行某些操作 + return 0; +} + +// 常數階 +fn constant(n: i32) void { + // 常數、變數、物件佔用 O(1) 空間 + const a: i32 = 0; + var b: i32 = 0; + var nums = [_]i32{0}**10000; + var node = inc.ListNode(i32){.val = 0}; + var i: i32 = 0; + // 迴圈中的變數佔用 O(1) 空間 + while (i < n) : (i += 1) { + var c: i32 = 0; + _ = c; + } + // 迴圈中的函式佔用 O(1) 空間 + i = 0; + while (i < n) : (i += 1) { + _ = function(); + } + _ = a; + _ = b; + _ = nums; + _ = node; +} + +// 線性階 +fn linear(comptime n: i32) !void { + // 長度為 n 的陣列佔用 O(n) 空間 + var nums = [_]i32{0}**n; + // 長度為 n 的串列佔用 O(n) 空間 + var nodes = std.ArrayList(i32).init(std.heap.page_allocator); + defer nodes.deinit(); + var i: i32 = 0; + while (i < n) : (i += 1) { + try nodes.append(i); + } + // 長度為 n 的雜湊表佔用 O(n) 空間 + var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator); + defer map.deinit(); + var j: i32 = 0; + while (j < n) : (j += 1) { + const string = try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{j}); + defer std.heap.page_allocator.free(string); + try map.put(i, string); + } + _ = nums; +} + +// 線性階(遞迴實現) +fn linearRecur(comptime n: i32) void { + std.debug.print("遞迴 n = {}\n", .{n}); + if (n == 1) return; + linearRecur(n - 1); +} + +// 平方階 +fn quadratic(n: i32) !void { + // 二維串列佔用 O(n^2) 空間 + var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator); + defer nodes.deinit(); + var i: i32 = 0; + while (i < n) : (i += 1) { + var tmp = std.ArrayList(i32).init(std.heap.page_allocator); + defer tmp.deinit(); + var j: i32 = 0; + while (j < n) : (j += 1) { + try tmp.append(0); + } + try nodes.append(tmp); + } +} + +// 平方階(遞迴實現) +fn quadraticRecur(comptime n: i32) i32 { + if (n <= 0) return 0; + var nums = [_]i32{0}**n; + std.debug.print("遞迴 n = {} 中的 nums 長度 = {}\n", .{n, nums.len}); + return quadraticRecur(n - 1); +} + +// 指數階(建立滿二元樹) +fn buildTree(mem_allocator: std.mem.Allocator, n: i32) !?*inc.TreeNode(i32) { + if (n == 0) return null; + const root = try mem_allocator.create(inc.TreeNode(i32)); + root.init(0); + root.left = try buildTree(mem_allocator, n - 1); + root.right = try buildTree(mem_allocator, n - 1); + return root; +} + +// Driver Code +pub fn main() !void { + const n: i32 = 5; + // 常數階 + constant(n); + // 線性階 + try linear(n); + linearRecur(n); + // 平方階 + try quadratic(n); + _ = quadraticRecur(n); + // 指數階 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + var root = blk_root: { + const mem_allocator = mem_arena.allocator(); + break :blk_root try buildTree(mem_allocator, n); + }; + try inc.PrintUtil.printTree(root, null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_computational_complexity/time_complexity.zig b/zh-hant/codes/zig/chapter_computational_complexity/time_complexity.zig new file mode 100644 index 000000000..3e592733d --- /dev/null +++ b/zh-hant/codes/zig/chapter_computational_complexity/time_complexity.zig @@ -0,0 +1,179 @@ +// File: time_complexity.zig +// Created Time: 2022-12-28 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 常數階 +fn constant(n: i32) i32 { + _ = n; + var count: i32 = 0; + const size: i32 = 100_000; + var i: i32 = 0; + while(i 0) : (i -= 1) { + var j: usize = 0; + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + var tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交換包含 3 個單元操作 + } + } + } + return count; +} + +// 指數階(迴圈實現) +fn exponential(n: i32) i32 { + var count: i32 = 0; + var bas: i32 = 1; + var i: i32 = 0; + // 細胞每輪一分為二,形成數列 1, 2, 4, 8, ..., 2^(n-1) + while (i < n) : (i += 1) { + var j: i32 = 0; + while (j < bas) : (j += 1) { + count += 1; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +// 指數階(遞迴實現) +fn expRecur(n: i32) i32 { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +// 對數階(迴圈實現) +fn logarithmic(n: i32) i32 { + var count: i32 = 0; + var n_var = n; + while (n_var > 1) + { + n_var = n_var / 2; + count +=1; + } + return count; +} + +// 對數階(遞迴實現) +fn logRecur(n: i32) i32 { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +// 線性對數階 +fn linearLogRecur(n: i32) i32 { + if (n <= 1) return 1; + var count: i32 = linearLogRecur(n / 2) + linearLogRecur(n / 2); + var i: i32 = 0; + while (i < n) : (i += 1) { + count += 1; + } + return count; +} + +// 階乘階(遞迴實現) +fn factorialRecur(n: i32) i32 { + if (n == 0) return 1; + var count: i32 = 0; + var i: i32 = 0; + // 從 1 個分裂出 n 個 + while (i < n) : (i += 1) { + count += factorialRecur(n - 1); + } + return count; +} + +// Driver Code +pub fn main() !void { + // 可以修改 n 執行,體會一下各種複雜度的操作數量變化趨勢 + const n: i32 = 8; + std.debug.print("輸入資料大小 n = {}\n", .{n}); + + var count = constant(n); + std.debug.print("常數階的操作數量 = {}\n", .{count}); + + count = linear(n); + std.debug.print("線性階的操作數量 = {}\n", .{count}); + var nums = [_]i32{0}**n; + count = arrayTraversal(&nums); + std.debug.print("線性階(走訪陣列)的操作數量 = {}\n", .{count}); + + count = quadratic(n); + std.debug.print("平方階的操作數量 = {}\n", .{count}); + for (&nums, 0..) |*num, i| { + num.* = n - @as(i32, @intCast(i)); // [n,n-1,...,2,1] + } + count = bubbleSort(&nums); + std.debug.print("平方階(泡沫排序)的操作數量 = {}\n", .{count}); + + count = exponential(n); + std.debug.print("指數階(迴圈實現)的操作數量 = {}\n", .{count}); + count = expRecur(n); + std.debug.print("指數階(遞迴實現)的操作數量 = {}\n", .{count}); + + count = logarithmic(n); + std.debug.print("對數階(迴圈實現)的操作數量 = {}\n", .{count}); + count = logRecur(n); + std.debug.print("對數階(遞迴實現)的操作數量 = {}\n", .{count}); + + count = linearLogRecur(n); + std.debug.print("線性對數階(遞迴實現)的操作數量 = {}\n", .{count}); + + count = factorialRecur(n); + std.debug.print("階乘階(遞迴實現)的操作數量 = {}\n", .{count}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig b/zh-hant/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig new file mode 100644 index 000000000..8cd57d847 --- /dev/null +++ b/zh-hant/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig @@ -0,0 +1,45 @@ +// File: worst_best_time_complexity.zig +// Created Time: 2022-12-28 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 生成一個陣列,元素為 { 1, 2, ..., n },順序被打亂 +pub fn randomNumbers(comptime n: usize) [n]i32 { + var nums: [n]i32 = undefined; + // 生成陣列 nums = { 1, 2, 3, ..., n } + for (&nums, 0..) |*num, i| { + num.* = @as(i32, @intCast(i)) + 1; + } + // 隨機打亂陣列元素 + const rand = std.crypto.random; + rand.shuffle(i32, &nums); + return nums; +} + +// 查詢陣列 nums 中數字 1 所在索引 +pub fn findOne(nums: []i32) i32 { + for (nums, 0..) |num, i| { + // 當元素 1 在陣列頭部時,達到最佳時間複雜度 O(1) + // 當元素 1 在陣列尾部時,達到最差時間複雜度 O(n) + if (num == 1) return @intCast(i); + } + return -1; +} + +// Driver Code +pub fn main() !void { + var i: i32 = 0; + while (i < 10) : (i += 1) { + const n: usize = 100; + var nums = randomNumbers(n); + var index = findOne(&nums); + std.debug.print("\n陣列 [ 1, 2, ..., n ] 被打亂後 = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + std.debug.print("數字 1 的索引為 {}\n", .{index}); + } + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig new file mode 100644 index 000000000..16423ed44 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig @@ -0,0 +1,44 @@ +// File: climbing_stairs_backtrack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 回溯 +fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { + // 當爬到第 n 階時,方案數量加 1 + if (state == n) { + res.items[0] = res.items[0] + 1; + } + // 走訪所有選擇 + for (choices) |choice| { + // 剪枝:不允許越過第 n 階 + if (state + choice > n) { + continue; + } + // 嘗試:做出選擇,更新狀態 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +// 爬樓梯:回溯 +fn climbingStairsBacktrack(n: usize) !i32 { + var choices = [_]i32{ 1, 2 }; // 可選擇向上爬 1 階或 2 階 + var state: i32 = 0; // 從第 0 階開始爬 + var res = std.ArrayList(i32).init(std.heap.page_allocator); + defer res.deinit(); + try res.append(0); // 使用 res[0] 記錄方案數量 + backtrack(&choices, state, @intCast(n), res); + return res.items[0]; +} + +// Driver Code +pub fn main() !void { + var n: usize = 9; + + var res = try climbingStairsBacktrack(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig new file mode 100644 index 000000000..1213aaa7a --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig @@ -0,0 +1,35 @@ +// File: climbing_stairs_constraint_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 帶約束爬樓梯:動態規劃 +fn climbingStairsConstraintDP(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return 1; + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); + // 初始狀態:預設最小子問題的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (3..n + 1) |i| { + 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 +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsConstraintDP(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig new file mode 100644 index 000000000..e9edfe4b0 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig @@ -0,0 +1,31 @@ +// File: climbing_stairs_dfs.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 搜尋 +fn dfs(i: usize) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 or i == 2) { + return @intCast(i); + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1) + dfs(i - 2); + return count; +} + +// 爬樓梯:搜尋 +fn climbingStairsDFS(comptime n: usize) i32 { + return dfs(n); +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDFS(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig new file mode 100644 index 000000000..9faf633fe --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig @@ -0,0 +1,39 @@ +// File: climbing_stairs_dfs_mem.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 記憶化搜尋 +fn dfs(i: usize, mem: []i32) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 or i == 2) { + return @intCast(i); + } + // 若存在記錄 dp[i] ,則直接返回之 + if (mem[i] != -1) { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 記錄 dp[i] + mem[i] = count; + return count; +} + +// 爬樓梯:記憶化搜尋 +fn climbingStairsDFSMem(comptime n: usize) i32 { + // mem[i] 記錄爬到第 i 階的方案總數,-1 代表無記錄 + var mem = [_]i32{ -1 } ** (n + 1); + return dfs(n, &mem); +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDFSMem(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig new file mode 100644 index 000000000..d63da34ea --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig @@ -0,0 +1,51 @@ +// File: climbing_stairs_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 爬樓梯:動態規劃 +fn climbingStairsDP(comptime n: usize) i32 { + // 已知 dp[1] 和 dp[2] ,返回之 + if (n == 1 or n == 2) { + return @intCast(n); + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = [_]i32{-1} ** (n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = 1; + dp[2] = 2; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (3..n + 1) |i| { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +// 爬樓梯:空間最佳化後的動態規劃 +fn climbingStairsDPComp(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return @intCast(n); + } + var a: i32 = 1; + var b: i32 = 2; + for (3..n + 1) |_| { + var tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDP(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + res = climbingStairsDPComp(n); + std.debug.print("爬 {} 階樓梯共有 {} 種方案\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/coin_change.zig b/zh-hant/codes/zig/chapter_dynamic_programming/coin_change.zig new file mode 100644 index 000000000..eb6b02e02 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/coin_change.zig @@ -0,0 +1,77 @@ +// File: coin_change.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 零錢兌換:動態規劃 +fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // 初始化 dp 表 + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // 狀態轉移:首行首列 + for (1..amt + 1) |a| { + dp[0][a] = max; + } + // 狀態轉移:其餘行和列 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[n][amt] != max) { + return @intCast(dp[n][amt]); + } else { + return -1; + } +} + +// 零錢兌換:空間最佳化後的動態規劃 +fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // 初始化 dp 表 + var dp = [_]i32{0} ** (amt + 1); + @memset(&dp, max); + dp[0] = 0; + // 狀態轉移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[amt] != max) { + return @intCast(dp[amt]); + } else { + return -1; + } +} + +// Driver Code +pub fn main() !void { + comptime var coins = [_]i32{ 1, 2, 5 }; + comptime var amt: usize = 4; + + // 動態規劃 + var res = coinChangeDP(&coins, amt); + std.debug.print("湊到目標金額所需的最少硬幣數量為 {}\n", .{res}); + + // 空間最佳化後的動態規劃 + res = coinChangeDPComp(&coins, amt); + std.debug.print("湊到目標金額所需的最少硬幣數量為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/coin_change_ii.zig b/zh-hant/codes/zig/chapter_dynamic_programming/coin_change_ii.zig new file mode 100644 index 000000000..e3d9b7103 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/coin_change_ii.zig @@ -0,0 +1,66 @@ +// File: coin_change_ii.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 零錢兌換 II:動態規劃 +fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // 初始化 dp 表 + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // 初始化首列 + for (0..n + 1) |i| { + dp[i][0] = 1; + } + // 狀態轉移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超過目標金額,則不選硬幣 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[n][amt]; +} + +// 零錢兌換 II:空間最佳化後的動態規劃 +fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (amt + 1); + dp[0] = 1; + // 狀態轉移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 若超過目標金額,則不選硬幣 i + dp[a] = dp[a]; + } else { + // 不選和選硬幣 i 這兩種方案的較小值 + dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[amt]; +} + +// Driver Code +pub fn main() !void { + comptime var coins = [_]i32{ 1, 2, 5 }; + comptime var amt: usize = 5; + + // 動態規劃 + var res = coinChangeIIDP(&coins, amt); + std.debug.print("湊出目標金額的硬幣組合數量為 {}\n", .{res}); + + // 空間最佳化後的動態規劃 + res = coinChangeIIDPComp(&coins, amt); + std.debug.print("湊出目標金額的硬幣組合數量為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/edit_distance.zig b/zh-hant/codes/zig/chapter_dynamic_programming/edit_distance.zig new file mode 100644 index 000000000..9b8929057 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/edit_distance.zig @@ -0,0 +1,146 @@ +// File: edit_distance.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 編輯距離:暴力搜尋 +fn editDistanceDFS(comptime s: []const u8, comptime t: []const u8, i: usize, j: usize) i32 { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 and j == 0) { + return 0; + } + // 若 s 為空,則返回 t 長度 + if (i == 0) { + return @intCast(j); + } + // 若 t 為空,則返回 s 長度 + if (j == 0) { + return @intCast(i); + } + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) { + return editDistanceDFS(s, t, i - 1, j - 1); + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + var insert = editDistanceDFS(s, t, i, j - 1); + var delete = editDistanceDFS(s, t, i - 1, j); + var replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少編輯步數 + return @min(@min(insert, delete), replace) + 1; +} + +// 編輯距離:記憶化搜尋 +fn editDistanceDFSMem(comptime s: []const u8, comptime t: []const u8, mem: anytype, i: usize, j: usize) i32 { + // 若 s 和 t 都為空,則返回 0 + if (i == 0 and j == 0) { + return 0; + } + // 若 s 為空,則返回 t 長度 + if (i == 0) { + return @intCast(j); + } + // 若 t 為空,則返回 s 長度 + if (j == 0) { + return @intCast(i); + } + // 若已有記錄,則直接返回之 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 若兩字元相等,則直接跳過此兩字元 + if (s[i - 1] == t[j - 1]) { + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + } + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + var insert = editDistanceDFSMem(s, t, mem, i, j - 1); + var delete = editDistanceDFSMem(s, t, mem, i - 1, j); + var replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 記錄並返回最少編輯步數 + mem[i][j] = @min(@min(insert, delete), replace) + 1; + return mem[i][j]; +} + +// 編輯距離:動態規劃 +fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); + // 狀態轉移:首行首列 + for (1..n + 1) |i| { + dp[i][0] = @intCast(i); + } + for (1..m + 1) |j| { + dp[0][j] = @intCast(j); + } + // 狀態轉移:其餘行和列 + for (1..n + 1) |i| { + for (1..m + 1) |j| { + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +// 編輯距離:空間最佳化後的動態規劃 +fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_]i32{0} ** (m + 1); + // 狀態轉移:首行 + for (1..m + 1) |j| { + dp[j] = @intCast(j); + } + // 狀態轉移:其餘行 + for (1..n + 1) |i| { + // 狀態轉移:首列 + var leftup = dp[0]; // 暫存 dp[i-1, j-1] + dp[0] = @intCast(i); + // 狀態轉移:其餘列 + for (1..m + 1) |j| { + var temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若兩字元相等,則直接跳過此兩字元 + dp[j] = leftup; + } else { + // 最少編輯步數 = 插入、刪除、替換這三種操作的最少編輯步數 + 1 + dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新為下一輪的 dp[i-1, j-1] + } + } + return dp[m]; +} + +// Driver Code +pub fn main() !void { + const s = "bag"; + const t = "pack"; + comptime var n = s.len; + comptime var m = t.len; + + // 暴力搜尋 + var res = editDistanceDFS(s, t, n, m); + std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); + + // 記憶搜尋 + var mem = [_][m + 1]i32{[_]i32{-1} ** (m + 1)} ** (n + 1); + res = editDistanceDFSMem(s, t, @constCast(&mem), n, m); + std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); + + // 動態規劃 + res = editDistanceDP(s, t); + std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); + + // 空間最佳化後的動態規劃 + res = editDistanceDPComp(s, t); + std.debug.print("將 {s} 更改為 {s} 最少需要編輯 {} 步\n", .{ s, t, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/knapsack.zig b/zh-hant/codes/zig/chapter_dynamic_programming/knapsack.zig new file mode 100644 index 000000000..37fbfc15d --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/knapsack.zig @@ -0,0 +1,110 @@ +// File: knapsack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 0-1 背包:暴力搜尋 +fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 or c == 0) { + return 0; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + var no = knapsackDFS(wgt, val, i - 1, c); + var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // 返回兩種方案中價值更大的那一個 + return @max(no, yes); +} + +// 0-1 背包:記憶化搜尋 +fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { + // 若已選完所有物品或背包無剩餘容量,則返回價值 0 + if (i == 0 or c == 0) { + return 0; + } + // 若已有記錄,則直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超過背包容量,則只能選擇不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 計算不放入和放入物品 i 的最大價值 + var no = knapsackDFSMem(wgt, val, mem, i - 1, c); + var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // 記錄並返回兩種方案中價值更大的那一個 + mem[i][c] = @max(no, yes); + return mem[i][c]; +} + +// 0-1 背包:動態規劃 +fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // 狀態轉移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + 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 - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +// 0-1 背包:空間最佳化後的動態規劃 +fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { + var n = wgt.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (cap + 1); + // 狀態轉移 + for (1..n + 1) |i| { + // 倒序走訪 + var c = cap; + while (c > 0) : (c -= 1) { + if (wgt[i - 1] < c) { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; +} + +// Driver Code +pub fn main() !void { + comptime var wgt = [_]i32{ 10, 20, 30, 40, 50 }; + comptime var val = [_]i32{ 50, 120, 150, 210, 240 }; + comptime var cap = 50; + comptime var n = wgt.len; + + // 暴力搜尋 + var res = knapsackDFS(&wgt, &val, n, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + // 記憶搜尋 + var mem = [_][cap + 1]i32{[_]i32{-1} ** (cap + 1)} ** (n + 1); + res = knapsackDFSMem(&wgt, &val, @constCast(&mem), n, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + // 動態規劃 + res = knapsackDP(&wgt, &val, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + // 空間最佳化後的動態規劃 + res = knapsackDPComp(&wgt, &val, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig b/zh-hant/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig new file mode 100644 index 000000000..97ea39275 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig @@ -0,0 +1,54 @@ +// File: min_cost_climbing_stairs_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 爬樓梯最小代價:動態規劃 +fn minCostClimbingStairsDP(comptime cost: []i32) i32 { + comptime var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + // 初始化 dp 表,用於儲存子問題的解 + var dp = [_]i32{-1} ** (n + 1); + // 初始狀態:預設最小子問題的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (3..n + 1) |i| { + dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +// 爬樓梯最小代價:空間最佳化後的動態規劃 +fn minCostClimbingStairsDPComp(cost: []i32) i32 { + var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + var a = cost[1]; + var b = cost[2]; + // 狀態轉移:從較小子問題逐步求解較大子問題 + for (3..n + 1) |i| { + var tmp = b; + b = @min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +// Driver Code +pub fn main() !void { + comptime var cost = [_]i32{ 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; + std.debug.print("輸入樓梯的代價串列為 {any}\n", .{cost}); + + var res = minCostClimbingStairsDP(&cost); + std.debug.print("輸入樓梯的代價串列為 {}\n", .{res}); + + res = minCostClimbingStairsDPComp(&cost); + std.debug.print("輸入樓梯的代價串列為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/min_path_sum.zig b/zh-hant/codes/zig/chapter_dynamic_programming/min_path_sum.zig new file mode 100644 index 000000000..0c3e49dc8 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/min_path_sum.zig @@ -0,0 +1,122 @@ +// File: min_path_sum.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 最小路徑和:暴力搜尋 +fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { + // 若為左上角單元格,則終止搜尋 + if (i == 0 and j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + var up = minPathSumDFS(grid, i - 1, j); + var left = minPathSumDFS(grid, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; +} + +// 最小路徑和:記憶化搜尋 +fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { + // 若為左上角單元格,則終止搜尋 + if (i == 0 and j == 0) { + return grid[0][0]; + } + // 若行列索引越界,則返回 +∞ 代價 + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // 若已有記錄,則直接返回 + if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + } + // 計算從左上角到 (i-1, j) 和 (i, j-1) 的最小路徑代價 + var up = minPathSumDFSMem(grid, mem, i - 1, j); + var left = minPathSumDFSMem(grid, mem, i, j - 1); + // 返回從左上角到 (i, j) 的最小路徑代價 + // 記錄並返回左上角到 (i, j) 的最小路徑代價 + mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; +} + +// 最小路徑和:動態規劃 +fn minPathSumDP(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // 初始化 dp 表 + var dp = [_][m]i32{[_]i32{0} ** m} ** n; + dp[0][0] = grid[0][0]; + // 狀態轉移:首行 + for (1..m) |j| { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 狀態轉移:首列 + for (1..n) |i| { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 狀態轉移:其餘行和列 + for (1..n) |i| { + for (1..m) |j| { + dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +// 最小路徑和:空間最佳化後的動態規劃 +fn minPathSumDPComp(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // 初始化 dp 表 + var dp = [_]i32{0} ** m; + // 狀態轉移:首行 + dp[0] = grid[0][0]; + for (1..m) |j| { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 狀態轉移:其餘行 + for (1..n) |i| { + // 狀態轉移:首列 + dp[0] = dp[0] + grid[i][0]; + for (1..m) |j| { + dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +// Driver Code +pub fn main() !void { + comptime var grid = [_][4]i32{ + [_]i32{ 1, 3, 1, 5 }, + [_]i32{ 2, 2, 4, 2 }, + [_]i32{ 5, 3, 2, 1 }, + [_]i32{ 4, 3, 5, 2 }, + }; + comptime var n = grid.len; + comptime var m = grid[0].len; + + // 暴力搜尋 + var res = minPathSumDFS(&grid, n - 1, m - 1); + std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); + + // 記憶化搜尋 + var mem = [_][m]i32{[_]i32{-1} ** m} ** n; + res = minPathSumDFSMem(&grid, &mem, n - 1, m - 1); + std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); + + // 動態規劃 + res = minPathSumDP(&grid); + std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); + + // 空間最佳化後的動態規劃 + res = minPathSumDPComp(&grid); + std.debug.print("從左上角到右下角的最小路徑和為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig b/zh-hant/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig new file mode 100644 index 000000000..a890a8071 --- /dev/null +++ b/zh-hant/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig @@ -0,0 +1,62 @@ +// File: unbounded_knapsack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 完全背包:動態規劃 +fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // 狀態轉移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + 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 - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +// 完全背包:空間最佳化後的動態規劃 +fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // 初始化 dp 表 + var dp = [_]i32{0} ** (cap + 1); + // 狀態轉移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // 若超過背包容量,則不選物品 i + dp[c] = dp[c]; + } else { + // 不選和選物品 i 這兩種方案的較大值 + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; +} + +// Driver Code +pub fn main() !void { + comptime var wgt = [_]i32{ 1, 2, 3 }; + comptime var val = [_]i32{ 5, 11, 15 }; + comptime var cap = 4; + + // 動態規劃 + var res = unboundedKnapsackDP(&wgt, &val, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + // 空間最佳化後的動態規劃 + res = unboundedKnapsackDPComp(&wgt, &val, cap); + std.debug.print("不超過背包容量的最大物品價值為 {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_hashing/array_hash_map.zig b/zh-hant/codes/zig/chapter_hashing/array_hash_map.zig new file mode 100644 index 000000000..e07fe3a34 --- /dev/null +++ b/zh-hant/codes/zig/chapter_hashing/array_hash_map.zig @@ -0,0 +1,162 @@ +// File: array_hash_map.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 鍵值對 +const Pair = struct { + key: usize = undefined, + val: []const u8 = undefined, + + pub fn init(key: usize, val: []const u8) Pair { + return Pair { + .key = key, + .val = val, + }; + } +}; + +// 基於陣列實現的雜湊表 +pub fn ArrayHashMap(comptime T: type) type { + return struct { + bucket: ?std.ArrayList(?T) = null, + mem_allocator: std.mem.Allocator = undefined, + + const Self = @This(); + + // 建構子 + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + self.mem_allocator = allocator; + // 初始化一個長度為 100 的桶(陣列) + self.bucket = std.ArrayList(?T).init(self.mem_allocator); + var i: i32 = 0; + while (i < 100) : (i += 1) { + try self.bucket.?.append(null); + } + } + + // 析構函式 + pub fn deinit(self: *Self) void { + if (self.bucket != null) self.bucket.?.deinit(); + } + + // 雜湊函式 + fn hashFunc(key: usize) usize { + var index = key % 100; + return index; + } + + // 查詢操作 + pub fn get(self: *Self, key: usize) []const u8 { + var index = hashFunc(key); + var pair = self.bucket.?.items[index]; + return pair.?.val; + } + + // 新增操作 + pub fn put(self: *Self, key: usize, val: []const u8) !void { + var pair = Pair.init(key, val); + var index = hashFunc(key); + self.bucket.?.items[index] = pair; + } + + // 刪除操作 + pub fn remove(self: *Self, key: usize) !void { + var index = hashFunc(key); + // 置為 null ,代表刪除 + self.bucket.?.items[index] = null; + } + + // 獲取所有鍵值對 + pub fn pairSet(self: *Self) !std.ArrayList(T) { + var entry_set = std.ArrayList(T).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try entry_set.append(item.?); + } + return entry_set; + } + + // 獲取所有鍵 + pub fn keySet(self: *Self) !std.ArrayList(usize) { + var key_set = std.ArrayList(usize).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try key_set.append(item.?.key); + } + return key_set; + } + + // 獲取所有值 + pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { + var value_set = std.ArrayList([]const u8).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try value_set.append(item.?.val); + } + return value_set; + } + + // 列印雜湊表 + pub fn print(self: *Self) !void { + var entry_set = try self.pairSet(); + defer entry_set.deinit(); + for (entry_set.items) |item| { + std.debug.print("{} -> {s}\n", .{item.key, item.val}); + } + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化雜湊表 + var map = ArrayHashMap(Pair){}; + try map.init(std.heap.page_allocator); + defer map.deinit(); + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, value) + try map.put(12836, "小哈"); + try map.put(15937, "小囉"); + try map.put(16750, "小算"); + try map.put(13276, "小法"); + try map.put(10583, "小鴨"); + std.debug.print("\n新增完成後,雜湊表為\nKey -> Value\n", .{}); + try map.print(); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 value + var name = map.get(15937); + std.debug.print("\n輸入學號 15937 ,查詢到姓名 {s}\n", .{name}); + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, value) + try map.remove(10583); + std.debug.print("\n刪除 10583 後,雜湊表為\nKey -> Value\n", .{}); + try map.print(); + + // 走訪雜湊表 + std.debug.print("\n走訪鍵值對 Key->Value\n", .{}); + var entry_set = try map.pairSet(); + for (entry_set.items) |kv| { + std.debug.print("{} -> {s}\n", .{kv.key, kv.val}); + } + defer entry_set.deinit(); + std.debug.print("\n單獨走訪鍵 Key\n", .{}); + var key_set = try map.keySet(); + for (key_set.items) |key| { + std.debug.print("{}\n", .{key}); + } + defer key_set.deinit(); + std.debug.print("\n單獨走訪值 value\n", .{}); + var value_set = try map.valueSet(); + for (value_set.items) |val| { + std.debug.print("{s}\n", .{val}); + } + defer value_set.deinit(); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_hashing/hash_map.zig b/zh-hant/codes/zig/chapter_hashing/hash_map.zig new file mode 100644 index 000000000..7446fb3f9 --- /dev/null +++ b/zh-hant/codes/zig/chapter_hashing/hash_map.zig @@ -0,0 +1,54 @@ +// File: hash_map.zig +// Created Time: 2023-01-13 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化雜湊表 + var map = std.AutoHashMap(i32, []const u8).init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer map.deinit(); + + // 新增操作 + // 在雜湊表中新增鍵值對 (key, value) + try map.put(12836, "小哈"); + try map.put(15937, "小囉"); + try map.put(16750, "小算"); + try map.put(13276, "小法"); + try map.put(10583, "小鴨"); + std.debug.print("\n新增完成後,雜湊表為\nKey -> Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + // 查詢操作 + // 向雜湊表中輸入鍵 key ,得到值 value + var name = map.get(15937).?; + std.debug.print("\n輸入學號 15937 ,查詢到姓名 {s}\n", .{name}); + + // 刪除操作 + // 在雜湊表中刪除鍵值對 (key, value) + _ = map.remove(10583); + std.debug.print("\n刪除 10583 後,雜湊表為\nKey -> Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + // 走訪雜湊表 + std.debug.print("\n走訪鍵值對 Key->Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + std.debug.print("\n單獨走訪鍵 Key\n", .{}); + var it = map.iterator(); + while (it.next()) |kv| { + std.debug.print("{}\n", .{kv.key_ptr.*}); + } + + std.debug.print("\n單獨走訪值 value\n", .{}); + it = map.iterator(); + while (it.next()) |kv| { + std.debug.print("{s}\n", .{kv.value_ptr.*}); + } + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_heap/heap.zig b/zh-hant/codes/zig/chapter_heap/heap.zig new file mode 100644 index 000000000..95e1115c2 --- /dev/null +++ b/zh-hant/codes/zig/chapter_heap/heap.zig @@ -0,0 +1,80 @@ +// File: heap.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +fn lessThan(context: void, a: i32, b: i32) std.math.Order { + _ = context; + return std.math.order(a, b); +} + +fn greaterThan(context: void, a: i32, b: i32) std.math.Order { + return lessThan(context, a, b).invert(); +} + +fn testPush(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype, val: T) !void { + try heap.add(val); //元素入堆積 + std.debug.print("\n元素 {} 入堆積後\n", .{val}); + try inc.PrintUtil.printHeap(T, mem_allocator, heap); +} + +fn testPop(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype) !void { + var val = heap.remove(); //堆積頂元素出堆積 + std.debug.print("\n堆積頂元素 {} 出堆積後\n", .{val}); + try inc.PrintUtil.printHeap(T, mem_allocator, heap); +} + +// Driver Code +pub fn main() !void { + // 初始化記憶體分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化堆積 + // 初始化小頂堆積 + const PQlt = std.PriorityQueue(i32, void, lessThan); + var min_heap = PQlt.init(std.heap.page_allocator, {}); + defer min_heap.deinit(); + // 初始化大頂堆積 + const PQgt = std.PriorityQueue(i32, void, greaterThan); + var max_heap = PQgt.init(std.heap.page_allocator, {}); + defer max_heap.deinit(); + + std.debug.print("\n以下測試樣例為大頂堆積", .{}); + + // 元素入堆積 + try testPush(i32, mem_allocator, &max_heap, 1); + try testPush(i32, mem_allocator, &max_heap, 3); + try testPush(i32, mem_allocator, &max_heap, 2); + try testPush(i32, mem_allocator, &max_heap, 5); + try testPush(i32, mem_allocator, &max_heap, 4); + + // 獲取堆積頂元素 + var peek = max_heap.peek().?; + std.debug.print("\n堆積頂元素為 {}\n", .{peek}); + + // 堆積頂元素出堆積 + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + + // 獲取堆積的大小 + var size = max_heap.len; + std.debug.print("\n堆積元素數量為 {}\n", .{size}); + + // 判斷堆積是否為空 + var is_empty = if (max_heap.len == 0) true else false; + std.debug.print("\n堆積是否為空 {}\n", .{is_empty}); + + // 輸入串列並建堆積 + try min_heap.addSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + std.debug.print("\n輸入串列並建立小頂堆積後\n", .{}); + try inc.PrintUtil.printHeap(i32, mem_allocator, min_heap); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_heap/my_heap.zig b/zh-hant/codes/zig/chapter_heap/my_heap.zig new file mode 100644 index 000000000..e3118bd34 --- /dev/null +++ b/zh-hant/codes/zig/chapter_heap/my_heap.zig @@ -0,0 +1,186 @@ +// File: my_heap.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 堆積類別簡易實現 +pub fn MaxHeap(comptime T: type) type { + return struct { + const Self = @This(); + + max_heap: ?std.ArrayList(T) = null, // 使用串列而非陣列,這樣無須考慮擴容問題 + + // 建構子,根據輸入串列建堆積 + pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { + if (self.max_heap != null) return; + self.max_heap = std.ArrayList(T).init(allocator); + // 將串列元素原封不動新增進堆積 + try self.max_heap.?.appendSlice(nums); + // 堆積化除葉節點以外的其他所有節點 + var i: usize = parent(self.size() - 1) + 1; + while (i > 0) : (i -= 1) { + try self.siftDown(i - 1); + } + } + + // 析構方法,釋放記憶體 + pub fn deinit(self: *Self) void { + if (self.max_heap != null) self.max_heap.?.deinit(); + } + + // 獲取左子節點的索引 + fn left(i: usize) usize { + return 2 * i + 1; + } + + // 獲取右子節點的索引 + fn right(i: usize) usize { + return 2 * i + 2; + } + + // 獲取父節點的索引 + fn parent(i: usize) usize { + // return (i - 1) / 2; // 向下整除 + return @divFloor(i - 1, 2); + } + + // 交換元素 + fn swap(self: *Self, i: usize, j: usize) !void { + var tmp = self.max_heap.?.items[i]; + try self.max_heap.?.replaceRange(i, 1, &[_]T{self.max_heap.?.items[j]}); + try self.max_heap.?.replaceRange(j, 1, &[_]T{tmp}); + } + + // 獲取堆積大小 + pub fn size(self: *Self) usize { + return self.max_heap.?.items.len; + } + + // 判斷堆積是否為空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 訪問堆積頂元素 + pub fn peek(self: *Self) T { + return self.max_heap.?.items[0]; + } + + // 元素入堆積 + pub fn push(self: *Self, val: T) !void { + // 新增節點 + try self.max_heap.?.append(val); + // 從底至頂堆積化 + try self.siftUp(self.size() - 1); + } + + // 從節點 i 開始,從底至頂堆積化 + fn siftUp(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // 獲取節點 i 的父節點 + var p = parent(i); + // 當“越過根節點”或“節點無須修復”時,結束堆積化 + if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; + // 交換兩節點 + try self.swap(i, p); + // 迴圈向上堆積化 + i = p; + } + } + + // 元素出堆積 + pub fn pop(self: *Self) !T { + // 判斷處理 + if (self.isEmpty()) unreachable; + // 交換根節點與最右葉節點(交換首元素與尾元素) + try self.swap(0, self.size() - 1); + // 刪除節點 + var val = self.max_heap.?.pop(); + // 從頂至底堆積化 + try self.siftDown(0); + // 返回堆積頂元素 + return val; + } + + // 從節點 i 開始,從頂至底堆積化 + fn siftDown(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // 判斷節點 i, l, r 中值最大的節點,記為 ma + var l = left(i); + var r = right(i); + var ma = i; + if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; + if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; + // 若節點 i 最大或索引 l, r 越界,則無須繼續堆積化,跳出 + if (ma == i) break; + // 交換兩節點 + try self.swap(i, ma); + // 迴圈向下堆積化 + i = ma; + } + } + + fn lessThan(context: void, a: T, b: T) std.math.Order { + _ = context; + return std.math.order(a, b); + } + + fn greaterThan(context: void, a: T, b: T) std.math.Order { + return lessThan(context, a, b).invert(); + } + + // 列印堆積(二元樹) + pub fn print(self: *Self, mem_allocator: std.mem.Allocator) !void { + const PQgt = std.PriorityQueue(T, void, greaterThan); + var queue = PQgt.init(std.heap.page_allocator, {}); + defer queue.deinit(); + try queue.addSlice(self.max_heap.?.items); + try inc.PrintUtil.printHeap(T, mem_allocator, queue); + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化記憶體分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化大頂堆積 + var max_heap = MaxHeap(i32){}; + try max_heap.init(std.heap.page_allocator, &[_]i32{ 9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2 }); + defer max_heap.deinit(); + std.debug.print("\n輸入串列並建堆積後\n", .{}); + try max_heap.print(mem_allocator); + + // 獲取堆積頂元素 + var peek = max_heap.peek(); + std.debug.print("\n堆積頂元素為 {}\n", .{peek}); + + // 元素入堆積 + const val = 7; + try max_heap.push(val); + std.debug.print("\n元素 {} 入堆積後\n", .{val}); + try max_heap.print(mem_allocator); + + // 堆積頂元素出堆積 + peek = try max_heap.pop(); + std.debug.print("\n堆積頂元素 {} 出堆積後\n", .{peek}); + try max_heap.print(mem_allocator); + + // 獲取堆積的大小 + var size = max_heap.size(); + std.debug.print("\n堆積元素數量為 {}", .{size}); + + // 判斷堆積是否為空 + var is_empty = max_heap.isEmpty(); + std.debug.print("\n堆積是否為空 {}\n", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_searching/binary_search.zig b/zh-hant/codes/zig/chapter_searching/binary_search.zig new file mode 100644 index 000000000..4e1521e19 --- /dev/null +++ b/zh-hant/codes/zig/chapter_searching/binary_search.zig @@ -0,0 +1,64 @@ +// File: binary_search.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 二分搜尋(雙閉區間) +fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 初始化雙閉區間 [0, n-1] ,即 i, j 分別指向陣列首元素、尾元素 + var i: usize = 0; + var j: usize = nums.items.len - 1; + // 迴圈,當搜尋區間為空時跳出(當 i > j 時為空) + while (i <= j) { + var m = i + (j - i) / 2; // 計算中點索引 m + if (nums.items[m] < target) { // 此情況說明 target 在區間 [m+1, j] 中 + i = m + 1; + } else if (nums.items[m] > target) { // 此情況說明 target 在區間 [i, m-1] 中 + j = m - 1; + } else { // 找到目標元素,返回其索引 + return @intCast(m); + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +// 二分搜尋(左閉右開區間) +fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 初始化左閉右開區間 [0, n) ,即 i, j 分別指向陣列首元素、尾元素+1 + var i: usize = 0; + var j: usize = nums.items.len; + // 迴圈,當搜尋區間為空時跳出(當 i = j 時為空) + while (i <= j) { + var m = i + (j - i) / 2; // 計算中點索引 m + if (nums.items[m] < target) { // 此情況說明 target 在區間 [m+1, j) 中 + i = m + 1; + } else if (nums.items[m] > target) { // 此情況說明 target 在區間 [i, m) 中 + j = m; + } else { // 找到目標元素,返回其索引 + return @intCast(m); + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +// Driver Code +pub fn main() !void { + var target: i32 = 6; + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }); + + // 二分搜尋(雙閉區間) + var index = binarySearch(i32, nums, target); + std.debug.print("目標元素 6 的索引 = {}\n", .{index}); + + // 二分搜尋(左閉右開區間) + index = binarySearchLCRO(i32, nums, target); + std.debug.print("目標元素 6 的索引 = {}\n", .{index}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_searching/hashing_search.zig b/zh-hant/codes/zig/chapter_searching/hashing_search.zig new file mode 100644 index 000000000..cc381782d --- /dev/null +++ b/zh-hant/codes/zig/chapter_searching/hashing_search.zig @@ -0,0 +1,57 @@ +// File: hashing_search.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 雜湊查詢(陣列) +fn hashingSearchArray(comptime T: type, map: std.AutoHashMap(T, T), target: T) T { + // 雜湊表的 key: 目標元素,value: 索引 + // 若雜湊表中無此 key ,返回 -1 + if (map.getKey(target) == null) return -1; + return map.get(target).?; +} + +// 雜湊查詢(鏈結串列) +fn hashingSearchLinkedList(comptime T: type, map: std.AutoHashMap(T, *inc.ListNode(T)), target: T) ?*inc.ListNode(T) { + // 雜湊表的 key: 目標節點值,value: 節點物件 + // 若雜湊表中無此 key ,返回 null + if (map.getKey(target) == null) return null; + return map.get(target); +} + +// Driver Code +pub fn main() !void { + var target: i32 = 3; + + // 雜湊查詢(陣列) + var nums = [_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // 初始化雜湊表 + var map = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer map.deinit(); + for (nums, 0..) |num, i| { + try map.put(num, @as(i32, @intCast(i))); // key: 元素,value: 索引 + } + var index = hashingSearchArray(i32, map, target); + std.debug.print("目標元素 3 的索引 = {}\n", .{index}); + + // 雜湊查詢(鏈結串列) + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var head = try inc.ListUtil.arrToLinkedList(i32, mem_allocator, &nums); + // 初始化雜湊表 + var map1 = std.AutoHashMap(i32, *inc.ListNode(i32)).init(std.heap.page_allocator); + defer map1.deinit(); + while (head != null) { + try map1.put(head.?.val, head.?); + head = head.?.next; + } + var node = hashingSearchLinkedList(i32, map1, target); + std.debug.print("目標節點值 3 的對應節點物件為 ", .{}); + try inc.PrintUtil.printLinkedList(i32, node); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_searching/linear_search.zig b/zh-hant/codes/zig/chapter_searching/linear_search.zig new file mode 100644 index 000000000..b69123a25 --- /dev/null +++ b/zh-hant/codes/zig/chapter_searching/linear_search.zig @@ -0,0 +1,54 @@ +// File: linear_search.zig +// Created Time: 2023-01-13 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 線性查詢(陣列) +fn linearSearchArray(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 走訪陣列 + for (nums.items, 0..) |num, i| { + // 找到目標元素, 返回其索引 + if (num == target) { + return @intCast(i); + } + } + // 未找到目標元素,返回 -1 + return -1; +} + +// 線性查詢(鏈結串列) +pub fn linearSearchLinkedList(comptime T: type, node: ?*inc.ListNode(T), target: T) ?*inc.ListNode(T) { + var head = node; + // 走訪鏈結串列 + while (head != null) { + // 找到目標節點,返回之 + if (head.?.val == target) return head; + head = head.?.next; + } + return null; +} + +// Driver Code +pub fn main() !void { + var target: i32 = 3; + + // 在陣列中執行線性查詢 + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }); + var index = linearSearchArray(i32, nums, target); + std.debug.print("目標元素 3 的索引 = {}\n", .{index}); + + // 在鏈結串列中執行線性查詢 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var head = try inc.ListUtil.listToLinkedList(i32, mem_allocator, nums); + var node = linearSearchLinkedList(i32, head, target); + std.debug.print("目標節點值 3 的對應節點物件為 ", .{}); + try inc.PrintUtil.printLinkedList(i32, node); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_searching/two_sum.zig b/zh-hant/codes/zig/chapter_searching/two_sum.zig new file mode 100644 index 000000000..30e4a8230 --- /dev/null +++ b/zh-hant/codes/zig/chapter_searching/two_sum.zig @@ -0,0 +1,58 @@ +// File: two_sum.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 方法一:暴力列舉 +pub fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { + var size: usize = nums.len; + var i: usize = 0; + // 兩層迴圈,時間複雜度為 O(n^2) + while (i < size - 1) : (i += 1) { + var j = i + 1; + while (j < size) : (j += 1) { + if (nums[i] + nums[j] == target) { + return [_]i32{@intCast(i), @intCast(j)}; + } + } + } + return null; +} + +// 方法二:輔助雜湊表 +pub fn twoSumHashTable(nums: []i32, target: i32) !?[2]i32 { + var size: usize = nums.len; + // 輔助雜湊表,空間複雜度為 O(n) + var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer dic.deinit(); + var i: usize = 0; + // 單層迴圈,時間複雜度為 O(n) + while (i < size) : (i += 1) { + if (dic.contains(target - nums[i])) { + return [_]i32{dic.get(target - nums[i]).?, @intCast(i)}; + } + try dic.put(nums[i], @intCast(i)); + } + return null; +} + + +pub fn main() !void { + // ======= Test Case ======= + var nums = [_]i32{ 2, 7, 11, 15 }; + var target: i32 = 9; + + // ====== Driver Code ====== + // 方法一 + var res = twoSumBruteForce(&nums, target).?; + std.debug.print("方法一 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + // 方法二 + res = (try twoSumHashTable(&nums, target)).?; + std.debug.print("\n方法二 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_sorting/bubble_sort.zig b/zh-hant/codes/zig/chapter_sorting/bubble_sort.zig new file mode 100644 index 000000000..95fa15d6e --- /dev/null +++ b/zh-hant/codes/zig/chapter_sorting/bubble_sort.zig @@ -0,0 +1,61 @@ +// File: bubble_sort.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 泡沫排序 +fn bubbleSort(nums: []i32) void { + // 外迴圈:未排序區間為 [0, i] + var i: usize = nums.len - 1; + while (i > 0) : (i -= 1) { + var j: usize = 0; + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + var tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +// 泡沫排序(標誌最佳化) +fn bubbleSortWithFlag(nums: []i32) void { + // 外迴圈:未排序區間為 [0, i] + var i: usize = nums.len - 1; + while (i > 0) : (i -= 1) { + var flag = false; // 初始化標誌位 + var j: usize = 0; + // 內迴圈:將未排序區間 [0, i] 中的最大元素交換至該區間的最右端 + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // 交換 nums[j] 與 nums[j + 1] + var tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; + } + } + if (!flag) break; // 此輪“冒泡”未交換任何元素,直接跳出 + } +} + +// Driver Code +pub fn main() !void { + var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; + bubbleSort(&nums); + std.debug.print("泡沫排序完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + var nums1 = [_]i32{ 4, 1, 3, 1, 5, 2 }; + bubbleSortWithFlag(&nums1); + std.debug.print("\n泡沫排序完成後 nums1 = ", .{}); + inc.PrintUtil.printArray(i32, &nums1); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_sorting/insertion_sort.zig b/zh-hant/codes/zig/chapter_sorting/insertion_sort.zig new file mode 100644 index 000000000..4d34176e0 --- /dev/null +++ b/zh-hant/codes/zig/chapter_sorting/insertion_sort.zig @@ -0,0 +1,31 @@ +// File: insertion_sort.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 插入排序 +fn insertionSort(nums: []i32) void { + // 外迴圈:已排序區間為 [0, i-1] + var i: usize = 1; + while (i < nums.len) : (i += 1) { + var base = nums[i]; + var j: usize = i; + // 內迴圈:將 base 插入到已排序區間 [0, i-1] 中的正確位置 + while (j >= 1 and nums[j - 1] > base) : (j -= 1) { + nums[j] = nums[j - 1]; // 將 nums[j] 向右移動一位 + } + nums[j] = base; // 將 base 賦值到正確位置 + } +} + +// Driver Code +pub fn main() !void { + var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; + insertionSort(&nums); + std.debug.print("插入排序完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_sorting/merge_sort.zig b/zh-hant/codes/zig/chapter_sorting/merge_sort.zig new file mode 100644 index 000000000..0d756c48a --- /dev/null +++ b/zh-hant/codes/zig/chapter_sorting/merge_sort.zig @@ -0,0 +1,67 @@ +// File: merge_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 合併左子陣列和右子陣列 +// 左子陣列區間 [left, mid] +// 右子陣列區間 [mid + 1, right] +fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { + // 初始化輔助陣列 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var tmp = try mem_allocator.alloc(i32, right + 1 - left); + std.mem.copy(i32, tmp, nums[left..right+1]); + // 左子陣列的起始索引和結束索引 + var leftStart = left - left; + var leftEnd = mid - left; + // 右子陣列的起始索引和結束索引 + var rightStart = mid + 1 - left; + var rightEnd = right - left; + // i, j 分別指向左子陣列、右子陣列的首元素 + var i = leftStart; + var j = rightStart; + // 透過覆蓋原陣列 nums 來合併左子陣列和右子陣列 + var k = left; + while (k <= right) : (k += 1) { + // 若“左子陣列已全部合併完”,則選取右子陣列元素,並且 j++ + if (i > leftEnd) { + nums[k] = tmp[j]; + j += 1; + // 否則,若“右子陣列已全部合併完”或“左子陣列元素 <= 右子陣列元素”,則選取左子陣列元素,並且 i++ + } else if (j > rightEnd or tmp[i] <= tmp[j]) { + nums[k] = tmp[i]; + i += 1; + // 否則,若“左右子陣列都未全部合併完”且“左子陣列元素 > 右子陣列元素”,則選取右子陣列元素,並且 j++ + } else { + nums[k] = tmp[j]; + j += 1; + } + } +} + +// 合併排序 +fn mergeSort(nums: []i32, left: usize, right: usize) !void { + // 終止條件 + if (left >= right) return; // 當子陣列長度為 1 時終止遞迴 + // 劃分階段 + var mid = (left + right) / 2; // 計算中點 + try mergeSort(nums, left, mid); // 遞迴左子陣列 + try mergeSort(nums, mid + 1, right); // 遞迴右子陣列 + // 合併階段 + try merge(nums, left, mid, right); +} + +// Driver Code +pub fn main() !void { + // 合併排序 + var nums = [_]i32{ 7, 3, 2, 6, 0, 1, 5, 4 }; + try mergeSort(&nums, 0, nums.len - 1); + std.debug.print("合併排序完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_sorting/quick_sort.zig b/zh-hant/codes/zig/chapter_sorting/quick_sort.zig new file mode 100644 index 000000000..4c7bb59de --- /dev/null +++ b/zh-hant/codes/zig/chapter_sorting/quick_sort.zig @@ -0,0 +1,162 @@ +// File: quick_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 快速排序類別 +const QuickSort = struct { + + // 元素交換 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 哨兵劃分 + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // 以 nums[left] 為基準數 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 從右向左找首個小於基準數的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + // 快速排序 + pub fn quickSort(nums: []i32, left: usize, right: usize) void { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return; + // 哨兵劃分 + var pivot = partition(nums, left, right); + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +// 快速排序類別(中位基準數最佳化) +const QuickSortMedian = struct { + + // 元素交換 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 選取三個候選元素的中位數 + pub fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { + var l = nums[left]; + var m = nums[mid]; + var r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m 在 l 和 r 之間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l 在 m 和 r 之間 + return right; + } + + // 哨兵劃分(三數取中值) + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // 選取三個候選元素的中位數 + var med = medianThree(nums, left, (left + right) / 2, right); + // 將中位數交換至陣列最左端 + swap(nums, left, med); + // 以 nums[left] 為基準數 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 從右向左找首個小於基準數的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + // 快速排序 + pub fn quickSort(nums: []i32, left: usize, right: usize) void { + // 子陣列長度為 1 時終止遞迴 + if (left >= right) return; + // 哨兵劃分 + var pivot = partition(nums, left, right); + if (pivot == 0) return; + // 遞迴左子陣列、右子陣列 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +// 快速排序類別(尾遞迴最佳化) +const QuickSortTailCall = struct { + + // 元素交換 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 哨兵劃分 + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // 以 nums[left] 為基準數 + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 從右向左找首個小於基準數的元素 + while (i < j and nums[i] <= nums[left]) i += 1; // 從左向右找首個大於基準數的元素 + swap(nums, i, j); // 交換這兩個元素 + } + swap(nums, i, left); // 將基準數交換至兩子陣列的分界線 + return i; // 返回基準數的索引 + } + + // 快速排序(尾遞迴最佳化) + pub fn quickSort(nums: []i32, left_: usize, right_: usize) void { + var left = left_; + var right = right_; + // 子陣列長度為 1 時終止遞迴 + while (left < right) { + // 哨兵劃分操作 + var pivot = partition(nums, left, right); + // 對兩個子陣列中較短的那個執行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 遞迴排序左子陣列 + left = pivot + 1; // 剩餘未排序區間為 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 遞迴排序右子陣列 + right = pivot - 1; // 剩餘未排序區間為 [left, pivot - 1] + } + } + } +}; + +// Driver Code +pub fn main() !void { + // 快速排序 + var nums = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(&nums, 0, nums.len - 1); + std.debug.print("快速排序完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + // 快速排序(中位基準數最佳化) + var nums1 = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSortMedian.quickSort(&nums1, 0, nums1.len - 1); + std.debug.print("\n快速排序(中位基準數最佳化)完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums1); + + // 快速排序(尾遞迴最佳化) + var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1); + std.debug.print("\n快速排序(尾遞迴最佳化)完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums2); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_sorting/radix_sort.zig b/zh-hant/codes/zig/chapter_sorting/radix_sort.zig new file mode 100644 index 000000000..9f0424b58 --- /dev/null +++ b/zh-hant/codes/zig/chapter_sorting/radix_sort.zig @@ -0,0 +1,77 @@ +// File: radix_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 獲取元素 num 的第 k 位,其中 exp = 10^(k-1) +fn digit(num: i32, exp: i32) i32 { + // 傳入 exp 而非 k 可以避免在此重複執行昂貴的次方計算 + return @mod(@divFloor(num, exp), 10); +} + +// 計數排序(根據 nums 第 k 位排序) +fn countingSortDigit(nums: []i32, exp: i32) !void { + // 十進位制的位範圍為 0~9 ,因此需要長度為 10 的桶陣列 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + // defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var counter = try mem_allocator.alloc(usize, 10); + @memset(counter, 0); + var n = nums.len; + // 統計 0~9 各數字的出現次數 + for (nums) |num| { + var d: u32 = @bitCast(digit(num, exp)); // 獲取 nums[i] 第 k 位,記為 d + counter[d] += 1; // 統計數字 d 的出現次數 + } + // 求前綴和,將“出現個數”轉換為“陣列索引” + var i: usize = 1; + while (i < 10) : (i += 1) { + counter[i] += counter[i - 1]; + } + // 倒序走訪,根據桶內統計結果,將各元素填入 res + var res = try mem_allocator.alloc(i32, n); + i = n - 1; + while (i >= 0) : (i -= 1) { + var d: u32 = @bitCast(digit(nums[i], exp)); + var j = counter[d] - 1; // 獲取 d 在陣列中的索引 j + res[j] = nums[i]; // 將當前元素填入索引 j + counter[d] -= 1; // 將 d 的數量減 1 + if (i == 0) break; + } + // 使用結果覆蓋原陣列 nums + i = 0; + while (i < n) : (i += 1) { + nums[i] = res[i]; + } +} + +// 基數排序 +fn radixSort(nums: []i32) !void { + // 獲取陣列的最大元素,用於判斷最大位數 + var m: i32 = std.math.minInt(i32); + for (nums) |num| { + if (num > m) m = num; + } + // 按照從低位到高位的順序走訪 + var exp: i32 = 1; + while (exp <= m) : (exp *= 10) { + // 對陣列元素的第 k 位執行計數排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + try countingSortDigit(nums, exp); + } +} + +// Driver Code +pub fn main() !void { + // 基數排序 + var nums = [_]i32{ 23, 12, 3, 4, 788, 192 }; + try radixSort(&nums); + std.debug.print("基數排序完成後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/array_queue.zig b/zh-hant/codes/zig/chapter_stack_and_queue/array_queue.zig new file mode 100644 index 000000000..323a0e3c1 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/array_queue.zig @@ -0,0 +1,140 @@ +// File: array_queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 基於環形陣列實現的佇列 +pub fn ArrayQueue(comptime T: type) type { + return struct { + const Self = @This(); + + nums: []T = undefined, // 用於儲存佇列元素的陣列 + cap: usize = 0, // 佇列容量 + front: usize = 0, // 佇列首指標,指向佇列首元素 + queSize: usize = 0, // 尾指標,指向佇列尾 + 1 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子(分配記憶體+初始化陣列) + pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.cap = cap; + self.nums = try self.mem_allocator.alloc(T, self.cap); + @memset(self.nums, @as(T, 0)); + } + + // 析構函式(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取佇列的容量 + pub fn capacity(self: *Self) usize { + return self.cap; + } + + // 獲取佇列的長度 + pub fn size(self: *Self) usize { + return self.queSize; + } + + // 判斷佇列是否為空 + pub fn isEmpty(self: *Self) bool { + return self.queSize == 0; + } + + // 入列 + pub fn push(self: *Self, num: T) !void { + if (self.size() == self.capacity()) { + std.debug.print("佇列已滿\n", .{}); + return; + } + // 計算佇列尾指標,指向佇列尾索引 + 1 + // 透過取餘操作實現 rear 越過陣列尾部後回到頭部 + var rear = (self.front + self.queSize) % self.capacity(); + // 在尾節點後新增 num + self.nums[rear] = num; + self.queSize += 1; + } + + // 出列 + pub fn pop(self: *Self) T { + var num = self.peek(); + // 佇列首指標向後移動一位,若越過尾部,則返回到陣列頭部 + self.front = (self.front + 1) % self.capacity(); + self.queSize -= 1; + return num; + } + + // 訪問佇列首元素 + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("佇列為空"); + return self.nums[self.front]; + } + + // 返回陣列 + pub fn toArray(self: *Self) ![]T { + // 僅轉換有效長度範圍內的串列元素 + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + var j: usize = self.front; + while (i < self.size()) : ({ i += 1; j += 1; }) { + res[i] = self.nums[j % self.capacity()]; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化佇列 + var capacity: usize = 10; + var queue = ArrayQueue(i32){}; + try queue.init(std.heap.page_allocator, capacity); + defer queue.deinit(); + + // 元素入列 + try queue.push(1); + try queue.push(3); + try queue.push(2); + try queue.push(5); + try queue.push(4); + std.debug.print("佇列 queue = ", .{}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 訪問佇列首元素 + var peek = queue.peek(); + std.debug.print("\n佇列首元素 peek = {}", .{peek}); + + // 元素出列 + var pop = queue.pop(); + std.debug.print("\n出列元素 pop = {},出列後 queue = ", .{pop}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 獲取佇列的長度 + var size = queue.size(); + std.debug.print("\n佇列長度 size = {}", .{size}); + + // 判斷佇列是否為空 + var is_empty = queue.isEmpty(); + std.debug.print("\n佇列是否為空 = {}", .{is_empty}); + + // 測試環形陣列 + var i: i32 = 0; + while (i < 10) : (i += 1) { + try queue.push(i); + _ = queue.pop(); + std.debug.print("\n第 {} 輪入列 + 出列後 queue = ", .{i}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + } + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/array_stack.zig b/zh-hant/codes/zig/chapter_stack_and_queue/array_stack.zig new file mode 100644 index 000000000..609cd1459 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/array_stack.zig @@ -0,0 +1,97 @@ +// File: array_stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 基於陣列實現的堆疊 +pub fn ArrayStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack: ?std.ArrayList(T) = null, + + // 建構子(分配記憶體+初始化堆疊) + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.stack == null) { + self.stack = std.ArrayList(T).init(allocator); + } + } + + // 析構方法(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.stack == null) return; + self.stack.?.deinit(); + } + + // 獲取堆疊的長度 + pub fn size(self: *Self) usize { + return self.stack.?.items.len; + } + + // 判斷堆疊是否為空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 訪問堆疊頂元素 + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("堆疊為空"); + return self.stack.?.items[self.size() - 1]; + } + + // 入堆疊 + pub fn push(self: *Self, num: T) !void { + try self.stack.?.append(num); + } + + // 出堆疊 + pub fn pop(self: *Self) T { + var num = self.stack.?.pop(); + return num; + } + + // 返回 ArrayList + pub fn toList(self: *Self) std.ArrayList(T) { + return self.stack.?; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化堆疊 + var stack = ArrayStack(i32){}; + stack.init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer stack.deinit(); + + // 元素入堆疊 + try stack.push(1); + try stack.push(3); + try stack.push(2); + try stack.push(5); + try stack.push(4); + std.debug.print("堆疊 stack = ", .{}); + inc.PrintUtil.printList(i32, stack.toList()); + + // 訪問堆疊頂元素 + var peek = stack.peek(); + std.debug.print("\n堆疊頂元素 peek = {}", .{peek}); + + // 元素出堆疊 + var top = stack.pop(); + std.debug.print("\n出堆疊元素 pop = {},出堆疊後 stack = ", .{top}); + inc.PrintUtil.printList(i32, stack.toList()); + + // 獲取堆疊的長度 + var size = stack.size(); + std.debug.print("\n堆疊的長度 size = {}", .{size}); + + // 判斷堆疊是否為空 + var is_empty = stack.isEmpty(); + std.debug.print("\n堆疊是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/deque.zig b/zh-hant/codes/zig/chapter_stack_and_queue/deque.zig new file mode 100644 index 000000000..a6ca594a1 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/deque.zig @@ -0,0 +1,51 @@ +// File: deque.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化雙向佇列 + const L = std.TailQueue(i32); + var deque = L{}; + + // 元素入列 + var node1 = L.Node{ .data = 2 }; + var node2 = L.Node{ .data = 5 }; + var node3 = L.Node{ .data = 4 }; + var node4 = L.Node{ .data = 3 }; + var node5 = L.Node{ .data = 1 }; + deque.append(&node1); // 新增至佇列尾 + deque.append(&node2); + deque.append(&node3); + deque.prepend(&node4); // 新增至佇列首 + deque.prepend(&node5); + std.debug.print("雙向佇列 deque = ", .{}); + inc.PrintUtil.printQueue(i32, deque); + + // 訪問元素 + var peek_first = deque.first.?.data; // 佇列首元素 + std.debug.print("\n佇列首元素 peek_first = {}", .{peek_first}); + var peek_last = deque.last.?.data; // 佇列尾元素 + std.debug.print("\n佇列尾元素 peek_last = {}", .{peek_last}); + + // 元素出列 + var pop_first = deque.popFirst().?.data; // 佇列首元素出列 + std.debug.print("\n佇列首出列元素 pop_first = {},佇列首出列後 deque = ", .{pop_first}); + inc.PrintUtil.printQueue(i32, deque); + var pop_last = deque.pop().?.data; // 佇列尾元素出列 + std.debug.print("\n佇列尾出列元素 pop_last = {},佇列尾出列後 deque = ", .{pop_last}); + inc.PrintUtil.printQueue(i32, deque); + + // 獲取雙向佇列的長度 + var size = deque.len; + std.debug.print("\n雙向佇列長度 size = {}", .{size}); + + // 判斷雙向佇列是否為空 + var is_empty = if (deque.len == 0) true else false; + std.debug.print("\n雙向佇列是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig new file mode 100644 index 000000000..45639b437 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig @@ -0,0 +1,207 @@ +// File: linkedlist_deque.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 雙向鏈結串列節點 +pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // 節點值 + next: ?*Self = null, // 後繼節點指標 + prev: ?*Self = null, // 前驅節點指標 + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; +} + +// 基於雙向鏈結串列實現的雙向佇列 +pub fn LinkedListDeque(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*ListNode(T) = null, // 頭節點 front + rear: ?*ListNode(T) = null, // 尾節點 rear + que_size: usize = 0, // 雙向佇列的長度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子(分配記憶體+初始化佇列) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // 析構函式(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取雙向佇列的長度 + pub fn size(self: *Self) usize { + return self.que_size; + } + + // 判斷雙向佇列是否為空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 入列操作 + pub fn push(self: *Self, num: T, is_front: bool) !void { + var node = try self.mem_allocator.create(ListNode(T)); + node.init(num); + // 若鏈結串列為空,則令 front 和 rear 都指向 node + if (self.isEmpty()) { + self.front = node; + self.rear = node; + // 佇列首入列操作 + } else if (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.que_size += 1; // 更新佇列長度 + } + + // 佇列首入列 + pub fn pushFirst(self: *Self, num: T) !void { + try self.push(num, true); + } + + // 佇列尾入列 + pub fn pushLast(self: *Self, num: T) !void { + try self.push(num, false); + } + + // 出列操作 + pub fn pop(self: *Self, is_front: bool) T { + if (self.isEmpty()) @panic("雙向佇列為空"); + var val: T = undefined; + // 佇列首出列操作 + if (is_front) { + val = self.front.?.val; // 暫存頭節點值 + // 刪除頭節點 + var fNext = self.front.?.next; + if (fNext != null) { + fNext.?.prev = null; + self.front.?.next = null; + } + self.front = fNext; // 更新頭節點 + // 佇列尾出列操作 + } else { + val = self.rear.?.val; // 暫存尾節點值 + // 刪除尾節點 + var rPrev = self.rear.?.prev; + if (rPrev != null) { + rPrev.?.next = null; + self.rear.?.prev = null; + } + self.rear = rPrev; // 更新尾節點 + } + self.que_size -= 1; // 更新佇列長度 + return val; + } + + // 佇列首出列 + pub fn popFirst(self: *Self) T { + return self.pop(true); + } + + // 佇列尾出列 + pub fn popLast(self: *Self) T { + return self.pop(false); + } + + // 訪問佇列首元素 + pub fn peekFirst(self: *Self) T { + if (self.isEmpty()) @panic("雙向佇列為空"); + return self.front.?.val; + } + + // 訪問佇列尾元素 + pub fn peekLast(self: *Self) T { + if (self.isEmpty()) @panic("雙向佇列為空"); + return self.rear.?.val; + } + + // 返回陣列用於列印 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化雙向佇列 + var deque = LinkedListDeque(i32){}; + try deque.init(std.heap.page_allocator); + defer deque.deinit(); + try deque.pushLast(3); + try deque.pushLast(2); + try deque.pushLast(5); + std.debug.print("雙向佇列 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 訪問元素 + var peek_first = deque.peekFirst(); + std.debug.print("\n佇列首元素 peek_first = {}", .{peek_first}); + var peek_last = deque.peekLast(); + std.debug.print("\n佇列尾元素 peek_last = {}", .{peek_last}); + + // 元素入列 + try deque.pushLast(4); + std.debug.print("\n元素 4 佇列尾入列後 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + try deque.pushFirst(1); + std.debug.print("\n元素 1 佇列首入列後 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 元素出列 + var pop_last = deque.popLast(); + std.debug.print("\n佇列尾出列元素 = {},佇列尾出列後 deque = ", .{pop_last}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + var pop_first = deque.popFirst(); + std.debug.print("\n佇列首出列元素 = {},佇列首出列後 deque = ", .{pop_first}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 獲取雙向佇列的長度 + var size = deque.size(); + std.debug.print("\n雙向佇列長度 size = {}", .{size}); + + // 判斷雙向佇列是否為空 + var is_empty = deque.isEmpty(); + std.debug.print("\n雙向佇列是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig new file mode 100644 index 000000000..55c67877c --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig @@ -0,0 +1,127 @@ +// File: linkedlist_queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 基於鏈結串列實現的佇列 +pub fn LinkedListQueue(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*inc.ListNode(T) = null, // 頭節點 front + rear: ?*inc.ListNode(T) = null, // 尾節點 rear + que_size: usize = 0, // 佇列的長度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子(分配記憶體+初始化佇列) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // 析構函式(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取佇列的長度 + pub fn size(self: *Self) usize { + return self.que_size; + } + + // 判斷佇列是否為空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 訪問佇列首元素 + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("佇列為空"); + return self.front.?.val; + } + + // 入列 + pub fn push(self: *Self, num: T) !void { + // 在尾節點後新增 num + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + // 如果佇列為空,則令頭、尾節點都指向該節點 + if (self.front == null) { + self.front = node; + self.rear = node; + // 如果佇列不為空,則將該節點新增到尾節點後 + } else { + self.rear.?.next = node; + self.rear = node; + } + self.que_size += 1; + } + + // 出列 + pub fn pop(self: *Self) T { + var num = self.peek(); + // 刪除頭節點 + self.front = self.front.?.next; + self.que_size -= 1; + return num; + } + + // 將鏈結串列轉換為陣列 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化佇列 + var queue = LinkedListQueue(i32){}; + try queue.init(std.heap.page_allocator); + defer queue.deinit(); + + // 元素入列 + try queue.push(1); + try queue.push(3); + try queue.push(2); + try queue.push(5); + try queue.push(4); + std.debug.print("佇列 queue = ", .{}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 訪問佇列首元素 + var peek = queue.peek(); + std.debug.print("\n佇列首元素 peek = {}", .{peek}); + + // 元素出列 + var pop = queue.pop(); + std.debug.print("\n出列元素 pop = {},出列後 queue = ", .{pop}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // 獲取佇列的長度 + var size = queue.size(); + std.debug.print("\n佇列長度 size = {}", .{size}); + + // 判斷佇列是否為空 + var is_empty = queue.isEmpty(); + std.debug.print("\n佇列是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig new file mode 100644 index 000000000..22524ed15 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig @@ -0,0 +1,118 @@ +// File: linkedlist_stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 基於鏈結串列實現的堆疊 +pub fn LinkedListStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack_top: ?*inc.ListNode(T) = null, // 將頭節點作為堆疊頂 + stk_size: usize = 0, // 堆疊的長度 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子(分配記憶體+初始化堆疊) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.stack_top = null; + self.stk_size = 0; + } + + // 析構函式(釋放記憶體) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取堆疊的長度 + pub fn size(self: *Self) usize { + return self.stk_size; + } + + // 判斷堆疊是否為空 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // 訪問堆疊頂元素 + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("堆疊為空"); + return self.stack_top.?.val; + } + + // 入堆疊 + pub fn push(self: *Self, num: T) !void { + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + node.next = self.stack_top; + self.stack_top = node; + self.stk_size += 1; + } + + // 出堆疊 + pub fn pop(self: *Self) T { + var num = self.peek(); + self.stack_top = self.stack_top.?.next; + self.stk_size -= 1; + return num; + } + + // 將堆疊轉換為陣列 + pub fn toArray(self: *Self) ![]T { + var node = self.stack_top; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[res.len - i - 1] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化堆疊 + var stack = LinkedListStack(i32){}; + try stack.init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer stack.deinit(); + + // 元素入堆疊 + try stack.push(1); + try stack.push(3); + try stack.push(2); + try stack.push(5); + try stack.push(4); + std.debug.print("堆疊 stack = ", .{}); + inc.PrintUtil.printArray(i32, try stack.toArray()); + + // 訪問堆疊頂元素 + var peek = stack.peek(); + std.debug.print("\n堆疊頂元素 top = {}", .{peek}); + + // 元素出堆疊 + var pop = stack.pop(); + std.debug.print("\n出堆疊元素 pop = {},出堆疊後 stack = ", .{pop}); + inc.PrintUtil.printArray(i32, try stack.toArray()); + + // 獲取堆疊的長度 + var size = stack.size(); + std.debug.print("\n堆疊的長度 size = {}", .{size}); + + // 判斷堆疊是否為空 + var is_empty = stack.isEmpty(); + std.debug.print("\n堆疊是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/queue.zig b/zh-hant/codes/zig/chapter_stack_and_queue/queue.zig new file mode 100644 index 000000000..2d2464e86 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/queue.zig @@ -0,0 +1,46 @@ +// File: queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化佇列 + const L = std.TailQueue(i32); + var queue = L{}; + + // 元素入列 + var node1 = L.Node{ .data = 1 }; + var node2 = L.Node{ .data = 3 }; + var node3 = L.Node{ .data = 2 }; + var node4 = L.Node{ .data = 5 }; + var node5 = L.Node{ .data = 4 }; + queue.append(&node1); + queue.append(&node2); + queue.append(&node3); + queue.append(&node4); + queue.append(&node5); + std.debug.print("佇列 queue = ", .{}); + inc.PrintUtil.printQueue(i32, queue); + + // 訪問佇列首元素 + var peek = queue.first.?.data; + std.debug.print("\n佇列首元素 peek = {}", .{peek}); + + // 元素出列 + var pop = queue.popFirst().?.data; + std.debug.print("\n出列元素 pop = {},出列後 queue = ", .{pop}); + inc.PrintUtil.printQueue(i32, queue); + + // 獲取佇列的長度 + var size = queue.len; + std.debug.print("\n佇列長度 size = {}", .{size}); + + // 判斷佇列是否為空 + var is_empty = if (queue.len == 0) true else false; + std.debug.print("\n佇列是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_stack_and_queue/stack.zig b/zh-hant/codes/zig/chapter_stack_and_queue/stack.zig new file mode 100644 index 000000000..ffbaadbb7 --- /dev/null +++ b/zh-hant/codes/zig/chapter_stack_and_queue/stack.zig @@ -0,0 +1,43 @@ +// File: stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化堆疊 + // 在 zig 中,推薦將 ArrayList 當作堆疊來使用 + var stack = std.ArrayList(i32).init(std.heap.page_allocator); + // 延遲釋放記憶體 + defer stack.deinit(); + + // 元素入堆疊 + try stack.append(1); + try stack.append(3); + try stack.append(2); + try stack.append(5); + try stack.append(4); + std.debug.print("堆疊 stack = ", .{}); + inc.PrintUtil.printList(i32, stack); + + // 訪問堆疊頂元素 + var peek = stack.items[stack.items.len - 1]; + std.debug.print("\n堆疊頂元素 peek = {}", .{peek}); + + // 元素出堆疊 + var pop = stack.pop(); + std.debug.print("\n出堆疊元素 pop = {},出堆疊後 stack = ", .{pop}); + inc.PrintUtil.printList(i32, stack); + + // 獲取堆疊的長度 + var size = stack.items.len; + std.debug.print("\n堆疊的長度 size = {}", .{size}); + + // 判斷堆疊是否為空 + var is_empty = if (stack.items.len == 0) true else false; + std.debug.print("\n堆疊是否為空 = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/zh-hant/codes/zig/chapter_tree/avl_tree.zig b/zh-hant/codes/zig/chapter_tree/avl_tree.zig new file mode 100644 index 000000000..11d48a843 --- /dev/null +++ b/zh-hant/codes/zig/chapter_tree/avl_tree.zig @@ -0,0 +1,249 @@ +// File: avl_tree.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// AVL 樹 +pub fn AVLTree(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*inc.TreeNode(T) = null, // 根節點 + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子 + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + } + + // 析構方法 + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 獲取節點高度 + fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { + _ = self; + // 空節點高度為 -1 ,葉節點高度為 0 + return if (node == null) -1 else node.?.height; + } + + // 更新節點高度 + fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { + // 節點高度等於最高子樹高度 + 1 + node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; + } + + // 獲取平衡因子 + fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { + // 空節點平衡因子為 0 + if (node == null) return 0; + // 節點平衡因子 = 左子樹高度 - 右子樹高度 + return self.height(node.?.left) - self.height(node.?.right); + } + + // 右旋操作 + fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.left; + var grandChild = child.?.right; + // 以 child 為原點,將 node 向右旋轉 + child.?.right = node; + node.?.left = grandChild; + // 更新節點高度 + self.updateHeight(node); + self.updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + // 左旋操作 + fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.right; + var grandChild = child.?.left; + // 以 child 為原點,將 node 向左旋轉 + child.?.left = node; + node.?.right = grandChild; + // 更新節點高度 + self.updateHeight(node); + self.updateHeight(child); + // 返回旋轉後子樹的根節點 + return child; + } + + // 執行旋轉操作,使該子樹重新恢復平衡 + fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + // 獲取節點 node 的平衡因子 + var balance_factor = self.balanceFactor(node); + // 左偏樹 + if (balance_factor > 1) { + if (self.balanceFactor(node.?.left) >= 0) { + // 右旋 + return self.rightRotate(node); + } else { + // 先左旋後右旋 + node.?.left = self.leftRotate(node.?.left); + return self.rightRotate(node); + } + } + // 右偏樹 + if (balance_factor < -1) { + if (self.balanceFactor(node.?.right) <= 0) { + // 左旋 + return self.leftRotate(node); + } else { + // 先右旋後左旋 + node.?.right = self.rightRotate(node.?.right); + return self.leftRotate(node); + } + } + // 平衡樹,無須旋轉,直接返回 + return node; + } + + // 插入節點 + fn insert(self: *Self, val: T) !void { + self.root = (try self.insertHelper(self.root, val)).?; + } + + // 遞迴插入節點(輔助方法) + fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { + var node = node_; + if (node == null) { + var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); + tmp_node.init(val); + return tmp_node; + } + // 1. 查詢插入位置並插入節點 + if (val < node.?.val) { + node.?.left = try self.insertHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = try self.insertHelper(node.?.right, val); + } else { + return node; // 重複節點不插入,直接返回 + } + self.updateHeight(node); // 更新節點高度 + // 2. 執行旋轉操作,使該子樹重新恢復平衡 + node = self.rotate(node); + // 返回子樹的根節點 + return node; + } + + // 刪除節點 + fn remove(self: *Self, val: T) void { + self.root = self.removeHelper(self.root, val).?; + } + + // 遞迴刪除節點(輔助方法) + fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { + var node = node_; + if (node == null) return null; + // 1. 查詢節點並刪除 + if (val < node.?.val) { + node.?.left = self.removeHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = self.removeHelper(node.?.right, val); + } else { + if (node.?.left == null or node.?.right == null) { + var child = if (node.?.left != null) node.?.left else node.?.right; + // 子節點數量 = 0 ,直接刪除 node 並返回 + if (child == null) { + return null; + // 子節點數量 = 1 ,直接刪除 node + } else { + node = child; + } + } else { + // 子節點數量 = 2 ,則將中序走訪的下個節點刪除,並用該節點替換當前節點 + var temp = node.?.right; + while (temp.?.left != null) { + temp = temp.?.left; + } + node.?.right = self.removeHelper(node.?.right, temp.?.val); + node.?.val = temp.?.val; + } + } + self.updateHeight(node); // 更新節點高度 + // 2. 執行旋轉操作,使該子樹重新恢復平衡 + node = self.rotate(node); + // 返回子樹的根節點 + return node; + } + + // 查詢節點 + fn search(self: *Self, val: T) ?*inc.TreeNode(T) { + var cur = self.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.?.val < val) { + cur = cur.?.right; + // 目標節點在 cur 的左子樹中 + } else if (cur.?.val > val) { + cur = cur.?.left; + // 找到目標節點,跳出迴圈 + } else { + break; + } + } + // 返回目標節點 + return cur; + } + }; +} + +pub fn testInsert(comptime T: type, tree_: *AVLTree(T), val: T) !void { + var tree = tree_; + try tree.insert(val); + std.debug.print("\n插入節點 {} 後,AVL 樹為\n", .{val}); + try inc.PrintUtil.printTree(tree.root, null, false); +} + +pub fn testRemove(comptime T: type, tree_: *AVLTree(T), val: T) void { + var tree = tree_; + tree.remove(val); + std.debug.print("\n刪除節點 {} 後,AVL 樹為\n", .{val}); + try inc.PrintUtil.printTree(tree.root, null, false); +} + +// Driver Code +pub fn main() !void { + // 初始化空 AVL 樹 + var avl_tree = AVLTree(i32){}; + avl_tree.init(std.heap.page_allocator); + defer avl_tree.deinit(); + + // 插入節點 + // 請關注插入節點後,AVL 樹是如何保持平衡的 + try testInsert(i32, &avl_tree, 1); + try testInsert(i32, &avl_tree, 2); + try testInsert(i32, &avl_tree, 3); + try testInsert(i32, &avl_tree, 4); + try testInsert(i32, &avl_tree, 5); + try testInsert(i32, &avl_tree, 8); + try testInsert(i32, &avl_tree, 7); + try testInsert(i32, &avl_tree, 9); + try testInsert(i32, &avl_tree, 10); + try testInsert(i32, &avl_tree, 6); + + // 插入重複節點 + try testInsert(i32, &avl_tree, 7); + + // 刪除節點 + // 請關注刪除節點後,AVL 樹是如何保持平衡的 + testRemove(i32, &avl_tree, 8); // 刪除度為 0 的節點 + testRemove(i32, &avl_tree, 5); // 刪除度為 1 的節點 + testRemove(i32, &avl_tree, 4); // 刪除度為 2 的節點 + + // 查詢節點 + var node = avl_tree.search(7).?; + std.debug.print("\n查詢到的節點物件為 {any},節點值 = {}\n", .{node, node.val}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_tree/binary_search_tree.zig b/zh-hant/codes/zig/chapter_tree/binary_search_tree.zig new file mode 100644 index 000000000..d996dcb1a --- /dev/null +++ b/zh-hant/codes/zig/chapter_tree/binary_search_tree.zig @@ -0,0 +1,182 @@ +// File: binary_search_tree.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 二元搜尋樹 +pub fn BinarySearchTree(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*inc.TreeNode(T) = null, + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // 記憶體分配器 + + // 建構子 + pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []T) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + std.mem.sort(T, nums, {}, comptime std.sort.asc(T)); // 排序陣列 + self.root = try self.buildTree(nums, 0, nums.len - 1); // 構建二元搜尋樹 + } + + // 析構方法 + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 構建二元搜尋樹 + fn buildTree(self: *Self, nums: []T, i: usize, j: usize) !?*inc.TreeNode(T) { + if (i > j) return null; + // 將陣列中間節點作為根節點 + var mid = (i + j) / 2; + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(nums[mid]); + // 遞迴建立左子樹和右子樹 + if (mid >= 1) node.left = try self.buildTree(nums, i, mid - 1); + node.right = try self.buildTree(nums, mid + 1, j); + return node; + } + + // 獲取二元樹根節點 + fn getRoot(self: *Self) ?*inc.TreeNode(T) { + return self.root; + } + + // 查詢節點 + fn search(self: *Self, num: T) ?*inc.TreeNode(T) { + var cur = self.root; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 目標節點在 cur 的右子樹中 + if (cur.?.val < num) { + cur = cur.?.right; + // 目標節點在 cur 的左子樹中 + } else if (cur.?.val > num) { + cur = cur.?.left; + // 找到目標節點,跳出迴圈 + } else { + break; + } + } + // 返回目標節點 + return cur; + } + + // 插入節點 + fn insert(self: *Self, num: T) !void { + // 若樹為空,則初始化根節點 + if (self.root == null) { + self.root = try self.mem_allocator.create(inc.TreeNode(T)); + return; + } + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到重複節點,直接返回 + if (cur.?.val == num) return; + pre = cur; + // 插入位置在 cur 的右子樹中 + if (cur.?.val < num) { + cur = cur.?.right; + // 插入位置在 cur 的左子樹中 + } else { + cur = cur.?.left; + } + } + // 插入節點 + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(num); + if (pre.?.val < num) { + pre.?.right = node; + } else { + pre.?.left = node; + } + } + + // 刪除節點 + fn remove(self: *Self, num: T) void { + // 若樹為空,直接提前返回 + if (self.root == null) return; + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // 迴圈查詢,越過葉節點後跳出 + while (cur != null) { + // 找到待刪除節點,跳出迴圈 + if (cur.?.val == num) break; + pre = cur; + // 待刪除節點在 cur 的右子樹中 + if (cur.?.val < num) { + cur = cur.?.right; + // 待刪除節點在 cur 的左子樹中 + } else { + cur = cur.?.left; + } + } + // 若無待刪除節點,則直接返回 + if (cur == null) return; + // 子節點數量 = 0 or 1 + if (cur.?.left == null or cur.?.right == null) { + // 當子節點數量 = 0 / 1 時, child = null / 該子節點 + var child = if (cur.?.left != null) cur.?.left else cur.?.right; + // 刪除節點 cur + if (pre.?.left == cur) { + pre.?.left = child; + } else { + pre.?.right = child; + } + // 子節點數量 = 2 + } else { + // 獲取中序走訪中 cur 的下一個節點 + var tmp = cur.?.right; + while (tmp.?.left != null) { + tmp = tmp.?.left; + } + var tmp_val = tmp.?.val; + // 遞迴刪除節點 tmp + self.remove(tmp.?.val); + // 用 tmp 覆蓋 cur + cur.?.val = tmp_val; + } + } + }; +} + +// Driver Code +pub fn main() !void { + // 初始化二元樹 + var nums = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + var bst = BinarySearchTree(i32){}; + try bst.init(std.heap.page_allocator, &nums); + defer bst.deinit(); + std.debug.print("初始化的二元樹為\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + // 查詢節點 + var node = bst.search(7); + std.debug.print("\n查詢到的節點物件為 {any},節點值 = {}\n", .{node, node.?.val}); + + // 插入節點 + try bst.insert(16); + std.debug.print("\n插入節點 16 後,二元樹為\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + // 刪除節點 + bst.remove(1); + std.debug.print("\n刪除節點 1 後,二元樹為\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + bst.remove(2); + std.debug.print("\n刪除節點 2 後,二元樹為\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + bst.remove(4); + std.debug.print("\n刪除節點 4 後,二元樹為\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_tree/binary_tree.zig b/zh-hant/codes/zig/chapter_tree/binary_tree.zig new file mode 100644 index 000000000..4d4cf461d --- /dev/null +++ b/zh-hant/codes/zig/chapter_tree/binary_tree.zig @@ -0,0 +1,39 @@ +// File: binary_tree.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 初始化二元樹 + // 初始化節點 + var n1 = inc.TreeNode(i32){ .val = 1 }; + var n2 = inc.TreeNode(i32){ .val = 2 }; + var n3 = inc.TreeNode(i32){ .val = 3 }; + var n4 = inc.TreeNode(i32){ .val = 4 }; + var n5 = inc.TreeNode(i32){ .val = 5 }; + // 構建節點之間的引用(指標) + n1.left = &n2; + n1.right = &n3; + n2.left = &n4; + n2.right = &n5; + std.debug.print("初始化二元樹\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + + // 插入與刪除節點 + var p = inc.TreeNode(i32){ .val = 0 }; + // 在 n1 -> n2 中間插入節點 P + n1.left = &p; + p.left = &n2; + std.debug.print("插入節點 P 後\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + // 刪除節點 + n1.left = &n2; + std.debug.print("刪除節點 P 後\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/zh-hant/codes/zig/chapter_tree/binary_tree_bfs.zig b/zh-hant/codes/zig/chapter_tree/binary_tree_bfs.zig new file mode 100644 index 000000000..b4f4c1b39 --- /dev/null +++ b/zh-hant/codes/zig/chapter_tree/binary_tree_bfs.zig @@ -0,0 +1,57 @@ +// File: binary_tree_bfs.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 層序走訪 +fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { + // 初始化佇列,加入根節點 + const L = std.TailQueue(*inc.TreeNode(T)); + var queue = L{}; + var root_node = try mem_allocator.create(L.Node); + root_node.data = root; + queue.append(root_node); + // 初始化一個串列,用於儲存走訪序列 + var list = std.ArrayList(T).init(std.heap.page_allocator); + while (queue.len > 0) { + var queue_node = queue.popFirst().?; // 隊列出隊 + var node = queue_node.data; + try list.append(node.val); // 儲存節點值 + if (node.left != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.left.?; + queue.append(tmp_node); // 左子節點入列 + } + if (node.right != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.right.?; + queue.append(tmp_node); // 右子節點入列 + } + } + return list; +} + +// Driver Code +pub fn main() !void { + // 初始化記憶體分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; + var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); + std.debug.print("初始化二元樹\n", .{}); + try inc.PrintUtil.printTree(root, null, false); + + // 層序走訪 + var list = try levelOrder(i32, mem_allocator, root.?); + defer list.deinit(); + std.debug.print("\n層序走訪的節點列印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/chapter_tree/binary_tree_dfs.zig b/zh-hant/codes/zig/chapter_tree/binary_tree_dfs.zig new file mode 100644 index 000000000..0a0a0771d --- /dev/null +++ b/zh-hant/codes/zig/chapter_tree/binary_tree_dfs.zig @@ -0,0 +1,70 @@ +// File: binary_tree_dfs.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +var list = std.ArrayList(i32).init(std.heap.page_allocator); + +// 前序走訪 +fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 訪問優先順序:根節點 -> 左子樹 -> 右子樹 + try list.append(root.?.val); + try preOrder(T, root.?.left); + try preOrder(T, root.?.right); +} + +// 中序走訪 +fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 訪問優先順序:左子樹 -> 根節點 -> 右子樹 + try inOrder(T, root.?.left); + try list.append(root.?.val); + try inOrder(T, root.?.right); +} + +// 後序走訪 +fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 訪問優先順序:左子樹 -> 右子樹 -> 根節點 + try postOrder(T, root.?.left); + try postOrder(T, root.?.right); + try list.append(root.?.val); +} + +// Driver Code +pub fn main() !void { + // 初始化記憶體分配器 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 初始化二元樹 + // 這裡藉助了一個從陣列直接生成二元樹的函式 + var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; + var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); + std.debug.print("初始化二元樹\n", .{}); + try inc.PrintUtil.printTree(root, null, false); + + // 前序走訪 + list.clearRetainingCapacity(); + try preOrder(i32, root); + std.debug.print("\n前序走訪的節點列印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + // 中序走訪 + list.clearRetainingCapacity(); + try inOrder(i32, root); + std.debug.print("\n中序走訪的節點列印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + // 後序走訪 + list.clearRetainingCapacity(); + try postOrder(i32, root); + std.debug.print("\n後續走訪的節點列印序列 = ", .{}); + inc.PrintUtil.printList(i32, list); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/include/ListNode.zig b/zh-hant/codes/zig/include/ListNode.zig new file mode 100644 index 000000000..580399dc3 --- /dev/null +++ b/zh-hant/codes/zig/include/ListNode.zig @@ -0,0 +1,49 @@ +// File: ListNode.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 鏈結串列節點 +pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, + next: ?*Self = null, + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + } + }; +} + +// 將串列反序列化為鏈結串列 +pub fn listToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, list: std.ArrayList(T)) !?*ListNode(T) { + var dum = try mem_allocator.create(ListNode(T)); + dum.init(0); + var head = dum; + for (list.items) |val| { + var tmp = try mem_allocator.create(ListNode(T)); + tmp.init(val); + head.next = tmp; + head = head.next.?; + } + return dum.next; +} + +// 將陣列反序列化為鏈結串列 +pub fn arrToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*ListNode(T) { + var dum = try mem_allocator.create(ListNode(T)); + dum.init(0); + var head = dum; + for (arr) |val| { + var tmp = try mem_allocator.create(ListNode(T)); + tmp.init(val); + head.next = tmp; + head = head.next.?; + } + return dum.next; +} \ No newline at end of file diff --git a/zh-hant/codes/zig/include/PrintUtil.zig b/zh-hant/codes/zig/include/PrintUtil.zig new file mode 100644 index 000000000..c7444e99f --- /dev/null +++ b/zh-hant/codes/zig/include/PrintUtil.zig @@ -0,0 +1,132 @@ +// File: PrintUtil.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +pub const ListUtil = @import("ListNode.zig"); +pub const ListNode = ListUtil.ListNode; +pub const TreeUtil = @import("TreeNode.zig"); +pub const TreeNode = TreeUtil.TreeNode; + +// 列印陣列 +pub fn printArray(comptime T: type, nums: []T) void { + std.debug.print("[", .{}); + if (nums.len > 0) { + for (nums, 0..) |num, j| { + std.debug.print("{}{s}", .{num, if (j == nums.len-1) "]" else ", " }); + } + } else { + std.debug.print("]", .{}); + } +} + +// 列印串列 +pub fn printList(comptime T: type, list: std.ArrayList(T)) void { + std.debug.print("[", .{}); + if (list.items.len > 0) { + for (list.items, 0..) |value, i| { + std.debug.print("{}{s}", .{value, if (i == list.items.len-1) "]" else ", " }); + } + } else { + std.debug.print("]", .{}); + } +} + +// 列印鏈結串列 +pub fn printLinkedList(comptime T: type, node: ?*ListNode(T)) !void { + if (node == null) return; + var list = std.ArrayList(T).init(std.heap.page_allocator); + defer list.deinit(); + var head = node; + while (head != null) { + try list.append(head.?.val); + head = head.?.next; + } + for (list.items, 0..) |value, i| { + std.debug.print("{}{s}", .{value, if (i == list.items.len-1) "\n" else "->" }); + } +} + +// 列印佇列 +pub fn printQueue(comptime T: type, queue: std.TailQueue(T)) void { + var node = queue.first; + std.debug.print("[", .{}); + var i: i32 = 0; + while (node != null) : (i += 1) { + var data = node.?.data; + std.debug.print("{}{s}", .{data, if (i == queue.len - 1) "]" else ", " }); + node = node.?.next; + } +} + +// 列印雜湊表 +pub fn printHashMap(comptime TKey: type, comptime TValue: type, map: std.AutoHashMap(TKey, TValue)) void { + var it = map.iterator(); + while (it.next()) |kv| { + var key = kv.key_ptr.*; + var value = kv.value_ptr.*; + std.debug.print("{} -> {s}\n", .{key, value}); + } +} + +// 列印堆積 +pub fn printHeap(comptime T: type, mem_allocator: std.mem.Allocator, queue: anytype) !void { + var arr = queue.items; + var len = queue.len; + std.debug.print("堆積的陣列表示:", .{}); + printArray(T, arr[0..len]); + std.debug.print("\n堆積的樹狀表示:\n", .{}); + var root = try TreeUtil.arrToTree(T, mem_allocator, arr[0..len]); + try printTree(root, null, false); +} + +// 列印二元樹 +// This tree printer is borrowed from TECHIE DELIGHT +// https://www.techiedelight.com/c-program-print-binary-tree/ +const Trunk = struct { + prev: ?*Trunk = null, + str: []const u8 = undefined, + + pub fn init(self: *Trunk, prev: ?*Trunk, str: []const u8) void { + self.prev = prev; + self.str = str; + } +}; + +pub fn showTrunks(p: ?*Trunk) void { + if (p == null) return; + showTrunks(p.?.prev); + std.debug.print("{s}", .{p.?.str}); +} + +// 列印二元樹 +pub fn printTree(root: ?*TreeNode(i32), prev: ?*Trunk, isRight: bool) !void { + if (root == null) { + return; + } + + var prev_str = " "; + var trunk = Trunk{.prev = prev, .str = prev_str}; + + try printTree(root.?.right, &trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.?.str = prev_str; + } + + showTrunks(&trunk); + std.debug.print(" {}\n", .{root.?.val}); + + if (prev) |_| { + prev.?.str = prev_str; + } + trunk.str = " |"; + + try printTree(root.?.left, &trunk, false); +} \ No newline at end of file diff --git a/zh-hant/codes/zig/include/TreeNode.zig b/zh-hant/codes/zig/include/TreeNode.zig new file mode 100644 index 000000000..39cdf330e --- /dev/null +++ b/zh-hant/codes/zig/include/TreeNode.zig @@ -0,0 +1,63 @@ +// File: TreeNode.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 二元樹節點 +pub fn TreeNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // 節點值 + height: i32 = undefined, // 節點高度 + left: ?*Self = null, // 左子節點指標 + right: ?*Self = null, // 右子節點指標 + + // Initialize a tree node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.height = 0; + self.left = null; + self.right = null; + } + }; +} + +// 將陣列反序列化為二元樹 +pub fn arrToTree(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*TreeNode(T) { + if (arr.len == 0) return null; + var root = try mem_allocator.create(TreeNode(T)); + root.init(arr[0]); + const L = std.TailQueue(*TreeNode(T)); + var que = L{}; + var root_node = try mem_allocator.create(L.Node); + root_node.data = root; + que.append(root_node); + var index: usize = 0; + while (que.len > 0) { + var que_node = que.popFirst().?; + var node = que_node.data; + index += 1; + if (index >= arr.len) break; + if (index < arr.len) { + var tmp = try mem_allocator.create(TreeNode(T)); + tmp.init(arr[index]); + node.left = tmp; + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.left.?; + que.append(tmp_node); + } + index += 1; + if (index >= arr.len) break; + if (index < arr.len) { + var tmp = try mem_allocator.create(TreeNode(T)); + tmp.init(arr[index]); + node.right = tmp; + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.right.?; + que.append(tmp_node); + } + } + return root; +} \ No newline at end of file diff --git a/zh-hant/codes/zig/include/include.zig b/zh-hant/codes/zig/include/include.zig new file mode 100644 index 000000000..cb98ffc0d --- /dev/null +++ b/zh-hant/codes/zig/include/include.zig @@ -0,0 +1,9 @@ +// File: include.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +pub const PrintUtil = @import("PrintUtil.zig"); +pub const ListUtil = @import("ListNode.zig"); +pub const ListNode = ListUtil.ListNode; +pub const TreeUtil = @import("TreeNode.zig"); +pub const TreeNode = TreeUtil.TreeNode; \ No newline at end of file diff --git a/zh-hant/docs/assets/covers/chapter_appendix.jpg b/zh-hant/docs/assets/covers/chapter_appendix.jpg new file mode 100644 index 000000000..42262a225 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_appendix.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_array_and_linkedlist.jpg b/zh-hant/docs/assets/covers/chapter_array_and_linkedlist.jpg new file mode 100644 index 000000000..e6b0c12e0 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_array_and_linkedlist.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_backtracking.jpg b/zh-hant/docs/assets/covers/chapter_backtracking.jpg new file mode 100644 index 000000000..b5e09ba50 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_backtracking.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_complexity_analysis.jpg b/zh-hant/docs/assets/covers/chapter_complexity_analysis.jpg new file mode 100644 index 000000000..2a63d531f Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_complexity_analysis.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_data_structure.jpg b/zh-hant/docs/assets/covers/chapter_data_structure.jpg new file mode 100644 index 000000000..2511b6423 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_data_structure.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_divide_and_conquer.jpg b/zh-hant/docs/assets/covers/chapter_divide_and_conquer.jpg new file mode 100644 index 000000000..56283b4c8 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_divide_and_conquer.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_dynamic_programming.jpg b/zh-hant/docs/assets/covers/chapter_dynamic_programming.jpg new file mode 100644 index 000000000..cf1c569e5 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_dynamic_programming.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_graph.jpg b/zh-hant/docs/assets/covers/chapter_graph.jpg new file mode 100644 index 000000000..c70179dc5 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_graph.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_greedy.jpg b/zh-hant/docs/assets/covers/chapter_greedy.jpg new file mode 100644 index 000000000..cbf74cf47 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_greedy.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_hashing.jpg b/zh-hant/docs/assets/covers/chapter_hashing.jpg new file mode 100644 index 000000000..cd1aa8c88 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_hashing.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_heap.jpg b/zh-hant/docs/assets/covers/chapter_heap.jpg new file mode 100644 index 000000000..4672af479 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_heap.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_hello_algo.jpg b/zh-hant/docs/assets/covers/chapter_hello_algo.jpg new file mode 100644 index 000000000..8e347b3a4 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_hello_algo.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_introduction.jpg b/zh-hant/docs/assets/covers/chapter_introduction.jpg new file mode 100644 index 000000000..27d50a964 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_introduction.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_preface.jpg b/zh-hant/docs/assets/covers/chapter_preface.jpg new file mode 100644 index 000000000..379370309 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_preface.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_searching.jpg b/zh-hant/docs/assets/covers/chapter_searching.jpg new file mode 100644 index 000000000..f5f20a0cc Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_searching.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_sorting.jpg b/zh-hant/docs/assets/covers/chapter_sorting.jpg new file mode 100644 index 000000000..ea866f6b7 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_sorting.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_stack_and_queue.jpg b/zh-hant/docs/assets/covers/chapter_stack_and_queue.jpg new file mode 100644 index 000000000..bcc187ce1 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_stack_and_queue.jpg differ diff --git a/zh-hant/docs/assets/covers/chapter_tree.jpg b/zh-hant/docs/assets/covers/chapter_tree.jpg new file mode 100644 index 000000000..4f40c5c87 Binary files /dev/null and b/zh-hant/docs/assets/covers/chapter_tree.jpg differ diff --git a/zh-hant/docs/chapter_appendix/contribution.assets/edit_markdown.png b/zh-hant/docs/chapter_appendix/contribution.assets/edit_markdown.png new file mode 100644 index 000000000..d91ab3a2a Binary files /dev/null and b/zh-hant/docs/chapter_appendix/contribution.assets/edit_markdown.png differ diff --git a/zh-hant/docs/chapter_appendix/contribution.md b/zh-hant/docs/chapter_appendix/contribution.md new file mode 100644 index 000000000..e5e000b6e --- /dev/null +++ b/zh-hant/docs/chapter_appendix/contribution.md @@ -0,0 +1,47 @@ +# 一起參與創作 + +由於筆者能力有限,書中難免存在一些遺漏和錯誤,請您諒解。如果您發現了筆誤、連結失效、內容缺失、文字歧義、解釋不清晰或行文結構不合理等問題,請協助我們進行修正,以給讀者提供更優質的學習資源。 + +所有[撰稿人](https://github.com/krahets/hello-algo/graphs/contributors)的 GitHub ID 將在本書倉庫、網頁版和 PDF 版的主頁上進行展示,以感謝他們對開源社群的無私奉獻。 + +!!! success "開源的魅力" + + 紙質圖書的兩次印刷的間隔時間往往較久,內容更新非常不方便。 + + 而在本開源書中,內容更迭的時間被縮短至數日甚至幾個小時。 + +### 內容微調 + +如下圖所示,每個頁面的右上角都有“編輯圖示”。您可以按照以下步驟修改文字或程式碼。 + +1. 點選“編輯圖示”,如果遇到“需要 Fork 此倉庫”的提示,請同意該操作。 +2. 修改 Markdown 源檔案內容,檢查內容的正確性,並儘量保持排版格式的統一。 +3. 在頁面底部填寫修改說明,然後點選“Propose file change”按鈕。頁面跳轉後,點選“Create pull request”按鈕即可發起拉取請求。 + +![頁面編輯按鍵](contribution.assets/edit_markdown.png) + +圖片無法直接修改,需要透過新建 [Issue](https://github.com/krahets/hello-algo/issues) 或評論留言來描述問題,我們會盡快重新繪製並替換圖片。 + +### 內容創作 + +如果您有興趣參與此開源專案,包括將程式碼翻譯成其他程式語言、擴展文章內容等,那麼需要實施以下 Pull Request 工作流程。 + +1. 登入 GitHub ,將本書的[程式碼倉庫](https://github.com/krahets/hello-algo) Fork 到個人帳號下。 +2. 進入您的 Fork 倉庫網頁,使用 `git clone` 命令將倉庫克隆至本地。 +3. 在本地進行內容創作,並進行完整測試,驗證程式碼的正確性。 +4. 將本地所做更改 Commit ,然後 Push 至遠端倉庫。 +5. 重新整理倉庫網頁,點選“Create pull request”按鈕即可發起拉取請求。 + +### Docker 部署 + +在 `hello-algo` 根目錄下,執行以下 Docker 指令碼,即可在 `http://localhost:8000` 訪問本專案: + +```shell +docker-compose up -d +``` + +使用以下命令即可刪除部署: + +```shell +docker-compose down +``` diff --git a/zh-hant/docs/chapter_appendix/index.md b/zh-hant/docs/chapter_appendix/index.md new file mode 100644 index 000000000..e9098b9c0 --- /dev/null +++ b/zh-hant/docs/chapter_appendix/index.md @@ -0,0 +1,3 @@ +# 附錄 + +![附錄](../assets/covers/chapter_appendix.jpg) diff --git a/zh-hant/docs/chapter_appendix/installation.assets/vscode_extension_installation.png b/zh-hant/docs/chapter_appendix/installation.assets/vscode_extension_installation.png new file mode 100644 index 000000000..bdc3b2892 Binary files /dev/null and b/zh-hant/docs/chapter_appendix/installation.assets/vscode_extension_installation.png differ diff --git a/zh-hant/docs/chapter_appendix/installation.assets/vscode_installation.png b/zh-hant/docs/chapter_appendix/installation.assets/vscode_installation.png new file mode 100644 index 000000000..0f9fb7dc8 Binary files /dev/null and b/zh-hant/docs/chapter_appendix/installation.assets/vscode_installation.png differ diff --git a/zh-hant/docs/chapter_appendix/installation.md b/zh-hant/docs/chapter_appendix/installation.md new file mode 100644 index 000000000..f989eee0d --- /dev/null +++ b/zh-hant/docs/chapter_appendix/installation.md @@ -0,0 +1,63 @@ +# 程式設計環境安裝 + +## 安裝 IDE + +推薦使用開源、輕量的 VS Code 作為本地整合開發環境(IDE)。訪問 [VS Code 官網](https://code.visualstudio.com/),根據作業系統選擇相應版本的 VS Code 進行下載和安裝。 + +![從官網下載 VS Code](installation.assets/vscode_installation.png) + +VS Code 擁有強大的擴展包生態系統,支持大多數程式語言的執行和除錯。以 Python 為例,安裝“Python Extension Pack”擴展包之後,即可進行 Python 程式碼除錯。安裝步驟如下圖所示。 + +![安裝 VS Code 擴展包](installation.assets/vscode_extension_installation.png) + +## 安裝語言環境 + +### Python 環境 + +1. 下載並安裝 [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) ,需要 Python 3.10 或更新版本。 +2. 在 VS Code 的擴充功能市場中搜索 `python` ,安裝 Python Extension Pack 。 +3. (可選)在命令列輸入 `pip install black` ,安裝程式碼格式化工具。 + +### C/C++ 環境 + +1. Windows 系統需要安裝 [MinGW](https://sourceforge.net/projects/mingw-w64/files/)([配置教程](https://blog.csdn.net/qq_33698226/article/details/129031241));MacOS 自帶 Clang ,無須安裝。 +2. 在 VS Code 的擴充功能市場中搜索 `c++` ,安裝 C/C++ Extension Pack 。 +3. (可選)開啟 Settings 頁面,搜尋 `Clang_format_fallback Style` 程式碼格式化選項,設定為 `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }` 。 + +### Java 環境 + +1. 下載並安裝 [OpenJDK](https://jdk.java.net/18/)(版本需滿足 > JDK 9)。 +2. 在 VS Code 的擴充功能市場中搜索 `java` ,安裝 Extension Pack for Java 。 + +### C# 環境 + +1. 下載並安裝 [.Net 8.0](https://dotnet.microsoft.com/en-us/download) 。 +2. 在 VS Code 的擴充功能市場中搜索 `C# Dev Kit` ,安裝 C# Dev Kit ([配置教程](https://code.visualstudio.com/docs/csharp/get-started))。 +3. 也可使用 Visual Studio([安裝教程](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022))。 + +### Go 環境 + +1. 下載並安裝 [go](https://go.dev/dl/) 。 +2. 在 VS Code 的擴充功能市場中搜索 `go` ,安裝 Go 。 +3. 按快捷鍵 `Ctrl + Shift + P` 撥出命令欄,輸入 go ,選擇 `Go: Install/Update Tools` ,全部勾選並安裝即可。 + +### Swift 環境 + +1. 下載並安裝 [Swift](https://www.swift.org/download/) 。 +2. 在 VS Code 的擴充功能市場中搜索 `swift` ,安裝 [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) 。 + +### JavaScript 環境 + +1. 下載並安裝 [node.js](https://nodejs.org/en/) 。 +2. 在 VS Code 的擴充功能市場中搜索 `javascript` ,安裝 JavaScript (ES6) code snippets 。 +3. (可選)在 VS Code 的擴充功能市場中搜索 `Prettier` ,安裝程式碼格式化工具。 + +### Dart 環境 + +1. 下載並安裝 [Dart](https://dart.dev/get-dart) 。 +2. 在 VS Code 的擴充功能市場中搜索 `dart` ,安裝 [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code) 。 + +### Rust 環境 + +1. 下載並安裝 [Rust](https://www.rust-lang.org/tools/install) 。 +2. 在 VS Code 的擴充功能市場中搜索 `rust` ,安裝 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 。 diff --git a/zh-hant/docs/chapter_appendix/terminology.md b/zh-hant/docs/chapter_appendix/terminology.md new file mode 100644 index 000000000..c8a0811f1 --- /dev/null +++ b/zh-hant/docs/chapter_appendix/terminology.md @@ -0,0 +1,136 @@ +# 術語表 + +下表列出了書中出現的重要術語,值得注意以下幾點。 + +- 建議記住名詞的英文叫法,以便閱讀英文文獻。 +- 部分名詞在簡體中文和繁體中文下的叫法不同。 + +

  資料結構與演算法的重要名詞

+ +| English | 简体中文 | 繁体中文 | +| ------------------------------ | -------------- | -------------- | +| algorithm | 算法 | 演算法 | +| data structure | 数据结构 | 資料結構 | +| code | 代码 | 程式碼 | +| file | 文件 | 檔案 | +| function | 函数 | 函式 | +| method | 方法 | 方法 | +| variable | 变量 | 變數 | +| asymptotic complexity analysis | 渐近复杂度分析 | 漸近複雜度分析 | +| time complexity | 时间复杂度 | 時間複雜度 | +| space complexity | 空间复杂度 | 空間複雜度 | +| loop | 循环 | 迴圈 | +| iteration | 迭代 | 迭代 | +| recursion | 递归 | 遞迴 | +| tail recursion | 尾递归 | 尾遞迴 | +| recursion tree | 递归树 | 遞迴樹 | +| big-$O$ notation | 大 $O$ 记号 | 大 $O$ 記號 | +| asymptotic upper bound | 渐近上界 | 漸近上界 | +| sign-magnitude | 原码 | 原碼 | +| 1’s complement | 反码 | 反碼 | +| 2’s complement | 补码 | 補碼 | +| array | 数组 | 陣列 | +| index | 索引 | 索引 | +| linked list | 链表 | 鏈結串列 | +| linked list node, list node | 链表节点 | 鏈結串列節點 | +| head node | 头节点 | 頭節點 | +| tail node | 尾节点 | 尾節點 | +| list | 列表 | 列表 | +| dynamic array | 动态数组 | 動態陣列 | +| hard disk | 硬盘 | 硬碟 | +| random-access memory (RAM) | 内存 | 內存 | +| cache memory | 缓存 | 快取 | +| cache miss | 缓存未命中 | 快取未命中 | +| cache hit rate | 缓存命中率 | 快取命中率 | +| stack | 栈 | 堆疊 | +| top of the stack | 栈顶 | 堆疊頂 | +| bottom of the stack | 栈底 | 堆疊底 | +| queue | 队列 | 佇列 | +| double-ended queue | 双向队列 | 雙向佇列 | +| front of the queue | 队首 | 佇列首 | +| rear of the queue | 队尾 | 佇列尾 | +| hash table | 哈希表 | 雜湊表 | +| bucket | 桶 | 桶 | +| hash function | 哈希函数 | 雜湊函式 | +| hash collision | 哈希冲突 | 雜湊衝突 | +| load factor | 负载因子 | 負載因子 | +| separate chaining | 链式地址 | 鏈結位址 | +| open addressing | 开放寻址 | 開放定址 | +| linear probing | 线性探测 | 線性探查 | +| lazy deletion | 懒删除 | 懶刪除 | +| binary tree | 二叉树 | 二元樹 | +| tree node | 树节点 | 樹節點 | +| left-child node | 左子节点 | 左子節點 | +| right-child node | 右子节点 | 右子節點 | +| parent node | 父节点 | 父節點 | +| left subtree | 左子树 | 左子樹 | +| right subtree | 右子树 | 右子樹 | +| root node | 根节点 | 根節點 | +| leaf node | 叶节点 | 葉節點 | +| edge | 边 | 邊 | +| level | 层 | 層 | +| degree | 度 | 度 | +| height | 高度 | 高度 | +| depth | 深度 | 深度 | +| perfect binary tree | 完美二叉树 | 完美二元樹 | +| complete binary tree | 完全二叉树 | 完全二元樹 | +| full binary tree | 完满二叉树 | 完滿二元樹 | +| balanced binary tree | 平衡二叉树 | 平衡二元樹 | +| binary search tree | 二叉搜索树 | 二元搜尋樹 | +| AVL tree | AVL 树 | AVL 樹 | +| red-black tree | 红黑树 | 紅黑樹 | +| level-order traversal | 层序遍历 | 層序走訪 | +| breadth-first traversal | 广度优先遍历 | 廣度優先走訪 | +| depth-first traversal | 深度优先遍历 | 深度優先走訪 | +| binary search tree | 二叉搜索树 | 二元搜尋樹 | +| balanced binary search tree | 平衡二叉搜索树 | 平衡二元搜尋樹 | +| balance factor | 平衡因子 | 平衡因子 | +| heap | 堆 | 堆積 | +| max heap | 大顶堆 | 大頂堆積 | +| min heap | 小顶堆 | 小頂堆積 | +| priority queue | 优先队列 | 優先佇列 | +| heapify | 堆化 | 堆積化 | +| top-$k$ problem | Top-$k$ 问题 | Top-$k$ 問題 | +| graph | 图 | 圖 | +| vertex | 顶点 | 頂點 | +| undirected graph | 无向图 | 無向圖 | +| directed graph | 有向图 | 有向圖 | +| connected graph | 连通图 | 連通圖 | +| disconnected graph | 非连通图 | 非連通圖 | +| weighted graph | 有权图 | 有權圖 | +| adjacency | 邻接 | 鄰接 | +| path | 路径 | 路徑 | +| in-degree | 入度 | 入度 | +| out-degree | 出度 | 出度 | +| adjacency matrix | 邻接矩阵 | 鄰接矩陣 | +| adjacency list | 邻接表 | 鄰接表 | +| breadth-first search | 广度优先搜索 | 廣度優先搜尋 | +| depth-first search | 深度优先搜索 | 深度優先搜尋 | +| binary search | 二分查找 | 二分查找 | +| searching algorithm | 搜索算法 | 搜尋演算法 | +| sorting algorithm | 排序算法 | 排序演算法 | +| selection sort | 选择排序 | 選擇排序 | +| bubble sort | 冒泡排序 | 泡沫排序 | +| insertion sort | 插入排序 | 插入排序 | +| quick sort | 快速排序 | 快速排序 | +| merge sort | 归并排序 | 合併排序 | +| heap sort | 堆排序 | 堆積排序 | +| bucket sort | 桶排序 | 桶排序 | +| counting sort | 计数排序 | 計數排序 | +| radix sort | 基数排序 | 基數排序 | +| divide and conquer | 分治 | 分治 | +| hanota problem | 汉诺塔问题 | 河內塔問題 | +| backtracking algorithm | 回溯算法 | 回溯演算法 | +| constraint | 约束 | 約束 | +| solution | 解 | 解 | +| state | 状态 | 狀態 | +| pruning | 剪枝 | 剪枝 | +| permutations problem | 全排列问题 | 全排列問題 | +| subset-sum problem | 子集和问题 | 子集合問題 | +| $n$-queens problem | $n$ 皇后问题 | $n$ 皇后問題 | +| dynamic programming | 动态规划 | 動態規劃 | +| initial state | 初始状态 | 初始狀態 | +| state-trasition equation | 状态转移方程 | 狀態轉移方程 | +| knapsack problem | 背包问题 | 背包問題 | +| edit distance problem | 编辑距离问题 | 編輯距離問題 | +| greedy algorithm | 贪心算法 | 貪婪演算法 | diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_definition.png b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_definition.png new file mode 100644 index 000000000..4c0990400 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_definition.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png new file mode 100644 index 000000000..7d9378116 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png new file mode 100644 index 000000000..933beae7a Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png new file mode 100644 index 000000000..ff291deb7 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/array.md b/zh-hant/docs/chapter_array_and_linkedlist/array.md new file mode 100755 index 000000000..e8bafb27c --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/array.md @@ -0,0 +1,230 @@ +# 陣列 + +陣列(array)是一種線性資料結構,其將相同型別的元素儲存在連續的記憶體空間中。我們將元素在陣列中的位置稱為該元素的索引(index)。下圖展示了陣列的主要概念和儲存方式。 + +![陣列定義與儲存方式](array.assets/array_definition.png) + +## 陣列常用操作 + +### 初始化陣列 + +我們可以根據需求選用陣列的兩種初始化方式:無初始值、給定初始值。在未指定初始值的情況下,大多數程式語言會將陣列元素初始化為 $0$ : + +=== "Python" + + ```python title="array.py" + # 初始化陣列 + arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="array.cpp" + /* 初始化陣列 */ + // 儲存在堆疊上 + int arr[5]; + int nums[5] = { 1, 3, 2, 5, 4 }; + // 儲存在堆積上(需要手動釋放空間) + int* arr1 = new int[5]; + int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; + ``` + +=== "Java" + + ```java title="array.java" + /* 初始化陣列 */ + int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } + int[] nums = { 1, 3, 2, 5, 4 }; + ``` + +=== "C#" + + ```csharp title="array.cs" + /* 初始化陣列 */ + int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] + int[] nums = [1, 3, 2, 5, 4]; + ``` + +=== "Go" + + ```go title="array.go" + /* 初始化陣列 */ + var arr [5]int + // 在 Go 中,指定長度時([5]int)為陣列,不指定長度時([]int)為切片 + // 由於 Go 的陣列被設計為在編譯期確定長度,因此只能使用常數來指定長度 + // 為了方便實現擴容 extend() 方法,以下將切片(Slice)看作陣列(Array) + nums := []int{1, 3, 2, 5, 4} + ``` + +=== "Swift" + + ```swift title="array.swift" + /* 初始化陣列 */ + let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] + let nums = [1, 3, 2, 5, 4] + ``` + +=== "JS" + + ```javascript title="array.js" + /* 初始化陣列 */ + var arr = new Array(5).fill(0); + var nums = [1, 3, 2, 5, 4]; + ``` + +=== "TS" + + ```typescript title="array.ts" + /* 初始化陣列 */ + let arr: number[] = new Array(5).fill(0); + let nums: number[] = [1, 3, 2, 5, 4]; + ``` + +=== "Dart" + + ```dart title="array.dart" + /* 初始化陣列 */ + List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] + List nums = [1, 3, 2, 5, 4]; + ``` + +=== "Rust" + + ```rust title="array.rs" + /* 初始化陣列 */ + let arr: Vec = vec![0; 5]; // [0, 0, 0, 0, 0] + let nums: Vec = vec![1, 3, 2, 5, 4]; + ``` + +=== "C" + + ```c title="array.c" + /* 初始化陣列 */ + int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } + int nums[5] = { 1, 3, 2, 5, 4 }; + ``` + +=== "Kotlin" + + ```kotlin title="array.kt" + /* 初始化陣列 */ + var arr = IntArray(5) // { 0, 0, 0, 0, 0 } + var nums = intArrayOf(1, 3, 2, 5, 4) + ``` + +=== "Ruby" + + ```ruby title="array.rb" + # 初始化陣列 + arr = Array.new(5, 0) + nums = [1, 3, 2, 5, 4] + ``` + +=== "Zig" + + ```zig title="array.zig" + // 初始化陣列 + var arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 } + var nums = [_]i32{ 1, 3, 2, 5, 4 }; + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0Aarr%20%3D%20%5B0%5D%20*%205%20%20%23%20%5B%200,%200,%200,%200,%200%20%5D%0Anums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 訪問元素 + +陣列元素被儲存在連續的記憶體空間中,這意味著計算陣列元素的記憶體位址非常容易。給定陣列記憶體位址(首元素記憶體位址)和某個元素的索引,我們可以使用下圖所示的公式計算得到該元素的記憶體位址,從而直接訪問該元素。 + +![陣列元素的記憶體位址計算](array.assets/array_memory_location_calculation.png) + +觀察上圖,我們發現陣列首個元素的索引為 $0$ ,這似乎有些反直覺,因為從 $1$ 開始計數會更自然。但從位址計算公式的角度看,**索引本質上是記憶體位址的偏移量**。首個元素的位址偏移量是 $0$ ,因此它的索引為 $0$ 是合理的。 + +在陣列中訪問元素非常高效,我們可以在 $O(1)$ 時間內隨機訪問陣列中的任意一個元素。 + +```src +[file]{array}-[class]{}-[func]{random_access} +``` + +### 插入元素 + +陣列元素在記憶體中是“緊挨著的”,它們之間沒有空間再存放任何資料。如下圖所示,如果想在陣列中間插入一個元素,則需要將該元素之後的所有元素都向後移動一位,之後再把元素賦值給該索引。 + +![陣列插入元素示例](array.assets/array_insert_element.png) + +值得注意的是,由於陣列的長度是固定的,因此插入一個元素必定會導致陣列尾部元素“丟失”。我們將這個問題的解決方案留在“串列”章節中討論。 + +```src +[file]{array}-[class]{}-[func]{insert} +``` + +### 刪除元素 + +同理,如下圖所示,若想刪除索引 $i$ 處的元素,則需要把索引 $i$ 之後的元素都向前移動一位。 + +![陣列刪除元素示例](array.assets/array_remove_element.png) + +請注意,刪除元素完成後,原先末尾的元素變得“無意義”了,所以我們無須特意去修改它。 + +```src +[file]{array}-[class]{}-[func]{remove} +``` + +總的來看,陣列的插入與刪除操作有以下缺點。 + +- **時間複雜度高**:陣列的插入和刪除的平均時間複雜度均為 $O(n)$ ,其中 $n$ 為陣列長度。 +- **丟失元素**:由於陣列的長度不可變,因此在插入元素後,超出陣列長度範圍的元素會丟失。 +- **記憶體浪費**:我們可以初始化一個比較長的陣列,只用前面一部分,這樣在插入資料時,丟失的末尾元素都是“無意義”的,但這樣做會造成部分記憶體空間浪費。 + +### 走訪陣列 + +在大多數程式語言中,我們既可以透過索引走訪陣列,也可以直接走訪獲取陣列中的每個元素: + +```src +[file]{array}-[class]{}-[func]{traverse} +``` + +### 查詢元素 + +在陣列中查詢指定元素需要走訪陣列,每輪判斷元素值是否匹配,若匹配則輸出對應索引。 + +因為陣列是線性資料結構,所以上述查詢操作被稱為“線性查詢”。 + +```src +[file]{array}-[class]{}-[func]{find} +``` + +### 擴容陣列 + +在複雜的系統環境中,程式難以保證陣列之後的記憶體空間是可用的,從而無法安全地擴展陣列容量。因此在大多數程式語言中,**陣列的長度是不可變的**。 + +如果我們希望擴容陣列,則需重新建立一個更大的陣列,然後把原陣列元素依次複製到新陣列。這是一個 $O(n)$ 的操作,在陣列很大的情況下非常耗時。程式碼如下所示: + +```src +[file]{array}-[class]{}-[func]{extend} +``` + +## 陣列的優點與侷限性 + +陣列儲存在連續的記憶體空間內,且元素型別相同。這種做法包含豐富的先驗資訊,系統可以利用這些資訊來最佳化資料結構的操作效率。 + +- **空間效率高**:陣列為資料分配了連續的記憶體塊,無須額外的結構開銷。 +- **支持隨機訪問**:陣列允許在 $O(1)$ 時間內訪問任何元素。 +- **快取區域性**:當訪問陣列元素時,計算機不僅會載入它,還會快取其周圍的其他資料,從而藉助高速快取來提升後續操作的執行速度。 + +連續空間儲存是一把雙刃劍,其存在以下侷限性。 + +- **插入與刪除效率低**:當陣列中元素較多時,插入與刪除操作需要移動大量的元素。 +- **長度不可變**:陣列在初始化後長度就固定了,擴容陣列需要將所有資料複製到新陣列,開銷很大。 +- **空間浪費**:如果陣列分配的大小超過實際所需,那麼多餘的空間就被浪費了。 + +## 陣列典型應用 + +陣列是一種基礎且常見的資料結構,既頻繁應用在各類演算法之中,也可用於實現各種複雜資料結構。 + +- **隨機訪問**:如果我們想隨機抽取一些樣本,那麼可以用陣列儲存,並生成一個隨機序列,根據索引實現隨機抽樣。 +- **排序和搜尋**:陣列是排序和搜尋演算法最常用的資料結構。快速排序、合併排序、二分搜尋等都主要在陣列上進行。 +- **查詢表**:當需要快速查詢一個元素或其對應關係時,可以使用陣列作為查詢表。假如我們想實現字元到 ASCII 碼的對映,則可以將字元的 ASCII 碼值作為索引,對應的元素存放在陣列中的對應位置。 +- **機器學習**:神經網路中大量使用了向量、矩陣、張量之間的線性代數運算,這些資料都是以陣列的形式構建的。陣列是神經網路程式設計中最常使用的資料結構。 +- **資料結構實現**:陣列可以用於實現堆疊、佇列、雜湊表、堆積、圖等資料結構。例如,圖的鄰接矩陣表示實際上是一個二維陣列。 diff --git a/zh-hant/docs/chapter_array_and_linkedlist/index.md b/zh-hant/docs/chapter_array_and_linkedlist/index.md new file mode 100644 index 000000000..d5753ad47 --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/index.md @@ -0,0 +1,9 @@ +# 陣列與鏈結串列 + +![陣列與鏈結串列](../assets/covers/chapter_array_and_linkedlist.jpg) + +!!! abstract + + 資料結構的世界如同一堵厚實的磚牆。 + + 陣列的磚塊整齊排列,逐個緊貼。鏈結串列的磚塊分散各處,連線的藤蔓自由地穿梭於磚縫之間。 diff --git a/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png new file mode 100644 index 000000000..8c3c4bdfe Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png new file mode 100644 index 000000000..94318af1e Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png new file mode 100644 index 000000000..ff5905ce1 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png new file mode 100644 index 000000000..e8ef0e3a1 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/linked_list.md b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.md new file mode 100755 index 000000000..a1b067e7e --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/linked_list.md @@ -0,0 +1,761 @@ +# 鏈結串列 + +記憶體空間是所有程式的公共資源,在一個複雜的系統執行環境下,空閒的記憶體空間可能散落在記憶體各處。我們知道,儲存陣列的記憶體空間必須是連續的,而當陣列非常大時,記憶體可能無法提供如此大的連續空間。此時鏈結串列的靈活性優勢就體現出來了。 + +鏈結串列(linked list)是一種線性資料結構,其中的每個元素都是一個節點物件,各個節點透過“引用”相連線。引用記錄了下一個節點的記憶體位址,透過它可以從當前節點訪問到下一個節點。 + +鏈結串列的設計使得各個節點可以分散儲存在記憶體各處,它們的記憶體位址無須連續。 + +![鏈結串列定義與儲存方式](linked_list.assets/linkedlist_definition.png) + +觀察上圖,鏈結串列的組成單位是節點(node)物件。每個節點都包含兩項資料:節點的“值”和指向下一節點的“引用”。 + +- 鏈結串列的首個節點被稱為“頭節點”,最後一個節點被稱為“尾節點”。 +- 尾節點指向的是“空”,它在 Java、C++ 和 Python 中分別被記為 `null`、`nullptr` 和 `None` 。 +- 在 C、C++、Go 和 Rust 等支持指標的語言中,上述“引用”應被替換為“指標”。 + +如以下程式碼所示,鏈結串列節點 `ListNode` 除了包含值,還需額外儲存一個引用(指標)。因此在相同資料量下,**鏈結串列比陣列佔用更多的記憶體空間**。 + +=== "Python" + + ```python title="" + class ListNode: + """鏈結串列節點類別""" + def __init__(self, val: int): + self.val: int = val # 節點值 + self.next: ListNode | None = None # 指向下一節點的引用 + ``` + +=== "C++" + + ```cpp title="" + /* 鏈結串列節點結構體 */ + struct ListNode { + int val; // 節點值 + ListNode *next; // 指向下一節點的指標 + ListNode(int x) : val(x), next(nullptr) {} // 建構子 + }; + ``` + +=== "Java" + + ```java title="" + /* 鏈結串列節點類別 */ + class ListNode { + int val; // 節點值 + ListNode next; // 指向下一節點的引用 + ListNode(int x) { val = x; } // 建構子 + } + ``` + +=== "C#" + + ```csharp title="" + /* 鏈結串列節點類別 */ + class ListNode(int x) { //建構子 + int val = x; // 節點值 + ListNode? next; // 指向下一節點的引用 + } + ``` + +=== "Go" + + ```go title="" + /* 鏈結串列節點結構體 */ + type ListNode struct { + Val int // 節點值 + Next *ListNode // 指向下一節點的指標 + } + + // NewListNode 建構子,建立一個新的鏈結串列 + func NewListNode(val int) *ListNode { + return &ListNode{ + Val: val, + Next: nil, + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 鏈結串列節點類別 */ + class ListNode { + var val: Int // 節點值 + var next: ListNode? // 指向下一節點的引用 + + init(x: Int) { // 建構子 + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 鏈結串列節點類別 */ + class ListNode { + constructor(val, next) { + this.val = (val === undefined ? 0 : val); // 節點值 + this.next = (next === undefined ? null : next); // 指向下一節點的引用 + } + } + ``` + +=== "TS" + + ```typescript title="" + /* 鏈結串列節點類別 */ + class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; // 節點值 + this.next = next === undefined ? null : next; // 指向下一節點的引用 + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 鏈結串列節點類別 */ + class ListNode { + int val; // 節點值 + ListNode? next; // 指向下一節點的引用 + ListNode(this.val, [this.next]); // 建構子 + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + /* 鏈結串列節點類別 */ + #[derive(Debug)] + struct ListNode { + val: i32, // 節點值 + next: Option>>, // 指向下一節點的指標 + } + ``` + +=== "C" + + ```c title="" + /* 鏈結串列節點結構體 */ + typedef struct ListNode { + int val; // 節點值 + struct ListNode *next; // 指向下一節點的指標 + } ListNode; + + /* 建構子 */ + ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 鏈結串列節點類別 */ + // 建構子 + class ListNode(x: Int) { + val _val: Int = x // 節點值 + val next: ListNode? = null // 指向下一個節點的引用 + } + ``` + +=== "Ruby" + + ```ruby title="" + # 鏈結串列節點類別 + class ListNode + attr_accessor :val # 節點值 + attr_accessor :next # 指向下一節點的引用 + + def initialize(val=0, next_node=nil) + @val = val + @next = next_node + end + end + ``` + +=== "Zig" + + ```zig title="" + // 鏈結串列節點類別 + pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, // 節點值 + next: ?*Self = null, // 指向下一節點的指標 + + // 建構子 + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + } + }; + } + ``` + +## 鏈結串列常用操作 + +### 初始化鏈結串列 + +建立鏈結串列分為兩步,第一步是初始化各個節點物件,第二步是構建節點之間的引用關係。初始化完成後,我們就可以從鏈結串列的頭節點出發,透過引用指向 `next` 依次訪問所有節點。 + +=== "Python" + + ```python title="linked_list.py" + # 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 + # 初始化各個節點 + 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 + ``` + +=== "C++" + + ```cpp title="linked_list.cpp" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + ListNode* n0 = new ListNode(1); + ListNode* n1 = new ListNode(3); + ListNode* n2 = new ListNode(2); + ListNode* n3 = new ListNode(5); + ListNode* n4 = new ListNode(4); + // 構建節點之間的引用 + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + ``` + +=== "Java" + + ```java title="linked_list.java" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + ListNode n0 = new ListNode(1); + ListNode n1 = new ListNode(3); + ListNode n2 = new ListNode(2); + ListNode n3 = new ListNode(5); + ListNode n4 = new ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "C#" + + ```csharp title="linked_list.cs" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + ListNode n0 = new(1); + ListNode n1 = new(3); + ListNode n2 = new(2); + ListNode n3 = new(5); + ListNode n4 = new(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Go" + + ```go title="linked_list.go" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + n0 := NewListNode(1) + n1 := NewListNode(3) + n2 := NewListNode(2) + n3 := NewListNode(5) + n4 := NewListNode(4) + // 構建節點之間的引用 + n0.Next = n1 + n1.Next = n2 + n2.Next = n3 + n3.Next = n4 + ``` + +=== "Swift" + + ```swift title="linked_list.swift" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // 構建節點之間的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +=== "JS" + + ```javascript title="linked_list.js" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + const n0 = new ListNode(1); + const n1 = new ListNode(3); + const n2 = new ListNode(2); + const n3 = new ListNode(5); + const n4 = new ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "TS" + + ```typescript title="linked_list.ts" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + const n0 = new ListNode(1); + const n1 = new ListNode(3); + const n2 = new ListNode(2); + const n3 = new ListNode(5); + const n4 = new ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Dart" + + ```dart title="linked_list.dart" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */\ + // 初始化各個節點 + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Rust" + + ```rust title="linked_list.rs" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); + let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); + let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None })); + let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None })); + let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None })); + + // 構建節點之間的引用 + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); + ``` + +=== "C" + + ```c title="linked_list.c" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + ListNode* n0 = newListNode(1); + ListNode* n1 = newListNode(3); + ListNode* n2 = newListNode(2); + ListNode* n3 = newListNode(5); + ListNode* n4 = newListNode(4); + // 構建節點之間的引用 + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + ``` + +=== "Kotlin" + + ```kotlin title="linked_list.kt" + /* 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各個節點 + val n0 = ListNode(1) + val n1 = ListNode(3) + val n2 = ListNode(2) + val n3 = ListNode(5) + val n4 = ListNode(4) + // 構建節點之間的引用 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + ``` + +=== "Ruby" + + ```ruby title="linked_list.rb" + # 初始化鏈結串列 1 -> 3 -> 2 -> 5 -> 4 + # 初始化各個節點 + n0 = ListNode.new(1) + n1 = ListNode.new(3) + n2 = ListNode.new(2) + n3 = ListNode.new(5) + n4 = ListNode.new(4) + # 構建節點之間的引用 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +=== "Zig" + + ```zig title="linked_list.zig" + // 初始化鏈結串列 + // 初始化各個節點 + var n0 = inc.ListNode(i32){.val = 1}; + var n1 = inc.ListNode(i32){.val = 3}; + var n2 = inc.ListNode(i32){.val = 2}; + var n3 = inc.ListNode(i32){.val = 5}; + var n4 = inc.ListNode(i32){.val = 4}; + // 構建節點之間的引用 + n0.next = &n1; + n1.next = &n2; + n2.next = &n3; + n3.next = &n4; + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +陣列整體是一個變數,比如陣列 `nums` 包含元素 `nums[0]` 和 `nums[1]` 等,而鏈結串列是由多個獨立的節點物件組成的。**我們通常將頭節點當作鏈結串列的代稱**,比如以上程式碼中的鏈結串列可記作鏈結串列 `n0` 。 + +### 插入節點 + +在鏈結串列中插入節點非常容易。如下圖所示,假設我們想在相鄰的兩個節點 `n0` 和 `n1` 之間插入一個新節點 `P` ,**則只需改變兩個節點引用(指標)即可**,時間複雜度為 $O(1)$ 。 + +相比之下,在陣列中插入元素的時間複雜度為 $O(n)$ ,在大資料量下的效率較低。 + +![鏈結串列插入節點示例](linked_list.assets/linkedlist_insert_node.png) + +```src +[file]{linked_list}-[class]{}-[func]{insert} +``` + +### 刪除節點 + +如下圖所示,在鏈結串列中刪除節點也非常方便,**只需改變一個節點的引用(指標)即可**。 + +請注意,儘管在刪除操作完成後節點 `P` 仍然指向 `n1` ,但實際上走訪此鏈結串列已經無法訪問到 `P` ,這意味著 `P` 已經不再屬於該鏈結串列了。 + +![鏈結串列刪除節點](linked_list.assets/linkedlist_remove_node.png) + +```src +[file]{linked_list}-[class]{}-[func]{remove} +``` + +### 訪問節點 + +**在鏈結串列中訪問節點的效率較低**。如上一節所述,我們可以在 $O(1)$ 時間下訪問陣列中的任意元素。鏈結串列則不然,程式需要從頭節點出發,逐個向後走訪,直至找到目標節點。也就是說,訪問鏈結串列的第 $i$ 個節點需要迴圈 $i - 1$ 輪,時間複雜度為 $O(n)$ 。 + +```src +[file]{linked_list}-[class]{}-[func]{access} +``` + +### 查詢節點 + +走訪鏈結串列,查詢其中值為 `target` 的節點,輸出該節點在鏈結串列中的索引。此過程也屬於線性查詢。程式碼如下所示: + +```src +[file]{linked_list}-[class]{}-[func]{find} +``` + +## 陣列 vs. 鏈結串列 + +下表總結了陣列和鏈結串列的各項特點並對比了操作效率。由於它們採用兩種相反的儲存策略,因此各種性質和操作效率也呈現對立的特點。 + +

  陣列與鏈結串列的效率對比

+ +| | 陣列 | 鏈結串列 | +| -------- | ------------------------------ | -------------- | +| 儲存方式 | 連續記憶體空間 | 分散記憶體空間 | +| 容量擴展 | 長度不可變 | 可靈活擴展 | +| 記憶體效率 | 元素佔用記憶體少、但可能浪費空間 | 元素佔用記憶體多 | +| 訪問元素 | $O(1)$ | $O(n)$ | +| 新增元素 | $O(n)$ | $O(1)$ | +| 刪除元素 | $O(n)$ | $O(1)$ | + +## 常見鏈結串列型別 + +如下圖所示,常見的鏈結串列型別包括三種。 + +- **單向鏈結串列**:即前面介紹的普通鏈結串列。單向鏈結串列的節點包含值和指向下一節點的引用兩項資料。我們將首個節點稱為頭節點,將最後一個節點稱為尾節點,尾節點指向空 `None` 。 +- **環形鏈結串列**:如果我們令單向鏈結串列的尾節點指向頭節點(首尾相接),則得到一個環形鏈結串列。在環形鏈結串列中,任意節點都可以視作頭節點。 +- **雙向鏈結串列**:與單向鏈結串列相比,雙向鏈結串列記錄了兩個方向的引用。雙向鏈結串列的節點定義同時包含指向後繼節點(下一個節點)和前驅節點(上一個節點)的引用(指標)。相較於單向鏈結串列,雙向鏈結串列更具靈活性,可以朝兩個方向走訪鏈結串列,但相應地也需要佔用更多的記憶體空間。 + +=== "Python" + + ```python title="" + class ListNode: + """雙向鏈結串列節點類別""" + def __init__(self, val: int): + self.val: int = val # 節點值 + self.next: ListNode | None = None # 指向後繼節點的引用 + self.prev: ListNode | None = None # 指向前驅節點的引用 + ``` + +=== "C++" + + ```cpp title="" + /* 雙向鏈結串列節點結構體 */ + struct ListNode { + int val; // 節點值 + ListNode *next; // 指向後繼節點的指標 + ListNode *prev; // 指向前驅節點的指標 + ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 建構子 + }; + ``` + +=== "Java" + + ```java title="" + /* 雙向鏈結串列節點類別 */ + class ListNode { + int val; // 節點值 + ListNode next; // 指向後繼節點的引用 + ListNode prev; // 指向前驅節點的引用 + ListNode(int x) { val = x; } // 建構子 + } + ``` + +=== "C#" + + ```csharp title="" + /* 雙向鏈結串列節點類別 */ + class ListNode(int x) { // 建構子 + int val = x; // 節點值 + ListNode next; // 指向後繼節點的引用 + ListNode prev; // 指向前驅節點的引用 + } + ``` + +=== "Go" + + ```go title="" + /* 雙向鏈結串列節點結構體 */ + type DoublyListNode struct { + Val int // 節點值 + Next *DoublyListNode // 指向後繼節點的指標 + Prev *DoublyListNode // 指向前驅節點的指標 + } + + // NewDoublyListNode 初始化 + func NewDoublyListNode(val int) *DoublyListNode { + return &DoublyListNode{ + Val: val, + Next: nil, + Prev: nil, + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 雙向鏈結串列節點類別 */ + class ListNode { + var val: Int // 節點值 + var next: ListNode? // 指向後繼節點的引用 + var prev: ListNode? // 指向前驅節點的引用 + + init(x: Int) { // 建構子 + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 雙向鏈結串列節點類別 */ + class ListNode { + constructor(val, next, prev) { + this.val = val === undefined ? 0 : val; // 節點值 + this.next = next === undefined ? null : next; // 指向後繼節點的引用 + this.prev = prev === undefined ? null : prev; // 指向前驅節點的引用 + } + } + ``` + +=== "TS" + + ```typescript title="" + /* 雙向鏈結串列節點類別 */ + class ListNode { + val: number; + next: ListNode | null; + prev: ListNode | null; + constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { + this.val = val === undefined ? 0 : val; // 節點值 + this.next = next === undefined ? null : next; // 指向後繼節點的引用 + this.prev = prev === undefined ? null : prev; // 指向前驅節點的引用 + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 雙向鏈結串列節點類別 */ + class ListNode { + int val; // 節點值 + ListNode next; // 指向後繼節點的引用 + ListNode prev; // 指向前驅節點的引用 + ListNode(this.val, [this.next, this.prev]); // 建構子 + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* 雙向鏈結串列節點型別 */ + #[derive(Debug)] + struct ListNode { + val: i32, // 節點值 + next: Option>>, // 指向後繼節點的指標 + prev: Option>>, // 指向前驅節點的指標 + } + + /* 建構子 */ + impl ListNode { + fn new(val: i32) -> Self { + ListNode { + val, + next: None, + prev: None, + } + } + } + ``` + +=== "C" + + ```c title="" + /* 雙向鏈結串列節點結構體 */ + typedef struct ListNode { + int val; // 節點值 + struct ListNode *next; // 指向後繼節點的指標 + struct ListNode *prev; // 指向前驅節點的指標 + } ListNode; + + /* 建構子 */ + ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *) malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + node->prev = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 雙向鏈結串列節點類別 */ + // 建構子 + class ListNode(x: Int) { + val _val: Int = x // 節點值 + val next: ListNode? = null // 指向後繼節點的引用 + val prev: ListNode? = null // 指向前驅節點的引用 + } + ``` + +=== "Ruby" + + ```ruby title="" + # 雙向鏈結串列節點類別 + class ListNode + attr_accessor :val # 節點值 + attr_accessor :next # 指向後繼節點的引用 + attr_accessor :prev # 指向前驅節點的引用 + + def initialize(val=0, next_node=nil, prev_node=nil) + @val = val + @next = next_node + @prev = prev_node + end + end + ``` + +=== "Zig" + + ```zig title="" + // 雙向鏈結串列節點類別 + pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, // 節點值 + next: ?*Self = null, // 指向後繼節點的指標 + prev: ?*Self = null, // 指向前驅節點的指標 + + // 建構子 + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; + } + ``` + +![常見鏈結串列種類](linked_list.assets/linkedlist_common_types.png) + +## 鏈結串列典型應用 + +單向鏈結串列通常用於實現堆疊、佇列、雜湊表和圖等資料結構。 + +- **堆疊與佇列**:當插入和刪除操作都在鏈結串列的一端進行時,它表現出先進後出的特性,對應堆疊;當插入操作在鏈結串列的一端進行,刪除操作在鏈結串列的另一端進行,它表現出先進先出的特性,對應佇列。 +- **雜湊表**:鏈式位址是解決雜湊衝突的主流方案之一,在該方案中,所有衝突的元素都會被放到一個鏈結串列中。 +- **圖**:鄰接表是表示圖的一種常用方式,其中圖的每個頂點都與一個鏈結串列相關聯,鏈結串列中的每個元素都代表與該頂點相連的其他頂點。 + +雙向鏈結串列常用於需要快速查詢前一個和後一個元素的場景。 + +- **高階資料結構**:比如在紅黑樹、B 樹中,我們需要訪問節點的父節點,這可以透過在節點中儲存一個指向父節點的引用來實現,類似於雙向鏈結串列。 +- **瀏覽器歷史**:在網頁瀏覽器中,當用戶點選前進或後退按鈕時,瀏覽器需要知道使用者訪問過的前一個和後一個網頁。雙向鏈結串列的特性使得這種操作變得簡單。 +- **LRU 演算法**:在快取淘汰(LRU)演算法中,我們需要快速找到最近最少使用的資料,以及支持快速新增和刪除節點。這時候使用雙向鏈結串列就非常合適。 + +環形鏈結串列常用於需要週期性操作的場景,比如作業系統的資源排程。 + +- **時間片輪轉排程演算法**:在作業系統中,時間片輪轉排程演算法是一種常見的 CPU 排程演算法,它需要對一組程序進行迴圈。每個程序被賦予一個時間片,當時間片用完時,CPU 將切換到下一個程序。這種迴圈操作可以透過環形鏈結串列來實現。 +- **資料緩衝區**:在某些資料緩衝區的實現中,也可能會使用環形鏈結串列。比如在音訊、影片播放器中,資料流可能會被分成多個緩衝塊並放入一個環形鏈結串列,以便實現無縫播放。 diff --git a/zh-hant/docs/chapter_array_and_linkedlist/list.md b/zh-hant/docs/chapter_array_and_linkedlist/list.md new file mode 100755 index 000000000..7e43efed4 --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/list.md @@ -0,0 +1,1034 @@ +# 串列 + +串列(list)是一個抽象的資料結構概念,它表示元素的有序集合,支持元素訪問、修改、新增、刪除和走訪等操作,無須使用者考慮容量限制的問題。串列可以基於鏈結串列或陣列實現。 + +- 鏈結串列天然可以看作一個串列,其支持元素增刪查改操作,並且可以靈活動態擴容。 +- 陣列也支持元素增刪查改,但由於其長度不可變,因此只能看作一個具有長度限制的串列。 + +當使用陣列實現串列時,**長度不可變的性質會導致串列的實用性降低**。這是因為我們通常無法事先確定需要儲存多少資料,從而難以選擇合適的串列長度。若長度過小,則很可能無法滿足使用需求;若長度過大,則會造成記憶體空間浪費。 + +為解決此問題,我們可以使用動態陣列(dynamic array)來實現串列。它繼承了陣列的各項優點,並且可以在程式執行過程中進行動態擴容。 + +實際上,**許多程式語言中的標準庫提供的串列是基於動態陣列實現的**,例如 Python 中的 `list` 、Java 中的 `ArrayList` 、C++ 中的 `vector` 和 C# 中的 `List` 等。在接下來的討論中,我們將把“串列”和“動態陣列”視為等同的概念。 + +## 串列常用操作 + +### 初始化串列 + +我們通常使用“無初始值”和“有初始值”這兩種初始化方法: + +=== "Python" + + ```python title="list.py" + # 初始化串列 + # 無初始值 + nums1: list[int] = [] + # 有初始值 + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 初始化串列 */ + // 需注意,C++ 中 vector 即是本文描述的 nums + // 無初始值 + vector nums1; + // 有初始值 + vector nums = { 1, 3, 2, 5, 4 }; + ``` + +=== "Java" + + ```java title="list.java" + /* 初始化串列 */ + // 無初始值 + List nums1 = new ArrayList<>(); + // 有初始值(注意陣列的元素型別需為 int[] 的包裝類別 Integer[]) + Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; + List nums = new ArrayList<>(Arrays.asList(numbers)); + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 初始化串列 */ + // 無初始值 + List nums1 = []; + // 有初始值 + int[] numbers = [1, 3, 2, 5, 4]; + List nums = [.. numbers]; + ``` + +=== "Go" + + ```go title="list_test.go" + /* 初始化串列 */ + // 無初始值 + nums1 := []int{} + // 有初始值 + nums := []int{1, 3, 2, 5, 4} + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 初始化串列 */ + // 無初始值 + let nums1: [Int] = [] + // 有初始值 + var nums = [1, 3, 2, 5, 4] + ``` + +=== "JS" + + ```javascript title="list.js" + /* 初始化串列 */ + // 無初始值 + const nums1 = []; + // 有初始值 + const nums = [1, 3, 2, 5, 4]; + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 初始化串列 */ + // 無初始值 + const nums1: number[] = []; + // 有初始值 + const nums: number[] = [1, 3, 2, 5, 4]; + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 初始化串列 */ + // 無初始值 + List nums1 = []; + // 有初始值 + List nums = [1, 3, 2, 5, 4]; + ``` + +=== "Rust" + + ```rust title="list.rs" + /* 初始化串列 */ + // 無初始值 + let nums1: Vec = Vec::new(); + // 有初始值 + let nums: Vec = vec![1, 3, 2, 5, 4]; + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 初始化串列 */ + // 無初始值 + var nums1 = listOf() + // 有初始值 + var numbers = arrayOf(1, 3, 2, 5, 4) + var nums = numbers.toMutableList() + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 初始化串列 + # 無初始值 + nums1 = [] + # 有初始值 + nums = [1, 3, 2, 5, 4] + ``` + +=== "Zig" + + ```zig title="list.zig" + // 初始化串列 + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20%23%20%E6%97%A0%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 訪問元素 + +串列本質上是陣列,因此可以在 $O(1)$ 時間內訪問和更新元素,效率很高。 + +=== "Python" + + ```python title="list.py" + # 訪問元素 + num: int = nums[1] # 訪問索引 1 處的元素 + + # 更新元素 + nums[1] = 0 # 將索引 1 處的元素更新為 0 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 訪問元素 */ + int num = nums[1]; // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "Java" + + ```java title="list.java" + /* 訪問元素 */ + int num = nums.get(1); // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums.set(1, 0); // 將索引 1 處的元素更新為 0 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 訪問元素 */ + int num = nums[1]; // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "Go" + + ```go title="list_test.go" + /* 訪問元素 */ + num := nums[1] // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0 // 將索引 1 處的元素更新為 0 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 訪問元素 */ + let num = nums[1] // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0 // 將索引 1 處的元素更新為 0 + ``` + +=== "JS" + + ```javascript title="list.js" + /* 訪問元素 */ + const num = nums[1]; // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 訪問元素 */ + const num: number = nums[1]; // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 訪問元素 */ + int num = nums[1]; // 訪問索引 1 處的元素 + + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* 訪問元素 */ + let num: i32 = nums[1]; // 訪問索引 1 處的元素 + /* 更新元素 */ + nums[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 訪問元素 */ + val num = nums[1] // 訪問索引 1 處的元素 + /* 更新元素 */ + nums[1] = 0 // 將索引 1 處的元素更新為 0 + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 訪問元素 + num = nums[1] # 訪問索引 1 處的元素 + # 更新元素 + nums[1] = 0 # 將索引 1 處的元素更新為 0 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 訪問元素 + var num = nums.items[1]; // 訪問索引 1 處的元素 + + // 更新元素 + nums.items[1] = 0; // 將索引 1 處的元素更新為 0 + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%E8%AE%BF%E9%97%AE%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%E5%B0%86%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%E6%9B%B4%E6%96%B0%E4%B8%BA%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 插入與刪除元素 + +相較於陣列,串列可以自由地新增與刪除元素。在串列尾部新增元素的時間複雜度為 $O(1)$ ,但插入和刪除元素的效率仍與陣列相同,時間複雜度為 $O(n)$ 。 + +=== "Python" + + ```python title="list.py" + # 清空串列 + nums.clear() + + # 在尾部新增元素 + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + + # 在中間插入元素 + nums.insert(3, 6) # 在索引 3 處插入數字 6 + + # 刪除元素 + nums.pop(3) # 刪除索引 3 處的元素 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 清空串列 */ + nums.clear(); + + /* 在尾部新增元素 */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + + /* 在中間插入元素 */ + nums.insert(nums.begin() + 3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.erase(nums.begin() + 3); // 刪除索引 3 處的元素 + ``` + +=== "Java" + + ```java title="list.java" + /* 清空串列 */ + nums.clear(); + + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* 在中間插入元素 */ + nums.add(3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.remove(3); // 刪除索引 3 處的元素 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 清空串列 */ + nums.Clear(); + + /* 在尾部新增元素 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + + /* 在中間插入元素 */ + nums.Insert(3, 6); + + /* 刪除元素 */ + nums.RemoveAt(3); + ``` + +=== "Go" + + ```go title="list_test.go" + /* 清空串列 */ + nums = nil + + /* 在尾部新增元素 */ + nums = append(nums, 1) + nums = append(nums, 3) + nums = append(nums, 2) + nums = append(nums, 5) + nums = append(nums, 4) + + /* 在中間插入元素 */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums = append(nums[:3], nums[4:]...) // 刪除索引 3 處的元素 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 清空串列 */ + nums.removeAll() + + /* 在尾部新增元素 */ + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + + /* 在中間插入元素 */ + nums.insert(6, at: 3) // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.remove(at: 3) // 刪除索引 3 處的元素 + ``` + +=== "JS" + + ```javascript title="list.js" + /* 清空串列 */ + nums.length = 0; + + /* 在尾部新增元素 */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* 在中間插入元素 */ + nums.splice(3, 0, 6); + + /* 刪除元素 */ + nums.splice(3, 1); + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 清空串列 */ + nums.length = 0; + + /* 在尾部新增元素 */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* 在中間插入元素 */ + nums.splice(3, 0, 6); + + /* 刪除元素 */ + nums.splice(3, 1); + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 清空串列 */ + nums.clear(); + + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* 在中間插入元素 */ + nums.insert(3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.removeAt(3); // 刪除索引 3 處的元素 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* 清空串列 */ + nums.clear(); + + /* 在尾部新增元素 */ + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + + /* 在中間插入元素 */ + nums.insert(3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.remove(3); // 刪除索引 3 處的元素 + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 清空串列 */ + nums.clear(); + + /* 在尾部新增元素 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* 在中間插入元素 */ + nums.add(3, 6); // 在索引 3 處插入數字 6 + + /* 刪除元素 */ + nums.remove(3); // 刪除索引 3 處的元素 + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 清空串列 + nums.clear + + # 在尾部新增元素 + nums << 1 + nums << 3 + nums << 2 + nums << 5 + nums << 4 + + # 在中間插入元素 + nums.insert(3, 6) # 在索引 3 處插入數字 6 + + # 刪除元素 + nums.delete_at(3) # 刪除索引 3 處的元素 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 清空串列 + nums.clearRetainingCapacity(); + + // 在尾部新增元素 + try nums.append(1); + try nums.append(3); + try nums.append(2); + try nums.append(5); + try nums.append(4); + + // 在中間插入元素 + try nums.insert(3, 6); // 在索引 3 處插入數字 6 + + // 刪除元素 + _ = nums.orderedRemove(3); // 刪除索引 3 處的元素 + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B8%85%E7%A9%BA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%283,%206%29%20%20%23%20%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E6%8F%92%E5%85%A5%E6%95%B0%E5%AD%97%206%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 走訪串列 + +與陣列一樣,串列可以根據索引走訪,也可以直接走訪各元素。 + +=== "Python" + + ```python title="list.py" + # 透過索引走訪串列 + count = 0 + for i in range(len(nums)): + count += nums[i] + + # 直接走訪串列元素 + for num in nums: + count += num + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; + } + + /* 直接走訪串列元素 */ + count = 0; + for (int num : nums) { + count += num; + } + ``` + +=== "Java" + + ```java title="list.java" + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums.get(i); + } + + /* 直接走訪串列元素 */ + for (int num : nums) { + count += num; + } + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 透過索引走訪串列 */ + int count = 0; + for (int i = 0; i < nums.Count; i++) { + count += nums[i]; + } + + /* 直接走訪串列元素 */ + count = 0; + foreach (int num in nums) { + count += num; + } + ``` + +=== "Go" + + ```go title="list_test.go" + /* 透過索引走訪串列 */ + count := 0 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + + /* 直接走訪串列元素 */ + count = 0 + for _, num := range nums { + count += num + } + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 透過索引走訪串列 */ + var count = 0 + for i in nums.indices { + count += nums[i] + } + + /* 直接走訪串列元素 */ + count = 0 + for num in nums { + count += num + } + ``` + +=== "JS" + + ```javascript title="list.js" + /* 透過索引走訪串列 */ + let count = 0; + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* 直接走訪串列元素 */ + count = 0; + for (const num of nums) { + count += num; + } + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 透過索引走訪串列 */ + let count = 0; + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* 直接走訪串列元素 */ + count = 0; + for (const num of nums) { + count += num; + } + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 透過索引走訪串列 */ + int count = 0; + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + + /* 直接走訪串列元素 */ + count = 0; + for (var num in nums) { + count += num; + } + ``` + +=== "Rust" + + ```rust title="list.rs" + // 透過索引走訪串列 + let mut _count = 0; + for i in 0..nums.len() { + _count += nums[i]; + } + + // 直接走訪串列元素 + _count = 0; + for num in &nums { + _count += num; + } + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 透過索引走訪串列 */ + var count = 0 + for (i in nums.indices) { + count += nums[i] + } + + /* 直接走訪串列元素 */ + for (num in nums) { + count += num + } + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 透過索引走訪串列 + count = 0 + for i in 0...nums.length + count += nums[i] + end + + # 直接走訪串列元素 + count = 0 + for num in nums + count += num + end + ``` + +=== "Zig" + + ```zig title="list.zig" + // 透過索引走訪串列 + var count: i32 = 0; + var i: i32 = 0; + while (i < nums.items.len) : (i += 1) { + count += nums[i]; + } + + // 直接走訪串列元素 + count = 0; + for (nums.items) |num| { + count += num; + } + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E7%B4%A2%E5%BC%95%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 拼接串列 + +給定一個新串列 `nums1` ,我們可以將其拼接到原串列的尾部。 + +=== "Python" + + ```python title="list.py" + # 拼接兩個串列 + nums1: list[int] = [6, 8, 7, 10, 9] + nums += nums1 # 將串列 nums1 拼接到 nums 之後 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 拼接兩個串列 */ + vector nums1 = { 6, 8, 7, 10, 9 }; + // 將串列 nums1 拼接到 nums 之後 + nums.insert(nums.end(), nums1.begin(), nums1.end()); + ``` + +=== "Java" + + ```java title="list.java" + /* 拼接兩個串列 */ + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 拼接兩個串列 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.AddRange(nums1); // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "Go" + + ```go title="list_test.go" + /* 拼接兩個串列 */ + nums1 := []int{6, 8, 7, 10, 9} + nums = append(nums, nums1...) // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 拼接兩個串列 */ + let nums1 = [6, 8, 7, 10, 9] + nums.append(contentsOf: nums1) // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "JS" + + ```javascript title="list.js" + /* 拼接兩個串列 */ + const nums1 = [6, 8, 7, 10, 9]; + nums.push(...nums1); // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 拼接兩個串列 */ + const nums1: number[] = [6, 8, 7, 10, 9]; + nums.push(...nums1); // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 拼接兩個串列 */ + List nums1 = [6, 8, 7, 10, 9]; + nums.addAll(nums1); // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* 拼接兩個串列 */ + let nums1: Vec = vec![6, 8, 7, 10, 9]; + nums.extend(nums1); + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 拼接兩個串列 */ + val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList() + nums.addAll(nums1) // 將串列 nums1 拼接到 nums 之後 + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 拼接兩個串列 + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 拼接兩個串列 + var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums1.deinit(); + try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); + try nums.insertSlice(nums.items.len, nums1.items); // 將串列 nums1 拼接到 nums 之後 + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8B%BC%E6%8E%A5%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums1%20%3D%20%5B6,%208,%207,%2010,%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%20nums1%20%E6%8B%BC%E6%8E%A5%E5%88%B0%20nums%20%E4%B9%8B%E5%90%8E&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 排序串列 + +完成串列排序後,我們便可以使用在陣列類別演算法題中經常考查的“二分搜尋”和“雙指標”演算法。 + +=== "Python" + + ```python title="list.py" + # 排序串列 + nums.sort() # 排序後,串列元素從小到大排列 + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 排序串列 */ + sort(nums.begin(), nums.end()); // 排序後,串列元素從小到大排列 + ``` + +=== "Java" + + ```java title="list.java" + /* 排序串列 */ + Collections.sort(nums); // 排序後,串列元素從小到大排列 + ``` + +=== "C#" + + ```csharp title="list.cs" + /* 排序串列 */ + nums.Sort(); // 排序後,串列元素從小到大排列 + ``` + +=== "Go" + + ```go title="list_test.go" + /* 排序串列 */ + sort.Ints(nums) // 排序後,串列元素從小到大排列 + ``` + +=== "Swift" + + ```swift title="list.swift" + /* 排序串列 */ + nums.sort() // 排序後,串列元素從小到大排列 + ``` + +=== "JS" + + ```javascript title="list.js" + /* 排序串列 */ + nums.sort((a, b) => a - b); // 排序後,串列元素從小到大排列 + ``` + +=== "TS" + + ```typescript title="list.ts" + /* 排序串列 */ + nums.sort((a, b) => a - b); // 排序後,串列元素從小到大排列 + ``` + +=== "Dart" + + ```dart title="list.dart" + /* 排序串列 */ + nums.sort(); // 排序後,串列元素從小到大排列 + ``` + +=== "Rust" + + ```rust title="list.rs" + /* 排序串列 */ + nums.sort(); // 排序後,串列元素從小到大排列 + ``` + +=== "C" + + ```c title="list.c" + // C 未提供內建動態陣列 + ``` + +=== "Kotlin" + + ```kotlin title="list.kt" + /* 排序串列 */ + nums.sort() // 排序後,串列元素從小到大排列 + ``` + +=== "Ruby" + + ```ruby title="list.rb" + # 排序串列 + nums = nums.sort { |a, b| a <=> b } # 排序後,串列元素從小到大排列 + ``` + +=== "Zig" + + ```zig title="list.zig" + // 排序串列 + std.sort.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8E%92%E5%BA%8F%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E6%8E%92%E5%BA%8F%E5%90%8E%EF%BC%8C%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E4%BB%8E%E5%B0%8F%E5%88%B0%E5%A4%A7%E6%8E%92%E5%88%97&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 串列實現 + +許多程式語言內建了串列,例如 Java、C++、Python 等。它們的實現比較複雜,各個參數的設定也非常考究,例如初始容量、擴容倍數等。感興趣的讀者可以查閱原始碼進行學習。 + +為了加深對串列工作原理的理解,我們嘗試實現一個簡易版串列,包括以下三個重點設計。 + +- **初始容量**:選取一個合理的陣列初始容量。在本示例中,我們選擇 10 作為初始容量。 +- **數量記錄**:宣告一個變數 `size` ,用於記錄串列當前元素數量,並隨著元素插入和刪除實時更新。根據此變數,我們可以定位串列尾部,以及判斷是否需要擴容。 +- **擴容機制**:若插入元素時串列容量已滿,則需要進行擴容。先根據擴容倍數建立一個更大的陣列,再將當前陣列的所有元素依次移動至新陣列。在本示例中,我們規定每次將陣列擴容至之前的 2 倍。 + +```src +[file]{my_list}-[class]{my_list}-[func]{} +``` diff --git a/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png new file mode 100644 index 000000000..2a558930f Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png new file mode 100644 index 000000000..660543bc5 Binary files /dev/null and b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png differ diff --git a/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.md b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.md new file mode 100644 index 000000000..65d90ddf8 --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/ram_and_cache.md @@ -0,0 +1,71 @@ +# 記憶體與快取 * + +在本章的前兩節中,我們探討了陣列和鏈結串列這兩種基礎且重要的資料結構,它們分別代表了“連續儲存”和“分散儲存”兩種物理結構。 + +實際上,**物理結構在很大程度上決定了程式對記憶體和快取的使用效率**,進而影響演算法程式的整體效能。 + +## 計算機儲存裝置 + +計算機中包括三種類型的儲存裝置:硬碟(hard disk)記憶體(random-access memory, RAM)快取(cache memory)。下表展示了它們在計算機系統中的不同角色和效能特點。 + +

  計算機的儲存裝置

+ +| | 硬碟 | 記憶體 | 快取 | +| ------ | ---------------------------------------- | -------------------------------------- | ------------------------------------------------- | +| 用途 | 長期儲存資料,包括作業系統、程式、檔案等 | 臨時儲存當前執行的程式和正在處理的資料 | 儲存經常訪問的資料和指令,減少 CPU 訪問記憶體的次數 | +| 易失性 | 斷電後資料不會丟失 | 斷電後資料會丟失 | 斷電後資料會丟失 | +| 容量 | 較大,TB 級別 | 較小,GB 級別 | 非常小,MB 級別 | +| 速度 | 較慢,幾百到幾千 MB/s | 較快,幾十 GB/s | 非常快,幾十到幾百 GB/s | +| 價格 | 較便宜,幾毛到幾元 / GB | 較貴,幾十到幾百元 / GB | 非常貴,隨 CPU 打包計價 | + +我們可以將計算機儲存系統想象為下圖所示的金字塔結構。越靠近金字塔頂端的儲存裝置的速度越快、容量越小、成本越高。這種多層級的設計並非偶然,而是計算機科學家和工程師們經過深思熟慮的結果。 + +- **硬碟難以被記憶體取代**。首先,記憶體中的資料在斷電後會丟失,因此它不適合長期儲存資料;其次,記憶體的成本是硬碟的幾十倍,這使得它難以在消費者市場普及。 +- **快取的大容量和高速度難以兼得**。隨著 L1、L2、L3 快取的容量逐步增大,其物理尺寸會變大,與 CPU 核心之間的物理距離會變遠,從而導致資料傳輸時間增加,元素訪問延遲變高。在當前技術下,多層級的快取結構是容量、速度和成本之間的最佳平衡點。 + +![計算機儲存系統](ram_and_cache.assets/storage_pyramid.png) + +!!! note + + 計算機的儲存層次結構體現了速度、容量和成本三者之間的精妙平衡。實際上,這種權衡普遍存在於所有工業領域,它要求我們在不同的優勢和限制之間找到最佳平衡點。 + +總的來說,**硬碟用於長期儲存大量資料,記憶體用於臨時儲存程式執行中正在處理的資料,而快取則用於儲存經常訪問的資料和指令**,以提高程式執行效率。三者共同協作,確保計算機系統高效執行。 + +如下圖所示,在程式執行時,資料會從硬碟中被讀取到記憶體中,供 CPU 計算使用。快取可以看作 CPU 的一部分,**它透過智慧地從記憶體載入資料**,給 CPU 提供高速的資料讀取,從而顯著提升程式的執行效率,減少對較慢的記憶體的依賴。 + +![硬碟、記憶體和快取之間的資料流通](ram_and_cache.assets/computer_storage_devices.png) + +## 資料結構的記憶體效率 + +在記憶體空間利用方面,陣列和鏈結串列各自具有優勢和侷限性。 + +一方面,**記憶體是有限的,且同一塊記憶體不能被多個程式共享**,因此我們希望資料結構能夠儘可能高效地利用空間。陣列的元素緊密排列,不需要額外的空間來儲存鏈結串列節點間的引用(指標),因此空間效率更高。然而,陣列需要一次性分配足夠的連續記憶體空間,這可能導致記憶體浪費,陣列擴容也需要額外的時間和空間成本。相比之下,鏈結串列以“節點”為單位進行動態記憶體分配和回收,提供了更大的靈活性。 + +另一方面,在程式執行時,**隨著反覆申請與釋放記憶體,空閒記憶體的碎片化程度會越來越高**,從而導致記憶體的利用效率降低。陣列由於其連續的儲存方式,相對不容易導致記憶體碎片化。相反,鏈結串列的元素是分散儲存的,在頻繁的插入與刪除操作中,更容易導致記憶體碎片化。 + +## 資料結構的快取效率 + +快取雖然在空間容量上遠小於記憶體,但它比記憶體快得多,在程式執行速度上起著至關重要的作用。由於快取的容量有限,只能儲存一小部分頻繁訪問的資料,因此當 CPU 嘗試訪問的資料不在快取中時,就會發生快取未命中(cache miss),此時 CPU 不得不從速度較慢的記憶體中載入所需資料。 + +顯然,**“快取未命中”越少,CPU 讀寫資料的效率就越高**,程式效能也就越好。我們將 CPU 從快取中成功獲取資料的比例稱為快取命中率(cache hit rate),這個指標通常用來衡量快取效率。 + +為了儘可能達到更高的效率,快取會採取以下資料載入機制。 + +- **快取行**:快取不是單個位元組地儲存與載入資料,而是以快取行為單位。相比於單個位元組的傳輸,快取行的傳輸形式更加高效。 +- **預取機制**:處理器會嘗試預測資料訪問模式(例如順序訪問、固定步長跳躍訪問等),並根據特定模式將資料載入至快取之中,從而提升命中率。 +- **空間區域性**:如果一個數據被訪問,那麼它附近的資料可能近期也會被訪問。因此,快取在載入某一資料時,也會載入其附近的資料,以提高命中率。 +- **時間區域性**:如果一個數據被訪問,那麼它在不久的將來很可能再次被訪問。快取利用這一原理,透過保留最近訪問過的資料來提高命中率。 + +實際上,**陣列和鏈結串列對快取的利用效率是不同的**,主要體現在以下幾個方面。 + +- **佔用空間**:鏈結串列元素比陣列元素佔用空間更多,導致快取中容納的有效資料量更少。 +- **快取行**:鏈結串列資料分散在記憶體各處,而快取是“按行載入”的,因此載入到無效資料的比例更高。 +- **預取機制**:陣列比鏈結串列的資料訪問模式更具“可預測性”,即系統更容易猜出即將被載入的資料。 +- **空間區域性**:陣列被儲存在集中的記憶體空間中,因此被載入資料附近的資料更有可能即將被訪問。 + +總體而言,**陣列具有更高的快取命中率,因此它在操作效率上通常優於鏈結串列**。這使得在解決演算法問題時,基於陣列實現的資料結構往往更受歡迎。 + +需要注意的是,**高快取效率並不意味著陣列在所有情況下都優於鏈結串列**。實際應用中選擇哪種資料結構,應根據具體需求來決定。例如,陣列和鏈結串列都可以實現“堆疊”資料結構(下一章會詳細介紹),但它們適用於不同場景。 + +- 在做演算法題時,我們會傾向於選擇基於陣列實現的堆疊,因為它提供了更高的操作效率和隨機訪問的能力,代價僅是需要預先為陣列分配一定的記憶體空間。 +- 如果資料量非常大、動態性很高、堆疊的預期大小難以估計,那麼基於鏈結串列實現的堆疊更加合適。鏈結串列能夠將大量資料分散儲存於記憶體的不同部分,並且避免了陣列擴容產生的額外開銷。 diff --git a/zh-hant/docs/chapter_array_and_linkedlist/summary.md b/zh-hant/docs/chapter_array_and_linkedlist/summary.md new file mode 100644 index 000000000..41d7c3559 --- /dev/null +++ b/zh-hant/docs/chapter_array_and_linkedlist/summary.md @@ -0,0 +1,76 @@ +# 小結 + +### 重點回顧 + +- 陣列和鏈結串列是兩種基本的資料結構,分別代表資料在計算機記憶體中的兩種儲存方式:連續空間儲存和分散空間儲存。兩者的特點呈現出互補的特性。 +- 陣列支持隨機訪問、佔用記憶體較少;但插入和刪除元素效率低,且初始化後長度不可變。 +- 鏈結串列透過更改引用(指標)實現高效的節點插入與刪除,且可以靈活調整長度;但節點訪問效率低、佔用記憶體較多。常見的鏈結串列型別包括單向鏈結串列、環形鏈結串列、雙向鏈結串列。 +- 串列是一種支持增刪查改的元素有序集合,通常基於動態陣列實現。它保留了陣列的優勢,同時可以靈活調整長度。 +- 串列的出現大幅提高了陣列的實用性,但可能導致部分記憶體空間浪費。 +- 程式執行時,資料主要儲存在記憶體中。陣列可提供更高的記憶體空間效率,而鏈結串列則在記憶體使用上更加靈活。 +- 快取透過快取行、預取機制以及空間區域性和時間區域性等資料載入機制,為 CPU 提供快速資料訪問,顯著提升程式的執行效率。 +- 由於陣列具有更高的快取命中率,因此它通常比鏈結串列更高效。在選擇資料結構時,應根據具體需求和場景做出恰當選擇。 + +### Q & A + +**Q**:陣列儲存在堆疊上和儲存在堆積上,對時間效率和空間效率是否有影響? + +儲存在堆疊上和堆積上的陣列都被儲存在連續記憶體空間內,資料操作效率基本一致。然而,堆疊和堆積具有各自的特點,從而導致以下不同點。 + +1. 分配和釋放效率:堆疊是一塊較小的記憶體,分配由編譯器自動完成;而堆積記憶體相對更大,可以在程式碼中動態分配,更容易碎片化。因此,堆積上的分配和釋放操作通常比堆疊上的慢。 +2. 大小限制:堆疊記憶體相對較小,堆積的大小一般受限於可用記憶體。因此堆積更加適合儲存大型陣列。 +3. 靈活性:堆疊上的陣列的大小需要在編譯時確定,而堆積上的陣列的大小可以在執行時動態確定。 + +**Q**:為什麼陣列要求相同型別的元素,而在鏈結串列中卻沒有強調相同型別呢? + +鏈結串列由節點組成,節點之間透過引用(指標)連線,各個節點可以儲存不同型別的資料,例如 `int`、`double`、`string`、`object` 等。 + +相對地,陣列元素則必須是相同型別的,這樣才能透過計算偏移量來獲取對應元素位置。例如,陣列同時包含 `int` 和 `long` 兩種型別,單個元素分別佔用 4 位元組 和 8 位元組 ,此時就不能用以下公式計算偏移量了,因為陣列中包含了兩種“元素長度”。 + +```shell +# 元素記憶體位址 = 陣列記憶體位址(首元素記憶體位址) + 元素長度 * 元素索引 +``` + +**Q**:刪除節點 `P` 後,是否需要把 `P.next` 設為 `None` 呢? + +不修改 `P.next` 也可以。從該鏈結串列的角度看,從頭節點走訪到尾節點已經不會遇到 `P` 了。這意味著節點 `P` 已經從鏈結串列中刪除了,此時節點 `P` 指向哪裡都不會對該鏈結串列產生影響。 + +從資料結構與演算法(做題)的角度看,不斷開沒有關係,只要保證程式的邏輯是正確的就行。從標準庫的角度看,斷開更加安全、邏輯更加清晰。如果不斷開,假設被刪除節點未被正常回收,那麼它會影響後繼節點的記憶體回收。 + +**Q**:在鏈結串列中插入和刪除操作的時間複雜度是 $O(1)$ 。但是增刪之前都需要 $O(n)$ 的時間查詢元素,那為什麼時間複雜度不是 $O(n)$ 呢? + +如果是先查詢元素、再刪除元素,時間複雜度確實是 $O(n)$ 。然而,鏈結串列的 $O(1)$ 增刪的優勢可以在其他應用上得到體現。例如,雙向佇列適合使用鏈結串列實現,我們維護一個指標變數始終指向頭節點、尾節點,每次插入與刪除操作都是 $O(1)$ 。 + +**Q**:圖“鏈結串列定義與儲存方式”中,淺藍色的儲存節點指標是佔用一塊記憶體位址嗎?還是和節點值各佔一半呢? + +該示意圖只是定性表示,定量表示需要根據具體情況進行分析。 + +- 不同型別的節點值佔用的空間是不同的,比如 `int`、`long`、`double` 和例項物件等。 +- 指標變數佔用的記憶體空間大小根據所使用的作業系統及編譯環境而定,大多為 8 位元組或 4 位元組。 + +**Q**:在串列末尾新增元素是否時時刻刻都為 $O(1)$ ? + +如果新增元素時超出串列長度,則需要先擴容串列再新增。系統會申請一塊新的記憶體,並將原串列的所有元素搬運過去,這時候時間複雜度就會是 $O(n)$ 。 + +**Q**:“串列的出現極大地提高了陣列的實用性,但可能導致部分記憶體空間浪費”,這裡的空間浪費是指額外增加的變數如容量、長度、擴容倍數所佔的記憶體嗎? + +這裡的空間浪費主要有兩方面含義:一方面,串列都會設定一個初始長度,我們不一定需要用這麼多;另一方面,為了防止頻繁擴容,擴容一般會乘以一個係數,比如 $\times 1.5$ 。這樣一來,也會出現很多空位,我們通常不能完全填滿它們。 + +**Q**:在 Python 中初始化 `n = [1, 2, 3]` 後,這 3 個元素的位址是相連的,但是初始化 `m = [2, 1, 3]` 會發現它們每個元素的 id 並不是連續的,而是分別跟 `n` 中的相同。這些元素的位址不連續,那麼 `m` 還是陣列嗎? + +假如把串列元素換成鏈結串列節點 `n = [n1, n2, n3, n4, n5]` ,通常情況下這 5 個節點物件也分散儲存在記憶體各處。然而,給定一個串列索引,我們仍然可以在 $O(1)$ 時間內獲取節點記憶體位址,從而訪問到對應的節點。這是因為陣列中儲存的是節點的引用,而非節點本身。 + +與許多語言不同,Python 中的數字也被包裝為物件,串列中儲存的不是數字本身,而是對數字的引用。因此,我們會發現兩個陣列中的相同數字擁有同一個 id ,並且這些數字的記憶體位址無須連續。 + +**Q**:C++ STL 裡面的 `std::list` 已經實現了雙向鏈結串列,但好像一些演算法書上不怎麼直接使用它,是不是因為有什麼侷限性呢? + +一方面,我們往往更青睞使用陣列實現演算法,而只在必要時才使用鏈結串列,主要有兩個原因。 + +- 空間開銷:由於每個元素需要兩個額外的指標(一個用於前一個元素,一個用於後一個元素),所以 `std::list` 通常比 `std::vector` 更佔用空間。 +- 快取不友好:由於資料不是連續存放的,因此 `std::list` 對快取的利用率較低。一般情況下,`std::vector` 的效能會更好。 + +另一方面,必要使用鏈結串列的情況主要是二元樹和圖。堆疊和佇列往往會使用程式語言提供的 `stack` 和 `queue` ,而非鏈結串列。 + +**Q**:初始化串列 `res = [0] * self.size()` 操作,會導致 `res` 的每個元素引用相同的位址嗎? + +不會。但二維陣列會有這個問題,例如初始化二維串列 `res = [[0] * self.size()]` ,則多次引用了同一個串列 `[0]` 。 diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png new file mode 100644 index 000000000..cc7f41d55 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png new file mode 100644 index 000000000..e33aeea63 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png new file mode 100644 index 000000000..19c644389 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png new file mode 100644 index 000000000..9ce590d27 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png new file mode 100644 index 000000000..c72be171a Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png new file mode 100644 index 000000000..1bcdf7dfa Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png new file mode 100644 index 000000000..4300b1f7d Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png new file mode 100644 index 000000000..1f671f4ad Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png new file mode 100644 index 000000000..0935b83fd Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png new file mode 100644 index 000000000..53bbddbec Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png new file mode 100644 index 000000000..b96ac3b30 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png new file mode 100644 index 000000000..133c70e24 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png new file mode 100644 index 000000000..f3631448f Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png new file mode 100644 index 000000000..18d8a2a12 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png differ diff --git a/zh-hant/docs/chapter_backtracking/backtracking_algorithm.md b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.md new file mode 100644 index 000000000..f4c2bda29 --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/backtracking_algorithm.md @@ -0,0 +1,489 @@ +# 回溯演算法 + +回溯演算法(backtracking algorithm)是一種透過窮舉來解決問題的方法,它的核心思想是從一個初始狀態出發,暴力搜尋所有可能的解決方案,當遇到正確的解則將其記錄,直到找到解或者嘗試了所有可能的選擇都無法找到解為止。 + +回溯演算法通常採用“深度優先搜尋”來走訪解空間。在“二元樹”章節中,我們提到前序、中序和後序走訪都屬於深度優先搜尋。接下來,我們利用前序走訪構造一個回溯問題,逐步瞭解回溯演算法的工作原理。 + +!!! question "例題一" + + 給定一棵二元樹,搜尋並記錄所有值為 $7$ 的節點,請返回節點串列。 + +對於此題,我們前序走訪這棵樹,並判斷當前節點的值是否為 $7$ ,若是,則將該節點的值加入結果串列 `res` 之中。相關過程實現如下圖和以下程式碼所示: + +```src +[file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} +``` + +![在前序走訪中搜索節點](backtracking_algorithm.assets/preorder_find_nodes.png) + +## 嘗試與回退 + +**之所以稱之為回溯演算法,是因為該演算法在搜尋解空間時會採用“嘗試”與“回退”的策略**。當演算法在搜尋過程中遇到某個狀態無法繼續前進或無法得到滿足條件的解時,它會撤銷上一步的選擇,退回到之前的狀態,並嘗試其他可能的選擇。 + +對於例題一,訪問每個節點都代表一次“嘗試”,而越過葉節點或返回父節點的 `return` 則表示“回退”。 + +值得說明的是,**回退並不僅僅包括函式返回**。為解釋這一點,我們對例題一稍作拓展。 + +!!! question "例題二" + + 在二元樹中搜索所有值為 $7$ 的節點,**請返回根節點到這些節點的路徑**。 + +在例題一程式碼的基礎上,我們需要藉助一個串列 `path` 記錄訪問過的節點路徑。當訪問到值為 $7$ 的節點時,則複製 `path` 並新增進結果串列 `res` 。走訪完成後,`res` 中儲存的就是所有的解。程式碼如下所示: + +```src +[file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} +``` + +在每次“嘗試”中,我們透過將當前節點新增進 `path` 來記錄路徑;而在“回退”前,我們需要將該節點從 `path` 中彈出,**以恢復本次嘗試之前的狀態**。 + +觀察下圖所示的過程,**我們可以將嘗試和回退理解為“前進”與“撤銷”**,兩個操作互為逆向。 + +=== "<1>" + ![嘗試與回退](backtracking_algorithm.assets/preorder_find_paths_step1.png) + +=== "<2>" + ![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png) + +=== "<3>" + ![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png) + +=== "<4>" + ![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png) + +=== "<5>" + ![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png) + +=== "<6>" + ![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png) + +=== "<7>" + ![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png) + +=== "<8>" + ![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png) + +=== "<9>" + ![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png) + +=== "<10>" + ![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png) + +=== "<11>" + ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) + +## 剪枝 + +複雜的回溯問題通常包含一個或多個約束條件,**約束條件通常可用於“剪枝”**。 + +!!! question "例題三" + + 在二元樹中搜索所有值為 $7$ 的節點,請返回根節點到這些節點的路徑,**並要求路徑中不包含值為 $3$ 的節點**。 + +為了滿足以上約束條件,**我們需要新增剪枝操作**:在搜尋過程中,若遇到值為 $3$ 的節點,則提前返回,不再繼續搜尋。程式碼如下所示: + +```src +[file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} +``` + +“剪枝”是一個非常形象的名詞。如下圖所示,在搜尋過程中,**我們“剪掉”了不滿足約束條件的搜尋分支**,避免許多無意義的嘗試,從而提高了搜尋效率。 + +![根據約束條件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png) + +## 框架程式碼 + +接下來,我們嘗試將回溯的“嘗試、回退、剪枝”的主體框架提煉出來,提升程式碼的通用性。 + +在以下框架程式碼中,`state` 表示問題的當前狀態,`choices` 表示當前狀態下可以做出的選擇: + +=== "Python" + + ```python title="" + def backtrack(state: State, choices: list[choice], res: list[state]): + """回溯演算法框架""" + # 判斷是否為解 + if is_solution(state): + # 記錄解 + record_solution(state, res) + # 不再繼續搜尋 + return + # 走訪所有選擇 + for choice in choices: + # 剪枝:判斷選擇是否合法 + if is_valid(state, choice): + # 嘗試:做出選擇,更新狀態 + make_choice(state, choice) + backtrack(state, choices, res) + # 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice) + ``` + +=== "C++" + + ```cpp title="" + /* 回溯演算法框架 */ + void backtrack(State *state, vector &choices, vector &res) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (Choice choice : choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + ``` + +=== "Java" + + ```java title="" + /* 回溯演算法框架 */ + void backtrack(State state, List choices, List res) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (Choice choice : choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + ``` + +=== "C#" + + ```csharp title="" + /* 回溯演算法框架 */ + void Backtrack(State state, List choices, List res) { + // 判斷是否為解 + if (IsSolution(state)) { + // 記錄解 + RecordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + foreach (Choice choice in choices) { + // 剪枝:判斷選擇是否合法 + if (IsValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + MakeChoice(state, choice); + Backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + UndoChoice(state, choice); + } + } + } + ``` + +=== "Go" + + ```go title="" + /* 回溯演算法框架 */ + func backtrack(state *State, choices []Choice, res *[]State) { + // 判斷是否為解 + if isSolution(state) { + // 記錄解 + recordSolution(state, res) + // 不再繼續搜尋 + return + } + // 走訪所有選擇 + for _, choice := range choices { + // 剪枝:判斷選擇是否合法 + if isValid(state, choice) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice) + backtrack(state, choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice) + } + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 回溯演算法框架 */ + func backtrack(state: inout State, choices: [Choice], res: inout [State]) { + // 判斷是否為解 + if isSolution(state: state) { + // 記錄解 + recordSolution(state: state, res: &res) + // 不再繼續搜尋 + return + } + // 走訪所有選擇 + for choice in choices { + // 剪枝:判斷選擇是否合法 + if isValid(state: state, choice: choice) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state: &state, choice: choice) + backtrack(state: &state, choices: choices, res: &res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state: &state, choice: choice) + } + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 回溯演算法框架 */ + function backtrack(state, choices, res) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (let choice of choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + ``` + +=== "TS" + + ```typescript title="" + /* 回溯演算法框架 */ + function backtrack(state: State, choices: Choice[], res: State[]): void { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (let choice of choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 回溯演算法框架 */ + void backtrack(State state, List, List res) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (Choice choice in choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice); + } + } + } + ``` + +=== "Rust" + + ```rust title="" + /* 回溯演算法框架 */ + fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) { + // 判斷是否為解 + if is_solution(state) { + // 記錄解 + record_solution(state, res); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for choice in choices { + // 剪枝:判斷選擇是否合法 + if is_valid(state, choice) { + // 嘗試:做出選擇,更新狀態 + make_choice(state, choice); + backtrack(state, choices, res); + // 回退:撤銷選擇,恢復到之前的狀態 + undo_choice(state, choice); + } + } + } + ``` + +=== "C" + + ```c title="" + /* 回溯演算法框架 */ + void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res, numRes); + // 不再繼續搜尋 + return; + } + // 走訪所有選擇 + for (int i = 0; i < numChoices; i++) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, &choices[i])) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, &choices[i]); + backtrack(state, choices, numChoices, res, numRes); + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, &choices[i]); + } + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 回溯演算法框架 */ + fun backtrack(state: State?, choices: List, res: List?) { + // 判斷是否為解 + if (isSolution(state)) { + // 記錄解 + recordSolution(state, res) + // 不再繼續搜尋 + return + } + // 走訪所有選擇 + for (choice in choices) { + // 剪枝:判斷選擇是否合法 + if (isValid(state, choice)) { + // 嘗試:做出選擇,更新狀態 + makeChoice(state, choice) + backtrack(state, choices, res) + // 回退:撤銷選擇,恢復到之前的狀態 + undoChoice(state, choice) + } + } + } + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +接下來,我們基於框架程式碼來解決例題三。狀態 `state` 為節點走訪路徑,選擇 `choices` 為當前節點的左子節點和右子節點,結果 `res` 是路徑串列: + +```src +[file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} +``` + +根據題意,我們在找到值為 $7$ 的節點後應該繼續搜尋,**因此需要將記錄解之後的 `return` 語句刪除**。下圖對比了保留或刪除 `return` 語句的搜尋過程。 + +![保留與刪除 return 的搜尋過程對比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) + +相比基於前序走訪的程式碼實現,基於回溯演算法框架的程式碼實現雖然顯得囉唆,但通用性更好。實際上,**許多回溯問題可以在該框架下解決**。我們只需根據具體問題來定義 `state` 和 `choices` ,並實現框架中的各個方法即可。 + +## 常用術語 + +為了更清晰地分析演算法問題,我們總結一下回溯演算法中常用術語的含義,並對照例題三給出對應示例,如下表所示。 + +

  常見的回溯演算法術語

+ +| 名詞 | 定義 | 例題三 | +| ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| 解(solution) | 解是滿足問題特定條件的答案,可能有一個或多個 | 根節點到節點 $7$ 的滿足約束條件的所有路徑 | +| 約束條件(constraint) | 約束條件是問題中限制解的可行性的條件,通常用於剪枝 | 路徑中不包含節點 $3$ | +| 狀態(state) | 狀態表示問題在某一時刻的情況,包括已經做出的選擇 | 當前已訪問的節點路徑,即 `path` 節點串列 | +| 嘗試(attempt) | 嘗試是根據可用選擇來探索解空間的過程,包括做出選擇,更新狀態,檢查是否為解 | 遞迴訪問左(右)子節點,將節點新增進 `path` ,判斷節點的值是否為 $7$ | +| 回退(backtracking) | 回退指遇到不滿足約束條件的狀態時,撤銷前面做出的選擇,回到上一個狀態 | 當越過葉節點、結束節點訪問、遇到值為 $3$ 的節點時終止搜尋,函式返回 | +| 剪枝(pruning) | 剪枝是根據問題特性和約束條件避免無意義的搜尋路徑的方法,可提高搜尋效率 | 當遇到值為 $3$ 的節點時,則不再繼續搜尋 | + +!!! tip + + 問題、解、狀態等概念是通用的,在分治、回溯、動態規劃、貪婪等演算法中都有涉及。 + +## 優點與侷限性 + +回溯演算法本質上是一種深度優先搜尋演算法,它嘗試所有可能的解決方案直到找到滿足條件的解。這種方法的優點在於能夠找到所有可能的解決方案,而且在合理的剪枝操作下,具有很高的效率。 + +然而,在處理大規模或者複雜問題時,**回溯演算法的執行效率可能難以接受**。 + +- **時間**:回溯演算法通常需要走訪狀態空間的所有可能,時間複雜度可以達到指數階或階乘階。 +- **空間**:在遞迴呼叫中需要儲存當前的狀態(例如路徑、用於剪枝的輔助變數等),當深度很大時,空間需求可能會變得很大。 + +即便如此,**回溯演算法仍然是某些搜尋問題和約束滿足問題的最佳解決方案**。對於這些問題,由於無法預測哪些選擇可生成有效的解,因此我們必須對所有可能的選擇進行走訪。在這種情況下,**關鍵是如何最佳化效率**,常見的效率最佳化方法有兩種。 + +- **剪枝**:避免搜尋那些肯定不會產生解的路徑,從而節省時間和空間。 +- **啟發式搜尋**:在搜尋過程中引入一些策略或者估計值,從而優先搜尋最有可能產生有效解的路徑。 + +## 回溯典型例題 + +回溯演算法可用於解決許多搜尋問題、約束滿足問題和組合最佳化問題。 + +**搜尋問題**:這類問題的目標是找到滿足特定條件的解決方案。 + +- 全排列問題:給定一個集合,求出其所有可能的排列組合。 +- 子集和問題:給定一個集合和一個目標和,找到集合中所有和為目標和的子集。 +- 河內塔問題:給定三根柱子和一系列大小不同的圓盤,要求將所有圓盤從一根柱子移動到另一根柱子,每次只能移動一個圓盤,且不能將大圓盤放在小圓盤上。 + +**約束滿足問題**:這類問題的目標是找到滿足所有約束條件的解。 + +- $n$ 皇后:在 $n \times n$ 的棋盤上放置 $n$ 個皇后,使得它們互不攻擊。 +- 數獨:在 $9 \times 9$ 的網格中填入數字 $1$ ~ $9$ ,使得每行、每列和每個 $3 \times 3$ 子網格中的數字不重複。 +- 圖著色問題:給定一個無向圖,用最少的顏色給圖的每個頂點著色,使得相鄰頂點顏色不同。 + +**組合最佳化問題**:這類問題的目標是在一個組合空間中找到滿足某些條件的最優解。 + +- 0-1 背包問題:給定一組物品和一個背包,每個物品有一定的價值和重量,要求在背包容量限制內,選擇物品使得總價值最大。 +- 旅行商問題:在一個圖中,從一個點出發,訪問所有其他點恰好一次後返回起點,求最短路徑。 +- 最大團問題:給定一個無向圖,找到最大的完全子圖,即子圖中的任意兩個頂點之間都有邊相連。 + +請注意,對於許多組合最佳化問題,回溯不是最優解決方案。 + +- 0-1 背包問題通常使用動態規劃解決,以達到更高的時間效率。 +- 旅行商是一個著名的 NP-Hard 問題,常用解法有遺傳演算法和蟻群演算法等。 +- 最大團問題是圖論中的一個經典問題,可用貪婪演算法等啟發式演算法來解決。 diff --git a/zh-hant/docs/chapter_backtracking/index.md b/zh-hant/docs/chapter_backtracking/index.md new file mode 100644 index 000000000..cb547d1e2 --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/index.md @@ -0,0 +1,9 @@ +# 回溯 + +![回溯](../assets/covers/chapter_backtracking.jpg) + +!!! abstract + + 我們如同迷宮中的探索者,在前進的道路上可能會遇到困難。 + + 回溯的力量讓我們能夠重新開始,不斷嘗試,最終找到通往光明的出口。 diff --git a/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png new file mode 100644 index 000000000..2f7a8bedd Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png differ diff --git a/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png new file mode 100644 index 000000000..3de1e97f6 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png differ diff --git a/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png new file mode 100644 index 000000000..2ea164610 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png differ diff --git a/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png new file mode 100644 index 000000000..34d795059 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png differ diff --git a/zh-hant/docs/chapter_backtracking/n_queens_problem.md b/zh-hant/docs/chapter_backtracking/n_queens_problem.md new file mode 100644 index 000000000..87cd69b2c --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/n_queens_problem.md @@ -0,0 +1,49 @@ +# n 皇后問題 + +!!! question + + 根據國際象棋的規則,皇后可以攻擊與同處一行、一列或一條斜線上的棋子。給定 $n$ 個皇后和一個 $n \times n$ 大小的棋盤,尋找使得所有皇后之間無法相互攻擊的擺放方案。 + +如下圖所示,當 $n = 4$ 時,共可以找到兩個解。從回溯演算法的角度看,$n \times n$ 大小的棋盤共有 $n^2$ 個格子,給出了所有的選擇 `choices` 。在逐個放置皇后的過程中,棋盤狀態在不斷地變化,每個時刻的棋盤就是狀態 `state` 。 + +![4 皇后問題的解](n_queens_problem.assets/solution_4_queens.png) + +下圖展示了本題的三個約束條件:**多個皇后不能在同一行、同一列、同一條對角線上**。值得注意的是,對角線分為主對角線 `\` 和次對角線 `/` 兩種。 + +![n 皇后問題的約束條件](n_queens_problem.assets/n_queens_constraints.png) + +### 逐行放置策略 + +皇后的數量和棋盤的行數都為 $n$ ,因此我們容易得到一個推論:**棋盤每行都允許且只允許放置一個皇后**。 + +也就是說,我們可以採取逐行放置策略:從第一行開始,在每行放置一個皇后,直至最後一行結束。 + +下圖所示為 $4$ 皇后問題的逐行放置過程。受畫幅限制,下圖僅展開了第一行的其中一個搜尋分支,並且將不滿足列約束和對角線約束的方案都進行了剪枝。 + +![逐行放置策略](n_queens_problem.assets/n_queens_placing.png) + +從本質上看,**逐行放置策略起到了剪枝的作用**,它避免了同一行出現多個皇后的所有搜尋分支。 + +### 列與對角線剪枝 + +為了滿足列約束,我們可以利用一個長度為 $n$ 的布林型陣列 `cols` 記錄每一列是否有皇后。在每次決定放置前,我們透過 `cols` 將已有皇后的列進行剪枝,並在回溯中動態更新 `cols` 的狀態。 + +那麼,如何處理對角線約束呢?設棋盤中某個格子的行列索引為 $(row, col)$ ,選定矩陣中的某條主對角線,我們發現該對角線上所有格子的行索引減列索引都相等,**即對角線上所有格子的 $row - col$ 為恆定值**。 + +也就是說,如果兩個格子滿足 $row_1 - col_1 = row_2 - col_2$ ,則它們一定處在同一條主對角線上。利用該規律,我們可以藉助下圖所示的陣列 `diags1` 記錄每條主對角線上是否有皇后。 + +同理,**次對角線上的所有格子的 $row + col$ 是恆定值**。我們同樣也可以藉助陣列 `diags2` 來處理次對角線約束。 + +![處理列約束和對角線約束](n_queens_problem.assets/n_queens_cols_diagonals.png) + +### 程式碼實現 + +請注意,$n$ 維方陣中 $row - col$ 的範圍是 $[-n + 1, n - 1]$ ,$row + col$ 的範圍是 $[0, 2n - 2]$ ,所以主對角線和次對角線的數量都為 $2n - 1$ ,即陣列 `diags1` 和 `diags2` 的長度都為 $2n - 1$ 。 + +```src +[file]{n_queens}-[class]{}-[func]{n_queens} +``` + +逐行放置 $n$ 次,考慮列約束,則從第一行到最後一行分別有 $n$、$n-1$、$\dots$、$2$、$1$ 個選擇,使用 $O(n!)$ 時間。當記錄解時,需要複製矩陣 `state` 並新增進 `res` ,複製操作使用 $O(n^2)$ 時間。因此,**總體時間複雜度為 $O(n! \cdot n^2)$** 。實際上,根據對角線約束的剪枝也能夠大幅縮小搜尋空間,因而搜尋效率往往優於以上時間複雜度。 + +陣列 `state` 使用 $O(n^2)$ 空間,陣列 `cols`、`diags1` 和 `diags2` 皆使用 $O(n)$ 空間。最大遞迴深度為 $n$ ,使用 $O(n)$ 堆疊幀空間。因此,**空間複雜度為 $O(n^2)$** 。 diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png new file mode 100644 index 000000000..da0636de1 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png differ diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png new file mode 100644 index 000000000..57e41d968 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png differ diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png new file mode 100644 index 000000000..0512f4ec5 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png differ diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png new file mode 100644 index 000000000..78b5a5978 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png differ diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png new file mode 100644 index 000000000..d91a86601 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png differ diff --git a/zh-hant/docs/chapter_backtracking/permutations_problem.md b/zh-hant/docs/chapter_backtracking/permutations_problem.md new file mode 100644 index 000000000..9539b9096 --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/permutations_problem.md @@ -0,0 +1,95 @@ +# 全排列問題 + +全排列問題是回溯演算法的一個典型應用。它的定義是在給定一個集合(如一個陣列或字串)的情況下,找出其中元素的所有可能的排列。 + +下表列舉了幾個示例資料,包括輸入陣列和對應的所有排列。 + +

  全排列示例

+ +| 輸入陣列 | 所有排列 | +| :---------- | :----------------------------------------------------------------- | +| $[1]$ | $[1]$ | +| $[1, 2]$ | $[1, 2], [2, 1]$ | +| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | + +## 無相等元素的情況 + +!!! question + + 輸入一個整數陣列,其中不包含重複元素,返回所有可能的排列。 + +從回溯演算法的角度看,**我們可以把生成排列的過程想象成一系列選擇的結果**。假設輸入陣列為 $[1, 2, 3]$ ,如果我們先選擇 $1$ ,再選擇 $3$ ,最後選擇 $2$ ,則獲得排列 $[1, 3, 2]$ 。回退表示撤銷一個選擇,之後繼續嘗試其他選擇。 + +從回溯程式碼的角度看,候選集合 `choices` 是輸入陣列中的所有元素,狀態 `state` 是直至目前已被選擇的元素。請注意,每個元素只允許被選擇一次,**因此 `state` 中的所有元素都應該是唯一的**。 + +如下圖所示,我們可以將搜尋過程展開成一棵遞迴樹,樹中的每個節點代表當前狀態 `state` 。從根節點開始,經過三輪選擇後到達葉節點,每個葉節點都對應一個排列。 + +![全排列的遞迴樹](permutations_problem.assets/permutations_i.png) + +### 重複選擇剪枝 + +為了實現每個元素只被選擇一次,我們考慮引入一個布林型陣列 `selected` ,其中 `selected[i]` 表示 `choices[i]` 是否已被選擇,並基於它實現以下剪枝操作。 + +- 在做出選擇 `choice[i]` 後,我們就將 `selected[i]` 賦值為 $\text{True}$ ,代表它已被選擇。 +- 走訪選擇串列 `choices` 時,跳過所有已被選擇的節點,即剪枝。 + +如下圖所示,假設我們第一輪選擇 1 ,第二輪選擇 3 ,第三輪選擇 2 ,則需要在第二輪剪掉元素 1 的分支,在第三輪剪掉元素 1 和元素 3 的分支。 + +![全排列剪枝示例](permutations_problem.assets/permutations_i_pruning.png) + +觀察上圖發現,該剪枝操作將搜尋空間大小從 $O(n^n)$ 減小至 $O(n!)$ 。 + +### 程式碼實現 + +想清楚以上資訊之後,我們就可以在框架程式碼中做“完形填空”了。為了縮短整體程式碼,我們不單獨實現框架程式碼中的各個函式,而是將它們展開在 `backtrack()` 函式中: + +```src +[file]{permutations_i}-[class]{}-[func]{permutations_i} +``` + +## 考慮相等元素的情況 + +!!! question + + 輸入一個整數陣列,**陣列中可能包含重複元素**,返回所有不重複的排列。 + +假設輸入陣列為 $[1, 1, 2]$ 。為了方便區分兩個重複元素 $1$ ,我們將第二個 $1$ 記為 $\hat{1}$ 。 + +如下圖所示,上述方法生成的排列有一半是重複的。 + +![重複排列](permutations_problem.assets/permutations_ii.png) + +那麼如何去除重複的排列呢?最直接地,考慮藉助一個雜湊表,直接對排列結果進行去重。然而這樣做不夠優雅,**因為生成重複排列的搜尋分支沒有必要,應當提前識別並剪枝**,這樣可以進一步提升演算法效率。 + +### 相等元素剪枝 + +觀察下圖,在第一輪中,選擇 $1$ 或選擇 $\hat{1}$ 是等價的,在這兩個選擇之下生成的所有排列都是重複的。因此應該把 $\hat{1}$ 剪枝。 + +同理,在第一輪選擇 $2$ 之後,第二輪選擇中的 $1$ 和 $\hat{1}$ 也會產生重複分支,因此也應將第二輪的 $\hat{1}$ 剪枝。 + +從本質上看,**我們的目標是在某一輪選擇中,保證多個相等的元素僅被選擇一次**。 + +![重複排列剪枝](permutations_problem.assets/permutations_ii_pruning.png) + +### 程式碼實現 + +在上一題的程式碼的基礎上,我們考慮在每一輪選擇中開啟一個雜湊表 `duplicated` ,用於記錄該輪中已經嘗試過的元素,並將重複元素剪枝: + +```src +[file]{permutations_ii}-[class]{}-[func]{permutations_ii} +``` + +假設元素兩兩之間互不相同,則 $n$ 個元素共有 $n!$ 種排列(階乘);在記錄結果時,需要複製長度為 $n$ 的串列,使用 $O(n)$ 時間。**因此時間複雜度為 $O(n!n)$** 。 + +最大遞迴深度為 $n$ ,使用 $O(n)$ 堆疊幀空間。`selected` 使用 $O(n)$ 空間。同一時刻最多共有 $n$ 個 `duplicated` ,使用 $O(n^2)$ 空間。**因此空間複雜度為 $O(n^2)$** 。 + +### 兩種剪枝對比 + +請注意,雖然 `selected` 和 `duplicated` 都用於剪枝,但兩者的目標不同。 + +- **重複選擇剪枝**:整個搜尋過程中只有一個 `selected` 。它記錄的是當前狀態中包含哪些元素,其作用是避免某個元素在 `state` 中重複出現。 +- **相等元素剪枝**:每輪選擇(每個呼叫的 `backtrack` 函式)都包含一個 `duplicated` 。它記錄的是在本輪走訪(`for` 迴圈)中哪些元素已被選擇過,其作用是保證相等元素只被選擇一次。 + +下圖展示了兩個剪枝條件的生效範圍。注意,樹中的每個節點代表一個選擇,從根節點到葉節點的路徑上的各個節點構成一個排列。 + +![兩種剪枝條件的作用範圍](permutations_problem.assets/permutations_ii_pruning_summary.png) diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png new file mode 100644 index 000000000..0ffad4aa0 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png differ diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png new file mode 100644 index 000000000..5835f8170 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png differ diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png new file mode 100644 index 000000000..530ea82ba Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png differ diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png new file mode 100644 index 000000000..98af02116 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png differ diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png new file mode 100644 index 000000000..b4850d3c8 Binary files /dev/null and b/zh-hant/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png differ diff --git a/zh-hant/docs/chapter_backtracking/subset_sum_problem.md b/zh-hant/docs/chapter_backtracking/subset_sum_problem.md new file mode 100644 index 000000000..f6418f836 --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/subset_sum_problem.md @@ -0,0 +1,95 @@ +# 子集和問題 + +## 無重複元素的情況 + +!!! question + + 給定一個正整數陣列 `nums` 和一個目標正整數 `target` ,請找出所有可能的組合,使得組合中的元素和等於 `target` 。給定陣列無重複元素,每個元素可以被選取多次。請以串列形式返回這些組合,串列中不應包含重複組合。 + +例如,輸入集合 $\{3, 4, 5\}$ 和目標整數 $9$ ,解為 $\{3, 3, 3\}, \{4, 5\}$ 。需要注意以下兩點。 + +- 輸入集合中的元素可以被無限次重複選取。 +- 子集不區分元素順序,比如 $\{4, 5\}$ 和 $\{5, 4\}$ 是同一個子集。 + +### 參考全排列解法 + +類似於全排列問題,我們可以把子集的生成過程想象成一系列選擇的結果,並在選擇過程中實時更新“元素和”,當元素和等於 `target` 時,就將子集記錄至結果串列。 + +而與全排列問題不同的是,**本題集合中的元素可以被無限次選取**,因此無須藉助 `selected` 布林串列來記錄元素是否已被選擇。我們可以對全排列程式碼進行小幅修改,初步得到解題程式碼: + +```src +[file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive} +``` + +向以上程式碼輸入陣列 $[3, 4, 5]$ 和目標元素 $9$ ,輸出結果為 $[3, 3, 3], [4, 5], [5, 4]$ 。**雖然成功找出了所有和為 $9$ 的子集,但其中存在重複的子集 $[4, 5]$ 和 $[5, 4]$** 。 + +這是因為搜尋過程是區分選擇順序的,然而子集不區分選擇順序。如下圖所示,先選 $4$ 後選 $5$ 與先選 $5$ 後選 $4$ 是不同的分支,但對應同一個子集。 + +![子集搜尋與越界剪枝](subset_sum_problem.assets/subset_sum_i_naive.png) + +為了去除重複子集,**一種直接的思路是對結果串列進行去重**。但這個方法效率很低,有兩方面原因。 + +- 當陣列元素較多,尤其是當 `target` 較大時,搜尋過程會產生大量的重複子集。 +- 比較子集(陣列)的異同非常耗時,需要先排序陣列,再比較陣列中每個元素的異同。 + +### 重複子集剪枝 + +**我們考慮在搜尋過程中透過剪枝進行去重**。觀察下圖,重複子集是在以不同順序選擇陣列元素時產生的,例如以下情況。 + +1. 當第一輪和第二輪分別選擇 $3$ 和 $4$ 時,會生成包含這兩個元素的所有子集,記為 $[3, 4, \dots]$ 。 +2. 之後,當第一輪選擇 $4$ 時,**則第二輪應該跳過 $3$** ,因為該選擇產生的子集 $[4, 3, \dots]$ 和第 `1.` 步中生成的子集完全重複。 + +在搜尋過程中,每一層的選擇都是從左到右被逐個嘗試的,因此越靠右的分支被剪掉的越多。 + +1. 前兩輪選擇 $3$ 和 $5$ ,生成子集 $[3, 5, \dots]$ 。 +2. 前兩輪選擇 $4$ 和 $5$ ,生成子集 $[4, 5, \dots]$ 。 +3. 若第一輪選擇 $5$ ,**則第二輪應該跳過 $3$ 和 $4$** ,因為子集 $[5, 3, \dots]$ 和 $[5, 4, \dots]$ 與第 `1.` 步和第 `2.` 步中描述的子集完全重複。 + +![不同選擇順序導致的重複子集](subset_sum_problem.assets/subset_sum_i_pruning.png) + +總結來看,給定輸入陣列 $[x_1, x_2, \dots, x_n]$ ,設搜尋過程中的選擇序列為 $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$ ,則該選擇序列需要滿足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,**不滿足該條件的選擇序列都會造成重複,應當剪枝**。 + +### 程式碼實現 + +為實現該剪枝,我們初始化變數 `start` ,用於指示走訪起始點。**當做出選擇 $x_{i}$ 後,設定下一輪從索引 $i$ 開始走訪**。這樣做就可以讓選擇序列滿足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,從而保證子集唯一。 + +除此之外,我們還對程式碼進行了以下兩項最佳化。 + +- 在開啟搜尋前,先將陣列 `nums` 排序。在走訪所有選擇時,**當子集和超過 `target` 時直接結束迴圈**,因為後邊的元素更大,其子集和一定超過 `target` 。 +- 省去元素和變數 `total` ,**透過在 `target` 上執行減法來統計元素和**,當 `target` 等於 $0$ 時記錄解。 + +```src +[file]{subset_sum_i}-[class]{}-[func]{subset_sum_i} +``` + +下圖所示為將陣列 $[3, 4, 5]$ 和目標元素 $9$ 輸入以上程式碼後的整體回溯過程。 + +![子集和 I 回溯過程](subset_sum_problem.assets/subset_sum_i.png) + +## 考慮重複元素的情況 + +!!! question + + 給定一個正整數陣列 `nums` 和一個目標正整數 `target` ,請找出所有可能的組合,使得組合中的元素和等於 `target` 。**給定陣列可能包含重複元素,每個元素只可被選擇一次**。請以串列形式返回這些組合,串列中不應包含重複組合。 + +相比於上題,**本題的輸入陣列可能包含重複元素**,這引入了新的問題。例如,給定陣列 $[4, \hat{4}, 5]$ 和目標元素 $9$ ,則現有程式碼的輸出結果為 $[4, 5], [\hat{4}, 5]$ ,出現了重複子集。 + +**造成這種重複的原因是相等元素在某輪中被多次選擇**。在下圖中,第一輪共有三個選擇,其中兩個都為 $4$ ,會產生兩個重複的搜尋分支,從而輸出重複子集;同理,第二輪的兩個 $4$ 也會產生重複子集。 + +![相等元素導致的重複子集](subset_sum_problem.assets/subset_sum_ii_repeat.png) + +### 相等元素剪枝 + +為解決此問題,**我們需要限制相等元素在每一輪中只能被選擇一次**。實現方式比較巧妙:由於陣列是已排序的,因此相等元素都是相鄰的。這意味著在某輪選擇中,若當前元素與其左邊元素相等,則說明它已經被選擇過,因此直接跳過當前元素。 + +與此同時,**本題規定每個陣列元素只能被選擇一次**。幸運的是,我們也可以利用變數 `start` 來滿足該約束:當做出選擇 $x_{i}$ 後,設定下一輪從索引 $i + 1$ 開始向後走訪。這樣既能去除重複子集,也能避免重複選擇元素。 + +### 程式碼實現 + +```src +[file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii} +``` + +下圖展示了陣列 $[4, 4, 5]$ 和目標元素 $9$ 的回溯過程,共包含四種剪枝操作。請你將圖示與程式碼註釋相結合,理解整個搜尋過程,以及每種剪枝操作是如何工作的。 + +![子集和 II 回溯過程](subset_sum_problem.assets/subset_sum_ii.png) diff --git a/zh-hant/docs/chapter_backtracking/summary.md b/zh-hant/docs/chapter_backtracking/summary.md new file mode 100644 index 000000000..cee5f4427 --- /dev/null +++ b/zh-hant/docs/chapter_backtracking/summary.md @@ -0,0 +1,23 @@ +# 小結 + +### 重點回顧 + +- 回溯演算法本質是窮舉法,透過對解空間進行深度優先走訪來尋找符合條件的解。在搜尋過程中,遇到滿足條件的解則記錄,直至找到所有解或走訪完成後結束。 +- 回溯演算法的搜尋過程包括嘗試與回退兩個部分。它透過深度優先搜尋來嘗試各種選擇,當遇到不滿足約束條件的情況時,則撤銷上一步的選擇,退回到之前的狀態,並繼續嘗試其他選擇。嘗試與回退是兩個方向相反的操作。 +- 回溯問題通常包含多個約束條件,它們可用於實現剪枝操作。剪枝可以提前結束不必要的搜尋分支,大幅提升搜尋效率。 +- 回溯演算法主要可用於解決搜尋問題和約束滿足問題。組合最佳化問題雖然可以用回溯演算法解決,但往往存在效率更高或效果更好的解法。 +- 全排列問題旨在搜尋給定集合元素的所有可能的排列。我們藉助一個陣列來記錄每個元素是否被選擇,剪掉重複選擇同一元素的搜尋分支,確保每個元素只被選擇一次。 +- 在全排列問題中,如果集合中存在重複元素,則最終結果會出現重複排列。我們需要約束相等元素在每輪中只能被選擇一次,這通常藉助一個雜湊表來實現。 +- 子集和問題的目標是在給定集合中找到和為目標值的所有子集。集合不區分元素順序,而搜尋過程會輸出所有順序的結果,產生重複子集。我們在回溯前將資料進行排序,並設定一個變數來指示每一輪的走訪起始點,從而將生成重複子集的搜尋分支進行剪枝。 +- 對於子集和問題,陣列中的相等元素會產生重複集合。我們利用陣列已排序的前置條件,透過判斷相鄰元素是否相等實現剪枝,從而確保相等元素在每輪中只能被選中一次。 +- $n$ 皇后問題旨在尋找將 $n$ 個皇后放置到 $n \times n$ 尺寸棋盤上的方案,要求所有皇后兩兩之間無法攻擊對方。該問題的約束條件有行約束、列約束、主對角線和次對角線約束。為滿足行約束,我們採用按行放置的策略,保證每一行放置一個皇后。 +- 列約束和對角線約束的處理方式類似。對於列約束,我們利用一個陣列來記錄每一列是否有皇后,從而指示選中的格子是否合法。對於對角線約束,我們藉助兩個陣列來分別記錄該主、次對角線上是否存在皇后;難點在於找處在到同一主(副)對角線上格子滿足的行列索引規律。 + +### Q & A + +**Q**:怎麼理解回溯和遞迴的關係? + +總的來看,回溯是一種“演算法策略”,而遞迴更像是一個“工具”。 + +- 回溯演算法通常基於遞迴實現。然而,回溯是遞迴的應用場景之一,是遞迴在搜尋問題中的應用。 +- 遞迴的結構體現了“子問題分解”的解題範式,常用於解決分治、回溯、動態規劃(記憶化遞迴)等問題。 diff --git a/zh-hant/docs/chapter_computational_complexity/index.md b/zh-hant/docs/chapter_computational_complexity/index.md new file mode 100644 index 000000000..a8fdf4e13 --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/index.md @@ -0,0 +1,9 @@ +# 複雜度分析 + +![複雜度分析](../assets/covers/chapter_complexity_analysis.jpg) + +!!! abstract + + 複雜度分析猶如浩瀚的演算法宇宙中的時空嚮導。 + + 它帶領我們在時間與空間這兩個維度上深入探索,尋找更優雅的解決方案。 diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png new file mode 100644 index 000000000..5585e178f Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png new file mode 100644 index 000000000..723ba7897 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png new file mode 100644 index 000000000..5673cef8e Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png new file mode 100644 index 000000000..f7c7b046c Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png new file mode 100644 index 000000000..597c92276 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png new file mode 100644 index 000000000..c8507d4d8 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.md b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.md new file mode 100644 index 000000000..66c4b1ba7 --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -0,0 +1,194 @@ +# 迭代與遞迴 + +在演算法中,重複執行某個任務是很常見的,它與複雜度分析息息相關。因此,在介紹時間複雜度和空間複雜度之前,我們先來了解如何在程式中實現重複執行任務,即兩種基本的程式控制結構:迭代、遞迴。 + +## 迭代 + +迭代(iteration)是一種重複執行某個任務的控制結構。在迭代中,程式會在滿足一定的條件下重複執行某段程式碼,直到這個條件不再滿足。 + +### for 迴圈 + +`for` 迴圈是最常見的迭代形式之一,**適合在預先知道迭代次數時使用**。 + +以下函式基於 `for` 迴圈實現了求和 $1 + 2 + \dots + n$ ,求和結果使用變數 `res` 記錄。需要注意的是,Python 中 `range(a, b)` 對應的區間是“左閉右開”的,對應的走訪範圍為 $a, a + 1, \dots, b-1$ : + +```src +[file]{iteration}-[class]{}-[func]{for_loop} +``` + +下圖是該求和函式的流程框圖。 + +![求和函式的流程框圖](iteration_and_recursion.assets/iteration.png) + +此求和函式的操作數量與輸入資料大小 $n$ 成正比,或者說成“線性關係”。實際上,**時間複雜度描述的就是這個“線性關係”**。相關內容將會在下一節中詳細介紹。 + +### while 迴圈 + +與 `for` 迴圈類似,`while` 迴圈也是一種實現迭代的方法。在 `while` 迴圈中,程式每輪都會先檢查條件,如果條件為真,則繼續執行,否則就結束迴圈。 + +下面我們用 `while` 迴圈來實現求和 $1 + 2 + \dots + n$ : + +```src +[file]{iteration}-[class]{}-[func]{while_loop} +``` + +**`while` 迴圈比 `for` 迴圈的自由度更高**。在 `while` 迴圈中,我們可以自由地設計條件變數的初始化和更新步驟。 + +例如在以下程式碼中,條件變數 $i$ 每輪進行兩次更新,這種情況就不太方便用 `for` 迴圈實現: + +```src +[file]{iteration}-[class]{}-[func]{while_loop_ii} +``` + +總的來說,**`for` 迴圈的程式碼更加緊湊,`while` 迴圈更加靈活**,兩者都可以實現迭代結構。選擇使用哪一個應該根據特定問題的需求來決定。 + +### 巢狀迴圈 + +我們可以在一個迴圈結構內巢狀另一個迴圈結構,下面以 `for` 迴圈為例: + +```src +[file]{iteration}-[class]{}-[func]{nested_for_loop} +``` + +下圖是該巢狀迴圈的流程框圖。 + +![巢狀迴圈的流程框圖](iteration_and_recursion.assets/nested_iteration.png) + +在這種情況下,函式的操作數量與 $n^2$ 成正比,或者說演算法執行時間和輸入資料大小 $n$ 成“平方關係”。 + +我們可以繼續新增巢狀迴圈,每一次巢狀都是一次“升維”,將會使時間複雜度提高至“立方關係”“四次方關係”,以此類推。 + +## 遞迴 + + 遞迴(recursion)是一種演算法策略,透過函式呼叫自身來解決問題。它主要包含兩個階段。 + +1. **遞**:程式不斷深入地呼叫自身,通常傳入更小或更簡化的參數,直到達到“終止條件”。 +2. **迴**:觸發“終止條件”後,程式從最深層的遞迴函式開始逐層返回,匯聚每一層的結果。 + +而從實現的角度看,遞迴程式碼主要包含三個要素。 + +1. **終止條件**:用於決定什麼時候由“遞”轉“迴”。 +2. **遞迴呼叫**:對應“遞”,函式呼叫自身,通常輸入更小或更簡化的參數。 +3. **返回結果**:對應“迴”,將當前遞迴層級的結果返回至上一層。 + +觀察以下程式碼,我們只需呼叫函式 `recur(n)` ,就可以完成 $1 + 2 + \dots + n$ 的計算: + +```src +[file]{recursion}-[class]{}-[func]{recur} +``` + +下圖展示了該函式的遞迴過程。 + +![求和函式的遞迴過程](iteration_and_recursion.assets/recursion_sum.png) + +雖然從計算角度看,迭代與遞迴可以得到相同的結果,**但它們代表了兩種完全不同的思考和解決問題的範式**。 + +- **迭代**:“自下而上”地解決問題。從最基礎的步驟開始,然後不斷重複或累加這些步驟,直到任務完成。 +- **遞迴**:“自上而下”地解決問題。將原問題分解為更小的子問題,這些子問題和原問題具有相同的形式。接下來將子問題繼續分解為更小的子問題,直到基本情況時停止(基本情況的解是已知的)。 + +以上述求和函式為例,設問題 $f(n) = 1 + 2 + \dots + n$ 。 + +- **迭代**:在迴圈中模擬求和過程,從 $1$ 走訪到 $n$ ,每輪執行求和操作,即可求得 $f(n)$ 。 +- **遞迴**:將問題分解為子問題 $f(n) = n + f(n-1)$ ,不斷(遞迴地)分解下去,直至基本情況 $f(1) = 1$ 時終止。 + +### 呼叫堆疊 + +遞迴函式每次呼叫自身時,系統都會為新開啟的函式分配記憶體,以儲存區域性變數、呼叫位址和其他資訊等。這將導致兩方面的結果。 + +- 函式的上下文資料都儲存在稱為“堆疊幀空間”的記憶體區域中,直至函式返回後才會被釋放。因此,**遞迴通常比迭代更加耗費記憶體空間**。 +- 遞迴呼叫函式會產生額外的開銷。**因此遞迴通常比迴圈的時間效率更低**。 + +如下圖所示,在觸發終止條件前,同時存在 $n$ 個未返回的遞迴函式,**遞迴深度為 $n$** 。 + +![遞迴呼叫深度](iteration_and_recursion.assets/recursion_sum_depth.png) + +在實際中,程式語言允許的遞迴深度通常是有限的,過深的遞迴可能導致堆疊溢位錯誤。 + +### 尾遞迴 + +有趣的是,**如果函式在返回前的最後一步才進行遞迴呼叫**,則該函式可以被編譯器或直譯器最佳化,使其在空間效率上與迭代相當。這種情況被稱為尾遞迴(tail recursion)。 + +- **普通遞迴**:當函式返回到上一層級的函式後,需要繼續執行程式碼,因此系統需要儲存上一層呼叫的上下文。 +- **尾遞迴**:遞迴呼叫是函式返回前的最後一個操作,這意味著函式返回到上一層級後,無須繼續執行其他操作,因此系統無須儲存上一層函式的上下文。 + +以計算 $1 + 2 + \dots + n$ 為例,我們可以將結果變數 `res` 設為函式參數,從而實現尾遞迴: + +```src +[file]{recursion}-[class]{}-[func]{tail_recur} +``` + +尾遞迴的執行過程如下圖所示。對比普通遞迴和尾遞迴,兩者的求和操作的執行點是不同的。 + +- **普通遞迴**:求和操作是在“迴”的過程中執行的,每層返回後都要再執行一次求和操作。 +- **尾遞迴**:求和操作是在“遞”的過程中執行的,“迴”的過程只需層層返回。 + +![尾遞迴過程](iteration_and_recursion.assets/tail_recursion_sum.png) + +!!! tip + + 請注意,許多編譯器或直譯器並不支持尾遞迴最佳化。例如,Python 預設不支持尾遞迴最佳化,因此即使函式是尾遞迴形式,仍然可能會遇到堆疊溢位問題。 + +### 遞迴樹 + +當處理與“分治”相關的演算法問題時,遞迴往往比迭代的思路更加直觀、程式碼更加易讀。以“費波那契數列”為例。 + +!!! question + + 給定一個費波那契數列 $0, 1, 1, 2, 3, 5, 8, 13, \dots$ ,求該數列的第 $n$ 個數字。 + +設費波那契數列的第 $n$ 個數字為 $f(n)$ ,易得兩個結論。 + +- 數列的前兩個數字為 $f(1) = 0$ 和 $f(2) = 1$ 。 +- 數列中的每個數字是前兩個數字的和,即 $f(n) = f(n - 1) + f(n - 2)$ 。 + +按照遞推關係進行遞迴呼叫,將前兩個數字作為終止條件,便可寫出遞迴程式碼。呼叫 `fib(n)` 即可得到費波那契數列的第 $n$ 個數字: + +```src +[file]{recursion}-[class]{}-[func]{fib} +``` + +觀察以上程式碼,我們在函式內遞迴呼叫了兩個函式,**這意味著從一個呼叫產生了兩個呼叫分支**。如下圖所示,這樣不斷遞迴呼叫下去,最終將產生一棵層數為 $n$ 的遞迴樹(recursion tree)。 + +![費波那契數列的遞迴樹](iteration_and_recursion.assets/recursion_tree.png) + +從本質上看,遞迴體現了“將問題分解為更小子問題”的思維範式,這種分治策略至關重要。 + +- 從演算法角度看,搜尋、排序、回溯、分治、動態規劃等許多重要演算法策略直接或間接地應用了這種思維方式。 +- 從資料結構角度看,遞迴天然適合處理鏈結串列、樹和圖的相關問題,因為它們非常適合用分治思想進行分析。 + +## 兩者對比 + +總結以上內容,如下表所示,迭代和遞迴在實現、效能和適用性上有所不同。 + +

  迭代與遞迴特點對比

+ +| | 迭代 | 遞迴 | +| -------- | -------------------------------------- | ------------------------------------------------------------ | +| 實現方式 | 迴圈結構 | 函式呼叫自身 | +| 時間效率 | 效率通常較高,無函式呼叫開銷 | 每次函式呼叫都會產生開銷 | +| 記憶體使用 | 通常使用固定大小的記憶體空間 | 累積函式呼叫可能使用大量的堆疊幀空間 | +| 適用問題 | 適用於簡單迴圈任務,程式碼直觀、可讀性好 | 適用於子問題分解,如樹、圖、分治、回溯等,程式碼結構簡潔、清晰 | + +!!! tip + + 如果感覺以下內容理解困難,可以在讀完“堆疊”章節後再來複習。 + +那麼,迭代和遞迴具有什麼內在關聯呢?以上述遞迴函式為例,求和操作在遞迴的“迴”階段進行。這意味著最初被呼叫的函式實際上是最後完成其求和操作的,**這種工作機制與堆疊的“先入後出”原則異曲同工**。 + +事實上,“呼叫堆疊”和“堆疊幀空間”這類遞迴術語已經暗示了遞迴與堆疊之間的密切關係。 + +1. **遞**:當函式被呼叫時,系統會在“呼叫堆疊”上為該函式分配新的堆疊幀,用於儲存函式的區域性變數、參數、返回位址等資料。 +2. **迴**:當函式完成執行並返回時,對應的堆疊幀會被從“呼叫堆疊”上移除,恢復之前函式的執行環境。 + +因此,**我們可以使用一個顯式的堆疊來模擬呼叫堆疊的行為**,從而將遞迴轉化為迭代形式: + +```src +[file]{recursion}-[class]{}-[func]{for_loop_recur} +``` + +觀察以上程式碼,當遞迴轉化為迭代後,程式碼變得更加複雜了。儘管迭代和遞迴在很多情況下可以互相轉化,但不一定值得這樣做,有以下兩點原因。 + +- 轉化後的程式碼可能更加難以理解,可讀性更差。 +- 對於某些複雜問題,模擬系統呼叫堆疊的行為可能非常困難。 + +總之,**選擇迭代還是遞迴取決於特定問題的性質**。在程式設計實踐中,權衡兩者的優劣並根據情境選擇合適的方法至關重要。 diff --git a/zh-hant/docs/chapter_computational_complexity/performance_evaluation.md b/zh-hant/docs/chapter_computational_complexity/performance_evaluation.md new file mode 100644 index 000000000..a32d8fe8f --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/performance_evaluation.md @@ -0,0 +1,48 @@ +# 演算法效率評估 + +在演算法設計中,我們先後追求以下兩個層面的目標。 + +1. **找到問題解法**:演算法需要在規定的輸入範圍內可靠地求得問題的正確解。 +2. **尋求最優解法**:同一個問題可能存在多種解法,我們希望找到儘可能高效的演算法。 + +也就是說,在能夠解決問題的前提下,演算法效率已成為衡量演算法優劣的主要評價指標,它包括以下兩個維度。 + +- **時間效率**:演算法執行速度的快慢。 +- **空間效率**:演算法佔用記憶體空間的大小。 + +簡而言之,**我們的目標是設計“既快又省”的資料結構與演算法**。而有效地評估演算法效率至關重要,因為只有這樣,我們才能將各種演算法進行對比,進而指導演算法設計與最佳化過程。 + +效率評估方法主要分為兩種:實際測試、理論估算。 + +## 實際測試 + +假設我們現在有演算法 `A` 和演算法 `B` ,它們都能解決同一問題,現在需要對比這兩個演算法的效率。最直接的方法是找一臺計算機,執行這兩個演算法,並監控記錄它們的執行時間和記憶體佔用情況。這種評估方式能夠反映真實情況,但也存在較大的侷限性。 + +一方面,**難以排除測試環境的干擾因素**。硬體配置會影響演算法的效能。比如在某臺計算機中,演算法 `A` 的執行時間比演算法 `B` 短;但在另一臺配置不同的計算機中,可能得到相反的測試結果。這意味著我們需要在各種機器上進行測試,統計平均效率,而這是不現實的。 + +另一方面,**展開完整測試非常耗費資源**。隨著輸入資料量的變化,演算法會表現出不同的效率。例如,在輸入資料量較小時,演算法 `A` 的執行時間比演算法 `B` 短;而在輸入資料量較大時,測試結果可能恰恰相反。因此,為了得到有說服力的結論,我們需要測試各種規模的輸入資料,而這需要耗費大量的計算資源。 + +## 理論估算 + +由於實際測試具有較大的侷限性,因此我們可以考慮僅透過一些計算來評估演算法的效率。這種估算方法被稱為漸近複雜度分析(asymptotic complexity analysis),簡稱複雜度分析。 + +複雜度分析能夠體現演算法執行所需的時間和空間資源與輸入資料大小之間的關係。**它描述了隨著輸入資料大小的增加,演算法執行所需時間和空間的增長趨勢**。這個定義有些拗口,我們可以將其分為三個重點來理解。 + +- “時間和空間資源”分別對應時間複雜度(time complexity)空間複雜度(space complexity)。 +- “隨著輸入資料大小的增加”意味著複雜度反映了演算法執行效率與輸入資料體量之間的關係。 +- “時間和空間的增長趨勢”表示複雜度分析關注的不是執行時間或佔用空間的具體值,而是時間或空間增長的“快慢”。 + +**複雜度分析克服了實際測試方法的弊端**,體現在以下兩個方面。 + +- 它獨立於測試環境,分析結果適用於所有執行平臺。 +- 它可以體現不同資料量下的演算法效率,尤其是在大資料量下的演算法效能。 + +!!! tip + + 如果你仍對複雜度的概念感到困惑,無須擔心,我們會在後續章節中詳細介紹。 + +複雜度分析為我們提供了一把評估演算法效率的“標尺”,使我們可以衡量執行某個演算法所需的時間和空間資源,對比不同演算法之間的效率。 + +複雜度是個數學概念,對於初學者可能比較抽象,學習難度相對較高。從這個角度看,複雜度分析可能不太適合作為最先介紹的內容。然而,當我們討論某個資料結構或演算法的特點時,難以避免要分析其執行速度和空間使用情況。 + +綜上所述,建議你在深入學習資料結構與演算法之前,**先對複雜度分析建立初步的瞭解,以便能夠完成簡單演算法的複雜度分析**。 diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png new file mode 100644 index 000000000..dd29f93ad Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png new file mode 100644 index 000000000..8699eec55 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png new file mode 100644 index 000000000..7549ecaa4 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png new file mode 100644 index 000000000..28cfa7643 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_types.png b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_types.png new file mode 100644 index 000000000..240af5b6e Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/space_complexity.assets/space_types.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/space_complexity.md b/zh-hant/docs/chapter_computational_complexity/space_complexity.md new file mode 100755 index 000000000..ee4d3d7f5 --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/space_complexity.md @@ -0,0 +1,898 @@ +# 空間複雜度 + +空間複雜度(space complexity)用於衡量演算法佔用記憶體空間隨著資料量變大時的增長趨勢。這個概念與時間複雜度非常類似,只需將“執行時間”替換為“佔用記憶體空間”。 + +## 演算法相關空間 + +演算法在執行過程中使用的記憶體空間主要包括以下幾種。 + +- **輸入空間**:用於儲存演算法的輸入資料。 +- **暫存空間**:用於儲存演算法在執行過程中的變數、物件、函式上下文等資料。 +- **輸出空間**:用於儲存演算法的輸出資料。 + +一般情況下,空間複雜度的統計範圍是“暫存空間”加上“輸出空間”。 + +暫存空間可以進一步劃分為三個部分。 + +- **暫存資料**:用於儲存演算法執行過程中的各種常數、變數、物件等。 +- **堆疊幀空間**:用於儲存呼叫函式的上下文資料。系統在每次呼叫函式時都會在堆疊頂部建立一個堆疊幀,函式返回後,堆疊幀空間會被釋放。 +- **指令空間**:用於儲存編譯後的程式指令,在實際統計中通常忽略不計。 + +在分析一段程式的空間複雜度時,**我們通常統計暫存資料、堆疊幀空間和輸出資料三部分**,如下圖所示。 + +![演算法使用的相關空間](space_complexity.assets/space_types.png) + +相關程式碼如下: + +=== "Python" + + ```python title="" + class Node: + """類別""" + def __init__(self, x: int): + self.val: int = x # 節點值 + self.next: Node | None = None # 指向下一節點的引用 + + def function() -> int: + """函式""" + # 執行某些操作... + return 0 + + def algorithm(n) -> int: # 輸入資料 + A = 0 # 暫存資料(常數,一般用大寫字母表示) + b = 0 # 暫存資料(變數) + node = Node(0) # 暫存資料(物件) + c = function() # 堆疊幀空間(呼叫函式) + return A + b + c # 輸出資料 + ``` + +=== "C++" + + ```cpp title="" + /* 結構體 */ + struct Node { + int val; + Node *next; + Node(int x) : val(x), next(nullptr) {} + }; + + /* 函式 */ + int func() { + // 執行某些操作... + return 0; + } + + int algorithm(int n) { // 輸入資料 + const int a = 0; // 暫存資料(常數) + int b = 0; // 暫存資料(變數) + Node* node = new Node(0); // 暫存資料(物件) + int c = func(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "Java" + + ```java title="" + /* 類別 */ + class Node { + int val; + Node next; + Node(int x) { val = x; } + } + + /* 函式 */ + int function() { + // 執行某些操作... + return 0; + } + + int algorithm(int n) { // 輸入資料 + final int a = 0; // 暫存資料(常數) + int b = 0; // 暫存資料(變數) + Node node = new Node(0); // 暫存資料(物件) + int c = function(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "C#" + + ```csharp title="" + /* 類別 */ + class Node(int x) { + int val = x; + Node next; + } + + /* 函式 */ + int Function() { + // 執行某些操作... + return 0; + } + + int Algorithm(int n) { // 輸入資料 + const int a = 0; // 暫存資料(常數) + int b = 0; // 暫存資料(變數) + Node node = new(0); // 暫存資料(物件) + int c = Function(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "Go" + + ```go title="" + /* 結構體 */ + type node struct { + val int + next *node + } + + /* 建立 node 結構體 */ + func newNode(val int) *node { + return &node{val: val} + } + + /* 函式 */ + func function() int { + // 執行某些操作... + return 0 + } + + func algorithm(n int) int { // 輸入資料 + const a = 0 // 暫存資料(常數) + b := 0 // 暫存資料(變數) + newNode(0) // 暫存資料(物件) + c := function() // 堆疊幀空間(呼叫函式) + return a + b + c // 輸出資料 + } + ``` + +=== "Swift" + + ```swift title="" + /* 類別 */ + class Node { + var val: Int + var next: Node? + + init(x: Int) { + val = x + } + } + + /* 函式 */ + func function() -> Int { + // 執行某些操作... + return 0 + } + + func algorithm(n: Int) -> Int { // 輸入資料 + let a = 0 // 暫存資料(常數) + var b = 0 // 暫存資料(變數) + let node = Node(x: 0) // 暫存資料(物件) + let c = function() // 堆疊幀空間(呼叫函式) + return a + b + c // 輸出資料 + } + ``` + +=== "JS" + + ```javascript title="" + /* 類別 */ + class Node { + val; + next; + constructor(val) { + this.val = val === undefined ? 0 : val; // 節點值 + this.next = null; // 指向下一節點的引用 + } + } + + /* 函式 */ + function constFunc() { + // 執行某些操作 + return 0; + } + + function algorithm(n) { // 輸入資料 + const a = 0; // 暫存資料(常數) + let b = 0; // 暫存資料(變數) + const node = new Node(0); // 暫存資料(物件) + const c = constFunc(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "TS" + + ```typescript title="" + /* 類別 */ + class Node { + val: number; + next: Node | null; + constructor(val?: number) { + this.val = val === undefined ? 0 : val; // 節點值 + this.next = null; // 指向下一節點的引用 + } + } + + /* 函式 */ + function constFunc(): number { + // 執行某些操作 + return 0; + } + + function algorithm(n: number): number { // 輸入資料 + const a = 0; // 暫存資料(常數) + let b = 0; // 暫存資料(變數) + const node = new Node(0); // 暫存資料(物件) + const c = constFunc(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "Dart" + + ```dart title="" + /* 類別 */ + class Node { + int val; + Node next; + Node(this.val, [this.next]); + } + + /* 函式 */ + int function() { + // 執行某些操作... + return 0; + } + + int algorithm(int n) { // 輸入資料 + const int a = 0; // 暫存資料(常數) + int b = 0; // 暫存資料(變數) + Node node = Node(0); // 暫存資料(物件) + int c = function(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* 結構體 */ + struct Node { + val: i32, + next: Option>>, + } + + /* 建立 Node 結構體 */ + impl Node { + fn new(val: i32) -> Self { + Self { val: val, next: None } + } + } + + /* 函式 */ + fn function() -> i32 { + // 執行某些操作... + return 0; + } + + fn algorithm(n: i32) -> i32 { // 輸入資料 + const a: i32 = 0; // 暫存資料(常數) + let mut b = 0; // 暫存資料(變數) + let node = Node::new(0); // 暫存資料(物件) + let c = function(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "C" + + ```c title="" + /* 函式 */ + int func() { + // 執行某些操作... + return 0; + } + + int algorithm(int n) { // 輸入資料 + const int a = 0; // 暫存資料(常數) + int b = 0; // 暫存資料(變數) + int c = func(); // 堆疊幀空間(呼叫函式) + return a + b + c; // 輸出資料 + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 類別 */ + class Node(var _val: Int) { + var next: Node? = null + } + + /* 函式 */ + fun function(): Int { + // 執行某些操作... + return 0 + } + + fun algorithm(n: Int): Int { // 輸入資料 + val a = 0 // 暫存資料(常數) + var b = 0 // 暫存資料(變數) + val node = Node(0) // 暫存資料(物件) + val c = function() // 堆疊幀空間(呼叫函式) + return a + b + c // 輸出資料 + } + ``` + +=== "Ruby" + + ```ruby title="" + ### 類別 ### + class Node + attr_accessor :val # 節點值 + attr_accessor :next # 指向下一節點的引用 + + def initialize(x) + @val = x + end + end + + ### 函式 ### + def function + # 執行某些操作... + 0 + end + + ### 演算法 ### + def algorithm(n) # 輸入資料 + a = 0 # 暫存資料(常數) + b = 0 # 暫存資料(變數) + node = Node.new(0) # 暫存資料(物件) + c = function # 堆疊幀空間(呼叫函式) + a + b + c # 輸出資料 + end + ``` + +=== "Zig" + + ```zig title="" + + ``` + +## 推算方法 + +空間複雜度的推算方法與時間複雜度大致相同,只需將統計物件從“操作數量”轉為“使用空間大小”。 + +而與時間複雜度不同的是,**我們通常只關注最差空間複雜度**。這是因為記憶體空間是一項硬性要求,我們必須確保在所有輸入資料下都有足夠的記憶體空間預留。 + +觀察以下程式碼,最差空間複雜度中的“最差”有兩層含義。 + +1. **以最差輸入資料為準**:當 $n < 10$ 時,空間複雜度為 $O(1)$ ;但當 $n > 10$ 時,初始化的陣列 `nums` 佔用 $O(n)$ 空間,因此最差空間複雜度為 $O(n)$ 。 +2. **以演算法執行中的峰值記憶體為準**:例如,程式在執行最後一行之前,佔用 $O(1)$ 空間;當初始化陣列 `nums` 時,程式佔用 $O(n)$ 空間,因此最差空間複雜度為 $O(n)$ 。 + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 0 # O(1) + b = [0] * 10000 # O(1) + if n > 10: + nums = [0] * n # O(n) + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 0; // O(1) + vector b(10000); // O(1) + if (n > 10) + vector nums(n); // O(n) + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) + int[] nums = new int[n]; // O(n) + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) { + int[] nums = new int[n]; // O(n) + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 0 // O(1) + b := make([]int, 10000) // O(1) + var nums []int + if n > 10 { + nums := make([]int, n) // O(n) + } + fmt.Println(a, b, nums) + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + let a = 0 // O(1) + let b = Array(repeating: 0, count: 10000) // O(1) + if n > 10 { + let nums = Array(repeating: 0, count: n) // O(n) + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + const a = 0; // O(1) + const b = new Array(10000); // O(1) + if (n > 10) { + const nums = new Array(n); // O(n) + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void { + const a = 0; // O(1) + const b = new Array(10000); // O(1) + if (n > 10) { + const nums = new Array(n); // O(n) + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 0; // O(1) + List b = List.filled(10000, 0); // O(1) + if (n > 10) { + List nums = List.filled(n, 0); // O(n) + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let a = 0; // O(1) + let b = [0; 10000]; // O(1) + if n > 10 { + let nums = vec![0; n as usize]; // O(n) + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 0; // O(1) + int b[10000]; // O(1) + if (n > 10) + int nums[n] = {0}; // O(n) + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun algorithm(n: Int) { + val a = 0 // O(1) + val b = IntArray(10000) // O(1) + if (n > 10) { + val nums = IntArray(n) // O(n) + } + } + ``` + +=== "Ruby" + + ```ruby title="" + def algorithm(n) + a = 0 # O(1) + b = Array.new(10000) # O(1) + nums = Array.new(n) if n > 10 # O(n) + end + ``` + +=== "Zig" + + ```zig title="" + + ``` + +**在遞迴函式中,需要注意統計堆疊幀空間**。觀察以下程式碼: + +=== "Python" + + ```python title="" + def function() -> int: + # 執行某些操作 + return 0 + + def loop(n: int): + """迴圈的空間複雜度為 O(1)""" + for _ in range(n): + function() + + def recur(n: int): + """遞迴的空間複雜度為 O(n)""" + if n == 1: + return + return recur(n - 1) + ``` + +=== "C++" + + ```cpp title="" + int func() { + // 執行某些操作 + return 0; + } + /* 迴圈 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + func(); + } + } + /* 遞迴 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "Java" + + ```java title="" + int function() { + // 執行某些操作 + return 0; + } + /* 迴圈 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } + } + /* 遞迴 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "C#" + + ```csharp title="" + int Function() { + // 執行某些操作 + return 0; + } + /* 迴圈 O(1) */ + void Loop(int n) { + for (int i = 0; i < n; i++) { + Function(); + } + } + /* 遞迴 O(n) */ + int Recur(int n) { + if (n == 1) return 1; + return Recur(n - 1); + } + ``` + +=== "Go" + + ```go title="" + func function() int { + // 執行某些操作 + return 0 + } + + /* 迴圈 O(1) */ + func loop(n int) { + for i := 0; i < n; i++ { + function() + } + } + + /* 遞迴 O(n) */ + func recur(n int) { + if n == 1 { + return + } + recur(n - 1) + } + ``` + +=== "Swift" + + ```swift title="" + @discardableResult + func function() -> Int { + // 執行某些操作 + return 0 + } + + /* 迴圈 O(1) */ + func loop(n: Int) { + for _ in 0 ..< n { + function() + } + } + + /* 遞迴 O(n) */ + func recur(n: Int) { + if n == 1 { + return + } + recur(n: n - 1) + } + ``` + +=== "JS" + + ```javascript title="" + function constFunc() { + // 執行某些操作 + return 0; + } + /* 迴圈 O(1) */ + function loop(n) { + for (let i = 0; i < n; i++) { + constFunc(); + } + } + /* 遞迴 O(n) */ + function recur(n) { + if (n === 1) return; + return recur(n - 1); + } + ``` + +=== "TS" + + ```typescript title="" + function constFunc(): number { + // 執行某些操作 + return 0; + } + /* 迴圈 O(1) */ + function loop(n: number): void { + for (let i = 0; i < n; i++) { + constFunc(); + } + } + /* 遞迴 O(n) */ + function recur(n: number): void { + if (n === 1) return; + return recur(n - 1); + } + ``` + +=== "Dart" + + ```dart title="" + int function() { + // 執行某些操作 + return 0; + } + /* 迴圈 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + function(); + } + } + /* 遞迴 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "Rust" + + ```rust title="" + fn function() -> i32 { + // 執行某些操作 + return 0; + } + /* 迴圈 O(1) */ + fn loop(n: i32) { + for i in 0..n { + function(); + } + } + /* 遞迴 O(n) */ + fn recur(n: i32) { + if n == 1 { + return; + } + recur(n - 1); + } + ``` + +=== "C" + + ```c title="" + int func() { + // 執行某些操作 + return 0; + } + /* 迴圈 O(1) */ + void loop(int n) { + for (int i = 0; i < n; i++) { + func(); + } + } + /* 遞迴 O(n) */ + void recur(int n) { + if (n == 1) return; + return recur(n - 1); + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun function(): Int { + // 執行某些操作 + return 0 + } + /* 迴圈 O(1) */ + fun loop(n: Int) { + for (i in 0..函式(function)可以被獨立執行,所有參數都以顯式傳遞。方法(method)與一個物件關聯,被隱式傳遞給呼叫它的物件,能夠對類別的例項中包含的資料進行操作。 + +下面以幾種常見的程式語言為例來說明。 + +- C 語言是程序式程式設計語言,沒有物件導向的概念,所以只有函式。但我們可以透過建立結構體(struct)來模擬物件導向程式設計,與結構體相關聯的函式就相當於其他程式語言中的方法。 +- Java 和 C# 是物件導向的程式語言,程式碼塊(方法)通常作為某個類別的一部分。靜態方法的行為類似於函式,因為它被繫結在類別上,不能訪問特定的例項變數。 +- C++ 和 Python 既支持程序式程式設計(函式),也支持物件導向程式設計(方法)。 + +**Q**:圖解“常見的空間複雜度型別”反映的是否是佔用空間的絕對大小? + +不是,該圖展示的是空間複雜度,其反映的是增長趨勢,而不是佔用空間的絕對大小。 + +假設取 $n = 8$ ,你可能會發現每條曲線的值與函式對應不上。這是因為每條曲線都包含一個常數項,用於將取值範圍壓縮到一個視覺舒適的範圍內。 + +在實際中,因為我們通常不知道每個方法的“常數項”複雜度是多少,所以一般無法僅憑複雜度來選擇 $n = 8$ 之下的最優解法。但對於 $n = 8^5$ 就很好選了,這時增長趨勢已經佔主導了。 diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png new file mode 100644 index 000000000..530910e61 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png new file mode 100644 index 000000000..5d051f8af Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png new file mode 100644 index 000000000..e383a084a Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png new file mode 100644 index 000000000..ad4ee89dc Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png new file mode 100644 index 000000000..f9d2b2b5e Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png new file mode 100644 index 000000000..c449093f7 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png new file mode 100644 index 000000000..eb497bb68 Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png new file mode 100644 index 000000000..f39ab16bf Binary files /dev/null and b/zh-hant/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png differ diff --git a/zh-hant/docs/chapter_computational_complexity/time_complexity.md b/zh-hant/docs/chapter_computational_complexity/time_complexity.md new file mode 100755 index 000000000..550456576 --- /dev/null +++ b/zh-hant/docs/chapter_computational_complexity/time_complexity.md @@ -0,0 +1,1224 @@ +# 時間複雜度 + +執行時間可以直觀且準確地反映演算法的效率。如果我們想準確預估一段程式碼的執行時間,應該如何操作呢? + +1. **確定執行平臺**,包括硬體配置、程式語言、系統環境等,這些因素都會影響程式碼的執行效率。 +2. **評估各種計算操作所需的執行時間**,例如加法操作 `+` 需要 1 ns ,乘法操作 `*` 需要 10 ns ,列印操作 `print()` 需要 5 ns 等。 +3. **統計程式碼中所有的計算操作**,並將所有操作的執行時間求和,從而得到執行時間。 + +例如在以下程式碼中,輸入資料大小為 $n$ : + +=== "Python" + + ```python title="" + # 在某執行平臺下 + def algorithm(n: int): + a = 2 # 1 ns + a = a + 1 # 1 ns + a = a * 2 # 10 ns + # 迴圈 n 次 + for _ in range(n): # 1 ns + print(0) # 5 ns + ``` + +=== "C++" + + ```cpp title="" + // 在某執行平臺下 + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // 1 ns ,每輪都要執行 i++ + cout << 0 << endl; // 5 ns + } + } + ``` + +=== "Java" + + ```java title="" + // 在某執行平臺下 + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // 1 ns ,每輪都要執行 i++ + System.out.println(0); // 5 ns + } + } + ``` + +=== "C#" + + ```csharp title="" + // 在某執行平臺下 + void Algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // 1 ns ,每輪都要執行 i++ + Console.WriteLine(0); // 5 ns + } + } + ``` + +=== "Go" + + ```go title="" + // 在某執行平臺下 + func algorithm(n int) { + a := 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns + // 迴圈 n 次 + for i := 0; i < n; i++ { // 1 ns + fmt.Println(a) // 5 ns + } + } + ``` + +=== "Swift" + + ```swift title="" + // 在某執行平臺下 + func algorithm(n: Int) { + var a = 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns + // 迴圈 n 次 + for _ in 0 ..< n { // 1 ns + print(0) // 5 ns + } + } + ``` + +=== "JS" + + ```javascript title="" + // 在某執行平臺下 + function algorithm(n) { + var a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for(let i = 0; i < n; i++) { // 1 ns ,每輪都要執行 i++ + console.log(0); // 5 ns + } + } + ``` + +=== "TS" + + ```typescript title="" + // 在某執行平臺下 + function algorithm(n: number): void { + var a: number = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for(let i = 0; i < n; i++) { // 1 ns ,每輪都要執行 i++ + console.log(0); // 5 ns + } + } + ``` + +=== "Dart" + + ```dart title="" + // 在某執行平臺下 + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // 1 ns ,每輪都要執行 i++ + print(0); // 5 ns + } + } + ``` + +=== "Rust" + + ```rust title="" + // 在某執行平臺下 + fn algorithm(n: i32) { + let mut a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for _ in 0..n { // 1 ns ,每輪都要執行 i++ + println!("{}", 0); // 5 ns + } + } + ``` + +=== "C" + + ```c title="" + // 在某執行平臺下 + void algorithm(int n) { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // 1 ns ,每輪都要執行 i++ + printf("%d", 0); // 5 ns + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + // 在某執行平臺下 + fun algorithm(n: Int) { + var a = 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns + // 迴圈 n 次 + for (i in 0.. 1$ 時比演算法 `A` 更慢,在 $n > 1000000$ 時比演算法 `C` 更慢。事實上,只要輸入資料大小 $n$ 足夠大,複雜度為“常數階”的演算法一定優於“線性階”的演算法,這正是時間增長趨勢的含義。 +- **時間複雜度的推算方法更簡便**。顯然,執行平臺和計算操作型別都與演算法執行時間的增長趨勢無關。因此在時間複雜度分析中,我們可以簡單地將所有計算操作的執行時間視為相同的“單位時間”,從而將“計算操作執行時間統計”簡化為“計算操作數量統計”,這樣一來估算難度就大大降低了。 +- **時間複雜度也存在一定的侷限性**。例如,儘管演算法 `A` 和 `C` 的時間複雜度相同,但實際執行時間差別很大。同樣,儘管演算法 `B` 的時間複雜度比 `C` 高,但在輸入資料大小 $n$ 較小時,演算法 `B` 明顯優於演算法 `C` 。在這些情況下,我們很難僅憑時間複雜度判斷演算法效率的高低。當然,儘管存在上述問題,複雜度分析仍然是評判演算法效率最有效且常用的方法。 + +## 函式漸近上界 + +給定一個輸入大小為 $n$ 的函式: + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 1 # +1 + a = a + 1 # +1 + a = a * 2 # +1 + # 迴圈 n 次 + for i in range(n): # +1 + print(0) # +1 + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) + cout << 0 << endl; // +1 + } + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) + System.out.println(0); // +1 + } + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) + Console.WriteLine(0); // +1 + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // 迴圈 n 次 + for i := 0; i < n; i++ { // +1 + fmt.Println(a) // +1 + } + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + var a = 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // 迴圈 n 次 + for _ in 0 ..< n { // +1 + print(0) // +1 + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + var a = 1; // +1 + a += 1; // +1 + a *= 2; // +1 + // 迴圈 n 次 + for(let i = 0; i < n; i++){ // +1(每輪都執行 i ++) + console.log(0); // +1 + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void{ + var a: number = 1; // +1 + a += 1; // +1 + a *= 2; // +1 + // 迴圈 n 次 + for(let i = 0; i < n; i++){ // +1(每輪都執行 i ++) + console.log(0); // +1 + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) + print(0); // +1 + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + + // 迴圈 n 次 + for _ in 0..n { // +1(每輪都執行 i ++) + println!("{}", 0); // +1 + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 迴圈 n 次 + for (int i = 0; i < n; i++) { // +1(每輪都執行 i ++) + printf("%d", 0); // +1 + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun algorithm(n: Int) { + var a = 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // 迴圈 n 次 + for (i in 0..大($O$ 記號 big-$O$ notation),表示函式 $T(n)$ 的漸近上界(asymptotic upper bound)。 + +時間複雜度分析本質上是計算“操作數量 $T(n)$”的漸近上界,它具有明確的數學定義。 + +!!! abstract "函式漸近上界" + + 若存在正實數 $c$ 和實數 $n_0$ ,使得對於所有的 $n > n_0$ ,均有 $T(n) \leq c \cdot f(n)$ ,則可認為 $f(n)$ 給出了 $T(n)$ 的一個漸近上界,記為 $T(n) = O(f(n))$ 。 + +如下圖所示,計算漸近上界就是尋找一個函式 $f(n)$ ,使得當 $n$ 趨向於無窮大時,$T(n)$ 和 $f(n)$ 處於相同的增長級別,僅相差一個常數項 $c$ 的倍數。 + +![函式的漸近上界](time_complexity.assets/asymptotic_upper_bound.png) + +## 推算方法 + +漸近上界的數學味兒有點重,如果你感覺沒有完全理解,也無須擔心。我們可以先掌握推算方法,在不斷的實踐中,就可以逐漸領悟其數學意義。 + +根據定義,確定 $f(n)$ 之後,我們便可得到時間複雜度 $O(f(n))$ 。那麼如何確定漸近上界 $f(n)$ 呢?總體分為兩步:首先統計操作數量,然後判斷漸近上界。 + +### 第一步:統計操作數量 + +針對程式碼,逐行從上到下計算即可。然而,由於上述 $c \cdot f(n)$ 中的常數項 $c$ 可以取任意大小,**因此操作數量 $T(n)$ 中的各種係數、常數項都可以忽略**。根據此原則,可以總結出以下計數簡化技巧。 + +1. **忽略 $T(n)$ 中的常數項**。因為它們都與 $n$ 無關,所以對時間複雜度不產生影響。 +2. **省略所有係數**。例如,迴圈 $2n$ 次、$5n + 1$ 次等,都可以簡化記為 $n$ 次,因為 $n$ 前面的係數對時間複雜度沒有影響。 +3. **迴圈巢狀時使用乘法**。總操作數量等於外層迴圈和內層迴圈操作數量之積,每一層迴圈依然可以分別套用第 `1.` 點和第 `2.` 點的技巧。 + +給定一個函式,我們可以用上述技巧來統計操作數量: + +=== "Python" + + ```python title="" + def algorithm(n: int): + a = 1 # +0(技巧 1) + a = a + n # +0(技巧 1) + # +n(技巧 2) + for i in range(5 * n + 1): + print(0) + # +n*n(技巧 3) + for i in range(2 * n): + for j in range(n + 1): + print(0) + ``` + +=== "C++" + + ```cpp title="" + void algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + cout << 0 << endl; + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + cout << 0 << endl; + } + } + } + ``` + +=== "Java" + + ```java title="" + void algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + System.out.println(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + System.out.println(0); + } + } + } + ``` + +=== "C#" + + ```csharp title="" + void Algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + Console.WriteLine(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + Console.WriteLine(0); + } + } + } + ``` + +=== "Go" + + ```go title="" + func algorithm(n int) { + a := 1 // +0(技巧 1) + a = a + n // +0(技巧 1) + // +n(技巧 2) + for i := 0; i < 5 * n + 1; i++ { + fmt.Println(0) + } + // +n*n(技巧 3) + for i := 0; i < 2 * n; i++ { + for j := 0; j < n + 1; j++ { + fmt.Println(0) + } + } + } + ``` + +=== "Swift" + + ```swift title="" + func algorithm(n: Int) { + var a = 1 // +0(技巧 1) + a = a + n // +0(技巧 1) + // +n(技巧 2) + for _ in 0 ..< (5 * n + 1) { + print(0) + } + // +n*n(技巧 3) + for _ in 0 ..< (2 * n) { + for _ in 0 ..< (n + 1) { + print(0) + } + } + } + ``` + +=== "JS" + + ```javascript title="" + function algorithm(n) { + let a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (let i = 0; i < 5 * n + 1; i++) { + console.log(0); + } + // +n*n(技巧 3) + for (let i = 0; i < 2 * n; i++) { + for (let j = 0; j < n + 1; j++) { + console.log(0); + } + } + } + ``` + +=== "TS" + + ```typescript title="" + function algorithm(n: number): void { + let a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (let i = 0; i < 5 * n + 1; i++) { + console.log(0); + } + // +n*n(技巧 3) + for (let i = 0; i < 2 * n; i++) { + for (let j = 0; j < n + 1; j++) { + console.log(0); + } + } + } + ``` + +=== "Dart" + + ```dart title="" + void algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + print(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + print(0); + } + } + } + ``` + +=== "Rust" + + ```rust title="" + fn algorithm(n: i32) { + let mut a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + + // +n(技巧 2) + for i in 0..(5 * n + 1) { + println!("{}", 0); + } + + // +n*n(技巧 3) + for i in 0..(2 * n) { + for j in 0..(n + 1) { + println!("{}", 0); + } + } + } + ``` + +=== "C" + + ```c title="" + void algorithm(int n) { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) { + printf("%d", 0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + printf("%d", 0); + } + } + } + ``` + +=== "Kotlin" + + ```kotlin title="" + fun algorithm(n: Int) { + var a = 1 // +0(技巧 1) + a = a + n // +0(技巧 1) + // +n(技巧 2) + for (i in 0..<5 * n + 1) { + println(0) + } + // +n*n(技巧 3) + for (i in 0..<2 * n) { + for (j in 0..   不同操作數量對應的時間複雜度

+ +| 操作數量 $T(n)$ | 時間複雜度 $O(f(n))$ | +| ---------------------- | -------------------- | +| $100000$ | $O(1)$ | +| $3n + 2$ | $O(n)$ | +| $2n^2 + 3n + 2$ | $O(n^2)$ | +| $n^3 + 10000n^2$ | $O(n^3)$ | +| $2^n + 10000n^{10000}$ | $O(2^n)$ | + +## 常見型別 + +設輸入資料大小為 $n$ ,常見的時間複雜度型別如下圖所示(按照從低到高的順序排列)。 + +$$ +\begin{aligned} +O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline +\text{常數階} < \text{對數階} < \text{線性階} < \text{線性對數階} < \text{平方階} < \text{指數階} < \text{階乘階} +\end{aligned} +$$ + +![常見的時間複雜度型別](time_complexity.assets/time_complexity_common_types.png) + +### 常數階 $O(1)$ + +常數階的操作數量與輸入資料大小 $n$ 無關,即不隨著 $n$ 的變化而變化。 + +在以下函式中,儘管操作數量 `size` 可能很大,但由於其與輸入資料大小 $n$ 無關,因此時間複雜度仍為 $O(1)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{constant} +``` + +### 線性階 $O(n)$ + +線性階的操作數量相對於輸入資料大小 $n$ 以線性級別增長。線性階通常出現在單層迴圈中: + +```src +[file]{time_complexity}-[class]{}-[func]{linear} +``` + +走訪陣列和走訪鏈結串列等操作的時間複雜度均為 $O(n)$ ,其中 $n$ 為陣列或鏈結串列的長度: + +```src +[file]{time_complexity}-[class]{}-[func]{array_traversal} +``` + +值得注意的是,**輸入資料大小 $n$ 需根據輸入資料的型別來具體確定**。比如在第一個示例中,變數 $n$ 為輸入資料大小;在第二個示例中,陣列長度 $n$ 為資料大小。 + +### 平方階 $O(n^2)$ + +平方階的操作數量相對於輸入資料大小 $n$ 以平方級別增長。平方階通常出現在巢狀迴圈中,外層迴圈和內層迴圈的時間複雜度都為 $O(n)$ ,因此總體的時間複雜度為 $O(n^2)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{quadratic} +``` + +下圖對比了常數階、線性階和平方階三種時間複雜度。 + +![常數階、線性階和平方階的時間複雜度](time_complexity.assets/time_complexity_constant_linear_quadratic.png) + +以泡沫排序為例,外層迴圈執行 $n - 1$ 次,內層迴圈執行 $n-1$、$n-2$、$\dots$、$2$、$1$ 次,平均為 $n / 2$ 次,因此時間複雜度為 $O((n - 1) n / 2) = O(n^2)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{bubble_sort} +``` + +### 指數階 $O(2^n)$ + +生物學的“細胞分裂”是指數階增長的典型例子:初始狀態為 $1$ 個細胞,分裂一輪後變為 $2$ 個,分裂兩輪後變為 $4$ 個,以此類推,分裂 $n$ 輪後有 $2^n$ 個細胞。 + +下圖和以下程式碼模擬了細胞分裂的過程,時間複雜度為 $O(2^n)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{exponential} +``` + +![指數階的時間複雜度](time_complexity.assets/time_complexity_exponential.png) + +在實際演算法中,指數階常出現於遞迴函式中。例如在以下程式碼中,其遞迴地一分為二,經過 $n$ 次分裂後停止: + +```src +[file]{time_complexity}-[class]{}-[func]{exp_recur} +``` + +指數階增長非常迅速,在窮舉法(暴力搜尋、回溯等)中比較常見。對於資料規模較大的問題,指數階是不可接受的,通常需要使用動態規劃或貪婪演算法等來解決。 + +### 對數階 $O(\log n)$ + +與指數階相反,對數階反映了“每輪縮減到一半”的情況。設輸入資料大小為 $n$ ,由於每輪縮減到一半,因此迴圈次數是 $\log_2 n$ ,即 $2^n$ 的反函式。 + +下圖和以下程式碼模擬了“每輪縮減到一半”的過程,時間複雜度為 $O(\log_2 n)$ ,簡記為 $O(\log n)$ : + +```src +[file]{time_complexity}-[class]{}-[func]{logarithmic} +``` + +![對數階的時間複雜度](time_complexity.assets/time_complexity_logarithmic.png) + +與指數階類似,對數階也常出現於遞迴函式中。以下程式碼形成了一棵高度為 $\log_2 n$ 的遞迴樹: + +```src +[file]{time_complexity}-[class]{}-[func]{log_recur} +``` + +對數階常出現於基於分治策略的演算法中,體現了“一分為多”和“化繁為簡”的演算法思想。它增長緩慢,是僅次於常數階的理想的時間複雜度。 + +!!! tip "$O(\log n)$ 的底數是多少?" + + 準確來說,“一分為 $m$”對應的時間複雜度是 $O(\log_m n)$ 。而透過對數換底公式,我們可以得到具有不同底數、相等的時間複雜度: + + $$ + O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n) + $$ + + 也就是說,底數 $m$ 可以在不影響複雜度的前提下轉換。因此我們通常會省略底數 $m$ ,將對數階直接記為 $O(\log n)$ 。 + +### 線性對數階 $O(n \log n)$ + +線性對數階常出現於巢狀迴圈中,兩層迴圈的時間複雜度分別為 $O(\log n)$ 和 $O(n)$ 。相關程式碼如下: + +```src +[file]{time_complexity}-[class]{}-[func]{linear_log_recur} +``` + +下圖展示了線性對數階的生成方式。二元樹的每一層的操作總數都為 $n$ ,樹共有 $\log_2 n + 1$ 層,因此時間複雜度為 $O(n \log n)$ 。 + +![線性對數階的時間複雜度](time_complexity.assets/time_complexity_logarithmic_linear.png) + +主流排序演算法的時間複雜度通常為 $O(n \log n)$ ,例如快速排序、合併排序、堆積排序等。 + +### 階乘階 $O(n!)$ + +階乘階對應數學上的“全排列”問題。給定 $n$ 個互不重複的元素,求其所有可能的排列方案,方案數量為: + +$$ +n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 +$$ + +階乘通常使用遞迴實現。如下圖和以下程式碼所示,第一層分裂出 $n$ 個,第二層分裂出 $n - 1$ 個,以此類推,直至第 $n$ 層時停止分裂: + +```src +[file]{time_complexity}-[class]{}-[func]{factorial_recur} +``` + +![階乘階的時間複雜度](time_complexity.assets/time_complexity_factorial.png) + +請注意,因為當 $n \geq 4$ 時恆有 $n! > 2^n$ ,所以階乘階比指數階增長得更快,在 $n$ 較大時也是不可接受的。 + +## 最差、最佳、平均時間複雜度 + +**演算法的時間效率往往不是固定的,而是與輸入資料的分佈有關**。假設輸入一個長度為 $n$ 的陣列 `nums` ,其中 `nums` 由從 $1$ 至 $n$ 的數字組成,每個數字只出現一次;但元素順序是隨機打亂的,任務目標是返回元素 $1$ 的索引。我們可以得出以下結論。 + +- 當 `nums = [?, ?, ..., 1]` ,即當末尾元素是 $1$ 時,需要完整走訪陣列,**達到最差時間複雜度 $O(n)$** 。 +- 當 `nums = [1, ?, ?, ...]` ,即當首個元素為 $1$ 時,無論陣列多長都不需要繼續走訪,**達到最佳時間複雜度 $\Omega(1)$** 。 + +“最差時間複雜度”對應函式漸近上界,使用大 $O$ 記號表示。相應地,“最佳時間複雜度”對應函式漸近下界,用 $\Omega$ 記號表示: + +```src +[file]{worst_best_time_complexity}-[class]{}-[func]{find_one} +``` + +值得說明的是,我們在實際中很少使用最佳時間複雜度,因為通常只有在很小機率下才能達到,可能會帶來一定的誤導性。**而最差時間複雜度更為實用,因為它給出了一個效率安全值**,讓我們可以放心地使用演算法。 + +從上述示例可以看出,最差時間複雜度和最佳時間複雜度只出現於“特殊的資料分佈”,這些情況的出現機率可能很小,並不能真實地反映演算法執行效率。相比之下,**平均時間複雜度可以體現演算法在隨機輸入資料下的執行效率**,用 $\Theta$ 記號來表示。 + +對於部分演算法,我們可以簡單地推算出隨機資料分佈下的平均情況。比如上述示例,由於輸入陣列是被打亂的,因此元素 $1$ 出現在任意索引的機率都是相等的,那麼演算法的平均迴圈次數就是陣列長度的一半 $n / 2$ ,平均時間複雜度為 $\Theta(n / 2) = \Theta(n)$ 。 + +但對於較為複雜的演算法,計算平均時間複雜度往往比較困難,因為很難分析出在資料分佈下的整體數學期望。在這種情況下,我們通常使用最差時間複雜度作為演算法效率的評判標準。 + +!!! question "為什麼很少看到 $\Theta$ 符號?" + + 可能由於 $O$ 符號過於朗朗上口,因此我們常常使用它來表示平均時間複雜度。但從嚴格意義上講,這種做法並不規範。在本書和其他資料中,若遇到類似“平均時間複雜度 $O(n)$”的表述,請將其直接理解為 $\Theta(n)$ 。 diff --git a/zh-hant/docs/chapter_data_structure/basic_data_types.md b/zh-hant/docs/chapter_data_structure/basic_data_types.md new file mode 100644 index 000000000..830f36943 --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/basic_data_types.md @@ -0,0 +1,180 @@ +# 基本資料型別 + +當談及計算機中的資料時,我們會想到文字、圖片、影片、語音、3D 模型等各種形式。儘管這些資料的組織形式各異,但它們都由各種基本資料型別構成。 + +**基本資料型別是 CPU 可以直接進行運算的型別**,在演算法中直接被使用,主要包括以下幾種。 + +- 整數型別 `byte`、`short`、`int`、`long` 。 +- 浮點數型別 `float`、`double` ,用於表示小數。 +- 字元型別 `char` ,用於表示各種語言的字母、標點符號甚至表情符號等。 +- 布林型別 `bool` ,用於表示“是”與“否”判斷。 + +**基本資料型別以二進位制的形式儲存在計算機中**。一個二進位制位即為 $1$ 位元。在絕大多數現代作業系統中,$1$ 位元組(byte)由 $8$ 位元(bit)組成。 + +基本資料型別的取值範圍取決於其佔用的空間大小。下面以 Java 為例。 + +- 整數型別 `byte` 佔用 $1$ 位元組 = $8$ 位元 ,可以表示 $2^{8}$ 個數字。 +- 整數型別 `int` 佔用 $4$ 位元組 = $32$ 位元 ,可以表示 $2^{32}$ 個數字。 + +下表列舉了 Java 中各種基本資料型別的佔用空間、取值範圍和預設值。此表格無須死記硬背,大致理解即可,需要時可以透過查表來回憶。 + +

  基本資料型別的佔用空間和取值範圍

+ +| 型別 | 符號 | 佔用空間 | 最小值 | 最大值 | 預設值 | +| ------ | -------- | -------- | ------------------------ | ----------------------- | -------------- | +| 整數 | `byte` | 1 位元組 | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ | +| | `short` | 2 位元組 | $-2^{15}$ | $2^{15} - 1$ | $0$ | +| | `int` | 4 位元組 | $-2^{31}$ | $2^{31} - 1$ | $0$ | +| | `long` | 8 位元組 | $-2^{63}$ | $2^{63} - 1$ | $0$ | +| 浮點數 | `float` | 4 位元組 | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | +| | `double` | 8 位元組 | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ | +| 字元 | `char` | 2 位元組 | $0$ | $2^{16} - 1$ | $0$ | +| 布林 | `bool` | 1 位元組 | $\text{false}$ | $\text{true}$ | $\text{false}$ | + +請注意,上表針對的是 Java 的基本資料型別的情況。每種程式語言都有各自的資料型別定義,它們的佔用空間、取值範圍和預設值可能會有所不同。 + +- 在 Python 中,整數型別 `int` 可以是任意大小,只受限於可用記憶體;浮點數 `float` 是雙精度 64 位;沒有 `char` 型別,單個字元實際上是長度為 1 的字串 `str` 。 +- C 和 C++ 未明確規定基本資料型別的大小,而因實現和平臺各異。上表遵循 LP64 [資料模型](https://en.cppreference.com/w/cpp/language/types#Properties),其用於包括 Linux 和 macOS 在內的 Unix 64 位作業系統。 +- 字元 `char` 的大小在 C 和 C++ 中為 1 位元組,在大多數程式語言中取決於特定的字元編碼方法,詳見“字元編碼”章節。 +- 即使表示布林量僅需 1 位($0$ 或 $1$),它在記憶體中通常也儲存為 1 位元組。這是因為現代計算機 CPU 通常將 1 位元組作為最小定址記憶體單元。 + +那麼,基本資料型別與資料結構之間有什麼關聯呢?我們知道,資料結構是在計算機中組織與儲存資料的方式。這句話的主語是“結構”而非“資料”。 + +如果想表示“一排數字”,我們自然會想到使用陣列。這是因為陣列的線性結構可以表示數字的相鄰關係和順序關係,但至於儲存的內容是整數 `int`、小數 `float` 還是字元 `char` ,則與“資料結構”無關。 + +換句話說,**基本資料型別提供了資料的“內容型別”,而資料結構提供了資料的“組織方式”**。例如以下程式碼,我們用相同的資料結構(陣列)來儲存與表示不同的基本資料型別,包括 `int`、`float`、`char`、`bool` 等。 + +=== "Python" + + ```python title="" + # 使用多種基本資料型別來初始化陣列 + numbers: list[int] = [0] * 5 + decimals: list[float] = [0.0] * 5 + # Python 的字元實際上是長度為 1 的字串 + characters: list[str] = ['0'] * 5 + bools: list[bool] = [False] * 5 + # Python 的串列可以自由儲存各種基本資料型別和物件引用 + data = [0, 0.0, 'a', False, ListNode(0)] + ``` + +=== "C++" + + ```cpp title="" + // 使用多種基本資料型別來初始化陣列 + int numbers[5]; + float decimals[5]; + char characters[5]; + bool bools[5]; + ``` + +=== "Java" + + ```java title="" + // 使用多種基本資料型別來初始化陣列 + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + boolean[] bools = new boolean[5]; + ``` + +=== "C#" + + ```csharp title="" + // 使用多種基本資料型別來初始化陣列 + int[] numbers = new int[5]; + float[] decimals = new float[5]; + char[] characters = new char[5]; + bool[] bools = new bool[5]; + ``` + +=== "Go" + + ```go title="" + // 使用多種基本資料型別來初始化陣列 + var numbers = [5]int{} + var decimals = [5]float64{} + var characters = [5]byte{} + var bools = [5]bool{} + ``` + +=== "Swift" + + ```swift title="" + // 使用多種基本資料型別來初始化陣列 + let numbers = Array(repeating: 0, count: 5) + let decimals = Array(repeating: 0.0, count: 5) + let characters: [Character] = Array(repeating: "a", count: 5) + let bools = Array(repeating: false, count: 5) + ``` + +=== "JS" + + ```javascript title="" + // JavaScript 的陣列可以自由儲存各種基本資料型別和物件 + const array = [0, 0.0, 'a', false]; + ``` + +=== "TS" + + ```typescript title="" + // 使用多種基本資料型別來初始化陣列 + const numbers: number[] = []; + const characters: string[] = []; + const bools: boolean[] = []; + ``` + +=== "Dart" + + ```dart title="" + // 使用多種基本資料型別來初始化陣列 + List numbers = List.filled(5, 0); + List decimals = List.filled(5, 0.0); + List characters = List.filled(5, 'a'); + List bools = List.filled(5, false); + ``` + +=== "Rust" + + ```rust title="" + // 使用多種基本資料型別來初始化陣列 + let numbers: Vec = vec![0; 5]; + let decimals: Vec = vec![0.0; 5]; + let characters: Vec = vec!['0'; 5]; + let bools: Vec = vec![false; 5]; + ``` + +=== "C" + + ```c title="" + // 使用多種基本資料型別來初始化陣列 + int numbers[10]; + float decimals[10]; + char characters[10]; + bool bools[10]; + ``` + +=== "Kotlin" + + ```kotlin title="" + // 使用多種基本資料型別來初始化陣列 + val numbers = IntArray(5) + val decinals = FloatArray(5) + val characters = CharArray(5) + val bools = BooleanArray(5) + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9D%A5%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E7%AC%A6%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%AD%98%E5%82%A8%E5%90%84%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/zh-hant/docs/chapter_data_structure/character_encoding.assets/ascii_table.png b/zh-hant/docs/chapter_data_structure/character_encoding.assets/ascii_table.png new file mode 100644 index 000000000..3ddd32ba7 Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/character_encoding.assets/ascii_table.png differ diff --git a/zh-hant/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png b/zh-hant/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png new file mode 100644 index 000000000..2a5ca022e Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png differ diff --git a/zh-hant/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png b/zh-hant/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png new file mode 100644 index 000000000..aa6589a3a Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png differ diff --git a/zh-hant/docs/chapter_data_structure/character_encoding.md b/zh-hant/docs/chapter_data_structure/character_encoding.md new file mode 100644 index 000000000..335882821 --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/character_encoding.md @@ -0,0 +1,87 @@ +# 字元編碼 * + +在計算機中,所有資料都是以二進位制數的形式儲存的,字元 `char` 也不例外。為了表示字元,我們需要建立一套“字符集”,規定每個字元和二進位制數之間的一一對應關係。有了字符集之後,計算機就可以透過查表完成二進位制數到字元的轉換。 + +## ASCII 字符集 + +ASCII 碼是最早出現的字符集,其全稱為 American Standard Code for Information Interchange(美國標準資訊交換程式碼)。它使用 7 位二進位制數(一個位元組的低 7 位)表示一個字元,最多能夠表示 128 個不同的字元。如下圖所示,ASCII 碼包括英文字母的大小寫、數字 0 ~ 9、一些標點符號,以及一些控制字元(如換行符和製表符)。 + +![ASCII 碼](character_encoding.assets/ascii_table.png) + +然而,**ASCII 碼僅能夠表示英文**。隨著計算機的全球化,誕生了一種能夠表示更多語言的 EASCII 字符集。它在 ASCII 的 7 位基礎上擴展到 8 位,能夠表示 256 個不同的字元。 + +在世界範圍內,陸續出現了一批適用於不同地區的 EASCII 字符集。這些字符集的前 128 個字元統一為 ASCII 碼,後 128 個字元定義不同,以適應不同語言的需求。 + +## GBK 字符集 + +後來人們發現,**EASCII 碼仍然無法滿足許多語言的字元數量要求**。比如漢字有近十萬個,光日常使用的就有幾千個。中國國家標準總局於 1980 年釋出了 GB2312 字符集,其收錄了 6763 個漢字,基本滿足了漢字的計算機處理需要。 + +然而,GB2312 無法處理部分罕見字和繁體字。GBK 字符集是在 GB2312 的基礎上擴展得到的,它共收錄了 21886 個漢字。在 GBK 的編碼方案中,ASCII 字元使用一個位元組表示,漢字使用兩個位元組表示。 + +## Unicode 字符集 + +隨著計算機技術的蓬勃發展,字符集與編碼標準百花齊放,而這帶來了許多問題。一方面,這些字符集一般只定義了特定語言的字元,無法在多語言環境下正常工作。另一方面,同一種語言存在多種字符集標準,如果兩臺計算機使用的是不同的編碼標準,則在資訊傳遞時就會出現亂碼。 + +那個時代的研究人員就在想:**如果推出一個足夠完整的字符集,將世界範圍內的所有語言和符號都收錄其中,不就可以解決跨語言環境和亂碼問題了嗎**?在這種想法的驅動下,一個大而全的字符集 Unicode 應運而生。 + +Unicode 的中文名稱為“統一碼”,理論上能容納 100 多萬個字元。它致力於將全球範圍內的字元納入統一的字符集之中,提供一種通用的字符集來處理和顯示各種語言文字,減少因為編碼標準不同而產生的亂碼問題。 + +自 1991 年釋出以來,Unicode 不斷擴充新的語言與字元。截至 2022 年 9 月,Unicode 已經包含 149186 個字元,包括各種語言的字元、符號甚至表情符號等。在龐大的 Unicode 字符集中,常用的字元佔用 2 位元組,有些生僻的字元佔用 3 位元組甚至 4 位元組。 + +Unicode 是一種通用字符集,本質上是給每個字元分配一個編號(稱為“碼點”),**但它並沒有規定在計算機中如何儲存這些字元碼點**。我們不禁會問:當多種長度的 Unicode 碼點同時出現在一個文字中時,系統如何解析字元?例如給定一個長度為 2 位元組的編碼,系統如何確認它是一個 2 位元組的字元還是兩個 1 位元組的字元? + +對於以上問題,**一種直接的解決方案是將所有字元儲存為等長的編碼**。如下圖所示,“Hello”中的每個字元佔用 1 位元組,“演算法”中的每個字元佔用 2 位元組。我們可以透過高位填 0 將“Hello 演算法”中的所有字元都編碼為 2 位元組長度。這樣系統就可以每隔 2 位元組解析一個字元,恢復這個短語的內容了。 + +![Unicode 編碼示例](character_encoding.assets/unicode_hello_algo.png) + +然而 ASCII 碼已經向我們證明,編碼英文只需 1 位元組。若採用上述方案,英文文字佔用空間的大小將會是 ASCII 編碼下的兩倍,非常浪費記憶體空間。因此,我們需要一種更加高效的 Unicode 編碼方法。 + +## UTF-8 編碼 + +目前,UTF-8 已成為國際上使用最廣泛的 Unicode 編碼方法。**它是一種可變長度的編碼**,使用 1 到 4 位元組來表示一個字元,根據字元的複雜性而變。ASCII 字元只需 1 位元組,拉丁字母和希臘字母需要 2 位元組,常用的中文字元需要 3 位元組,其他的一些生僻字元需要 4 位元組。 + +UTF-8 的編碼規則並不複雜,分為以下兩種情況。 + +- 對於長度為 1 位元組的字元,將最高位設定為 $0$ ,其餘 7 位設定為 Unicode 碼點。值得注意的是,ASCII 字元在 Unicode 字符集中佔據了前 128 個碼點。也就是說,**UTF-8 編碼可以向下相容 ASCII 碼**。這意味著我們可以使用 UTF-8 來解析年代久遠的 ASCII 碼文字。 +- 對於長度為 $n$ 位元組的字元(其中 $n > 1$),將首個位元組的高 $n$ 位都設定為 $1$ ,第 $n + 1$ 位設定為 $0$ ;從第二個位元組開始,將每個位元組的高 2 位都設定為 $10$ ;其餘所有位用於填充字元的 Unicode 碼點。 + +下圖展示了“Hello演算法”對應的 UTF-8 編碼。觀察發現,由於最高 $n$ 位都設定為 $1$ ,因此系統可以透過讀取最高位 $1$ 的個數來解析出字元的長度為 $n$ 。 + +但為什麼要將其餘所有位元組的高 2 位都設定為 $10$ 呢?實際上,這個 $10$ 能夠起到校驗符的作用。假設系統從一個錯誤的位元組開始解析文字,位元組頭部的 $10$ 能夠幫助系統快速判斷出異常。 + +之所以將 $10$ 當作校驗符,是因為在 UTF-8 編碼規則下,不可能有字元的最高兩位是 $10$ 。這個結論可以用反證法來證明:假設一個字元的最高兩位是 $10$ ,說明該字元的長度為 $1$ ,對應 ASCII 碼。而 ASCII 碼的最高位應該是 $0$ ,與假設矛盾。 + +![UTF-8 編碼示例](character_encoding.assets/utf-8_hello_algo.png) + +除了 UTF-8 之外,常見的編碼方式還包括以下兩種。 + +- **UTF-16 編碼**:使用 2 或 4 位元組來表示一個字元。所有的 ASCII 字元和常用的非英文字元,都用 2 位元組表示;少數字符需要用到 4 位元組表示。對於 2 位元組的字元,UTF-16 編碼與 Unicode 碼點相等。 +- **UTF-32 編碼**:每個字元都使用 4 位元組。這意味著 UTF-32 比 UTF-8 和 UTF-16 更佔用空間,特別是對於 ASCII 字元佔比較高的文字。 + +從儲存空間佔用的角度看,使用 UTF-8 表示英文字元非常高效,因為它僅需 1 位元組;使用 UTF-16 編碼某些非英文字元(例如中文)會更加高效,因為它僅需 2 位元組,而 UTF-8 可能需要 3 位元組。 + +從相容性的角度看,UTF-8 的通用性最佳,許多工具和庫優先支持 UTF-8 。 + +## 程式語言的字元編碼 + +對於以往的大多數程式語言,程式執行中的字串都採用 UTF-16 或 UTF-32 這類等長編碼。在等長編碼下,我們可以將字串看作陣列來處理,這種做法具有以下優點。 + +- **隨機訪問**:UTF-16 編碼的字串可以很容易地進行隨機訪問。UTF-8 是一種變長編碼,要想找到第 $i$ 個字元,我們需要從字串的開始處走訪到第 $i$ 個字元,這需要 $O(n)$ 的時間。 +- **字元計數**:與隨機訪問類似,計算 UTF-16 編碼的字串的長度也是 $O(1)$ 的操作。但是,計算 UTF-8 編碼的字串的長度需要走訪整個字串。 +- **字串操作**:在 UTF-16 編碼的字串上,很多字串操作(如分割、連線、插入、刪除等)更容易進行。在 UTF-8 編碼的字串上,進行這些操作通常需要額外的計算,以確保不會產生無效的 UTF-8 編碼。 + +實際上,程式語言的字元編碼方案設計是一個很有趣的話題,涉及許多因素。 + +- Java 的 `String` 型別使用 UTF-16 編碼,每個字元佔用 2 位元組。這是因為 Java 語言設計之初,人們認為 16 位足以表示所有可能的字元。然而,這是一個不正確的判斷。後來 Unicode 規範擴展到了超過 16 位,所以 Java 中的字元現在可能由一對 16 位的值(稱為“代理對”)表示。 +- JavaScript 和 TypeScript 的字串使用 UTF-16 編碼的原因與 Java 類似。當 1995 年 Netscape 公司首次推出 JavaScript 語言時,Unicode 還處於發展早期,那時候使用 16 位的編碼就足以表示所有的 Unicode 字元了。 +- C# 使用 UTF-16 編碼,主要是因為 .NET 平臺是由 Microsoft 設計的,而 Microsoft 的很多技術(包括 Windows 作業系統)都廣泛使用 UTF-16 編碼。 + +由於以上程式語言對字元數量的低估,它們不得不採取“代理對”的方式來表示超過 16 位長度的 Unicode 字元。這是一個不得已為之的無奈之舉。一方面,包含代理對的字串中,一個字元可能佔用 2 位元組或 4 位元組,從而喪失了等長編碼的優勢。另一方面,處理代理對需要額外增加程式碼,這提高了程式設計的複雜性和除錯難度。 + +出於以上原因,部分程式語言提出了一些不同的編碼方案。 + +- Python 中的 `str` 使用 Unicode 編碼,並採用一種靈活的字串表示,儲存的字元長度取決於字串中最大的 Unicode 碼點。若字串中全部是 ASCII 字元,則每個字元佔用 1 位元組;如果有字元超出了 ASCII 範圍,但全部在基本多語言平面(BMP)內,則每個字元佔用 2 位元組;如果有超出 BMP 的字元,則每個字元佔用 4 位元組。 +- Go 語言的 `string` 型別在內部使用 UTF-8 編碼。Go 語言還提供了 `rune` 型別,它用於表示單個 Unicode 碼點。 +- Rust 語言的 `str` 和 `String` 型別在內部使用 UTF-8 編碼。Rust 也提供了 `char` 型別,用於表示單個 Unicode 碼點。 + +需要注意的是,以上討論的都是字串在程式語言中的儲存方式,**這和字串如何在檔案中儲存或在網路中傳輸是不同的問題**。在檔案儲存或網路傳輸中,我們通常會將字串編碼為 UTF-8 格式,以達到最優的相容性和空間效率。 diff --git a/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png new file mode 100644 index 000000000..8f11a5ed3 Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png differ diff --git a/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png new file mode 100644 index 000000000..56544c1eb Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png differ diff --git a/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png new file mode 100644 index 000000000..7da7eca3f Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png differ diff --git a/zh-hant/docs/chapter_data_structure/classification_of_data_structure.md b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.md new file mode 100644 index 000000000..ac4f8b15c --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/classification_of_data_structure.md @@ -0,0 +1,48 @@ +# 資料結構分類 + +常見的資料結構包括陣列、鏈結串列、堆疊、佇列、雜湊表、樹、堆積、圖,它們可以從“邏輯結構”和“物理結構”兩個維度進行分類。 + +## 邏輯結構:線性與非線性 + +**邏輯結構揭示了資料元素之間的邏輯關係**。在陣列和鏈結串列中,資料按照一定順序排列,體現了資料之間的線性關係;而在樹中,資料從頂部向下按層次排列,表現出“祖先”與“後代”之間的派生關係;圖則由節點和邊構成,反映了複雜的網路關係。 + +如下圖所示,邏輯結構可分為“線性”和“非線性”兩大類。線性結構比較直觀,指資料在邏輯關係上呈線性排列;非線性結構則相反,呈非線性排列。 + +- **線性資料結構**:陣列、鏈結串列、堆疊、佇列、雜湊表,元素之間是一對一的順序關係。 +- **非線性資料結構**:樹、堆積、圖、雜湊表。 + +非線性資料結構可以進一步劃分為樹形結構和網狀結構。 + +- **樹形結構**:樹、堆積、雜湊表,元素之間是一對多的關係。 +- **網狀結構**:圖,元素之間是多對多的關係。 + +![線性資料結構與非線性資料結構](classification_of_data_structure.assets/classification_logic_structure.png) + +## 物理結構:連續與分散 + +**當演算法程式執行時,正在處理的資料主要儲存在記憶體中**。下圖展示了一個計算機記憶體條,其中每個黑色方塊都包含一塊記憶體空間。我們可以將記憶體想象成一個巨大的 Excel 表格,其中每個單元格都可以儲存一定大小的資料。 + +**系統透過記憶體位址來訪問目標位置的資料**。如下圖所示,計算機根據特定規則為表格中的每個單元格分配編號,確保每個記憶體空間都有唯一的記憶體位址。有了這些位址,程式便可以訪問記憶體中的資料。 + +![記憶體條、記憶體空間、記憶體位址](classification_of_data_structure.assets/computer_memory_location.png) + +!!! tip + + 值得說明的是,將記憶體比作 Excel 表格是一個簡化的類比,實際記憶體的工作機制比較複雜,涉及位址空間、記憶體管理、快取機制、虛擬記憶體和物理記憶體等概念。 + +記憶體是所有程式的共享資源,當某塊記憶體被某個程式佔用時,則無法被其他程式同時使用了。**因此在資料結構與演算法的設計中,記憶體資源是一個重要的考慮因素**。比如,演算法所佔用的記憶體峰值不應超過系統剩餘空閒記憶體;如果缺少連續大塊的記憶體空間,那麼所選用的資料結構必須能夠儲存在分散的記憶體空間內。 + +如下圖所示,**物理結構反映了資料在計算機記憶體中的儲存方式**,可分為連續空間儲存(陣列)和分散空間儲存(鏈結串列)。物理結構從底層決定了資料的訪問、更新、增刪等操作方法,兩種物理結構在時間效率和空間效率方面呈現出互補的特點。 + +![連續空間儲存與分散空間儲存](classification_of_data_structure.assets/classification_phisical_structure.png) + +值得說明的是,**所有資料結構都是基於陣列、鏈結串列或二者的組合實現的**。例如,堆疊和佇列既可以使用陣列實現,也可以使用鏈結串列實現;而雜湊表的實現可能同時包含陣列和鏈結串列。 + +- **基於陣列可實現**:堆疊、佇列、雜湊表、樹、堆積、圖、矩陣、張量(維度 $\geq 3$ 的陣列)等。 +- **基於鏈結串列可實現**:堆疊、佇列、雜湊表、樹、堆積、圖等。 + +鏈結串列在初始化後,仍可以在程式執行過程中對其長度進行調整,因此也稱“動態資料結構”。陣列在初始化後長度不可變,因此也稱“靜態資料結構”。值得注意的是,陣列可透過重新分配記憶體實現長度變化,從而具備一定的“動態性”。 + +!!! tip + + 如果你感覺物理結構理解起來有困難,建議先閱讀下一章,然後再回顧本節內容。 diff --git a/zh-hant/docs/chapter_data_structure/index.md b/zh-hant/docs/chapter_data_structure/index.md new file mode 100644 index 000000000..ad7b8ec0d --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/index.md @@ -0,0 +1,9 @@ +# 資料結構 + +![資料結構](../assets/covers/chapter_data_structure.jpg) + +!!! abstract + + 資料結構如同一副穩固而多樣的框架。 + + 它為資料的有序組織提供了藍圖,演算法得以在此基礎上生動起來。 diff --git a/zh-hant/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png b/zh-hant/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png new file mode 100644 index 000000000..0b17b3def Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png differ diff --git a/zh-hant/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png b/zh-hant/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png new file mode 100644 index 000000000..0edff64fe Binary files /dev/null and b/zh-hant/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png differ diff --git a/zh-hant/docs/chapter_data_structure/number_encoding.md b/zh-hant/docs/chapter_data_structure/number_encoding.md new file mode 100644 index 000000000..f92e8178a --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/number_encoding.md @@ -0,0 +1,150 @@ +# 數字編碼 * + +!!! note + + 在本書中,標題帶有 * 符號的是選讀章節。如果你時間有限或感到理解困難,可以先跳過,等學完必讀章節後再單獨攻克。 + +## 原碼、一補數和二補數 + +在上一節的表格中我們發現,所有整數型別能夠表示的負數都比正數多一個,例如 `byte` 的取值範圍是 $[-128, 127]$ 。這個現象比較反直覺,它的內在原因涉及原碼、一補數、二補數的相關知識。 + +首先需要指出,**數字是以“二補數”的形式儲存在計算機中的**。在分析這樣做的原因之前,首先給出三者的定義。 + +- **原碼**:我們將數字的二進位制表示的最高位視為符號位,其中 $0$ 表示正數,$1$ 表示負數,其餘位表示數字的值。 +- **一補數**:正數的一補數與其原碼相同,負數的一補數是對其原碼除符號位外的所有位取反。 +- **二補數**:正數的二補數與其原碼相同,負數的二補數是在其一補數的基礎上加 $1$ 。 + +下圖展示了原碼、一補數和二補數之間的轉換方法。 + +![原碼、一補數與二補數之間的相互轉換](number_encoding.assets/1s_2s_complement.png) + +原碼(sign-magnitude)雖然最直觀,但存在一些侷限性。一方面,**負數的原碼不能直接用於運算**。例如在原碼下計算 $1 + (-2)$ ,得到的結果是 $-3$ ,這顯然是不對的。 + +$$ +\begin{aligned} +& 1 + (-2) \newline +& \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline +& = 1000 \; 0011 \newline +& \rightarrow -3 +\end{aligned} +$$ + +為了解決此問題,計算機引入了一補數(1's complement)。如果我們先將原碼轉換為一補數,並在一補數下計算 $1 + (-2)$ ,最後將結果從一補數轉換回原碼,則可得到正確結果 $-1$ 。 + +$$ +\begin{aligned} +& 1 + (-2) \newline +& \rightarrow 0000 \; 0001 \; \text{(原碼)} + 1000 \; 0010 \; \text{(原碼)} \newline +& = 0000 \; 0001 \; \text{(一補數)} + 1111 \; 1101 \; \text{(一補數)} \newline +& = 1111 \; 1110 \; \text{(一補數)} \newline +& = 1000 \; 0001 \; \text{(原碼)} \newline +& \rightarrow -1 +\end{aligned} +$$ + +另一方面,**數字零的原碼有 $+0$ 和 $-0$ 兩種表示方式**。這意味著數字零對應兩個不同的二進位制編碼,這可能會帶來歧義。比如在條件判斷中,如果沒有區分正零和負零,則可能會導致判斷結果出錯。而如果我們想處理正零和負零歧義,則需要引入額外的判斷操作,這可能會降低計算機的運算效率。 + +$$ +\begin{aligned} ++0 & \rightarrow 0000 \; 0000 \newline +-0 & \rightarrow 1000 \; 0000 +\end{aligned} +$$ + +與原碼一樣,一補數也存在正負零歧義問題,因此計算機進一步引入了二補數(2's complement)。我們先來觀察一下負零的原碼、一補數、二補數的轉換過程: + +$$ +\begin{aligned} +-0 \rightarrow \; & 1000 \; 0000 \; \text{(原碼)} \newline += \; & 1111 \; 1111 \; \text{(一補數)} \newline += 1 \; & 0000 \; 0000 \; \text{(二補數)} \newline +\end{aligned} +$$ + +在負零的一補數基礎上加 $1$ 會產生進位,但 `byte` 型別的長度只有 8 位,因此溢位到第 9 位的 $1$ 會被捨棄。也就是說,**負零的二補數為 $0000 \; 0000$ ,與正零的二補數相同**。這意味著在二補數表示中只存在一個零,正負零歧義從而得到解決。 + +還剩最後一個疑惑:`byte` 型別的取值範圍是 $[-128, 127]$ ,多出來的一個負數 $-128$ 是如何得到的呢?我們注意到,區間 $[-127, +127]$ 內的所有整數都有對應的原碼、一補數和二補數,並且原碼和二補數之間可以互相轉換。 + +然而,**二補數 $1000 \; 0000$ 是一個例外,它並沒有對應的原碼**。根據轉換方法,我們得到該二補數的原碼為 $0000 \; 0000$ 。這顯然是矛盾的,因為該原碼表示數字 $0$ ,它的二補數應該是自身。計算機規定這個特殊的二補數 $1000 \; 0000$ 代表 $-128$ 。實際上,$(-1) + (-127)$ 在二補數下的計算結果就是 $-128$ 。 + +$$ +\begin{aligned} +& (-127) + (-1) \newline +& \rightarrow 1111 \; 1111 \; \text{(原碼)} + 1000 \; 0001 \; \text{(原碼)} \newline +& = 1000 \; 0000 \; \text{(一補數)} + 1111 \; 1110 \; \text{(一補數)} \newline +& = 1000 \; 0001 \; \text{(二補數)} + 1111 \; 1111 \; \text{(二補數)} \newline +& = 1000 \; 0000 \; \text{(二補數)} \newline +& \rightarrow -128 +\end{aligned} +$$ + +你可能已經發現了,上述所有計算都是加法運算。這暗示著一個重要事實:**計算機內部的硬體電路主要是基於加法運算設計的**。這是因為加法運算相對於其他運算(比如乘法、除法和減法)來說,硬體實現起來更簡單,更容易進行並行化處理,運算速度更快。 + +請注意,這並不意味著計算機只能做加法。**透過將加法與一些基本邏輯運算結合,計算機能夠實現各種其他的數學運算**。例如,計算減法 $a - b$ 可以轉換為計算加法 $a + (-b)$ ;計算乘法和除法可以轉換為計算多次加法或減法。 + +現在我們可以總結出計算機使用二補數的原因:基於二補數表示,計算機可以用同樣的電路和操作來處理正數和負數的加法,不需要設計特殊的硬體電路來處理減法,並且無須特別處理正負零的歧義問題。這大大簡化了硬體設計,提高了運算效率。 + +二補數的設計非常精妙,因篇幅關係我們就先介紹到這裡,建議有興趣的讀者進一步深入瞭解。 + +## 浮點數編碼 + +細心的你可能會發現:`int` 和 `float` 長度相同,都是 4 位元組 ,但為什麼 `float` 的取值範圍遠大於 `int` ?這非常反直覺,因為按理說 `float` 需要表示小數,取值範圍應該變小才對。 + +實際上,**這是因為浮點數 `float` 採用了不同的表示方式**。記一個 32 位元長度的二進位制數為: + +$$ +b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 +$$ + +根據 IEEE 754 標準,32-bit 長度的 `float` 由以下三個部分構成。 + +- 符號位 $\mathrm{S}$ :佔 1 位 ,對應 $b_{31}$ 。 +- 指數位 $\mathrm{E}$ :佔 8 位 ,對應 $b_{30} b_{29} \ldots b_{23}$ 。 +- 分數位 $\mathrm{N}$ :佔 23 位 ,對應 $b_{22} b_{21} \ldots b_0$ 。 + +二進位制數 `float` 對應值的計算方法為: + +$$ +\text {val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2 +$$ + +轉化到十進位制下的計算公式為: + +$$ +\text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N}) +$$ + +其中各項的取值範圍為: + +$$ +\begin{aligned} +\mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline +(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}] +\end{aligned} +$$ + +![IEEE 754 標準下的 float 的計算示例](number_encoding.assets/ieee_754_float.png) + +觀察上圖,給定一個示例資料 $\mathrm{S} = 0$ , $\mathrm{E} = 124$ ,$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ ,則有: + +$$ +\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 +$$ + +現在我們可以回答最初的問題:**`float` 的表示方式包含指數位,導致其取值範圍遠大於 `int`** 。根據以上計算,`float` 可表示的最大正數為 $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ,切換符號位便可得到最小負數。 + +**儘管浮點數 `float` 擴展了取值範圍,但其副作用是犧牲了精度**。整數型別 `int` 將全部 32 位元用於表示數字,數字是均勻分佈的;而由於指數位的存在,浮點數 `float` 的數值越大,相鄰兩個數字之間的差值就會趨向越大。 + +如下表所示,指數位 $E = 0$ 和 $E = 255$ 具有特殊含義,**用於表示零、無窮大、$\mathrm{NaN}$ 等**。 + +

  指數位含義

+ +| 指數位 E | 分數位 $\mathrm{N} = 0$ | 分數位 $\mathrm{N} \ne 0$ | 計算公式 | +| ------------------ | ----------------------- | ------------------------- | ---------------------------------------------------------------------- | +| $0$ | $\pm 0$ | 次正規數 | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | +| $1, 2, \dots, 254$ | 正規數 | 正規數 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | +| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | + +值得說明的是,次正規數顯著提升了浮點數的精度。最小正正規數為 $2^{-126}$ ,最小正次正規數為 $2^{-126} \times 2^{-23}$ 。 + +雙精度 `double` 也採用類似於 `float` 的表示方法,在此不做贅述。 diff --git a/zh-hant/docs/chapter_data_structure/summary.md b/zh-hant/docs/chapter_data_structure/summary.md new file mode 100644 index 000000000..e5f947a6a --- /dev/null +++ b/zh-hant/docs/chapter_data_structure/summary.md @@ -0,0 +1,34 @@ +# 小結 + +### 重點回顧 + +- 資料結構可以從邏輯結構和物理結構兩個角度進行分類。邏輯結構描述了資料元素之間的邏輯關係,而物理結構描述了資料在計算機記憶體中的儲存方式。 +- 常見的邏輯結構包括線性、樹狀和網狀等。通常我們根據邏輯結構將資料結構分為線性(陣列、鏈結串列、堆疊、佇列)和非線性(樹、圖、堆積)兩種。雜湊表的實現可能同時包含線性資料結構和非線性資料結構。 +- 當程式執行時,資料被儲存在計算機記憶體中。每個記憶體空間都擁有對應的記憶體位址,程式透過這些記憶體位址訪問資料。 +- 物理結構主要分為連續空間儲存(陣列)和分散空間儲存(鏈結串列)。所有資料結構都是由陣列、鏈結串列或兩者的組合實現的。 +- 計算機中的基本資料型別包括整數 `byte`、`short`、`int`、`long` ,浮點數 `float`、`double` ,字元 `char` 和布林 `bool` 。它們的取值範圍取決於佔用空間大小和表示方式。 +- 原碼、一補數和二補數是在計算機中編碼數字的三種方法,它們之間可以相互轉換。整數的原碼的最高位是符號位,其餘位是數字的值。 +- 整數在計算機中是以二補數的形式儲存的。在二補數表示下,計算機可以對正數和負數的加法一視同仁,不需要為減法操作單獨設計特殊的硬體電路,並且不存在正負零歧義的問題。 +- 浮點數的編碼由 1 位符號位、8 位指數位和 23 位分數位構成。由於存在指數位,因此浮點數的取值範圍遠大於整數,代價是犧牲了精度。 +- ASCII 碼是最早出現的英文字符集,長度為 1 位元組,共收錄 127 個字元。GBK 字符集是常用的中文字符集,共收錄兩萬多個漢字。Unicode 致力於提供一個完整的字符集標準,收錄世界上各種語言的字元,從而解決由於字元編碼方法不一致而導致的亂碼問題。 +- UTF-8 是最受歡迎的 Unicode 編碼方法,通用性非常好。它是一種變長的編碼方法,具有很好的擴展性,有效提升了儲存空間的使用效率。UTF-16 和 UTF-32 是等長的編碼方法。在編碼中文時,UTF-16 佔用的空間比 UTF-8 更小。Java 和 C# 等程式語言預設使用 UTF-16 編碼。 + +### Q & A + +**Q**:為什麼雜湊表同時包含線性資料結構和非線性資料結構? + +雜湊表底層是陣列,而為了解決雜湊衝突,我們可能會使用“鏈式位址”(後續“雜湊衝突”章節會講):陣列中每個桶指向一個鏈結串列,當鏈結串列長度超過一定閾值時,又可能被轉化為樹(通常為紅黑樹)。 + +從儲存的角度來看,雜湊表的底層是陣列,其中每一個桶槽位可能包含一個值,也可能包含一個鏈結串列或一棵樹。因此,雜湊表可能同時包含線性資料結構(陣列、鏈結串列)和非線性資料結構(樹)。 + +**Q**:`char` 型別的長度是 1 位元組嗎? + +`char` 型別的長度由程式語言採用的編碼方法決定。例如,Java、JavaScript、TypeScript、C# 都採用 UTF-16 編碼(儲存 Unicode 碼點),因此 `char` 型別的長度為 2 位元組。 + +**Q**:基於陣列實現的資料結構也稱“靜態資料結構” 是否有歧義?堆疊也可以進行出堆疊和入堆疊等操作,這些操作都是“動態”的。 + +堆疊確實可以實現動態的資料操作,但資料結構仍然是“靜態”(長度不可變)的。儘管基於陣列的資料結構可以動態地新增或刪除元素,但它們的容量是固定的。如果資料量超出了預分配的大小,就需要建立一個新的更大的陣列,並將舊陣列的內容複製到新陣列中。 + +**Q**:在構建堆疊(佇列)的時候,未指定它的大小,為什麼它們是“靜態資料結構”呢? + +在高階程式語言中,我們無須人工指定堆疊(佇列)的初始容量,這個工作由類別內部自動完成。例如,Java 的 `ArrayList` 的初始容量通常為 10。另外,擴容操作也是自動實現的。詳見後續的“串列”章節。 diff --git a/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png b/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png new file mode 100644 index 000000000..3ef9231e0 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.md b/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.md new file mode 100644 index 000000000..5f55ac4e8 --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/binary_search_recur.md @@ -0,0 +1,45 @@ +# 分治搜尋策略 + +我們已經學過,搜尋演算法分為兩大類。 + +- **暴力搜尋**:它透過走訪資料結構實現,時間複雜度為 $O(n)$ 。 +- **自適應搜尋**:它利用特有的資料組織形式或先驗資訊,時間複雜度可達到 $O(\log n)$ 甚至 $O(1)$ 。 + +實際上,**時間複雜度為 $O(\log n)$ 的搜尋演算法通常是基於分治策略實現的**,例如二分搜尋和樹。 + +- 二分搜尋的每一步都將問題(在陣列中搜索目標元素)分解為一個小問題(在陣列的一半中搜索目標元素),這個過程一直持續到陣列為空或找到目標元素為止。 +- 樹是分治思想的代表,在二元搜尋樹、AVL 樹、堆積等資料結構中,各種操作的時間複雜度皆為 $O(\log n)$ 。 + +二分搜尋的分治策略如下所示。 + +- **問題可以分解**:二分搜尋遞迴地將原問題(在陣列中進行查詢)分解為子問題(在陣列的一半中進行查詢),這是透過比較中間元素和目標元素來實現的。 +- **子問題是獨立的**:在二分搜尋中,每輪只處理一個子問題,它不受其他子問題的影響。 +- **子問題的解無須合併**:二分搜尋旨在查詢一個特定元素,因此不需要將子問題的解進行合併。當子問題得到解決時,原問題也會同時得到解決。 + +分治能夠提升搜尋效率,本質上是因為暴力搜尋每輪只能排除一個選項,**而分治搜尋每輪可以排除一半選項**。 + +### 基於分治實現二分搜尋 + +在之前的章節中,二分搜尋是基於遞推(迭代)實現的。現在我們基於分治(遞迴)來實現它。 + +!!! question + + 給定一個長度為 $n$ 的有序陣列 `nums` ,其中所有元素都是唯一的,請查詢元素 `target` 。 + +從分治角度,我們將搜尋區間 $[i, j]$ 對應的子問題記為 $f(i, j)$ 。 + +以原問題 $f(0, n-1)$ 為起始點,透過以下步驟進行二分搜尋。 + +1. 計算搜尋區間 $[i, j]$ 的中點 $m$ ,根據它排除一半搜尋區間。 +2. 遞迴求解規模減小一半的子問題,可能為 $f(i, m-1)$ 或 $f(m+1, j)$ 。 +3. 迴圈第 `1.` 步和第 `2.` 步,直至找到 `target` 或區間為空時返回。 + +下圖展示了在陣列中二分搜尋元素 $6$ 的分治過程。 + +![二分搜尋的分治過程](binary_search_recur.assets/binary_search_recur.png) + +在實現程式碼中,我們宣告一個遞迴函式 `dfs()` 來求解問題 $f(i, j)$ : + +```src +[file]{binary_search_recur}-[class]{}-[func]{binary_search} +``` diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png new file mode 100644 index 000000000..0e80a7c9a Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png new file mode 100644 index 000000000..dea1f4808 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png new file mode 100644 index 000000000..bb71c4589 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png new file mode 100644 index 000000000..69e238303 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png new file mode 100644 index 000000000..8b0d6bac6 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png new file mode 100644 index 000000000..d943bc9ea Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png new file mode 100644 index 000000000..c3bc4fbb2 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png new file mode 100644 index 000000000..c7b93878e Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png new file mode 100644 index 000000000..53afa898c Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png new file mode 100644 index 000000000..eebf21bec Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png new file mode 100644 index 000000000..cfff5c7d1 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png new file mode 100644 index 000000000..8e31db80a Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png new file mode 100644 index 000000000..59fe466b5 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.md new file mode 100644 index 000000000..88917b9bd --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -0,0 +1,99 @@ +# 構建二元樹問題 + +!!! question + + 給定一棵二元樹的前序走訪 `preorder` 和中序走訪 `inorder` ,請從中構建二元樹,返回二元樹的根節點。假設二元樹中沒有值重複的節點(如下圖所示)。 + +![構建二元樹的示例資料](build_binary_tree_problem.assets/build_tree_example.png) + +### 判斷是否為分治問題 + +原問題定義為從 `preorder` 和 `inorder` 構建二元樹,是一個典型的分治問題。 + +- **問題可以分解**:從分治的角度切入,我們可以將原問題劃分為兩個子問題:構建左子樹、構建右子樹,加上一步操作:初始化根節點。而對於每棵子樹(子問題),我們仍然可以複用以上劃分方法,將其劃分為更小的子樹(子問題),直至達到最小子問題(空子樹)時終止。 +- **子問題是獨立的**:左子樹和右子樹是相互獨立的,它們之間沒有交集。在構建左子樹時,我們只需關注中序走訪和前序走訪中與左子樹對應的部分。右子樹同理。 +- **子問題的解可以合併**:一旦得到了左子樹和右子樹(子問題的解),我們就可以將它們連結到根節點上,得到原問題的解。 + +### 如何劃分子樹 + +根據以上分析,這道題可以使用分治來求解,**但如何透過前序走訪 `preorder` 和中序走訪 `inorder` 來劃分左子樹和右子樹呢**? + +根據定義,`preorder` 和 `inorder` 都可以劃分為三個部分。 + +- 前序走訪:`[ 根節點 | 左子樹 | 右子樹 ]` ,例如上圖的樹對應 `[ 3 | 9 | 2 1 7 ]` 。 +- 中序走訪:`[ 左子樹 | 根節點 | 右子樹 ]` ,例如上圖的樹對應 `[ 9 | 3 | 1 2 7 ]` 。 + +以上圖資料為例,我們可以透過下圖所示的步驟得到劃分結果。 + +1. 前序走訪的首元素 3 是根節點的值。 +2. 查詢根節點 3 在 `inorder` 中的索引,利用該索引可將 `inorder` 劃分為 `[ 9 | 3 | 1 2 7 ]` 。 +3. 根據 `inorder` 的劃分結果,易得左子樹和右子樹的節點數量分別為 1 和 3 ,從而可將 `preorder` 劃分為 `[ 3 | 9 | 2 1 7 ]` 。 + +![在前序走訪和中序走訪中劃分子樹](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) + +### 基於變數描述子樹區間 + +根據以上劃分方法,**我們已經得到根節點、左子樹、右子樹在 `preorder` 和 `inorder` 中的索引區間**。而為了描述這些索引區間,我們需要藉助幾個指標變數。 + +- 將當前樹的根節點在 `preorder` 中的索引記為 $i$ 。 +- 將當前樹的根節點在 `inorder` 中的索引記為 $m$ 。 +- 將當前樹在 `inorder` 中的索引區間記為 $[l, r]$ 。 + +如下表所示,透過以上變數即可表示根節點在 `preorder` 中的索引,以及子樹在 `inorder` 中的索引區間。 + +

  根節點和子樹在前序走訪和中序走訪中的索引

+ +| | 根節點在 `preorder` 中的索引 | 子樹在 `inorder` 中的索引區間 | +| ------ | ---------------------------- | ----------------------------- | +| 當前樹 | $i$ | $[l, r]$ | +| 左子樹 | $i + 1$ | $[l, m-1]$ | +| 右子樹 | $i + 1 + (m - l)$ | $[m+1, r]$ | + +請注意,右子樹根節點索引中的 $(m-l)$ 的含義是“左子樹的節點數量”,建議結合下圖理解。 + +![根節點和左右子樹的索引區間表示](build_binary_tree_problem.assets/build_tree_division_pointers.png) + +### 程式碼實現 + +為了提升查詢 $m$ 的效率,我們藉助一個雜湊表 `hmap` 來儲存陣列 `inorder` 中元素到索引的對映: + +```src +[file]{build_tree}-[class]{}-[func]{build_tree} +``` + +下圖展示了構建二元樹的遞迴過程,各個節點是在向下“遞”的過程中建立的,而各條邊(引用)是在向上“迴”的過程中建立的。 + +=== "<1>" + ![構建二元樹的遞迴過程](build_binary_tree_problem.assets/built_tree_step1.png) + +=== "<2>" + ![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png) + +=== "<3>" + ![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png) + +=== "<4>" + ![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png) + +=== "<5>" + ![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png) + +=== "<6>" + ![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png) + +=== "<7>" + ![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png) + +=== "<8>" + ![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png) + +=== "<9>" + ![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png) + +每個遞迴函式內的前序走訪 `preorder` 和中序走訪 `inorder` 的劃分結果如下圖所示。 + +![每個遞迴函式中的劃分結果](build_binary_tree_problem.assets/built_tree_overall.png) + +設樹的節點數量為 $n$ ,初始化每一個節點(執行一個遞迴函式 `dfs()` )使用 $O(1)$ 時間。**因此總體時間複雜度為 $O(n)$** 。 + +雜湊表儲存 `inorder` 元素到索引的對映,空間複雜度為 $O(n)$ 。在最差情況下,即二元樹退化為鏈結串列時,遞迴深度達到 $n$ ,使用 $O(n)$ 的堆疊幀空間。**因此總體空間複雜度為 $O(n)$** 。 diff --git a/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png new file mode 100644 index 000000000..24264a6d7 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png new file mode 100644 index 000000000..dcb415b0a Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png new file mode 100644 index 000000000..bed8a0aad Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.md b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.md new file mode 100644 index 000000000..c80a5aeec --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.md @@ -0,0 +1,91 @@ +# 分治演算法 + +分治(divide and conquer),全稱分而治之,是一種非常重要且常見的演算法策略。分治通常基於遞迴實現,包括“分”和“治”兩個步驟。 + +1. **分(劃分階段)**:遞迴地將原問題分解為兩個或多個子問題,直至到達最小子問題時終止。 +2. **治(合併階段)**:從已知解的最小子問題開始,從底至頂地將子問題的解進行合併,從而構建出原問題的解。 + +如下圖所示,“合併排序”是分治策略的典型應用之一。 + +1. **分**:遞迴地將原陣列(原問題)劃分為兩個子陣列(子問題),直到子陣列只剩一個元素(最小子問題)。 +2. **治**:從底至頂地將有序的子陣列(子問題的解)進行合併,從而得到有序的原陣列(原問題的解)。 + +![合併排序的分治策略](divide_and_conquer.assets/divide_and_conquer_merge_sort.png) + +## 如何判斷分治問題 + +一個問題是否適合使用分治解決,通常可以參考以下幾個判斷依據。 + +1. **問題可以分解**:原問題可以分解成規模更小、類似的子問題,以及能夠以相同方式遞迴地進行劃分。 +2. **子問題是獨立的**:子問題之間沒有重疊,互不依賴,可以獨立解決。 +3. **子問題的解可以合併**:原問題的解透過合併子問題的解得來。 + +顯然,合併排序滿足以上三個判斷依據。 + +1. **問題可以分解**:遞迴地將陣列(原問題)劃分為兩個子陣列(子問題)。 +2. **子問題是獨立的**:每個子陣列都可以獨立地進行排序(子問題可以獨立進行求解)。 +3. **子問題的解可以合併**:兩個有序子陣列(子問題的解)可以合併為一個有序陣列(原問題的解)。 + +## 透過分治提升效率 + +**分治不僅可以有效地解決演算法問題,往往還可以提升演算法效率**。在排序演算法中,快速排序、合併排序、堆積排序相較於選擇、冒泡、插入排序更快,就是因為它們應用了分治策略。 + +那麼,我們不禁發問:**為什麼分治可以提升演算法效率,其底層邏輯是什麼**?換句話說,將大問題分解為多個子問題、解決子問題、將子問題的解合併為原問題的解,這幾步的效率為什麼比直接解決原問題的效率更高?這個問題可以從操作數量和平行計算兩方面來討論。 + +### 操作數量最佳化 + +以“泡沫排序”為例,其處理一個長度為 $n$ 的陣列需要 $O(n^2)$ 時間。假設我們按照下圖所示的方式,將陣列從中點處分為兩個子陣列,則劃分需要 $O(n)$ 時間,排序每個子陣列需要 $O((n / 2)^2)$ 時間,合併兩個子陣列需要 $O(n)$ 時間,總體時間複雜度為: + +$$ +O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) +$$ + +![劃分陣列前後的泡沫排序](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png) + +接下來,我們計算以下不等式,其左邊和右邊分別為劃分前和劃分後的操作總數: + +$$ +\begin{aligned} +n^2 & > \frac{n^2}{2} + 2n \newline +n^2 - \frac{n^2}{2} - 2n & > 0 \newline +n(n - 4) & > 0 +\end{aligned} +$$ + +**這意味著當 $n > 4$ 時,劃分後的操作數量更少,排序效率應該更高**。請注意,劃分後的時間複雜度仍然是平方階 $O(n^2)$ ,只是複雜度中的常數項變小了。 + +進一步想,**如果我們把子陣列不斷地再從中點處劃分為兩個子陣列**,直至子陣列只剩一個元素時停止劃分呢?這種思路實際上就是“合併排序”,時間複雜度為 $O(n \log n)$ 。 + +再思考,**如果我們多設定幾個劃分點**,將原陣列平均劃分為 $k$ 個子陣列呢?這種情況與“桶排序”非常類似,它非常適合排序海量資料,理論上時間複雜度可以達到 $O(n + k)$ 。 + +### 平行計算最佳化 + +我們知道,分治生成的子問題是相互獨立的,**因此通常可以並行解決**。也就是說,分治不僅可以降低演算法的時間複雜度,**還有利於作業系統的並行最佳化**。 + +並行最佳化在多核或多處理器的環境中尤其有效,因為系統可以同時處理多個子問題,更加充分地利用計算資源,從而顯著減少總體的執行時間。 + +比如在下圖所示的“桶排序”中,我們將海量的資料平均分配到各個桶中,則可所有桶的排序任務分散到各個計算單元,完成後再合併結果。 + +![桶排序的平行計算](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png) + +## 分治常見應用 + +一方面,分治可以用來解決許多經典演算法問題。 + +- **尋找最近點對**:該演算法首先將點集分成兩部分,然後分別找出兩部分中的最近點對,最後找出跨越兩部分的最近點對。 +- **大整數乘法**:例如 Karatsuba 演算法,它將大整數乘法分解為幾個較小的整數的乘法和加法。 +- **矩陣乘法**:例如 Strassen 演算法,它將大矩陣乘法分解為多個小矩陣的乘法和加法。 +- **河內塔問題**:河內塔問題可以透過遞迴解決,這是典型的分治策略應用。 +- **求解逆序對**:在一個序列中,如果前面的數字大於後面的數字,那麼這兩個數字構成一個逆序對。求解逆序對問題可以利用分治的思想,藉助合併排序進行求解。 + +另一方面,分治在演算法和資料結構的設計中應用得非常廣泛。 + +- **二分搜尋**:二分搜尋是將有序陣列從中點索引處分為兩部分,然後根據目標值與中間元素值比較結果,決定排除哪一半區間,並在剩餘區間執行相同的二分操作。 +- **合併排序**:本節開頭已介紹,不再贅述。 +- **快速排序**:快速排序是選取一個基準值,然後把陣列分為兩個子陣列,一個子陣列的元素比基準值小,另一子陣列的元素比基準值大,再對這兩部分進行相同的劃分操作,直至子陣列只剩下一個元素。 +- **桶排序**:桶排序的基本思想是將資料分散到多個桶,然後對每個桶內的元素進行排序,最後將各個桶的元素依次取出,從而得到一個有序陣列。 +- **樹**:例如二元搜尋樹、AVL 樹、紅黑樹、B 樹、B+ 樹等,它們的查詢、插入和刪除等操作都可以視為分治策略的應用。 +- **堆積**:堆積是一種特殊的完全二元樹,其各種操作,如插入、刪除和堆積化,實際上都隱含了分治的思想。 +- **雜湊表**:雖然雜湊表並不直接應用分治,但某些雜湊衝突解決方案間接應用了分治策略,例如,鏈式位址中的長鏈結串列會被轉化為紅黑樹,以提升查詢效率。 + +可以看出,**分治是一種“潤物細無聲”的演算法思想**,隱含在各種演算法與資料結構之中。 diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png new file mode 100644 index 000000000..5ef2874ef Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png new file mode 100644 index 000000000..f5444db7b Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png new file mode 100644 index 000000000..a12e038b4 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png new file mode 100644 index 000000000..0629abb3b Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png new file mode 100644 index 000000000..976d31afc Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png new file mode 100644 index 000000000..97971e5c6 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png new file mode 100644 index 000000000..87c379cad Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png new file mode 100644 index 000000000..768181a80 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png new file mode 100644 index 000000000..8515746c0 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png new file mode 100644 index 000000000..bdf70135d Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png new file mode 100644 index 000000000..999625401 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png new file mode 100644 index 000000000..0fdcb1548 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png new file mode 100644 index 000000000..3f506af59 Binary files /dev/null and b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png differ diff --git a/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.md b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.md new file mode 100644 index 000000000..32d94e2a3 --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/hanota_problem.md @@ -0,0 +1,97 @@ +# 河內塔問題 + +在合併排序和構建二元樹中,我們都是將原問題分解為兩個規模為原問題一半的子問題。然而對於河內塔問題,我們採用不同的分解策略。 + +!!! question + + 給定三根柱子,記為 `A`、`B` 和 `C` 。起始狀態下,柱子 `A` 上套著 $n$ 個圓盤,它們從上到下按照從小到大的順序排列。我們的任務是要把這 $n$ 個圓盤移到柱子 `C` 上,並保持它們的原有順序不變(如下圖所示)。在移動圓盤的過程中,需要遵守以下規則。 + + 1. 圓盤只能從一根柱子頂部拿出,從另一根柱子頂部放入。 + 2. 每次只能移動一個圓盤。 + 3. 小圓盤必須時刻位於大圓盤之上。 + +![河內塔問題示例](hanota_problem.assets/hanota_example.png) + +**我們將規模為 $i$ 的河內塔問題記作 $f(i)$** 。例如 $f(3)$ 代表將 $3$ 個圓盤從 `A` 移動至 `C` 的河內塔問題。 + +### 考慮基本情況 + +如下圖所示,對於問題 $f(1)$ ,即當只有一個圓盤時,我們將它直接從 `A` 移動至 `C` 即可。 + +=== "<1>" + ![規模為 1 的問題的解](hanota_problem.assets/hanota_f1_step1.png) + +=== "<2>" + ![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png) + +如下圖所示,對於問題 $f(2)$ ,即當有兩個圓盤時,**由於要時刻滿足小圓盤在大圓盤之上,因此需要藉助 `B` 來完成移動**。 + +1. 先將上面的小圓盤從 `A` 移至 `B` 。 +2. 再將大圓盤從 `A` 移至 `C` 。 +3. 最後將小圓盤從 `B` 移至 `C` 。 + +=== "<1>" + ![規模為 2 的問題的解](hanota_problem.assets/hanota_f2_step1.png) + +=== "<2>" + ![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png) + +=== "<3>" + ![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png) + +=== "<4>" + ![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png) + +解決問題 $f(2)$ 的過程可總結為:**將兩個圓盤藉助 `B` 從 `A` 移至 `C`** 。其中,`C` 稱為目標柱、`B` 稱為緩衝柱。 + +### 子問題分解 + +對於問題 $f(3)$ ,即當有三個圓盤時,情況變得稍微複雜了一些。 + +因為已知 $f(1)$ 和 $f(2)$ 的解,所以我們可從分治角度思考,**將 `A` 頂部的兩個圓盤看作一個整體**,執行下圖所示的步驟。這樣三個圓盤就被順利地從 `A` 移至 `C` 了。 + +1. 令 `B` 為目標柱、`C` 為緩衝柱,將兩個圓盤從 `A` 移至 `B` 。 +2. 將 `A` 中剩餘的一個圓盤從 `A` 直接移動至 `C` 。 +3. 令 `C` 為目標柱、`A` 為緩衝柱,將兩個圓盤從 `B` 移至 `C` 。 + +=== "<1>" + ![規模為 3 的問題的解](hanota_problem.assets/hanota_f3_step1.png) + +=== "<2>" + ![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png) + +=== "<3>" + ![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png) + +=== "<4>" + ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) + +從本質上看,**我們將問題 $f(3)$ 劃分為兩個子問題 $f(2)$ 和一個子問題 $f(1)$** 。按順序解決這三個子問題之後,原問題隨之得到解決。這說明子問題是獨立的,而且解可以合併。 + +至此,我們可總結出下圖所示的解決河內塔問題的分治策略:將原問題 $f(n)$ 劃分為兩個子問題 $f(n-1)$ 和一個子問題 $f(1)$ ,並按照以下順序解決這三個子問題。 + +1. 將 $n-1$ 個圓盤藉助 `C` 從 `A` 移至 `B` 。 +2. 將剩餘 $1$ 個圓盤從 `A` 直接移至 `C` 。 +3. 將 $n-1$ 個圓盤藉助 `A` 從 `B` 移至 `C` 。 + +對於這兩個子問題 $f(n-1)$ ,**可以透過相同的方式進行遞迴劃分**,直至達到最小子問題 $f(1)$ 。而 $f(1)$ 的解是已知的,只需一次移動操作即可。 + +![解決河內塔問題的分治策略](hanota_problem.assets/hanota_divide_and_conquer.png) + +### 程式碼實現 + +在程式碼中,我們宣告一個遞迴函式 `dfs(i, src, buf, tar)` ,它的作用是將柱 `src` 頂部的 $i$ 個圓盤藉助緩衝柱 `buf` 移動至目標柱 `tar` : + +```src +[file]{hanota}-[class]{}-[func]{solve_hanota} +``` + +如下圖所示,河內塔問題形成一棵高度為 $n$ 的遞迴樹,每個節點代表一個子問題,對應一個開啟的 `dfs()` 函式,**因此時間複雜度為 $O(2^n)$ ,空間複雜度為 $O(n)$** 。 + +![河內塔問題的遞迴樹](hanota_problem.assets/hanota_recursive_tree.png) + +!!! quote + + 河內塔問題源自一個古老的傳說。在古印度的一個寺廟裡,僧侶們有三根高大的鑽石柱子,以及 $64$ 個大小不一的金圓盤。僧侶們不斷地移動圓盤,他們相信在最後一個圓盤被正確放置的那一刻,這個世界就會結束。 + + 然而,即使僧侶們每秒鐘移動一次,總共需要大約 $2^{64} \approx 1.84×10^{19}$ 秒,合約 $5850$ 億年,遠遠超過了現在對宇宙年齡的估計。所以,倘若這個傳說是真的,我們應該不需要擔心世界末日的到來。 diff --git a/zh-hant/docs/chapter_divide_and_conquer/index.md b/zh-hant/docs/chapter_divide_and_conquer/index.md new file mode 100644 index 000000000..64b3009f2 --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/index.md @@ -0,0 +1,9 @@ +# 分治 + +![分治](../assets/covers/chapter_divide_and_conquer.jpg) + +!!! abstract + + 難題被逐層拆解,每一次的拆解都使它變得更為簡單。 + + 分而治之揭示了一個重要的事實:從簡單做起,一切都不再複雜。 diff --git a/zh-hant/docs/chapter_divide_and_conquer/summary.md b/zh-hant/docs/chapter_divide_and_conquer/summary.md new file mode 100644 index 000000000..8d544ce44 --- /dev/null +++ b/zh-hant/docs/chapter_divide_and_conquer/summary.md @@ -0,0 +1,11 @@ +# 小結 + +- 分治是一種常見的演算法設計策略,包括分(劃分)和治(合併)兩個階段,通常基於遞迴實現。 +- 判斷是否是分治演算法問題的依據包括:問題能否分解、子問題是否獨立、子問題能否合併。 +- 合併排序是分治策略的典型應用,其遞迴地將陣列劃分為等長的兩個子陣列,直到只剩一個元素時開始逐層合併,從而完成排序。 +- 引入分治策略往往可以提升演算法效率。一方面,分治策略減少了操作數量;另一方面,分治後有利於系統的並行最佳化。 +- 分治既可以解決許多演算法問題,也廣泛應用於資料結構與演算法設計中,處處可見其身影。 +- 相較於暴力搜尋,自適應搜尋效率更高。時間複雜度為 $O(\log n)$ 的搜尋演算法通常是基於分治策略實現的。 +- 二分搜尋是分治策略的另一個典型應用,它不包含將子問題的解進行合併的步驟。我們可以透過遞迴分治實現二分搜尋。 +- 在構建二元樹的問題中,構建樹(原問題)可以劃分為構建左子樹和右子樹(子問題),這可以透過劃分前序走訪和中序走訪的索引區間來實現。 +- 在河內塔問題中,一個規模為 $n$ 的問題可以劃分為兩個規模為 $n-1$ 的子問題和一個規模為 $1$ 的子問題。按順序解決這三個子問題後,原問題隨之得到解決。 diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png new file mode 100644 index 000000000..7fcd37781 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png new file mode 100644 index 000000000..5f560113a Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png new file mode 100644 index 000000000..e33064aa0 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png new file mode 100644 index 000000000..ce8307c2c Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.md b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.md new file mode 100644 index 000000000..feda346f2 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/dp_problem_features.md @@ -0,0 +1,101 @@ +# 動態規劃問題特性 + +在上一節中,我們學習了動態規劃是如何透過子問題分解來求解原問題的。實際上,子問題分解是一種通用的演算法思路,在分治、動態規劃、回溯中的側重點不同。 + +- 分治演算法遞迴地將原問題劃分為多個相互獨立的子問題,直至最小子問題,並在回溯中合併子問題的解,最終得到原問題的解。 +- 動態規劃也對問題進行遞迴分解,但與分治演算法的主要區別是,動態規劃中的子問題是相互依賴的,在分解過程中會出現許多重疊子問題。 +- 回溯演算法在嘗試和回退中窮舉所有可能的解,並透過剪枝避免不必要的搜尋分支。原問題的解由一系列決策步驟構成,我們可以將每個決策步驟之前的子序列看作一個子問題。 + +實際上,動態規劃常用來求解最最佳化問題,它們不僅包含重疊子問題,還具有另外兩大特性:最優子結構、無後效性。 + +## 最優子結構 + +我們對爬樓梯問題稍作改動,使之更加適合展示最優子結構概念。 + +!!! question "爬樓梯最小代價" + + 給定一個樓梯,你每步可以上 $1$ 階或者 $2$ 階,每一階樓梯上都貼有一個非負整數,表示你在該臺階所需要付出的代價。給定一個非負整數陣列 $cost$ ,其中 $cost[i]$ 表示在第 $i$ 個臺階需要付出的代價,$cost[0]$ 為地面(起始點)。請計算最少需要付出多少代價才能到達頂部? + +如下圖所示,若第 $1$、$2$、$3$ 階的代價分別為 $1$、$10$、$1$ ,則從地面爬到第 $3$ 階的最小代價為 $2$ 。 + +![爬到第 3 階的最小代價](dp_problem_features.assets/min_cost_cs_example.png) + +設 $dp[i]$ 為爬到第 $i$ 階累計付出的代價,由於第 $i$ 階只可能從 $i - 1$ 階或 $i - 2$ 階走來,因此 $dp[i]$ 只可能等於 $dp[i - 1] + cost[i]$ 或 $dp[i - 2] + cost[i]$ 。為了儘可能減少代價,我們應該選擇兩者中較小的那一個: + +$$ +dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] +$$ + +這便可以引出最優子結構的含義:**原問題的最優解是從子問題的最優解構建得來的**。 + +本題顯然具有最優子結構:我們從兩個子問題最優解 $dp[i-1]$ 和 $dp[i-2]$ 中挑選出較優的那一個,並用它構建出原問題 $dp[i]$ 的最優解。 + +那麼,上一節的爬樓梯題目有沒有最優子結構呢?它的目標是求解方案數量,看似是一個計數問題,但如果換一種問法:“求解最大方案數量”。我們意外地發現,**雖然題目修改前後是等價的,但最優子結構浮現出來了**:第 $n$ 階最大方案數量等於第 $n-1$ 階和第 $n-2$ 階最大方案數量之和。所以說,最優子結構的解釋方式比較靈活,在不同問題中會有不同的含義。 + +根據狀態轉移方程,以及初始狀態 $dp[1] = cost[1]$ 和 $dp[2] = cost[2]$ ,我們就可以得到動態規劃程式碼: + +```src +[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp} +``` + +下圖展示了以上程式碼的動態規劃過程。 + +![爬樓梯最小代價的動態規劃過程](dp_problem_features.assets/min_cost_cs_dp.png) + +本題也可以進行空間最佳化,將一維壓縮至零維,使得空間複雜度從 $O(n)$ 降至 $O(1)$ : + +```src +[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp} +``` + +## 無後效性 + +無後效性是動態規劃能夠有效解決問題的重要特性之一,其定義為:**給定一個確定的狀態,它的未來發展只與當前狀態有關,而與過去經歷的所有狀態無關**。 + +以爬樓梯問題為例,給定狀態 $i$ ,它會發展出狀態 $i+1$ 和狀態 $i+2$ ,分別對應跳 $1$ 步和跳 $2$ 步。在做出這兩種選擇時,我們無須考慮狀態 $i$ 之前的狀態,它們對狀態 $i$ 的未來沒有影響。 + +然而,如果我們給爬樓梯問題新增一個約束,情況就不一樣了。 + +!!! question "帶約束爬樓梯" + + 給定一個共有 $n$ 階的樓梯,你每步可以上 $1$ 階或者 $2$ 階,**但不能連續兩輪跳 $1$ 階**,請問有多少種方案可以爬到樓頂? + +如下圖所示,爬上第 $3$ 階僅剩 $2$ 種可行方案,其中連續三次跳 $1$ 階的方案不滿足約束條件,因此被捨棄。 + +![帶約束爬到第 3 階的方案數量](dp_problem_features.assets/climbing_stairs_constraint_example.png) + +在該問題中,如果上一輪是跳 $1$ 階上來的,那麼下一輪就必須跳 $2$ 階。這意味著,**下一步選擇不能由當前狀態(當前所在樓梯階數)獨立決定,還和前一個狀態(上一輪所在樓梯階數)有關**。 + +不難發現,此問題已不滿足無後效性,狀態轉移方程 $dp[i] = dp[i-1] + dp[i-2]$ 也失效了,因為 $dp[i-1]$ 代表本輪跳 $1$ 階,但其中包含了許多“上一輪是跳 $1$ 階上來的”方案,而為了滿足約束,我們就不能將 $dp[i-1]$ 直接計入 $dp[i]$ 中。 + +為此,我們需要擴展狀態定義:**狀態 $[i, j]$ 表示處在第 $i$ 階並且上一輪跳了 $j$ 階**,其中 $j \in \{1, 2\}$ 。此狀態定義有效地區分了上一輪跳了 $1$ 階還是 $2$ 階,我們可以據此判斷當前狀態是從何而來的。 + +- 當上一輪跳了 $1$ 階時,上上一輪只能選擇跳 $2$ 階,即 $dp[i, 1]$ 只能從 $dp[i-1, 2]$ 轉移過來。 +- 當上一輪跳了 $2$ 階時,上上一輪可選擇跳 $1$ 階或跳 $2$ 階,即 $dp[i, 2]$ 可以從 $dp[i-2, 1]$ 或 $dp[i-2, 2]$ 轉移過來。 + +如下圖所示,在該定義下,$dp[i, j]$ 表示狀態 $[i, j]$ 對應的方案數。此時狀態轉移方程為: + +$$ +\begin{cases} +dp[i, 1] = dp[i-1, 2] \\ +dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] +\end{cases} +$$ + +![考慮約束下的遞推關係](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) + +最終,返回 $dp[n, 1] + dp[n, 2]$ 即可,兩者之和代表爬到第 $n$ 階的方案總數: + +```src +[file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp} +``` + +在上面的案例中,由於僅需多考慮前面一個狀態,因此我們仍然可以透過擴展狀態定義,使得問題重新滿足無後效性。然而,某些問題具有非常嚴重的“有後效性”。 + +!!! question "爬樓梯與障礙生成" + + 給定一個共有 $n$ 階的樓梯,你每步可以上 $1$ 階或者 $2$ 階。**規定當爬到第 $i$ 階時,系統自動會在第 $2i$ 階上放上障礙物,之後所有輪都不允許跳到第 $2i$ 階上**。例如,前兩輪分別跳到了第 $2$、$3$ 階上,則之後就不能跳到第 $4$、$6$ 階上。請問有多少種方案可以爬到樓頂? + +在這個問題中,下次跳躍依賴過去所有的狀態,因為每一次跳躍都會在更高的階梯上設定障礙,並影響未來的跳躍。對於這類問題,動態規劃往往難以解決。 + +實際上,許多複雜的組合最佳化問題(例如旅行商問題)不滿足無後效性。對於這類問題,我們通常會選擇使用其他方法,例如啟發式搜尋、遺傳演算法、強化學習等,從而在有限時間內得到可用的區域性最優解。 diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png new file mode 100644 index 000000000..fd70c4914 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png new file mode 100644 index 000000000..a3fb1495e Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png new file mode 100644 index 000000000..2b5371622 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png new file mode 100644 index 000000000..30a3d2b04 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png new file mode 100644 index 000000000..a7f613d8a Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png new file mode 100644 index 000000000..192c6b547 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png new file mode 100644 index 000000000..092043385 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png new file mode 100644 index 000000000..6dd24e3bd Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png new file mode 100644 index 000000000..0f5a2cad1 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png new file mode 100644 index 000000000..003ebcd43 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png new file mode 100644 index 000000000..6cf30ae69 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png new file mode 100644 index 000000000..524b36b3c Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png new file mode 100644 index 000000000..571e36679 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png new file mode 100644 index 000000000..352fc8c82 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png new file mode 100644 index 000000000..bd5f3771e Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png new file mode 100644 index 000000000..b0694aea2 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png new file mode 100644 index 000000000..9edfa8f5e Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png new file mode 100644 index 000000000..bd1004eab Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.md new file mode 100644 index 000000000..7750266f9 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/dp_solution_pipeline.md @@ -0,0 +1,183 @@ +# 動態規劃解題思路 + +上兩節介紹了動態規劃問題的主要特徵,接下來我們一起探究兩個更加實用的問題。 + +1. 如何判斷一個問題是不是動態規劃問題? +2. 求解動態規劃問題該從何處入手,完整步驟是什麼? + +## 問題判斷 + +總的來說,如果一個問題包含重疊子問題、最優子結構,並滿足無後效性,那麼它通常適合用動態規劃求解。然而,我們很難從問題描述中直接提取出這些特性。因此我們通常會放寬條件,**先觀察問題是否適合使用回溯(窮舉)解決**。 + +**適合用回溯解決的問題通常滿足“決策樹模型”**,這種問題可以使用樹形結構來描述,其中每一個節點代表一個決策,每一條路徑代表一個決策序列。 + +換句話說,如果問題包含明確的決策概念,並且解是透過一系列決策產生的,那麼它就滿足決策樹模型,通常可以使用回溯來解決。 + +在此基礎上,動態規劃問題還有一些判斷的“加分項”。 + +- 問題包含最大(小)或最多(少)等最最佳化描述。 +- 問題的狀態能夠使用一個串列、多維矩陣或樹來表示,並且一個狀態與其周圍的狀態存在遞推關係。 + +相應地,也存在一些“減分項”。 + +- 問題的目標是找出所有可能的解決方案,而不是找出最優解。 +- 問題描述中有明顯的排列組合的特徵,需要返回具體的多個方案。 + +如果一個問題滿足決策樹模型,並具有較為明顯的“加分項”,我們就可以假設它是一個動態規劃問題,並在求解過程中驗證它。 + +## 問題求解步驟 + +動態規劃的解題流程會因問題的性質和難度而有所不同,但通常遵循以下步驟:描述決策,定義狀態,建立 $dp$ 表,推導狀態轉移方程,確定邊界條件等。 + +為了更形象地展示解題步驟,我們使用一個經典問題“最小路徑和”來舉例。 + +!!! question + + 給定一個 $n \times m$ 的二維網格 `grid` ,網格中的每個單元格包含一個非負整數,表示該單元格的代價。機器人以左上角單元格為起始點,每次只能向下或者向右移動一步,直至到達右下角單元格。請返回從左上角到右下角的最小路徑和。 + +下圖展示了一個例子,給定網格的最小路徑和為 $13$ 。 + +![最小路徑和示例資料](dp_solution_pipeline.assets/min_path_sum_example.png) + +**第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** + +本題的每一輪的決策就是從當前格子向下或向右走一步。設當前格子的行列索引為 $[i, j]$ ,則向下或向右走一步後,索引變為 $[i+1, j]$ 或 $[i, j+1]$ 。因此,狀態應包含行索引和列索引兩個變數,記為 $[i, j]$ 。 + +狀態 $[i, j]$ 對應的子問題為:從起始點 $[0, 0]$ 走到 $[i, j]$ 的最小路徑和,解記為 $dp[i, j]$ 。 + +至此,我們就得到了下圖所示的二維 $dp$ 矩陣,其尺寸與輸入網格 $grid$ 相同。 + +![狀態定義與 dp 表](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) + +!!! note + + 動態規劃和回溯過程可以描述為一個決策序列,而狀態由所有決策變數構成。它應當包含描述解題進度的所有變數,其包含了足夠的資訊,能夠用來推導出下一個狀態。 + + 每個狀態都對應一個子問題,我們會定義一個 $dp$ 表來儲存所有子問題的解,狀態的每個獨立變數都是 $dp$ 表的一個維度。從本質上看,$dp$ 表是狀態和子問題的解之間的對映。 + +**第二步:找出最優子結構,進而推導出狀態轉移方程** + +對於狀態 $[i, j]$ ,它只能從上邊格子 $[i-1, j]$ 和左邊格子 $[i, j-1]$ 轉移而來。因此最優子結構為:到達 $[i, j]$ 的最小路徑和由 $[i, j-1]$ 的最小路徑和與 $[i-1, j]$ 的最小路徑和中較小的那一個決定。 + +根據以上分析,可推出下圖所示的狀態轉移方程: + +$$ +dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] +$$ + +![最優子結構與狀態轉移方程](dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png) + +!!! note + + 根據定義好的 $dp$ 表,思考原問題和子問題的關係,找出透過子問題的最優解來構造原問題的最優解的方法,即最優子結構。 + + 一旦我們找到了最優子結構,就可以使用它來構建出狀態轉移方程。 + +**第三步:確定邊界條件和狀態轉移順序** + +在本題中,處在首行的狀態只能從其左邊的狀態得來,處在首列的狀態只能從其上邊的狀態得來,因此首行 $i = 0$ 和首列 $j = 0$ 是邊界條件。 + +如下圖所示,由於每個格子是由其左方格子和上方格子轉移而來,因此我們使用迴圈來走訪矩陣,外迴圈走訪各行,內迴圈走訪各列。 + +![邊界條件與狀態轉移順序](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) + +!!! note + + 邊界條件在動態規劃中用於初始化 $dp$ 表,在搜尋中用於剪枝。 + + 狀態轉移順序的核心是要保證在計算當前問題的解時,所有它依賴的更小子問題的解都已經被正確地計算出來。 + +根據以上分析,我們已經可以直接寫出動態規劃程式碼。然而子問題分解是一種從頂至底的思想,因此按照“暴力搜尋 $\rightarrow$ 記憶化搜尋 $\rightarrow$ 動態規劃”的順序實現更加符合思維習慣。 + +### 方法一:暴力搜尋 + +從狀態 $[i, j]$ 開始搜尋,不斷分解為更小的狀態 $[i-1, j]$ 和 $[i, j-1]$ ,遞迴函式包括以下要素。 + +- **遞迴參數**:狀態 $[i, j]$ 。 +- **返回值**:從 $[0, 0]$ 到 $[i, j]$ 的最小路徑和 $dp[i, j]$ 。 +- **終止條件**:當 $i = 0$ 且 $j = 0$ 時,返回代價 $grid[0, 0]$ 。 +- **剪枝**:當 $i < 0$ 時或 $j < 0$ 時索引越界,此時返回代價 $+\infty$ ,代表不可行。 + +實現程式碼如下: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs} +``` + +下圖給出了以 $dp[2, 1]$ 為根節點的遞迴樹,其中包含一些重疊子問題,其數量會隨著網格 `grid` 的尺寸變大而急劇增多。 + +從本質上看,造成重疊子問題的原因為:**存在多條路徑可以從左上角到達某一單元格**。 + +![暴力搜尋遞迴樹](dp_solution_pipeline.assets/min_path_sum_dfs.png) + +每個狀態都有向下和向右兩種選擇,從左上角走到右下角總共需要 $m + n - 2$ 步,所以最差時間複雜度為 $O(2^{m + n})$ 。請注意,這種計算方式未考慮臨近網格邊界的情況,當到達網路邊界時只剩下一種選擇,因此實際的路徑數量會少一些。 + +### 方法二:記憶化搜尋 + +我們引入一個和網格 `grid` 相同尺寸的記憶串列 `mem` ,用於記錄各個子問題的解,並將重疊子問題進行剪枝: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem} +``` + +如下圖所示,在引入記憶化後,所有子問題的解只需計算一次,因此時間複雜度取決於狀態總數,即網格尺寸 $O(nm)$ 。 + +![記憶化搜尋遞迴樹](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) + +### 方法三:動態規劃 + +基於迭代實現動態規劃解法,程式碼如下所示: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp} +``` + +下圖展示了最小路徑和的狀態轉移過程,其走訪了整個網格,**因此時間複雜度為 $O(nm)$** 。 + +陣列 `dp` 大小為 $n \times m$ ,**因此空間複雜度為 $O(nm)$** 。 + +=== "<1>" + ![最小路徑和的動態規劃過程](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) + +=== "<2>" + ![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png) + +=== "<3>" + ![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png) + +=== "<4>" + ![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png) + +=== "<5>" + ![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png) + +=== "<6>" + ![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png) + +=== "<7>" + ![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png) + +=== "<8>" + ![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png) + +=== "<9>" + ![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png) + +=== "<10>" + ![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png) + +=== "<11>" + ![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png) + +=== "<12>" + ![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png) + +### 空間最佳化 + +由於每個格子只與其左邊和上邊的格子有關,因此我們可以只用一個單行陣列來實現 $dp$ 表。 + +請注意,因為陣列 `dp` 只能表示一行的狀態,所以我們無法提前初始化首列狀態,而是在走訪每行時更新它: + +```src +[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp} +``` diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png new file mode 100644 index 000000000..76ee37f36 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png new file mode 100644 index 000000000..d6a10bc76 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png new file mode 100644 index 000000000..982847c37 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png new file mode 100644 index 000000000..bf98575c8 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png new file mode 100644 index 000000000..9be20e17d Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png new file mode 100644 index 000000000..1eed240b3 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png new file mode 100644 index 000000000..303b87613 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png new file mode 100644 index 000000000..8d69b05f5 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png new file mode 100644 index 000000000..75004aace Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png new file mode 100644 index 000000000..1a57f31be Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png new file mode 100644 index 000000000..75a7be8d1 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png new file mode 100644 index 000000000..cfaf189db Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png new file mode 100644 index 000000000..b46874db0 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png new file mode 100644 index 000000000..2f7d8fd54 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png new file mode 100644 index 000000000..c9c5f9b28 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png new file mode 100644 index 000000000..32abb4511 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png new file mode 100644 index 000000000..dcf922251 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png new file mode 100644 index 000000000..d075b54cc Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.md b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.md new file mode 100644 index 000000000..d8d8e407a --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/edit_distance_problem.md @@ -0,0 +1,129 @@ +# 編輯距離問題 + +編輯距離,也稱 Levenshtein 距離,指兩個字串之間互相轉換的最少修改次數,通常用於在資訊檢索和自然語言處理中度量兩個序列的相似度。 + +!!! question + + 輸入兩個字串 $s$ 和 $t$ ,返回將 $s$ 轉換為 $t$ 所需的最少編輯步數。 + + 你可以在一個字串中進行三種編輯操作:插入一個字元、刪除一個字元、將字元替換為任意一個字元。 + +如下圖所示,將 `kitten` 轉換為 `sitting` 需要編輯 3 步,包括 2 次替換操作與 1 次新增操作;將 `hello` 轉換為 `algo` 需要 3 步,包括 2 次替換操作和 1 次刪除操作。 + +![編輯距離的示例資料](edit_distance_problem.assets/edit_distance_example.png) + +**編輯距離問題可以很自然地用決策樹模型來解釋**。字串對應樹節點,一輪決策(一次編輯操作)對應樹的一條邊。 + +如下圖所示,在不限制操作的情況下,每個節點都可以派生出許多條邊,每條邊對應一種操作,這意味著從 `hello` 轉換到 `algo` 有許多種可能的路徑。 + +從決策樹的角度看,本題的目標是求解節點 `hello` 和節點 `algo` 之間的最短路徑。 + +![基於決策樹模型表示編輯距離問題](edit_distance_problem.assets/edit_distance_decision_tree.png) + +### 動態規劃思路 + +**第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** + +每一輪的決策是對字串 $s$ 進行一次編輯操作。 + +我們希望在編輯操作的過程中,問題的規模逐漸縮小,這樣才能構建子問題。設字串 $s$ 和 $t$ 的長度分別為 $n$ 和 $m$ ,我們先考慮兩字串尾部的字元 $s[n-1]$ 和 $t[m-1]$ 。 + +- 若 $s[n-1]$ 和 $t[m-1]$ 相同,我們可以跳過它們,直接考慮 $s[n-2]$ 和 $t[m-2]$ 。 +- 若 $s[n-1]$ 和 $t[m-1]$ 不同,我們需要對 $s$ 進行一次編輯(插入、刪除、替換),使得兩字串尾部的字元相同,從而可以跳過它們,考慮規模更小的問題。 + +也就是說,我們在字串 $s$ 中進行的每一輪決策(編輯操作),都會使得 $s$ 和 $t$ 中剩餘的待匹配字元發生變化。因此,狀態為當前在 $s$ 和 $t$ 中考慮的第 $i$ 和第 $j$ 個字元,記為 $[i, j]$ 。 + +狀態 $[i, j]$ 對應的子問題:**將 $s$ 的前 $i$ 個字元更改為 $t$ 的前 $j$ 個字元所需的最少編輯步數**。 + +至此,得到一個尺寸為 $(i+1) \times (j+1)$ 的二維 $dp$ 表。 + +**第二步:找出最優子結構,進而推導出狀態轉移方程** + +考慮子問題 $dp[i, j]$ ,其對應的兩個字串的尾部字元為 $s[i-1]$ 和 $t[j-1]$ ,可根據不同編輯操作分為下圖所示的三種情況。 + +1. 在 $s[i-1]$ 之後新增 $t[j-1]$ ,則剩餘子問題 $dp[i, j-1]$ 。 +2. 刪除 $s[i-1]$ ,則剩餘子問題 $dp[i-1, j]$ 。 +3. 將 $s[i-1]$ 替換為 $t[j-1]$ ,則剩餘子問題 $dp[i-1, j-1]$ 。 + +![編輯距離的狀態轉移](edit_distance_problem.assets/edit_distance_state_transfer.png) + +根據以上分析,可得最優子結構:$dp[i, j]$ 的最少編輯步數等於 $dp[i, j-1]$、$dp[i-1, j]$、$dp[i-1, j-1]$ 三者中的最少編輯步數,再加上本次的編輯步數 $1$ 。對應的狀態轉移方程為: + +$$ +dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 +$$ + +請注意,**當 $s[i-1]$ 和 $t[j-1]$ 相同時,無須編輯當前字元**,這種情況下的狀態轉移方程為: + +$$ +dp[i, j] = dp[i-1, j-1] +$$ + +**第三步:確定邊界條件和狀態轉移順序** + +當兩字串都為空時,編輯步數為 $0$ ,即 $dp[0, 0] = 0$ 。當 $s$ 為空但 $t$ 不為空時,最少編輯步數等於 $t$ 的長度,即首行 $dp[0, j] = j$ 。當 $s$ 不為空但 $t$ 為空時,最少編輯步數等於 $s$ 的長度,即首列 $dp[i, 0] = i$ 。 + +觀察狀態轉移方程,解 $dp[i, j]$ 依賴左方、上方、左上方的解,因此透過兩層迴圈正序走訪整個 $dp$ 表即可。 + +### 程式碼實現 + +```src +[file]{edit_distance}-[class]{}-[func]{edit_distance_dp} +``` + +如下圖所示,編輯距離問題的狀態轉移過程與背包問題非常類似,都可以看作填寫一個二維網格的過程。 + +=== "<1>" + ![編輯距離的動態規劃過程](edit_distance_problem.assets/edit_distance_dp_step1.png) + +=== "<2>" + ![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png) + +=== "<3>" + ![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png) + +=== "<4>" + ![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png) + +=== "<5>" + ![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png) + +=== "<6>" + ![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png) + +=== "<7>" + ![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png) + +=== "<8>" + ![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png) + +=== "<9>" + ![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png) + +=== "<10>" + ![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png) + +=== "<11>" + ![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png) + +=== "<12>" + ![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png) + +=== "<13>" + ![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png) + +=== "<14>" + ![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png) + +=== "<15>" + ![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png) + +### 空間最佳化 + +由於 $dp[i,j]$ 是由上方 $dp[i-1, j]$、左方 $dp[i, j-1]$、左上方 $dp[i-1, j-1]$ 轉移而來的,而正序走訪會丟失左上方 $dp[i-1, j-1]$ ,倒序走訪無法提前構建 $dp[i, j-1]$ ,因此兩種走訪順序都不可取。 + +為此,我們可以使用一個變數 `leftup` 來暫存左上方的解 $dp[i-1, j-1]$ ,從而只需考慮左方和上方的解。此時的情況與完全背包問題相同,可使用正序走訪。程式碼如下所示: + +```src +[file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp} +``` diff --git a/zh-hant/docs/chapter_dynamic_programming/index.md b/zh-hant/docs/chapter_dynamic_programming/index.md new file mode 100644 index 000000000..3df762bd8 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/index.md @@ -0,0 +1,9 @@ +# 動態規劃 + +![動態規劃](../assets/covers/chapter_dynamic_programming.jpg) + +!!! abstract + + 小溪匯入河流,江河匯入大海。 + + 動態規劃將小問題的解彙集成大問題的答案,一步步引領我們走向解決問題的彼岸。 diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png new file mode 100644 index 000000000..deab6c77e Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png new file mode 100644 index 000000000..245e6d025 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png new file mode 100644 index 000000000..ba771e58d Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png new file mode 100644 index 000000000..82d83110c Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png new file mode 100644 index 000000000..229e96f42 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md new file mode 100644 index 000000000..8f4370f50 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -0,0 +1,110 @@ +# 初探動態規劃 + +動態規劃(dynamic programming)是一個重要的演算法範式,它將一個問題分解為一系列更小的子問題,並透過儲存子問題的解來避免重複計算,從而大幅提升時間效率。 + +在本節中,我們從一個經典例題入手,先給出它的暴力回溯解法,觀察其中包含的重疊子問題,再逐步導出更高效的動態規劃解法。 + +!!! question "爬樓梯" + + 給定一個共有 $n$ 階的樓梯,你每步可以上 $1$ 階或者 $2$ 階,請問有多少種方案可以爬到樓頂? + +如下圖所示,對於一個 $3$ 階樓梯,共有 $3$ 種方案可以爬到樓頂。 + +![爬到第 3 階的方案數量](intro_to_dynamic_programming.assets/climbing_stairs_example.png) + +本題的目標是求解方案數量,**我們可以考慮透過回溯來窮舉所有可能性**。具體來說,將爬樓梯想象為一個多輪選擇的過程:從地面出發,每輪選擇上 $1$ 階或 $2$ 階,每當到達樓梯頂部時就將方案數量加 $1$ ,當越過樓梯頂部時就將其剪枝。程式碼如下所示: + +```src +[file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack} +``` + +## 方法一:暴力搜尋 + +回溯演算法通常並不顯式地對問題進行拆解,而是將求解問題看作一系列決策步驟,透過試探和剪枝,搜尋所有可能的解。 + +我們可以嘗試從問題分解的角度分析這道題。設爬到第 $i$ 階共有 $dp[i]$ 種方案,那麼 $dp[i]$ 就是原問題,其子問題包括: + +$$ +dp[i-1], dp[i-2], \dots, dp[2], dp[1] +$$ + +由於每輪只能上 $1$ 階或 $2$ 階,因此當我們站在第 $i$ 階樓梯上時,上一輪只可能站在第 $i - 1$ 階或第 $i - 2$ 階上。換句話說,我們只能從第 $i -1$ 階或第 $i - 2$ 階邁向第 $i$ 階。 + +由此便可得出一個重要推論:**爬到第 $i - 1$ 階的方案數加上爬到第 $i - 2$ 階的方案數就等於爬到第 $i$ 階的方案數**。公式如下: + +$$ +dp[i] = dp[i-1] + dp[i-2] +$$ + +這意味著在爬樓梯問題中,各個子問題之間存在遞推關係,**原問題的解可以由子問題的解構建得來**。下圖展示了該遞推關係。 + +![方案數量遞推關係](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) + +我們可以根據遞推公式得到暴力搜尋解法。以 $dp[n]$ 為起始點,**遞迴地將一個較大問題拆解為兩個較小問題的和**,直至到達最小子問題 $dp[1]$ 和 $dp[2]$ 時返回。其中,最小子問題的解是已知的,即 $dp[1] = 1$、$dp[2] = 2$ ,表示爬到第 $1$、$2$ 階分別有 $1$、$2$ 種方案。 + +觀察以下程式碼,它和標準回溯程式碼都屬於深度優先搜尋,但更加簡潔: + +```src +[file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs} +``` + +下圖展示了暴力搜尋形成的遞迴樹。對於問題 $dp[n]$ ,其遞迴樹的深度為 $n$ ,時間複雜度為 $O(2^n)$ 。指數階屬於爆炸式增長,如果我們輸入一個比較大的 $n$ ,則會陷入漫長的等待之中。 + +![爬樓梯對應遞迴樹](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) + +觀察上圖,**指數階的時間複雜度是“重疊子問題”導致的**。例如 $dp[9]$ 被分解為 $dp[8]$ 和 $dp[7]$ ,$dp[8]$ 被分解為 $dp[7]$ 和 $dp[6]$ ,兩者都包含子問題 $dp[7]$ 。 + +以此類推,子問題中包含更小的重疊子問題,子子孫孫無窮盡也。絕大部分計算資源都浪費在這些重疊的子問題上。 + +## 方法二:記憶化搜尋 + +為了提升演算法效率,**我們希望所有的重疊子問題都只被計算一次**。為此,我們宣告一個陣列 `mem` 來記錄每個子問題的解,並在搜尋過程中將重疊子問題剪枝。 + +1. 當首次計算 $dp[i]$ 時,我們將其記錄至 `mem[i]` ,以便之後使用。 +2. 當再次需要計算 $dp[i]$ 時,我們便可直接從 `mem[i]` 中獲取結果,從而避免重複計算該子問題。 + +程式碼如下所示: + +```src +[file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem} +``` + +觀察下圖,**經過記憶化處理後,所有重疊子問題都只需計算一次,時間複雜度最佳化至 $O(n)$** ,這是一個巨大的飛躍。 + +![記憶化搜尋對應遞迴樹](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) + +## 方法三:動態規劃 + +**記憶化搜尋是一種“從頂至底”的方法**:我們從原問題(根節點)開始,遞迴地將較大子問題分解為較小子問題,直至解已知的最小子問題(葉節點)。之後,透過回溯逐層收集子問題的解,構建出原問題的解。 + +與之相反,**動態規劃是一種“從底至頂”的方法**:從最小子問題的解開始,迭代地構建更大子問題的解,直至得到原問題的解。 + +由於動態規劃不包含回溯過程,因此只需使用迴圈迭代實現,無須使用遞迴。在以下程式碼中,我們初始化一個陣列 `dp` 來儲存子問題的解,它起到了與記憶化搜尋中陣列 `mem` 相同的記錄作用: + +```src +[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp} +``` + +下圖模擬了以上程式碼的執行過程。 + +![爬樓梯的動態規劃過程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) + +與回溯演算法一樣,動態規劃也使用“狀態”概念來表示問題求解的特定階段,每個狀態都對應一個子問題以及相應的區域性最優解。例如,爬樓梯問題的狀態定義為當前所在樓梯階數 $i$ 。 + +根據以上內容,我們可以總結出動態規劃的常用術語。 + +- 將陣列 `dp` 稱為 dp 表,$dp[i]$ 表示狀態 $i$ 對應子問題的解。 +- 將最小子問題對應的狀態(第 $1$ 階和第 $2$ 階樓梯)稱為初始狀態。 +- 將遞推公式 $dp[i] = dp[i-1] + dp[i-2]$ 稱為狀態轉移方程。 + +## 空間最佳化 + +細心的讀者可能發現了,**由於 $dp[i]$ 只與 $dp[i-1]$ 和 $dp[i-2]$ 有關,因此我們無須使用一個陣列 `dp` 來儲存所有子問題的解**,而只需兩個變數滾動前進即可。程式碼如下所示: + +```src +[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp} +``` + +觀察以上程式碼,由於省去了陣列 `dp` 佔用的空間,因此空間複雜度從 $O(n)$ 降至 $O(1)$ 。 + +在動態規劃問題中,當前狀態往往僅與前面有限個狀態有關,這時我們可以只保留必要的狀態,透過“降維”來節省記憶體空間。**這種空間最佳化技巧被稱為“滾動變數”或“滾動陣列”**。 diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png new file mode 100644 index 000000000..befc33d17 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png new file mode 100644 index 000000000..362b04ce4 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png new file mode 100644 index 000000000..32f53845c Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png new file mode 100644 index 000000000..e70abe4db Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png new file mode 100644 index 000000000..16497b8af Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png new file mode 100644 index 000000000..debbdd1f1 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png new file mode 100644 index 000000000..025faf391 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png new file mode 100644 index 000000000..5231c4ecd Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png new file mode 100644 index 000000000..e66acd5e8 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png new file mode 100644 index 000000000..4fb2fb96d Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png new file mode 100644 index 000000000..0e605b661 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png new file mode 100644 index 000000000..8c7878bcd Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png new file mode 100644 index 000000000..fc73a7be9 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png new file mode 100644 index 000000000..dfd4123c6 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png new file mode 100644 index 000000000..d22578053 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png new file mode 100644 index 000000000..bcca901ee Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png new file mode 100644 index 000000000..9601a7804 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png new file mode 100644 index 000000000..7221771cb Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png new file mode 100644 index 000000000..73f25bacf Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png new file mode 100644 index 000000000..079a713fe Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png new file mode 100644 index 000000000..d4e45a1e1 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png new file mode 100644 index 000000000..c04dd8f93 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png new file mode 100644 index 000000000..88a4a4d79 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.md b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.md new file mode 100644 index 000000000..ef83ff6d0 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/knapsack_problem.md @@ -0,0 +1,168 @@ +# 0-1 背包問題 + +背包問題是一個非常好的動態規劃入門題目,是動態規劃中最常見的問題形式。其具有很多變種,例如 0-1 背包問題、完全背包問題、多重背包問題等。 + +在本節中,我們先來求解最常見的 0-1 背包問題。 + +!!! question + + 給定 $n$ 個物品,第 $i$ 個物品的重量為 $wgt[i-1]$、價值為 $val[i-1]$ ,和一個容量為 $cap$ 的背包。每個物品只能選擇一次,問在限定背包容量下能放入物品的最大價值。 + +觀察下圖,由於物品編號 $i$ 從 $1$ 開始計數,陣列索引從 $0$ 開始計數,因此物品 $i$ 對應重量 $wgt[i-1]$ 和價值 $val[i-1]$ 。 + +![0-1 背包的示例資料](knapsack_problem.assets/knapsack_example.png) + +我們可以將 0-1 背包問題看作一個由 $n$ 輪決策組成的過程,對於每個物體都有不放入和放入兩種決策,因此該問題滿足決策樹模型。 + +該問題的目標是求解“在限定背包容量下能放入物品的最大價值”,因此較大機率是一個動態規劃問題。 + +**第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** + +對於每個物品來說,不放入背包,背包容量不變;放入背包,背包容量減小。由此可得狀態定義:當前物品編號 $i$ 和剩餘背包容量 $c$ ,記為 $[i, c]$ 。 + +狀態 $[i, c]$ 對應的子問題為:**前 $i$ 個物品在剩餘容量為 $c$ 的背包中的最大價值**,記為 $dp[i, c]$ 。 + +待求解的是 $dp[n, cap]$ ,因此需要一個尺寸為 $(n+1) \times (cap+1)$ 的二維 $dp$ 表。 + +**第二步:找出最優子結構,進而推導出狀態轉移方程** + +當我們做出物品 $i$ 的決策後,剩餘的是前 $i-1$ 個物品的決策,可分為以下兩種情況。 + +- **不放入物品 $i$** :背包容量不變,狀態變化為 $[i-1, c]$ 。 +- **放入物品 $i$** :背包容量減少 $wgt[i-1]$ ,價值增加 $val[i-1]$ ,狀態變化為 $[i-1, c-wgt[i-1]]$ 。 + +上述分析向我們揭示了本題的最優子結構:**最大價值 $dp[i, c]$ 等於不放入物品 $i$ 和放入物品 $i$ 兩種方案中價值更大的那一個**。由此可推導出狀態轉移方程: + +$$ +dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) +$$ + +需要注意的是,若當前物品重量 $wgt[i - 1]$ 超出剩餘背包容量 $c$ ,則只能選擇不放入背包。 + +**第三步:確定邊界條件和狀態轉移順序** + +當無物品或無剩餘背包容量時最大價值為 $0$ ,即首列 $dp[i, 0]$ 和首行 $dp[0, c]$ 都等於 $0$ 。 + +當前狀態 $[i, c]$ 從上方的狀態 $[i-1, c]$ 和左上方的狀態 $[i-1, c-wgt[i-1]]$ 轉移而來,因此透過兩層迴圈正序走訪整個 $dp$ 表即可。 + +根據以上分析,我們接下來按順序實現暴力搜尋、記憶化搜尋、動態規劃解法。 + +### 方法一:暴力搜尋 + +搜尋程式碼包含以下要素。 + +- **遞迴參數**:狀態 $[i, c]$ 。 +- **返回值**:子問題的解 $dp[i, c]$ 。 +- **終止條件**:當物品編號越界 $i = 0$ 或背包剩餘容量為 $0$ 時,終止遞迴並返回價值 $0$ 。 +- **剪枝**:若當前物品重量超出背包剩餘容量,則只能選擇不放入背包。 + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dfs} +``` + +如下圖所示,由於每個物品都會產生不選和選兩條搜尋分支,因此時間複雜度為 $O(2^n)$ 。 + +觀察遞迴樹,容易發現其中存在重疊子問題,例如 $dp[1, 10]$ 等。而當物品較多、背包容量較大,尤其是相同重量的物品較多時,重疊子問題的數量將會大幅增多。 + +![0-1 背包問題的暴力搜尋遞迴樹](knapsack_problem.assets/knapsack_dfs.png) + +### 方法二:記憶化搜尋 + +為了保證重疊子問題只被計算一次,我們藉助記憶串列 `mem` 來記錄子問題的解,其中 `mem[i][c]` 對應 $dp[i, c]$ 。 + +引入記憶化之後,**時間複雜度取決於子問題數量**,也就是 $O(n \times cap)$ 。實現程式碼如下: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem} +``` + +下圖展示了在記憶化搜尋中被剪掉的搜尋分支。 + +![0-1 背包問題的記憶化搜尋遞迴樹](knapsack_problem.assets/knapsack_dfs_mem.png) + +### 方法三:動態規劃 + +動態規劃實質上就是在狀態轉移中填充 $dp$ 表的過程,程式碼如下所示: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dp} +``` + +如下圖所示,時間複雜度和空間複雜度都由陣列 `dp` 大小決定,即 $O(n \times cap)$ 。 + +=== "<1>" + ![0-1 背包問題的動態規劃過程](knapsack_problem.assets/knapsack_dp_step1.png) + +=== "<2>" + ![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png) + +=== "<3>" + ![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png) + +=== "<4>" + ![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png) + +=== "<5>" + ![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png) + +=== "<6>" + ![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png) + +=== "<7>" + ![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png) + +=== "<8>" + ![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png) + +=== "<9>" + ![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png) + +=== "<10>" + ![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png) + +=== "<11>" + ![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png) + +=== "<12>" + ![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png) + +=== "<13>" + ![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png) + +=== "<14>" + ![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png) + +### 空間最佳化 + +由於每個狀態都只與其上一行的狀態有關,因此我們可以使用兩個陣列滾動前進,將空間複雜度從 $O(n^2)$ 降至 $O(n)$ 。 + +進一步思考,我們能否僅用一個陣列實現空間最佳化呢?觀察可知,每個狀態都是由正上方或左上方的格子轉移過來的。假設只有一個陣列,當開始走訪第 $i$ 行時,該陣列儲存的仍然是第 $i-1$ 行的狀態。 + +- 如果採取正序走訪,那麼走訪到 $dp[i, j]$ 時,左上方 $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ 值可能已經被覆蓋,此時就無法得到正確的狀態轉移結果。 +- 如果採取倒序走訪,則不會發生覆蓋問題,狀態轉移可以正確進行。 + +下圖展示了在單個陣列下從第 $i = 1$ 行轉換至第 $i = 2$ 行的過程。請思考正序走訪和倒序走訪的區別。 + +=== "<1>" + ![0-1 背包的空間最佳化後的動態規劃過程](knapsack_problem.assets/knapsack_dp_comp_step1.png) + +=== "<2>" + ![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png) + +=== "<3>" + ![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png) + +=== "<4>" + ![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png) + +=== "<5>" + ![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png) + +=== "<6>" + ![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png) + +在程式碼實現中,我們僅需將陣列 `dp` 的第一維 $i$ 直接刪除,並且把內迴圈更改為倒序走訪即可: + +```src +[file]{knapsack}-[class]{}-[func]{knapsack_dp_comp} +``` diff --git a/zh-hant/docs/chapter_dynamic_programming/summary.md b/zh-hant/docs/chapter_dynamic_programming/summary.md new file mode 100644 index 000000000..0d85f30b0 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/summary.md @@ -0,0 +1,23 @@ +# 小結 + +- 動態規劃對問題進行分解,並透過儲存子問題的解來規避重複計算,提高計算效率。 +- 不考慮時間的前提下,所有動態規劃問題都可以用回溯(暴力搜尋)進行求解,但遞迴樹中存在大量的重疊子問題,效率極低。透過引入記憶化串列,可以儲存所有計算過的子問題的解,從而保證重疊子問題只被計算一次。 +- 記憶化搜尋是一種從頂至底的遞迴式解法,而與之對應的動態規劃是一種從底至頂的遞推式解法,其如同“填寫表格”一樣。由於當前狀態僅依賴某些區域性狀態,因此我們可以消除 $dp$ 表的一個維度,從而降低空間複雜度。 +- 子問題分解是一種通用的演算法思路,在分治、動態規劃、回溯中具有不同的性質。 +- 動態規劃問題有三大特性:重疊子問題、最優子結構、無後效性。 +- 如果原問題的最優解可以從子問題的最優解構建得來,則它就具有最優子結構。 +- 無後效性指對於一個狀態,其未來發展只與該狀態有關,而與過去經歷的所有狀態無關。許多組合最佳化問題不具有無後效性,無法使用動態規劃快速求解。 + +**背包問題** + +- 背包問題是最典型的動態規劃問題之一,具有 0-1 背包、完全背包、多重背包等變種。 +- 0-1 背包的狀態定義為前 $i$ 個物品在剩餘容量為 $c$ 的背包中的最大價值。根據不放入背包和放入背包兩種決策,可得到最優子結構,並構建出狀態轉移方程。在空間最佳化中,由於每個狀態依賴正上方和左上方的狀態,因此需要倒序走訪串列,避免左上方狀態被覆蓋。 +- 完全背包問題的每種物品的選取數量無限制,因此選擇放入物品的狀態轉移與 0-1 背包問題不同。由於狀態依賴正上方和正左方的狀態,因此在空間最佳化中應當正序走訪。 +- 零錢兌換問題是完全背包問題的一個變種。它從求“最大”價值變為求“最小”硬幣數量,因此狀態轉移方程中的 $\max()$ 應改為 $\min()$ 。從追求“不超過”背包容量到追求“恰好”湊出目標金額,因此使用 $amt + 1$ 來表示“無法湊出目標金額”的無效解。 +- 零錢兌換問題 II 從求“最少硬幣數量”改為求“硬幣組合數量”,狀態轉移方程相應地從 $\min()$ 改為求和運算子。 + +**編輯距離問題** + +- 編輯距離(Levenshtein 距離)用於衡量兩個字串之間的相似度,其定義為從一個字串到另一個字串的最少編輯步數,編輯操作包括新增、刪除、替換。 +- 編輯距離問題的狀態定義為將 $s$ 的前 $i$ 個字元更改為 $t$ 的前 $j$ 個字元所需的最少編輯步數。當 $s[i] \ne t[j]$ 時,具有三種決策:新增、刪除、替換,它們都有相應的剩餘子問題。據此便可以找出最優子結構與構建狀態轉移方程。而當 $s[i] = t[j]$ 時,無須編輯當前字元。 +- 在編輯距離中,狀態依賴其正上方、正左方、左上方的狀態,因此空間最佳化後正序或倒序走訪都無法正確地進行狀態轉移。為此,我們利用一個變數暫存左上方狀態,從而轉化到與完全背包問題等價的情況,可以在空間最佳化後進行正序走訪。 diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png new file mode 100644 index 000000000..3143909c5 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png new file mode 100644 index 000000000..5e166ade3 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png new file mode 100644 index 000000000..0992ce8e4 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png new file mode 100644 index 000000000..93a4e8061 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png new file mode 100644 index 000000000..2e34e34c3 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png new file mode 100644 index 000000000..16fbb7fdf Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png new file mode 100644 index 000000000..1b28a73a0 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png new file mode 100644 index 000000000..47f9ebef2 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png new file mode 100644 index 000000000..541390fe1 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png new file mode 100644 index 000000000..90e93be15 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png new file mode 100644 index 000000000..ace896c66 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png new file mode 100644 index 000000000..901ad30f7 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png new file mode 100644 index 000000000..379173b24 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png new file mode 100644 index 000000000..8d8c03644 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png new file mode 100644 index 000000000..4534d89e3 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png new file mode 100644 index 000000000..b7a8a2ded Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png new file mode 100644 index 000000000..102cfe166 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png new file mode 100644 index 000000000..e86e9ab35 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png new file mode 100644 index 000000000..b0ef9c010 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png new file mode 100644 index 000000000..cdf9da162 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png new file mode 100644 index 000000000..8aab8e692 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png new file mode 100644 index 000000000..efad2353f Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png new file mode 100644 index 000000000..44445f930 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png new file mode 100644 index 000000000..a4976a066 Binary files /dev/null and b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png differ diff --git a/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md new file mode 100644 index 000000000..490facc15 --- /dev/null +++ b/zh-hant/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -0,0 +1,207 @@ +# 完全背包問題 + +在本節中,我們先求解另一個常見的背包問題:完全背包,再瞭解它的一種特例:零錢兌換。 + +## 完全背包問題 + +!!! question + + 給定 $n$ 個物品,第 $i$ 個物品的重量為 $wgt[i-1]$、價值為 $val[i-1]$ ,和一個容量為 $cap$ 的背包。**每個物品可以重複選取**,問在限定背包容量下能放入物品的最大價值。示例如下圖所示。 + +![完全背包問題的示例資料](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) + +### 動態規劃思路 + +完全背包問題和 0-1 背包問題非常相似,**區別僅在於不限制物品的選擇次數**。 + +- 在 0-1 背包問題中,每種物品只有一個,因此將物品 $i$ 放入背包後,只能從前 $i-1$ 個物品中選擇。 +- 在完全背包問題中,每種物品的數量是無限的,因此將物品 $i$ 放入背包後,**仍可以從前 $i$ 個物品中選擇**。 + +在完全背包問題的規定下,狀態 $[i, c]$ 的變化分為兩種情況。 + +- **不放入物品 $i$** :與 0-1 背包問題相同,轉移至 $[i-1, c]$ 。 +- **放入物品 $i$** :與 0-1 背包問題不同,轉移至 $[i, c-wgt[i-1]]$ 。 + +從而狀態轉移方程變為: + +$$ +dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) +$$ + +### 程式碼實現 + +對比兩道題目的程式碼,狀態轉移中有一處從 $i-1$ 變為 $i$ ,其餘完全一致: + +```src +[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp} +``` + +### 空間最佳化 + +由於當前狀態是從左邊和上邊的狀態轉移而來的,**因此空間最佳化後應該對 $dp$ 表中的每一行進行正序走訪**。 + +這個走訪順序與 0-1 背包正好相反。請藉助下圖來理解兩者的區別。 + +=== "<1>" + ![完全背包問題在空間最佳化後的動態規劃過程](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) + +=== "<2>" + ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png) + +=== "<3>" + ![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png) + +=== "<4>" + ![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png) + +=== "<5>" + ![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png) + +=== "<6>" + ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png) + +程式碼實現比較簡單,僅需將陣列 `dp` 的第一維刪除: + +```src +[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp} +``` + +## 零錢兌換問題 + +背包問題是一大類動態規劃問題的代表,其擁有很多變種,例如零錢兌換問題。 + +!!! question + + 給定 $n$ 種硬幣,第 $i$ 種硬幣的面值為 $coins[i - 1]$ ,目標金額為 $amt$ ,**每種硬幣可以重複選取**,問能夠湊出目標金額的最少硬幣數量。如果無法湊出目標金額,則返回 $-1$ 。示例如下圖所示。 + +![零錢兌換問題的示例資料](unbounded_knapsack_problem.assets/coin_change_example.png) + +### 動態規劃思路 + +**零錢兌換可以看作完全背包問題的一種特殊情況**,兩者具有以下關聯與不同點。 + +- 兩道題可以相互轉換,“物品”對應“硬幣”、“物品重量”對應“硬幣面值”、“背包容量”對應“目標金額”。 +- 最佳化目標相反,完全背包問題是要最大化物品價值,零錢兌換問題是要最小化硬幣數量。 +- 完全背包問題是求“不超過”背包容量下的解,零錢兌換是求“恰好”湊到目標金額的解。 + +**第一步:思考每輪的決策,定義狀態,從而得到 $dp$ 表** + +狀態 $[i, a]$ 對應的子問題為:**前 $i$ 種硬幣能夠湊出金額 $a$ 的最少硬幣數量**,記為 $dp[i, a]$ 。 + +二維 $dp$ 表的尺寸為 $(n+1) \times (amt+1)$ 。 + +**第二步:找出最優子結構,進而推導出狀態轉移方程** + +本題與完全背包問題的狀態轉移方程存在以下兩點差異。 + +- 本題要求最小值,因此需將運算子 $\max()$ 更改為 $\min()$ 。 +- 最佳化主體是硬幣數量而非商品價值,因此在選中硬幣時執行 $+1$ 即可。 + +$$ +dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) +$$ + +**第三步:確定邊界條件和狀態轉移順序** + +當目標金額為 $0$ 時,湊出它的最少硬幣數量為 $0$ ,即首列所有 $dp[i, 0]$ 都等於 $0$ 。 + +當無硬幣時,**無法湊出任意 $> 0$ 的目標金額**,即是無效解。為使狀態轉移方程中的 $\min()$ 函式能夠識別並過濾無效解,我們考慮使用 $+ \infty$ 來表示它們,即令首行所有 $dp[0, a]$ 都等於 $+ \infty$ 。 + +### 程式碼實現 + +大多數程式語言並未提供 $+ \infty$ 變數,只能使用整型 `int` 的最大值來代替。而這又會導致大數越界:狀態轉移方程中的 $+ 1$ 操作可能發生溢位。 + +為此,我們採用數字 $amt + 1$ 來表示無效解,因為湊出 $amt$ 的硬幣數量最多為 $amt$ 。最後返回前,判斷 $dp[n, amt]$ 是否等於 $amt + 1$ ,若是則返回 $-1$ ,代表無法湊出目標金額。程式碼如下所示: + +```src +[file]{coin_change}-[class]{}-[func]{coin_change_dp} +``` + +下圖展示了零錢兌換的動態規劃過程,和完全背包問題非常相似。 + +=== "<1>" + ![零錢兌換問題的動態規劃過程](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) + +=== "<2>" + ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png) + +=== "<3>" + ![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png) + +=== "<4>" + ![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png) + +=== "<5>" + ![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png) + +=== "<6>" + ![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png) + +=== "<7>" + ![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png) + +=== "<8>" + ![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png) + +=== "<9>" + ![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png) + +=== "<10>" + ![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png) + +=== "<11>" + ![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png) + +=== "<12>" + ![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png) + +=== "<13>" + ![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png) + +=== "<14>" + ![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png) + +=== "<15>" + ![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png) + +### 空間最佳化 + +零錢兌換的空間最佳化的處理方式和完全背包問題一致: + +```src +[file]{coin_change}-[class]{}-[func]{coin_change_dp_comp} +``` + +## 零錢兌換問題 II + +!!! question + + 給定 $n$ 種硬幣,第 $i$ 種硬幣的面值為 $coins[i - 1]$ ,目標金額為 $amt$ ,每種硬幣可以重複選取,**問湊出目標金額的硬幣組合數量**。示例如下圖所示。 + +![零錢兌換問題 II 的示例資料](unbounded_knapsack_problem.assets/coin_change_ii_example.png) + +### 動態規劃思路 + +相比於上一題,本題目標是求組合數量,因此子問題變為:**前 $i$ 種硬幣能夠湊出金額 $a$ 的組合數量**。而 $dp$ 表仍然是尺寸為 $(n+1) \times (amt + 1)$ 的二維矩陣。 + +當前狀態的組合數量等於不選當前硬幣與選當前硬幣這兩種決策的組合數量之和。狀態轉移方程為: + +$$ +dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] +$$ + +當目標金額為 $0$ 時,無須選擇任何硬幣即可湊出目標金額,因此應將首列所有 $dp[i, 0]$ 都初始化為 $1$ 。當無硬幣時,無法湊出任何 $>0$ 的目標金額,因此首行所有 $dp[0, a]$ 都等於 $0$ 。 + +### 程式碼實現 + +```src +[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp} +``` + +### 空間最佳化 + +空間最佳化處理方式相同,刪除硬幣維度即可: + +```src +[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp} +``` diff --git a/zh-hant/docs/chapter_graph/graph.assets/adjacency_list.png b/zh-hant/docs/chapter_graph/graph.assets/adjacency_list.png new file mode 100644 index 000000000..5b0332fa9 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/adjacency_list.png differ diff --git a/zh-hant/docs/chapter_graph/graph.assets/adjacency_matrix.png b/zh-hant/docs/chapter_graph/graph.assets/adjacency_matrix.png new file mode 100644 index 000000000..1650dff37 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/adjacency_matrix.png differ diff --git a/zh-hant/docs/chapter_graph/graph.assets/connected_graph.png b/zh-hant/docs/chapter_graph/graph.assets/connected_graph.png new file mode 100644 index 000000000..6ca5feb49 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/connected_graph.png differ diff --git a/zh-hant/docs/chapter_graph/graph.assets/directed_graph.png b/zh-hant/docs/chapter_graph/graph.assets/directed_graph.png new file mode 100644 index 000000000..7a3b8988c Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/directed_graph.png differ diff --git a/zh-hant/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png b/zh-hant/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png new file mode 100644 index 000000000..addca2a60 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png differ diff --git a/zh-hant/docs/chapter_graph/graph.assets/weighted_graph.png b/zh-hant/docs/chapter_graph/graph.assets/weighted_graph.png new file mode 100644 index 000000000..3049dc241 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph.assets/weighted_graph.png differ diff --git a/zh-hant/docs/chapter_graph/graph.md b/zh-hant/docs/chapter_graph/graph.md new file mode 100644 index 000000000..20c602c26 --- /dev/null +++ b/zh-hant/docs/chapter_graph/graph.md @@ -0,0 +1,83 @@ +# 圖 + +圖(graph)是一種非線性資料結構,由頂點(vertex)邊(edge)組成。我們可以將圖 $G$ 抽象地表示為一組頂點 $V$ 和一組邊 $E$ 的集合。以下示例展示了一個包含 5 個頂點和 7 條邊的圖。 + +$$ +\begin{aligned} +V & = \{ 1, 2, 3, 4, 5 \} \newline +E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline +G & = \{ V, E \} \newline +\end{aligned} +$$ + +如果將頂點看作節點,將邊看作連線各個節點的引用(指標),我們就可以將圖看作一種從鏈結串列拓展而來的資料結構。如下圖所示,**相較於線性關係(鏈結串列)和分治關係(樹),網路關係(圖)的自由度更高**,因而更為複雜。 + +![鏈結串列、樹、圖之間的關係](graph.assets/linkedlist_tree_graph.png) + +## 圖的常見型別與術語 + +根據邊是否具有方向,可分為無向圖(undirected graph)有向圖(directed graph),如下圖所示。 + +- 在無向圖中,邊表示兩頂點之間的“雙向”連線關係,例如微信或 QQ 中的“好友關係”。 +- 在有向圖中,邊具有方向性,即 $A \rightarrow B$ 和 $A \leftarrow B$ 兩個方向的邊是相互獨立的,例如微博或抖音上的“關注”與“被關注”關係。 + +![有向圖與無向圖](graph.assets/directed_graph.png) + +根據所有頂點是否連通,可分為連通圖(connected graph)非連通圖(disconnected graph),如下圖所示。 + +- 對於連通圖,從某個頂點出發,可以到達其餘任意頂點。 +- 對於非連通圖,從某個頂點出發,至少有一個頂點無法到達。 + +![連通圖與非連通圖](graph.assets/connected_graph.png) + +我們還可以為邊新增“權重”變數,從而得到如下圖所示的有權圖(weighted graph)。例如在《王者榮耀》等手遊中,系統會根據共同遊戲時間來計算玩家之間的“親密度”,這種親密度網路就可以用有權圖來表示。 + +![有權圖與無權圖](graph.assets/weighted_graph.png) + +圖資料結構包含以下常用術語。 + +- 鄰接(adjacency):當兩頂點之間存在邊相連時,稱這兩頂點“鄰接”。在上圖中,頂點 1 的鄰接頂點為頂點 2、3、5。 +- 路徑(path):從頂點 A 到頂點 B 經過的邊構成的序列被稱為從 A 到 B 的“路徑”。在上圖中,邊序列 1-5-2-4 是頂點 1 到頂點 4 的一條路徑。 +- 度(degree):一個頂點擁有的邊數。對於有向圖,入度(in-degree)表示有多少條邊指向該頂點,出度(out-degree)表示有多少條邊從該頂點指出。 + +## 圖的表示 + +圖的常用表示方式包括“鄰接矩陣”和“鄰接表”。以下使用無向圖進行舉例。 + +### 鄰接矩陣 + +設圖的頂點數量為 $n$ ,鄰接矩陣(adjacency matrix)使用一個 $n \times n$ 大小的矩陣來表示圖,每一行(列)代表一個頂點,矩陣元素代表邊,用 $1$ 或 $0$ 表示兩個頂點之間是否存在邊。 + +如下圖所示,設鄰接矩陣為 $M$、頂點串列為 $V$ ,那麼矩陣元素 $M[i, j] = 1$ 表示頂點 $V[i]$ 到頂點 $V[j]$ 之間存在邊,反之 $M[i, j] = 0$ 表示兩頂點之間無邊。 + +![圖的鄰接矩陣表示](graph.assets/adjacency_matrix.png) + +鄰接矩陣具有以下特性。 + +- 頂點不能與自身相連,因此鄰接矩陣主對角線元素沒有意義。 +- 對於無向圖,兩個方向的邊等價,此時鄰接矩陣關於主對角線對稱。 +- 將鄰接矩陣的元素從 $1$ 和 $0$ 替換為權重,則可表示有權圖。 + +使用鄰接矩陣表示圖時,我們可以直接訪問矩陣元素以獲取邊,因此增刪查改操作的效率很高,時間複雜度均為 $O(1)$ 。然而,矩陣的空間複雜度為 $O(n^2)$ ,記憶體佔用較多。 + +### 鄰接表 + +鄰接表(adjacency list)使用 $n$ 個鏈結串列來表示圖,鏈結串列節點表示頂點。第 $i$ 個鏈結串列對應頂點 $i$ ,其中儲存了該頂點的所有鄰接頂點(與該頂點相連的頂點)。下圖展示了一個使用鄰接表儲存的圖的示例。 + +![圖的鄰接表表示](graph.assets/adjacency_list.png) + +鄰接表僅儲存實際存在的邊,而邊的總數通常遠小於 $n^2$ ,因此它更加節省空間。然而,在鄰接表中需要透過走訪鏈結串列來查詢邊,因此其時間效率不如鄰接矩陣。 + +觀察上圖,**鄰接表結構與雜湊表中的“鏈式位址”非常相似,因此我們也可以採用類似的方法來最佳化效率**。比如當鏈結串列較長時,可以將鏈結串列轉化為 AVL 樹或紅黑樹,從而將時間效率從 $O(n)$ 最佳化至 $O(\log n)$ ;還可以把鏈結串列轉換為雜湊表,從而將時間複雜度降至 $O(1)$ 。 + +## 圖的常見應用 + +如下表所示,許多現實系統可以用圖來建模,相應的問題也可以約化為圖計算問題。 + +

  現實生活中常見的圖

+ +| | 頂點 | 邊 | 圖計算問題 | +| -------- | ---- | -------------------- | ------------ | +| 社交網路 | 使用者 | 好友關係 | 潛在好友推薦 | +| 地鐵線路 | 站點 | 站點間的連通性 | 最短路線推薦 | +| 太陽系 | 星體 | 星體間的萬有引力作用 | 行星軌道計算 | diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png new file mode 100644 index 000000000..fcf3d248d Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png new file mode 100644 index 000000000..a76e876ba Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png new file mode 100644 index 000000000..ad70c3dd4 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png new file mode 100644 index 000000000..85033d254 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png new file mode 100644 index 000000000..d3f8eeebf Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png new file mode 100644 index 000000000..0af80cfec Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png new file mode 100644 index 000000000..a4fbcd83d Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png new file mode 100644 index 000000000..b87ff1c9b Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png new file mode 100644 index 000000000..a1dbf5c5f Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png new file mode 100644 index 000000000..234a4a8f0 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png differ diff --git a/zh-hant/docs/chapter_graph/graph_operations.md b/zh-hant/docs/chapter_graph/graph_operations.md new file mode 100644 index 000000000..366ff1290 --- /dev/null +++ b/zh-hant/docs/chapter_graph/graph_operations.md @@ -0,0 +1,86 @@ +# 圖的基礎操作 + +圖的基礎操作可分為對“邊”的操作和對“頂點”的操作。在“鄰接矩陣”和“鄰接表”兩種表示方法下,實現方式有所不同。 + +## 基於鄰接矩陣的實現 + +給定一個頂點數量為 $n$ 的無向圖,則各種操作的實現方式如下圖所示。 + +- **新增或刪除邊**:直接在鄰接矩陣中修改指定的邊即可,使用 $O(1)$ 時間。而由於是無向圖,因此需要同時更新兩個方向的邊。 +- **新增頂點**:在鄰接矩陣的尾部新增一行一列,並全部填 $0$ 即可,使用 $O(n)$ 時間。 +- **刪除頂點**:在鄰接矩陣中刪除一行一列。當刪除首行首列時達到最差情況,需要將 $(n-1)^2$ 個元素“向左上移動”,從而使用 $O(n^2)$ 時間。 +- **初始化**:傳入 $n$ 個頂點,初始化長度為 $n$ 的頂點串列 `vertices` ,使用 $O(n)$ 時間;初始化 $n \times n$ 大小的鄰接矩陣 `adjMat` ,使用 $O(n^2)$ 時間。 + +=== "初始化鄰接矩陣" + ![鄰接矩陣的初始化、增刪邊、增刪頂點](graph_operations.assets/adjacency_matrix_step1_initialization.png) + +=== "新增邊" + ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png) + +=== "刪除邊" + ![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png) + +=== "新增頂點" + ![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png) + +=== "刪除頂點" + ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png) + +以下是基於鄰接矩陣表示圖的實現程式碼: + +```src +[file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{} +``` + +## 基於鄰接表的實現 + +設無向圖的頂點總數為 $n$、邊總數為 $m$ ,則可根據下圖所示的方法實現各種操作。 + +- **新增邊**:在頂點對應鏈結串列的末尾新增邊即可,使用 $O(1)$ 時間。因為是無向圖,所以需要同時新增兩個方向的邊。 +- **刪除邊**:在頂點對應鏈結串列中查詢並刪除指定邊,使用 $O(m)$ 時間。在無向圖中,需要同時刪除兩個方向的邊。 +- **新增頂點**:在鄰接表中新增一個鏈結串列,並將新增頂點作為鏈結串列頭節點,使用 $O(1)$ 時間。 +- **刪除頂點**:需走訪整個鄰接表,刪除包含指定頂點的所有邊,使用 $O(n + m)$ 時間。 +- **初始化**:在鄰接表中建立 $n$ 個頂點和 $2m$ 條邊,使用 $O(n + m)$ 時間。 + +=== "初始化鄰接表" + ![鄰接表的初始化、增刪邊、增刪頂點](graph_operations.assets/adjacency_list_step1_initialization.png) + +=== "新增邊" + ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png) + +=== "刪除邊" + ![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png) + +=== "新增頂點" + ![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png) + +=== "刪除頂點" + ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png) + +以下是鄰接表的程式碼實現。對比上圖,實際程式碼有以下不同。 + +- 為了方便新增與刪除頂點,以及簡化程式碼,我們使用串列(動態陣列)來代替鏈結串列。 +- 使用雜湊表來儲存鄰接表,`key` 為頂點例項,`value` 為該頂點的鄰接頂點串列(鏈結串列)。 + +另外,我們在鄰接表中使用 `Vertex` 類別來表示頂點,這樣做的原因是:如果與鄰接矩陣一樣,用串列索引來區分不同頂點,那麼假設要刪除索引為 $i$ 的頂點,則需走訪整個鄰接表,將所有大於 $i$ 的索引全部減 $1$ ,效率很低。而如果每個頂點都是唯一的 `Vertex` 例項,刪除某一頂點之後就無須改動其他頂點了。 + +```src +[file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{} +``` + +## 效率對比 + +設圖中共有 $n$ 個頂點和 $m$ 條邊,下表對比了鄰接矩陣和鄰接表的時間效率和空間效率。 + +

  鄰接矩陣與鄰接表對比

+ +| | 鄰接矩陣 | 鄰接表(鏈結串列) | 鄰接表(雜湊表) | +| ------------ | -------- | -------------- | ---------------- | +| 判斷是否鄰接 | $O(1)$ | $O(m)$ | $O(1)$ | +| 新增邊 | $O(1)$ | $O(1)$ | $O(1)$ | +| 刪除邊 | $O(1)$ | $O(m)$ | $O(1)$ | +| 新增頂點 | $O(n)$ | $O(1)$ | $O(1)$ | +| 刪除頂點 | $O(n^2)$ | $O(n + m)$ | $O(n)$ | +| 記憶體空間佔用 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | + +觀察上表,似乎鄰接表(雜湊表)的時間效率與空間效率最優。但實際上,在鄰接矩陣中操作邊的效率更高,只需一次陣列訪問或賦值操作即可。綜合來看,鄰接矩陣體現了“以空間換時間”的原則,而鄰接表體現了“以時間換空間”的原則。 diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs.png new file mode 100644 index 000000000..6cbe62135 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png new file mode 100644 index 000000000..8b70e7ac5 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png new file mode 100644 index 000000000..80dc83feb Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png new file mode 100644 index 000000000..408949826 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png new file mode 100644 index 000000000..f81b8b110 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png new file mode 100644 index 000000000..812bd3ae9 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png new file mode 100644 index 000000000..c06aaec4c Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png new file mode 100644 index 000000000..7c697a01e Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png new file mode 100644 index 000000000..ee4ab15fd Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png new file mode 100644 index 000000000..879dfb18b Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png new file mode 100644 index 000000000..35c15686c Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png new file mode 100644 index 000000000..22dba9f34 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs.png new file mode 100644 index 000000000..7f96442cc Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png new file mode 100644 index 000000000..6811da289 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png new file mode 100644 index 000000000..b4d191dac Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png new file mode 100644 index 000000000..343e4a6cb Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png new file mode 100644 index 000000000..fc4cecbe0 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png new file mode 100644 index 000000000..d35aa696a Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png new file mode 100644 index 000000000..ea63ae956 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png new file mode 100644 index 000000000..d7ac7ca54 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png new file mode 100644 index 000000000..60a78caf3 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png new file mode 100644 index 000000000..5153dd254 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png new file mode 100644 index 000000000..42b3fcb36 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png new file mode 100644 index 000000000..afbd16f39 Binary files /dev/null and b/zh-hant/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png differ diff --git a/zh-hant/docs/chapter_graph/graph_traversal.md b/zh-hant/docs/chapter_graph/graph_traversal.md new file mode 100644 index 000000000..3a55288a7 --- /dev/null +++ b/zh-hant/docs/chapter_graph/graph_traversal.md @@ -0,0 +1,136 @@ +# 圖的走訪 + +樹代表的是“一對多”的關係,而圖則具有更高的自由度,可以表示任意的“多對多”關係。因此,我們可以把樹看作圖的一種特例。顯然,**樹的走訪操作也是圖的走訪操作的一種特例**。 + +圖和樹都需要應用搜索演算法來實現走訪操作。圖的走訪方式也可分為兩種:廣度優先走訪深度優先走訪。 + +## 廣度優先走訪 + +**廣度優先走訪是一種由近及遠的走訪方式,從某個節點出發,始終優先訪問距離最近的頂點,並一層層向外擴張**。如下圖所示,從左上角頂點出發,首先走訪該頂點的所有鄰接頂點,然後走訪下一個頂點的所有鄰接頂點,以此類推,直至所有頂點訪問完畢。 + +![圖的廣度優先走訪](graph_traversal.assets/graph_bfs.png) + +### 演算法實現 + +BFS 通常藉助佇列來實現,程式碼如下所示。佇列具有“先入先出”的性質,這與 BFS 的“由近及遠”的思想異曲同工。 + +1. 將走訪起始頂點 `startVet` 加入列列,並開啟迴圈。 +2. 在迴圈的每輪迭代中,彈出佇列首頂點並記錄訪問,然後將該頂點的所有鄰接頂點加入到佇列尾部。 +3. 迴圈步驟 `2.` ,直到所有頂點被訪問完畢後結束。 + +為了防止重複走訪頂點,我們需要藉助一個雜湊表 `visited` 來記錄哪些節點已被訪問。 + +```src +[file]{graph_bfs}-[class]{}-[func]{graph_bfs} +``` + +程式碼相對抽象,建議對照下圖來加深理解。 + +=== "<1>" + ![圖的廣度優先走訪步驟](graph_traversal.assets/graph_bfs_step1.png) + +=== "<2>" + ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) + +=== "<3>" + ![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png) + +=== "<4>" + ![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png) + +=== "<5>" + ![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png) + +=== "<6>" + ![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png) + +=== "<7>" + ![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png) + +=== "<8>" + ![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png) + +=== "<9>" + ![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png) + +=== "<10>" + ![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png) + +=== "<11>" + ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) + +!!! question "廣度優先走訪的序列是否唯一?" + + 不唯一。廣度優先走訪只要求按“由近及遠”的順序走訪,**而多個相同距離的頂點的走訪順序允許被任意打亂**。以上圖為例,頂點 $1$、$3$ 的訪問順序可以交換,頂點 $2$、$4$、$6$ 的訪問順序也可以任意交換。 + +### 複雜度分析 + +**時間複雜度**:所有頂點都會入列並出隊一次,使用 $O(|V|)$ 時間;在走訪鄰接頂點的過程中,由於是無向圖,因此所有邊都會被訪問 $2$ 次,使用 $O(2|E|)$ 時間;總體使用 $O(|V| + |E|)$ 時間。 + +**空間複雜度**:串列 `res` ,雜湊表 `visited` ,佇列 `que` 中的頂點數量最多為 $|V|$ ,使用 $O(|V|)$ 空間。 + +## 深度優先走訪 + +**深度優先走訪是一種優先走到底、無路可走再回頭的走訪方式**。如下圖所示,從左上角頂點出發,訪問當前頂點的某個鄰接頂點,直到走到盡頭時返回,再繼續走到盡頭並返回,以此類推,直至所有頂點走訪完成。 + +![圖的深度優先走訪](graph_traversal.assets/graph_dfs.png) + +### 演算法實現 + +這種“走到盡頭再返回”的演算法範式通常基於遞迴來實現。與廣度優先走訪類似,在深度優先走訪中,我們也需要藉助一個雜湊表 `visited` 來記錄已被訪問的頂點,以避免重複訪問頂點。 + +```src +[file]{graph_dfs}-[class]{}-[func]{graph_dfs} +``` + +深度優先走訪的演算法流程如下圖所示。 + +- **直虛線代表向下遞推**,表示開啟了一個新的遞迴方法來訪問新頂點。 +- **曲虛線代表向上回溯**,表示此遞迴方法已經返回,回溯到了開啟此方法的位置。 + +為了加深理解,建議將下圖與程式碼結合起來,在腦中模擬(或者用筆畫下來)整個 DFS 過程,包括每個遞迴方法何時開啟、何時返回。 + +=== "<1>" + ![圖的深度優先走訪步驟](graph_traversal.assets/graph_dfs_step1.png) + +=== "<2>" + ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) + +=== "<3>" + ![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png) + +=== "<4>" + ![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png) + +=== "<5>" + ![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png) + +=== "<6>" + ![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png) + +=== "<7>" + ![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png) + +=== "<8>" + ![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png) + +=== "<9>" + ![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png) + +=== "<10>" + ![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png) + +=== "<11>" + ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) + +!!! question "深度優先走訪的序列是否唯一?" + + 與廣度優先走訪類似,深度優先走訪序列的順序也不是唯一的。給定某頂點,先往哪個方向探索都可以,即鄰接頂點的順序可以任意打亂,都是深度優先走訪。 + + 以樹的走訪為例,“根 $\rightarrow$ 左 $\rightarrow$ 右”“左 $\rightarrow$ 根 $\rightarrow$ 右”“左 $\rightarrow$ 右 $\rightarrow$ 根”分別對應前序、中序、後序走訪,它們展示了三種走訪優先順序,然而這三者都屬於深度優先走訪。 + +### 複雜度分析 + +**時間複雜度**:所有頂點都會被訪問 $1$ 次,使用 $O(|V|)$ 時間;所有邊都會被訪問 $2$ 次,使用 $O(2|E|)$ 時間;總體使用 $O(|V| + |E|)$ 時間。 + +**空間複雜度**:串列 `res` ,雜湊表 `visited` 頂點數量最多為 $|V|$ ,遞迴深度最大為 $|V|$ ,因此使用 $O(|V|)$ 空間。 diff --git a/zh-hant/docs/chapter_graph/index.md b/zh-hant/docs/chapter_graph/index.md new file mode 100644 index 000000000..e82ad1b86 --- /dev/null +++ b/zh-hant/docs/chapter_graph/index.md @@ -0,0 +1,9 @@ +# 圖 + +![圖](../assets/covers/chapter_graph.jpg) + +!!! abstract + + 在生命旅途中,我們就像是一個個節點,被無數看不見的邊相連。 + + 每一次的相識與相離,都在這張巨大的網路圖中留下獨特的印記。 diff --git a/zh-hant/docs/chapter_graph/summary.md b/zh-hant/docs/chapter_graph/summary.md new file mode 100644 index 000000000..5eed0e19d --- /dev/null +++ b/zh-hant/docs/chapter_graph/summary.md @@ -0,0 +1,31 @@ +# 小結 + +### 重點回顧 + +- 圖由頂點和邊組成,可以表示為一組頂點和一組邊構成的集合。 +- 相較於線性關係(鏈結串列)和分治關係(樹),網路關係(圖)具有更高的自由度,因而更為複雜。 +- 有向圖的邊具有方向性,連通圖中的任意頂點均可達,有權圖的每條邊都包含權重變數。 +- 鄰接矩陣利用矩陣來表示圖,每一行(列)代表一個頂點,矩陣元素代表邊,用 $1$ 或 $0$ 表示兩個頂點之間有邊或無邊。鄰接矩陣在增刪查改操作上效率很高,但空間佔用較多。 +- 鄰接表使用多個鏈結串列來表示圖,第 $i$ 個鏈結串列對應頂點 $i$ ,其中儲存了該頂點的所有鄰接頂點。鄰接表相對於鄰接矩陣更加節省空間,但由於需要走訪鏈結串列來查詢邊,因此時間效率較低。 +- 當鄰接表中的鏈結串列過長時,可以將其轉換為紅黑樹或雜湊表,從而提升查詢效率。 +- 從演算法思想的角度分析,鄰接矩陣體現了“以空間換時間”,鄰接表體現了“以時間換空間”。 +- 圖可用於建模各類現實系統,如社交網路、地鐵線路等。 +- 樹是圖的一種特例,樹的走訪也是圖的走訪的一種特例。 +- 圖的廣度優先走訪是一種由近及遠、層層擴張的搜尋方式,通常藉助佇列實現。 +- 圖的深度優先走訪是一種優先走到底、無路可走時再回溯的搜尋方式,常基於遞迴來實現。 + +### Q & A + +**Q**:路徑的定義是頂點序列還是邊序列? + +維基百科上不同語言版本的定義不一致:英文版是“路徑是一個邊序列”,而中文版是“路徑是一個頂點序列”。以下是英文版原文:In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. + +在本文中,路徑被視為一個邊序列,而不是一個頂點序列。這是因為兩個頂點之間可能存在多條邊連線,此時每條邊都對應一條路徑。 + +**Q**:非連通圖中是否會有無法走訪到的點? + +在非連通圖中,從某個頂點出發,至少有一個頂點無法到達。走訪非連通圖需要設定多個起點,以走訪到圖的所有連通分量。 + +**Q**:在鄰接表中,“與該頂點相連的所有頂點”的頂點順序是否有要求? + +可以是任意順序。但在實際應用中,可能需要按照指定規則來排序,比如按照頂點新增的次序,或者按照頂點值大小的順序等,這樣有助於快速查詢“帶有某種極值”的頂點。 diff --git a/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png new file mode 100644 index 000000000..79e250084 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png differ diff --git a/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png new file mode 100644 index 000000000..05461a7db Binary files /dev/null and b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png differ diff --git a/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png new file mode 100644 index 000000000..a5cdac1c5 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png differ diff --git a/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png new file mode 100644 index 000000000..7498699d3 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png differ diff --git a/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.md b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.md new file mode 100644 index 000000000..140b3acbd --- /dev/null +++ b/zh-hant/docs/chapter_greedy/fractional_knapsack_problem.md @@ -0,0 +1,50 @@ +# 分數背包問題 + +!!! question + + 給定 $n$ 個物品,第 $i$ 個物品的重量為 $wgt[i-1]$、價值為 $val[i-1]$ ,和一個容量為 $cap$ 的背包。每個物品只能選擇一次,**但可以選擇物品的一部分,價值根據選擇的重量比例計算**,問在限定背包容量下背包中物品的最大價值。示例如下圖所示。 + +![分數背包問題的示例資料](fractional_knapsack_problem.assets/fractional_knapsack_example.png) + +分數背包問題和 0-1 背包問題整體上非常相似,狀態包含當前物品 $i$ 和容量 $c$ ,目標是求限定背包容量下的最大價值。 + +不同點在於,本題允許只選擇物品的一部分。如下圖所示,**我們可以對物品任意地進行切分,並按照重量比例來計算相應價值**。 + +1. 對於物品 $i$ ,它在單位重量下的價值為 $val[i-1] / wgt[i-1]$ ,簡稱單位價值。 +2. 假設放入一部分物品 $i$ ,重量為 $w$ ,則背包增加的價值為 $w \times val[i-1] / wgt[i-1]$ 。 + +![物品在單位重量下的價值](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) + +### 貪婪策略確定 + +最大化背包內物品總價值,**本質上是最大化單位重量下的物品價值**。由此便可推理出下圖所示的貪婪策略。 + +1. 將物品按照單位價值從高到低進行排序。 +2. 走訪所有物品,**每輪貪婪地選擇單位價值最高的物品**。 +3. 若剩餘背包容量不足,則使用當前物品的一部分填滿背包。 + +![分數背包問題的貪婪策略](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png) + +### 程式碼實現 + +我們建立了一個物品類別 `Item` ,以便將物品按照單位價值進行排序。迴圈進行貪婪選擇,當背包已滿時跳出並返回解: + +```src +[file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack} +``` + +除排序之外,在最差情況下,需要走訪整個物品串列,**因此時間複雜度為 $O(n)$** ,其中 $n$ 為物品數量。 + +由於初始化了一個 `Item` 物件串列,**因此空間複雜度為 $O(n)$** 。 + +### 正確性證明 + +採用反證法。假設物品 $x$ 是單位價值最高的物品,使用某演算法求得最大價值為 `res` ,但該解中不包含物品 $x$ 。 + +現在從背包中拿出單位重量的任意物品,並替換為單位重量的物品 $x$ 。由於物品 $x$ 的單位價值最高,因此替換後的總價值一定大於 `res` 。**這與 `res` 是最優解矛盾,說明最優解中必須包含物品 $x$** 。 + +對於該解中的其他物品,我們也可以構建出上述矛盾。總而言之,**單位價值更大的物品總是更優選擇**,這說明貪婪策略是有效的。 + +如下圖所示,如果將物品重量和物品單位價值分別看作一張二維圖表的橫軸和縱軸,則分數背包問題可轉化為“求在有限橫軸區間下圍成的最大面積”。這個類比可以幫助我們從幾何角度理解貪婪策略的有效性。 + +![分數背包問題的幾何表示](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png) diff --git a/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png b/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png new file mode 100644 index 000000000..4cb560fe6 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png differ diff --git a/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png b/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png new file mode 100644 index 000000000..8598f26b8 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png differ diff --git a/zh-hant/docs/chapter_greedy/greedy_algorithm.md b/zh-hant/docs/chapter_greedy/greedy_algorithm.md new file mode 100644 index 000000000..f4a5a349f --- /dev/null +++ b/zh-hant/docs/chapter_greedy/greedy_algorithm.md @@ -0,0 +1,94 @@ +# 貪婪演算法 + +貪婪演算法(greedy algorithm)是一種常見的解決最佳化問題的演算法,其基本思想是在問題的每個決策階段,都選擇當前看起來最優的選擇,即貪婪地做出區域性最優的決策,以期獲得全域性最優解。貪婪演算法簡潔且高效,在許多實際問題中有著廣泛的應用。 + +貪婪演算法和動態規劃都常用於解決最佳化問題。它們之間存在一些相似之處,比如都依賴最優子結構性質,但工作原理不同。 + +- 動態規劃會根據之前階段的所有決策來考慮當前決策,並使用過去子問題的解來構建當前子問題的解。 +- 貪婪演算法不會考慮過去的決策,而是一路向前地進行貪婪選擇,不斷縮小問題範圍,直至問題被解決。 + +我們先透過例題“零錢兌換”瞭解貪婪演算法的工作原理。這道題已經在“完全背包問題”章節中介紹過,相信你對它並不陌生。 + +!!! question + + 給定 $n$ 種硬幣,第 $i$ 種硬幣的面值為 $coins[i - 1]$ ,目標金額為 $amt$ ,每種硬幣可以重複選取,問能夠湊出目標金額的最少硬幣數量。如果無法湊出目標金額,則返回 $-1$ 。 + +本題採取的貪婪策略如下圖所示。給定目標金額,**我們貪婪地選擇不大於且最接近它的硬幣**,不斷迴圈該步驟,直至湊出目標金額為止。 + +![零錢兌換的貪婪策略](greedy_algorithm.assets/coin_change_greedy_strategy.png) + +實現程式碼如下所示: + +```src +[file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy} +``` + +你可能會不由地發出感嘆:So clean !貪婪演算法僅用約十行程式碼就解決了零錢兌換問題。 + +## 貪婪演算法的優點與侷限性 + +**貪婪演算法不僅操作直接、實現簡單,而且通常效率也很高**。在以上程式碼中,記硬幣最小面值為 $\min(coins)$ ,則貪婪選擇最多迴圈 $amt / \min(coins)$ 次,時間複雜度為 $O(amt / \min(coins))$ 。這比動態規劃解法的時間複雜度 $O(n \times amt)$ 小了一個數量級。 + +然而,**對於某些硬幣面值組合,貪婪演算法並不能找到最優解**。下圖給出了兩個示例。 + +- **正例 $coins = [1, 5, 10, 20, 50, 100]$**:在該硬幣組合下,給定任意 $amt$ ,貪婪演算法都可以找到最優解。 +- **反例 $coins = [1, 20, 50]$**:假設 $amt = 60$ ,貪婪演算法只能找到 $50 + 1 \times 10$ 的兌換組合,共計 $11$ 枚硬幣,但動態規劃可以找到最優解 $20 + 20 + 20$ ,僅需 $3$ 枚硬幣。 +- **反例 $coins = [1, 49, 50]$**:假設 $amt = 98$ ,貪婪演算法只能找到 $50 + 1 \times 48$ 的兌換組合,共計 $49$ 枚硬幣,但動態規劃可以找到最優解 $49 + 49$ ,僅需 $2$ 枚硬幣。 + +![貪婪演算法無法找出最優解的示例](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) + +也就是說,對於零錢兌換問題,貪婪演算法無法保證找到全域性最優解,並且有可能找到非常差的解。它更適合用動態規劃解決。 + +一般情況下,貪婪演算法的適用情況分以下兩種。 + +1. **可以保證找到最優解**:貪婪演算法在這種情況下往往是最優選擇,因為它往往比回溯、動態規劃更高效。 +2. **可以找到近似最優解**:貪婪演算法在這種情況下也是可用的。對於很多複雜問題來說,尋找全域性最優解非常困難,能以較高效率找到次優解也是非常不錯的。 + +## 貪婪演算法特性 + +那麼問題來了,什麼樣的問題適合用貪婪演算法求解呢?或者說,貪婪演算法在什麼情況下可以保證找到最優解? + +相較於動態規劃,貪婪演算法的使用條件更加苛刻,其主要關注問題的兩個性質。 + +- **貪婪選擇性質**:只有當局部最優選擇始終可以導致全域性最優解時,貪婪演算法才能保證得到最優解。 +- **最優子結構**:原問題的最優解包含子問題的最優解。 + +最優子結構已經在“動態規劃”章節中介紹過,這裡不再贅述。值得注意的是,一些問題的最優子結構並不明顯,但仍然可使用貪婪演算法解決。 + +我們主要探究貪婪選擇性質的判斷方法。雖然它的描述看上去比較簡單,**但實際上對於許多問題,證明貪婪選擇性質並非易事**。 + +例如零錢兌換問題,我們雖然能夠容易地舉出反例,對貪婪選擇性質進行證偽,但證實的難度較大。如果問:**滿足什麼條件的硬幣組合可以使用貪婪演算法求解**?我們往往只能憑藉直覺或舉例子來給出一個模稜兩可的答案,而難以給出嚴謹的數學證明。 + +!!! quote + + 有一篇論文給出了一個 $O(n^3)$ 時間複雜度的演算法,用於判斷一個硬幣組合能否使用貪婪演算法找出任意金額的最優解。 + + Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234. + +## 貪婪演算法解題步驟 + +貪婪問題的解決流程大體可分為以下三步。 + +1. **問題分析**:梳理與理解問題特性,包括狀態定義、最佳化目標和約束條件等。這一步在回溯和動態規劃中都有涉及。 +2. **確定貪婪策略**:確定如何在每一步中做出貪婪選擇。這個策略能夠在每一步減小問題的規模,並最終解決整個問題。 +3. **正確性證明**:通常需要證明問題具有貪婪選擇性質和最優子結構。這個步驟可能需要用到數學證明,例如歸納法或反證法等。 + +確定貪婪策略是求解問題的核心步驟,但實施起來可能並不容易,主要有以下原因。 + +- **不同問題的貪婪策略的差異較大**。對於許多問題來說,貪婪策略比較淺顯,我們透過一些大概的思考與嘗試就能得出。而對於一些複雜問題,貪婪策略可能非常隱蔽,這種情況就非常考驗個人的解題經驗與演算法能力了。 +- **某些貪婪策略具有較強的迷惑性**。當我們滿懷信心設計好貪婪策略,寫出解題程式碼並提交執行,很可能發現部分測試樣例無法透過。這是因為設計的貪婪策略只是“部分正確”的,上文介紹的零錢兌換就是一個典型案例。 + +為了保證正確性,我們應該對貪婪策略進行嚴謹的數學證明,**通常需要用到反證法或數學歸納法**。 + +然而,正確性證明也很可能不是一件易事。如若沒有頭緒,我們通常會選擇面向測試用例進行程式碼除錯,一步步修改與驗證貪婪策略。 + +## 貪婪演算法典型例題 + +貪婪演算法常常應用在滿足貪婪選擇性質和最優子結構的最佳化問題中,以下列舉了一些典型的貪婪演算法問題。 + +- **硬幣找零問題**:在某些硬幣組合下,貪婪演算法總是可以得到最優解。 +- **區間排程問題**:假設你有一些任務,每個任務在一段時間內進行,你的目標是完成儘可能多的任務。如果每次都選擇結束時間最早的任務,那麼貪婪演算法就可以得到最優解。 +- **分數背包問題**:給定一組物品和一個載重量,你的目標是選擇一組物品,使得總重量不超過載重量,且總價值最大。如果每次都選擇價效比最高(價值 / 重量)的物品,那麼貪婪演算法在一些情況下可以得到最優解。 +- **股票買賣問題**:給定一組股票的歷史價格,你可以進行多次買賣,但如果你已經持有股票,那麼在賣出之前不能再買,目標是獲取最大利潤。 +- **霍夫曼編碼**:霍夫曼編碼是一種用於無損資料壓縮的貪婪演算法。透過構建霍夫曼樹,每次選擇出現頻率最低的兩個節點合併,最後得到的霍夫曼樹的帶權路徑長度(編碼長度)最小。 +- **Dijkstra 演算法**:它是一種解決給定源頂點到其餘各頂點的最短路徑問題的貪婪演算法。 diff --git a/zh-hant/docs/chapter_greedy/index.md b/zh-hant/docs/chapter_greedy/index.md new file mode 100644 index 000000000..7b06ea789 --- /dev/null +++ b/zh-hant/docs/chapter_greedy/index.md @@ -0,0 +1,9 @@ +# 貪婪 + +![貪婪](../assets/covers/chapter_greedy.jpg) + +!!! abstract + + 向日葵朝著太陽轉動,時刻追求自身成長的最大可能。 + + 貪婪策略在一輪輪的簡單選擇中,逐步導向最佳答案。 diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png new file mode 100644 index 000000000..33cb0d00b Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png new file mode 100644 index 000000000..8356d8949 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png new file mode 100644 index 000000000..2989dd4ab Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png new file mode 100644 index 000000000..99458088d Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png new file mode 100644 index 000000000..179465bbc Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png new file mode 100644 index 000000000..abe0d1e51 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png new file mode 100644 index 000000000..c4a378d86 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png new file mode 100644 index 000000000..ef733ea42 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png new file mode 100644 index 000000000..4c396d476 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png new file mode 100644 index 000000000..2be591550 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png new file mode 100644 index 000000000..a7aaca72f Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png new file mode 100644 index 000000000..11743e187 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png new file mode 100644 index 000000000..0cea208bb Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png new file mode 100644 index 000000000..86b1e5c67 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png differ diff --git a/zh-hant/docs/chapter_greedy/max_capacity_problem.md b/zh-hant/docs/chapter_greedy/max_capacity_problem.md new file mode 100644 index 000000000..e9b519fde --- /dev/null +++ b/zh-hant/docs/chapter_greedy/max_capacity_problem.md @@ -0,0 +1,99 @@ +# 最大容量問題 + +!!! question + + 輸入一個陣列 $ht$ ,其中的每個元素代表一個垂直隔板的高度。陣列中的任意兩個隔板,以及它們之間的空間可以組成一個容器。 + + 容器的容量等於高度和寬度的乘積(面積),其中高度由較短的隔板決定,寬度是兩個隔板的陣列索引之差。 + + 請在陣列中選擇兩個隔板,使得組成的容器的容量最大,返回最大容量。示例如下圖所示。 + +![最大容量問題的示例資料](max_capacity_problem.assets/max_capacity_example.png) + +容器由任意兩個隔板圍成,**因此本題的狀態為兩個隔板的索引,記為 $[i, j]$** 。 + +根據題意,容量等於高度乘以寬度,其中高度由短板決定,寬度是兩隔板的陣列索引之差。設容量為 $cap[i, j]$ ,則可得計算公式: + +$$ +cap[i, j] = \min(ht[i], ht[j]) \times (j - i) +$$ + +設陣列長度為 $n$ ,兩個隔板的組合數量(狀態總數)為 $C_n^2 = \frac{n(n - 1)}{2}$ 個。最直接地,**我們可以窮舉所有狀態**,從而求得最大容量,時間複雜度為 $O(n^2)$ 。 + +### 貪婪策略確定 + +這道題還有更高效率的解法。如下圖所示,現選取一個狀態 $[i, j]$ ,其滿足索引 $i < j$ 且高度 $ht[i] < ht[j]$ ,即 $i$ 為短板、$j$ 為長板。 + +![初始狀態](max_capacity_problem.assets/max_capacity_initial_state.png) + +如下圖所示,**若此時將長板 $j$ 向短板 $i$ 靠近,則容量一定變小**。 + +這是因為在移動長板 $j$ 後,寬度 $j-i$ 肯定變小;而高度由短板決定,因此高度只可能不變( $i$ 仍為短板)或變小(移動後的 $j$ 成為短板)。 + +![向內移動長板後的狀態](max_capacity_problem.assets/max_capacity_moving_long_board.png) + +反向思考,**我們只有向內收縮短板 $i$ ,才有可能使容量變大**。因為雖然寬度一定變小,**但高度可能會變大**(移動後的短板 $i$ 可能會變長)。例如在下圖中,移動短板後面積變大。 + +![向內移動短板後的狀態](max_capacity_problem.assets/max_capacity_moving_short_board.png) + +由此便可推出本題的貪婪策略:初始化兩指標,使其分列容器兩端,每輪向內收縮短板對應的指標,直至兩指標相遇。 + +下圖展示了貪婪策略的執行過程。 + +1. 初始狀態下,指標 $i$ 和 $j$ 分列陣列兩端。 +2. 計算當前狀態的容量 $cap[i, j]$ ,並更新最大容量。 +3. 比較板 $i$ 和 板 $j$ 的高度,並將短板向內移動一格。 +4. 迴圈執行第 `2.` 步和第 `3.` 步,直至 $i$ 和 $j$ 相遇時結束。 + +=== "<1>" + ![最大容量問題的貪婪過程](max_capacity_problem.assets/max_capacity_greedy_step1.png) + +=== "<2>" + ![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png) + +=== "<3>" + ![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png) + +=== "<4>" + ![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png) + +=== "<5>" + ![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png) + +=== "<6>" + ![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png) + +=== "<7>" + ![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png) + +=== "<8>" + ![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png) + +=== "<9>" + ![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png) + +### 程式碼實現 + +程式碼迴圈最多 $n$ 輪,**因此時間複雜度為 $O(n)$** 。 + +變數 $i$、$j$、$res$ 使用常數大小的額外空間,**因此空間複雜度為 $O(1)$** 。 + +```src +[file]{max_capacity}-[class]{}-[func]{max_capacity} +``` + +### 正確性證明 + +之所以貪婪比窮舉更快,是因為每輪的貪婪選擇都會“跳過”一些狀態。 + +比如在狀態 $cap[i, j]$ 下,$i$ 為短板、$j$ 為長板。若貪婪地將短板 $i$ 向內移動一格,會導致下圖所示的狀態被“跳過”。**這意味著之後無法驗證這些狀態的容量大小**。 + +$$ +cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] +$$ + +![移動短板導致被跳過的狀態](max_capacity_problem.assets/max_capacity_skipped_states.png) + +觀察發現,**這些被跳過的狀態實際上就是將長板 $j$ 向內移動的所有狀態**。前面我們已經證明內移長板一定會導致容量變小。也就是說,被跳過的狀態都不可能是最優解,**跳過它們不會導致錯過最優解**。 + +以上分析說明,移動短板的操作是“安全”的,貪婪策略是有效的。 diff --git a/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png new file mode 100644 index 000000000..d24da868d Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png differ diff --git a/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png new file mode 100644 index 000000000..166515853 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png differ diff --git a/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png new file mode 100644 index 000000000..5b31c6729 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png differ diff --git a/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png new file mode 100644 index 000000000..cb2727f58 Binary files /dev/null and b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png differ diff --git a/zh-hant/docs/chapter_greedy/max_product_cutting_problem.md b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.md new file mode 100644 index 000000000..1504b23c0 --- /dev/null +++ b/zh-hant/docs/chapter_greedy/max_product_cutting_problem.md @@ -0,0 +1,85 @@ +# 最大切分乘積問題 + +!!! question + + 給定一個正整數 $n$ ,將其切分為至少兩個正整數的和,求切分後所有整數的乘積最大是多少,如下圖所示。 + +![最大切分乘積的問題定義](max_product_cutting_problem.assets/max_product_cutting_definition.png) + +假設我們將 $n$ 切分為 $m$ 個整數因子,其中第 $i$ 個因子記為 $n_i$ ,即 + +$$ +n = \sum_{i=1}^{m}n_i +$$ + +本題的目標是求得所有整數因子的最大乘積,即 + +$$ +\max(\prod_{i=1}^{m}n_i) +$$ + +我們需要思考的是:切分數量 $m$ 應該多大,每個 $n_i$ 應該是多少? + +### 貪婪策略確定 + +根據經驗,兩個整數的乘積往往比它們的加和更大。假設從 $n$ 中分出一個因子 $2$ ,則它們的乘積為 $2(n-2)$ 。我們將該乘積與 $n$ 作比較: + +$$ +\begin{aligned} +2(n-2) & \geq n \newline +2n - n - 4 & \geq 0 \newline +n & \geq 4 +\end{aligned} +$$ + +如下圖所示,當 $n \geq 4$ 時,切分出一個 $2$ 後乘積會變大,**這說明大於等於 $4$ 的整數都應該被切分**。 + +**貪婪策略一**:如果切分方案中包含 $\geq 4$ 的因子,那麼它就應該被繼續切分。最終的切分方案只應出現 $1$、$2$、$3$ 這三種因子。 + +![切分導致乘積變大](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) + +接下來思考哪個因子是最優的。在 $1$、$2$、$3$ 這三個因子中,顯然 $1$ 是最差的,因為 $1 \times (n-1) < n$ 恆成立,即切分出 $1$ 反而會導致乘積減小。 + +如下圖所示,當 $n = 6$ 時,有 $3 \times 3 > 2 \times 2 \times 2$ 。**這意味著切分出 $3$ 比切分出 $2$ 更優**。 + +**貪婪策略二**:在切分方案中,最多隻應存在兩個 $2$ 。因為三個 $2$ 總是可以替換為兩個 $3$ ,從而獲得更大的乘積。 + +![最優切分因子](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png) + +綜上所述,可推理出以下貪婪策略。 + +1. 輸入整數 $n$ ,從其不斷地切分出因子 $3$ ,直至餘數為 $0$、$1$、$2$ 。 +2. 當餘數為 $0$ 時,代表 $n$ 是 $3$ 的倍數,因此不做任何處理。 +3. 當餘數為 $2$ 時,不繼續劃分,保留。 +4. 當餘數為 $1$ 時,由於 $2 \times 2 > 1 \times 3$ ,因此應將最後一個 $3$ 替換為 $2$ 。 + +### 程式碼實現 + +如下圖所示,我們無須透過迴圈來切分整數,而可以利用向下整除運算得到 $3$ 的個數 $a$ ,用取模運算得到餘數 $b$ ,此時有: + +$$ +n = 3 a + b +$$ + +請注意,對於 $n \leq 3$ 的邊界情況,必須拆分出一個 $1$ ,乘積為 $1 \times (n - 1)$ 。 + +```src +[file]{max_product_cutting}-[class]{}-[func]{max_product_cutting} +``` + +![最大切分乘積的計算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) + +**時間複雜度取決於程式語言的冪運算的實現方法**。以 Python 為例,常用的冪計算函式有三種。 + +- 運算子 `**` 和函式 `pow()` 的時間複雜度均為 $O(\log⁡ a)$ 。 +- 函式 `math.pow()` 內部呼叫 C 語言庫的 `pow()` 函式,其執行浮點取冪,時間複雜度為 $O(1)$ 。 + +變數 $a$ 和 $b$ 使用常數大小的額外空間,**因此空間複雜度為 $O(1)$** 。 + +### 正確性證明 + +使用反證法,只分析 $n \geq 3$ 的情況。 + +1. **所有因子 $\leq 3$** :假設最優切分方案中存在 $\geq 4$ 的因子 $x$ ,那麼一定可以將其繼續劃分為 $2(x-2)$ ,從而獲得更大的乘積。這與假設矛盾。 +2. **切分方案不包含 $1$** :假設最優切分方案中存在一個因子 $1$ ,那麼它一定可以合併入另外一個因子中,以獲得更大的乘積。這與假設矛盾。 +3. **切分方案最多包含兩個 $2$** :假設最優切分方案中包含三個 $2$ ,那麼一定可以替換為兩個 $3$ ,乘積更大。這與假設矛盾。 diff --git a/zh-hant/docs/chapter_greedy/summary.md b/zh-hant/docs/chapter_greedy/summary.md new file mode 100644 index 000000000..c312a04c5 --- /dev/null +++ b/zh-hant/docs/chapter_greedy/summary.md @@ -0,0 +1,12 @@ +# 小結 + +- 貪婪演算法通常用於解決最最佳化問題,其原理是在每個決策階段都做出區域性最優的決策,以期獲得全域性最優解。 +- 貪婪演算法會迭代地做出一個又一個的貪婪選擇,每輪都將問題轉化成一個規模更小的子問題,直到問題被解決。 +- 貪婪演算法不僅實現簡單,還具有很高的解題效率。相比於動態規劃,貪婪演算法的時間複雜度通常更低。 +- 在零錢兌換問題中,對於某些硬幣組合,貪婪演算法可以保證找到最優解;對於另外一些硬幣組合則不然,貪婪演算法可能找到很差的解。 +- 適合用貪婪演算法求解的問題具有兩大性質:貪婪選擇性質和最優子結構。貪婪選擇性質代表貪婪策略的有效性。 +- 對於某些複雜問題,貪婪選擇性質的證明並不簡單。相對來說,證偽更加容易,例如零錢兌換問題。 +- 求解貪婪問題主要分為三步:問題分析、確定貪婪策略、正確性證明。其中,確定貪婪策略是核心步驟,正確性證明往往是難點。 +- 分數背包問題在 0-1 背包的基礎上,允許選擇物品的一部分,因此可使用貪婪演算法求解。貪婪策略的正確性可以使用反證法來證明。 +- 最大容量問題可使用窮舉法求解,時間複雜度為 $O(n^2)$ 。透過設計貪婪策略,每輪向內移動短板,可將時間複雜度最佳化至 $O(n)$ 。 +- 在最大切分乘積問題中,我們先後推理出兩個貪婪策略:$\geq 4$ 的整數都應該繼續切分,最優切分因子為 $3$ 。程式碼中包含冪運算,時間複雜度取決於冪運算實現方法,通常為 $O(1)$ 或 $O(\log n)$ 。 diff --git a/zh-hant/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png b/zh-hant/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png new file mode 100644 index 000000000..a7cac4ed1 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_algorithm.md b/zh-hant/docs/chapter_hashing/hash_algorithm.md new file mode 100644 index 000000000..1650f5cf3 --- /dev/null +++ b/zh-hant/docs/chapter_hashing/hash_algorithm.md @@ -0,0 +1,394 @@ +# 雜湊演算法 + +前兩節介紹了雜湊表的工作原理和雜湊衝突的處理方法。然而無論是開放定址還是鏈式位址,**它們只能保證雜湊表可以在發生衝突時正常工作,而無法減少雜湊衝突的發生**。 + +如果雜湊衝突過於頻繁,雜湊表的效能則會急劇劣化。如下圖所示,對於鏈式位址雜湊表,理想情況下鍵值對均勻分佈在各個桶中,達到最佳查詢效率;最差情況下所有鍵值對都儲存到同一個桶中,時間複雜度退化至 $O(n)$ 。 + +![雜湊衝突的最佳情況與最差情況](hash_algorithm.assets/hash_collision_best_worst_condition.png) + +**鍵值對的分佈情況由雜湊函式決定**。回憶雜湊函式的計算步驟,先計算雜湊值,再對陣列長度取模: + +```shell +index = hash(key) % capacity +``` + +觀察以上公式,當雜湊表容量 `capacity` 固定時,**雜湊演算法 `hash()` 決定了輸出值**,進而決定了鍵值對在雜湊表中的分佈情況。 + +這意味著,為了降低雜湊衝突的發生機率,我們應當將注意力集中在雜湊演算法 `hash()` 的設計上。 + +## 雜湊演算法的目標 + +為了實現“既快又穩”的雜湊表資料結構,雜湊演算法應具備以下特點。 + +- **確定性**:對於相同的輸入,雜湊演算法應始終產生相同的輸出。這樣才能確保雜湊表是可靠的。 +- **效率高**:計算雜湊值的過程應該足夠快。計算開銷越小,雜湊表的實用性越高。 +- **均勻分佈**:雜湊演算法應使得鍵值對均勻分佈在雜湊表中。分佈越均勻,雜湊衝突的機率就越低。 + +實際上,雜湊演算法除了可以用於實現雜湊表,還廣泛應用於其他領域中。 + +- **密碼儲存**:為了保護使用者密碼的安全,系統通常不會直接儲存使用者的明文密碼,而是儲存密碼的雜湊值。當用戶輸入密碼時,系統會對輸入的密碼計算雜湊值,然後與儲存的雜湊值進行比較。如果兩者匹配,那麼密碼就被視為正確。 +- **資料完整性檢查**:資料傳送方可以計算資料的雜湊值並將其一同傳送;接收方可以重新計算接收到的資料的雜湊值,並與接收到的雜湊值進行比較。如果兩者匹配,那麼資料就被視為完整。 + +對於密碼學的相關應用,為了防止從雜湊值推導出原始密碼等逆向工程,雜湊演算法需要具備更高等級的安全特性。 + +- **單向性**:無法透過雜湊值反推出關於輸入資料的任何資訊。 +- **抗碰撞性**:應當極難找到兩個不同的輸入,使得它們的雜湊值相同。 +- **雪崩效應**:輸入的微小變化應當導致輸出的顯著且不可預測的變化。 + +請注意,**“均勻分佈”與“抗碰撞性”是兩個獨立的概念**,滿足均勻分佈不一定滿足抗碰撞性。例如,在隨機輸入 `key` 下,雜湊函式 `key % 100` 可以產生均勻分佈的輸出。然而該雜湊演算法過於簡單,所有後兩位相等的 `key` 的輸出都相同,因此我們可以很容易地從雜湊值反推出可用的 `key` ,從而破解密碼。 + +## 雜湊演算法的設計 + +雜湊演算法的設計是一個需要考慮許多因素的複雜問題。然而對於某些要求不高的場景,我們也能設計一些簡單的雜湊演算法。 + +- **加法雜湊**:對輸入的每個字元的 ASCII 碼進行相加,將得到的總和作為雜湊值。 +- **乘法雜湊**:利用乘法的不相關性,每輪乘以一個常數,將各個字元的 ASCII 碼累積到雜湊值中。 +- **互斥或雜湊**:將輸入資料的每個元素透過互斥或操作累積到一個雜湊值中。 +- **旋轉雜湊**:將每個字元的 ASCII 碼累積到一個雜湊值中,每次累積之前都會對雜湊值進行旋轉操作。 + +```src +[file]{simple_hash}-[class]{}-[func]{rot_hash} +``` + +觀察發現,每種雜湊演算法的最後一步都是對大質數 $1000000007$ 取模,以確保雜湊值在合適的範圍內。值得思考的是,為什麼要強調對質數取模,或者說對合數取模的弊端是什麼?這是一個有趣的問題。 + +先丟擲結論:**使用大質數作為模數,可以最大化地保證雜湊值的均勻分佈**。因為質數不與其他數字存在公約數,可以減少因取模操作而產生的週期性模式,從而避免雜湊衝突。 + +舉個例子,假設我們選擇合數 $9$ 作為模數,它可以被 $3$ 整除,那麼所有可以被 $3$ 整除的 `key` 都會被對映到 $0$、$3$、$6$ 這三個雜湊值。 + +$$ +\begin{aligned} +\text{modulus} & = 9 \newline +\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline +\text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \} +\end{aligned} +$$ + +如果輸入 `key` 恰好滿足這種等差數列的資料分佈,那麼雜湊值就會出現聚堆積,從而加重雜湊衝突。現在,假設將 `modulus` 替換為質數 $13$ ,由於 `key` 和 `modulus` 之間不存在公約數,因此輸出的雜湊值的均勻性會明顯提升。 + +$$ +\begin{aligned} +\text{modulus} & = 13 \newline +\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline +\text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \} +\end{aligned} +$$ + +值得說明的是,如果能夠保證 `key` 是隨機均勻分佈的,那麼選擇質數或者合數作為模數都可以,它們都能輸出均勻分佈的雜湊值。而當 `key` 的分佈存在某種週期性時,對合數取模更容易出現聚集現象。 + +總而言之,我們通常選取質數作為模數,並且這個質數最好足夠大,以儘可能消除週期性模式,提升雜湊演算法的穩健性。 + +## 常見雜湊演算法 + +不難發現,以上介紹的簡單雜湊演算法都比較“脆弱”,遠遠沒有達到雜湊演算法的設計目標。例如,由於加法和互斥或滿足交換律,因此加法雜湊和互斥或雜湊無法區分內容相同但順序不同的字串,這可能會加劇雜湊衝突,並引起一些安全問題。 + +在實際中,我們通常會用一些標準雜湊演算法,例如 MD5、SHA-1、SHA-2 和 SHA-3 等。它們可以將任意長度的輸入資料對映到恆定長度的雜湊值。 + +近一個世紀以來,雜湊演算法處在不斷升級與最佳化的過程中。一部分研究人員努力提升雜湊演算法的效能,另一部分研究人員和駭客則致力於尋找雜湊演算法的安全性問題。下表展示了在實際應用中常見的雜湊演算法。 + +- MD5 和 SHA-1 已多次被成功攻擊,因此它們被各類安全應用棄用。 +- SHA-2 系列中的 SHA-256 是最安全的雜湊演算法之一,仍未出現成功的攻擊案例,因此常用在各類安全應用與協議中。 +- SHA-3 相較 SHA-2 的實現開銷更低、計算效率更高,但目前使用覆蓋度不如 SHA-2 系列。 + +

  常見的雜湊演算法

+ +| | MD5 | SHA-1 | SHA-2 | SHA-3 | +| -------- | ------------------------------ | ---------------- | ---------------------------- | ------------------- | +| 推出時間 | 1992 | 1995 | 2002 | 2008 | +| 輸出長度 | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | +| 雜湊衝突 | 較多 | 較多 | 很少 | 很少 | +| 安全等級 | 低,已被成功攻擊 | 低,已被成功攻擊 | 高 | 高 | +| 應用 | 已被棄用,仍用於資料完整性檢查 | 已被棄用 | 加密貨幣交易驗證、數字簽名等 | 可用於替代 SHA-2 | + +## 資料結構的雜湊值 + +我們知道,雜湊表的 `key` 可以是整數、小數或字串等資料型別。程式語言通常會為這些資料型別提供內建的雜湊演算法,用於計算雜湊表中的桶索引。以 Python 為例,我們可以呼叫 `hash()` 函式來計算各種資料型別的雜湊值。 + +- 整數和布林量的雜湊值就是其本身。 +- 浮點數和字串的雜湊值計算較為複雜,有興趣的讀者請自行學習。 +- 元組的雜湊值是對其中每一個元素進行雜湊,然後將這些雜湊值組合起來,得到單一的雜湊值。 +- 物件的雜湊值基於其記憶體位址生成。透過重寫物件的雜湊方法,可實現基於內容生成雜湊值。 + +!!! tip + + 請注意,不同程式語言的內建雜湊值計算函式的定義和方法不同。 + +=== "Python" + + ```python title="built_in_hash.py" + num = 3 + hash_num = hash(num) + # 整數 3 的雜湊值為 3 + + bol = True + hash_bol = hash(bol) + # 布林量 True 的雜湊值為 1 + + dec = 3.14159 + hash_dec = hash(dec) + # 小數 3.14159 的雜湊值為 326484311674566659 + + str = "Hello 演算法" + hash_str = hash(str) + # 字串“Hello 演算法”的雜湊值為 4617003410720528961 + + tup = (12836, "小哈") + hash_tup = hash(tup) + # 元組 (12836, '小哈') 的雜湊值為 1029005403108185979 + + obj = ListNode(0) + hash_obj = hash(obj) + # 節點物件 的雜湊值為 274267521 + ``` + +=== "C++" + + ```cpp title="built_in_hash.cpp" + int num = 3; + size_t hashNum = hash()(num); + // 整數 3 的雜湊值為 3 + + bool bol = true; + size_t hashBol = hash()(bol); + // 布林量 1 的雜湊值為 1 + + double dec = 3.14159; + size_t hashDec = hash()(dec); + // 小數 3.14159 的雜湊值為 4614256650576692846 + + string str = "Hello 演算法"; + size_t hashStr = hash()(str); + // 字串“Hello 演算法”的雜湊值為 15466937326284535026 + + // 在 C++ 中,內建 std:hash() 僅提供基本資料型別的雜湊值計算 + // 陣列、物件的雜湊值計算需要自行實現 + ``` + +=== "Java" + + ```java title="built_in_hash.java" + int num = 3; + int hashNum = Integer.hashCode(num); + // 整數 3 的雜湊值為 3 + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + // 布林量 true 的雜湊值為 1231 + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + // 小數 3.14159 的雜湊值為 -1340954729 + + String str = "Hello 演算法"; + int hashStr = str.hashCode(); + // 字串“Hello 演算法”的雜湊值為 -727081396 + + Object[] arr = { 12836, "小哈" }; + int hashTup = Arrays.hashCode(arr); + // 陣列 [12836, 小哈] 的雜湊值為 1151158 + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + // 節點物件 utils.ListNode@7dc5e7b4 的雜湊值為 2110121908 + ``` + +=== "C#" + + ```csharp title="built_in_hash.cs" + int num = 3; + int hashNum = num.GetHashCode(); + // 整數 3 的雜湊值為 3; + + bool bol = true; + int hashBol = bol.GetHashCode(); + // 布林量 true 的雜湊值為 1; + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + // 小數 3.14159 的雜湊值為 -1340954729; + + string str = "Hello 演算法"; + int hashStr = str.GetHashCode(); + // 字串“Hello 演算法”的雜湊值為 -586107568; + + object[] arr = [12836, "小哈"]; + int hashTup = arr.GetHashCode(); + // 陣列 [12836, 小哈] 的雜湊值為 42931033; + + ListNode obj = new(0); + int hashObj = obj.GetHashCode(); + // 節點物件 0 的雜湊值為 39053774; + ``` + +=== "Go" + + ```go title="built_in_hash.go" + // Go 未提供內建 hash code 函式 + ``` + +=== "Swift" + + ```swift title="built_in_hash.swift" + let num = 3 + let hashNum = num.hashValue + // 整數 3 的雜湊值為 9047044699613009734 + + let bol = true + let hashBol = bol.hashValue + // 布林量 true 的雜湊值為 -4431640247352757451 + + let dec = 3.14159 + let hashDec = dec.hashValue + // 小數 3.14159 的雜湊值為 -2465384235396674631 + + let str = "Hello 演算法" + let hashStr = str.hashValue + // 字串“Hello 演算法”的雜湊值為 -7850626797806988787 + + let arr = [AnyHashable(12836), AnyHashable("小哈")] + let hashTup = arr.hashValue + // 陣列 [AnyHashable(12836), AnyHashable("小哈")] 的雜湊值為 -2308633508154532996 + + let obj = ListNode(x: 0) + let hashObj = obj.hashValue + // 節點物件 utils.ListNode 的雜湊值為 -2434780518035996159 + ``` + +=== "JS" + + ```javascript title="built_in_hash.js" + // JavaScript 未提供內建 hash code 函式 + ``` + +=== "TS" + + ```typescript title="built_in_hash.ts" + // TypeScript 未提供內建 hash code 函式 + ``` + +=== "Dart" + + ```dart title="built_in_hash.dart" + int num = 3; + int hashNum = num.hashCode; + // 整數 3 的雜湊值為 34803 + + bool bol = true; + int hashBol = bol.hashCode; + // 布林值 true 的雜湊值為 1231 + + double dec = 3.14159; + int hashDec = dec.hashCode; + // 小數 3.14159 的雜湊值為 2570631074981783 + + String str = "Hello 演算法"; + int hashStr = str.hashCode; + // 字串“Hello 演算法”的雜湊值為 468167534 + + List arr = [12836, "小哈"]; + int hashArr = arr.hashCode; + // 陣列 [12836, 小哈] 的雜湊值為 976512528 + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode; + // 節點物件 Instance of 'ListNode' 的雜湊值為 1033450432 + ``` + +=== "Rust" + + ```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); + let hash_num = num_hasher.finish(); + // 整數 3 的雜湊值為 568126464209439262 + + let bol = true; + let mut bol_hasher = DefaultHasher::new(); + bol.hash(&mut bol_hasher); + let hash_bol = bol_hasher.finish(); + // 布林量 true 的雜湊值為 4952851536318644461 + + let dec: f32 = 3.14159; + let mut dec_hasher = DefaultHasher::new(); + dec.to_bits().hash(&mut dec_hasher); + let hash_dec = dec_hasher.finish(); + // 小數 3.14159 的雜湊值為 2566941990314602357 + + let str = "Hello 演算法"; + let mut str_hasher = DefaultHasher::new(); + str.hash(&mut str_hasher); + let hash_str = str_hasher.finish(); + // 字串“Hello 演算法”的雜湊值為 16092673739211250988 + + let arr = (&12836, &"小哈"); + let mut tup_hasher = DefaultHasher::new(); + arr.hash(&mut tup_hasher); + let hash_tup = tup_hasher.finish(); + // 元組 (12836, "小哈") 的雜湊值為 1885128010422702749 + + let node = ListNode::new(42); + let mut hasher = DefaultHasher::new(); + node.borrow().val.hash(&mut hasher); + let hash = hasher.finish(); + // 節點物件 RefCell { value: ListNode { val: 42, next: None } } 的雜湊值為15387811073369036852 + ``` + +=== "C" + + ```c title="built_in_hash.c" + // C 未提供內建 hash code 函式 + ``` + +=== "Kotlin" + + ```kotlin title="built_in_hash.kt" + val num = 3 + val hashNum = num.hashCode() + // 整數 3 的雜湊值為 3 + + val bol = true + val hashBol = bol.hashCode() + // 布林量 true 的雜湊值為 1231 + + val dec = 3.14159 + val hashDec = dec.hashCode() + // 小數 3.14159 的雜湊值為 -1340954729 + + val str = "Hello 演算法" + val hashStr = str.hashCode() + // 字串“Hello 演算法”的雜湊值為 -727081396 + + val arr = arrayOf(12836, "小哈") + val hashTup = arr.hashCode() + // 陣列 [12836, 小哈] 的雜湊值為 189568618 + + val obj = ListNode(0) + val hashObj = obj.hashCode() + // 節點物件 utils.ListNode@1d81eb93 的雜湊值為 495053715 + ``` + +=== "Ruby" + + ```ruby title="built_in_hash.rb" + + ``` + +=== "Zig" + + ```zig title="built_in_hash.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +在許多程式語言中,**只有不可變物件才可作為雜湊表的 `key`** 。假如我們將串列(動態陣列)作為 `key` ,當串列的內容發生變化時,它的雜湊值也隨之改變,我們就無法在雜湊表中查詢到原先的 `value` 了。 + +雖然自定義物件(比如鏈結串列節點)的成員變數是可變的,但它是可雜湊的。**這是因為物件的雜湊值通常是基於記憶體位址生成的**,即使物件的內容發生了變化,但它的記憶體位址不變,雜湊值仍然是不變的。 + +細心的你可能發現在不同控制檯中執行程式時,輸出的雜湊值是不同的。**這是因為 Python 直譯器在每次啟動時,都會為字串雜湊函式加入一個隨機的鹽(salt)值**。這種做法可以有效防止 HashDoS 攻擊,提升雜湊演算法的安全性。 diff --git a/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png new file mode 100644 index 000000000..5a3e50b61 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png new file mode 100644 index 000000000..fd58b20e8 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png new file mode 100644 index 000000000..fe92f0af4 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_collision.md b/zh-hant/docs/chapter_hashing/hash_collision.md new file mode 100644 index 000000000..2b6ad7815 --- /dev/null +++ b/zh-hant/docs/chapter_hashing/hash_collision.md @@ -0,0 +1,108 @@ +# 雜湊衝突 + +上一節提到,**通常情況下雜湊函式的輸入空間遠大於輸出空間**,因此理論上雜湊衝突是不可避免的。比如,輸入空間為全體整數,輸出空間為陣列容量大小,則必然有多個整數對映至同一桶索引。 + +雜湊衝突會導致查詢結果錯誤,嚴重影響雜湊表的可用性。為了解決該問題,每當遇到雜湊衝突時,我們就進行雜湊表擴容,直至衝突消失為止。此方法簡單粗暴且有效,但效率太低,因為雜湊表擴容需要進行大量的資料搬運與雜湊值計算。為了提升效率,我們可以採用以下策略。 + +1. 改良雜湊表資料結構,**使得雜湊表可以在出現雜湊衝突時正常工作**。 +2. 僅在必要時,即當雜湊衝突比較嚴重時,才執行擴容操作。 + +雜湊表的結構改良方法主要包括“鏈式位址”和“開放定址”。 + +## 鏈式位址 + +在原始雜湊表中,每個桶僅能儲存一個鍵值對。鏈式位址(separate chaining)將單個元素轉換為鏈結串列,將鍵值對作為鏈結串列節點,將所有發生衝突的鍵值對都儲存在同一鏈結串列中。下圖展示了一個鏈式位址雜湊表的例子。 + +![鏈式位址雜湊表](hash_collision.assets/hash_table_chaining.png) + +基於鏈式位址實現的雜湊表的操作方法發生了以下變化。 + +- **查詢元素**:輸入 `key` ,經過雜湊函式得到桶索引,即可訪問鏈結串列頭節點,然後走訪鏈結串列並對比 `key` 以查詢目標鍵值對。 +- **新增元素**:首先透過雜湊函式訪問鏈結串列頭節點,然後將節點(鍵值對)新增到鏈結串列中。 +- **刪除元素**:根據雜湊函式的結果訪問鏈結串列頭部,接著走訪鏈結串列以查詢目標節點並將其刪除。 + +鏈式位址存在以下侷限性。 + +- **佔用空間增大**:鏈結串列包含節點指標,它相比陣列更加耗費記憶體空間。 +- **查詢效率降低**:因為需要線性走訪鏈結串列來查詢對應元素。 + +以下程式碼給出了鏈式位址雜湊表的簡單實現,需要注意兩點。 + +- 使用串列(動態陣列)代替鏈結串列,從而簡化程式碼。在這種設定下,雜湊表(陣列)包含多個桶,每個桶都是一個串列。 +- 以下實現包含雜湊表擴容方法。當負載因子超過 $\frac{2}{3}$ 時,我們將雜湊表擴容至原先的 $2$ 倍。 + +```src +[file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{} +``` + +值得注意的是,當鏈結串列很長時,查詢效率 $O(n)$ 很差。**此時可以將鏈結串列轉換為“AVL 樹”或“紅黑樹”**,從而將查詢操作的時間複雜度最佳化至 $O(\log n)$ 。 + +## 開放定址 + +開放定址(open addressing)不引入額外的資料結構,而是透過“多次探測”來處理雜湊衝突,探測方式主要包括線性探查、平方探測和多次雜湊等。 + +下面以線性探查為例,介紹開放定址雜湊表的工作機制。 + +### 線性探查 + +線性探查採用固定步長的線性搜尋來進行探測,其操作方法與普通雜湊表有所不同。 + +- **插入元素**:透過雜湊函式計算桶索引,若發現桶內已有元素,則從衝突位置向後線性走訪(步長通常為 $1$ ),直至找到空桶,將元素插入其中。 +- **查詢元素**:若發現雜湊衝突,則使用相同步長向後進行線性走訪,直到找到對應元素,返回 `value` 即可;如果遇到空桶,說明目標元素不在雜湊表中,返回 `None` 。 + +下圖展示了開放定址(線性探查)雜湊表的鍵值對分佈。根據此雜湊函式,最後兩位相同的 `key` 都會被對映到相同的桶。而透過線性探查,它們被依次儲存在該桶以及之下的桶中。 + +![開放定址(線性探查)雜湊表的鍵值對分佈](hash_collision.assets/hash_table_linear_probing.png) + +然而,**線性探查容易產生“聚集現象”**。具體來說,陣列中連續被佔用的位置越長,這些連續位置發生雜湊衝突的可能性越大,從而進一步促使該位置的聚堆積生長,形成惡性迴圈,最終導致增刪查改操作效率劣化。 + +值得注意的是,**我們不能在開放定址雜湊表中直接刪除元素**。這是因為刪除元素會在陣列內產生一個空桶 `None` ,而當查詢元素時,線性探查到該空桶就會返回,因此在該空桶之下的元素都無法再被訪問到,程式可能誤判這些元素不存在,如下圖所示。 + +![在開放定址中刪除元素導致的查詢問題](hash_collision.assets/hash_table_open_addressing_deletion.png) + +為了解決該問題,我們可以採用懶刪除(lazy deletion)機制:它不直接從雜湊表中移除元素,**而是利用一個常數 `TOMBSTONE` 來標記這個桶**。在該機制下,`None` 和 `TOMBSTONE` 都代表空桶,都可以放置鍵值對。但不同的是,線性探查到 `TOMBSTONE` 時應該繼續走訪,因為其之下可能還存在鍵值對。 + +然而,**懶刪除可能會加速雜湊表的效能退化**。這是因為每次刪除操作都會產生一個刪除標記,隨著 `TOMBSTONE` 的增加,搜尋時間也會增加,因為線性探查可能需要跳過多個 `TOMBSTONE` 才能找到目標元素。 + +為此,考慮線上性探查中記錄遇到的首個 `TOMBSTONE` 的索引,並將搜尋到的目標元素與該 `TOMBSTONE` 交換位置。這樣做的好處是當每次查詢或新增元素時,元素會被移動至距離理想位置(探測起始點)更近的桶,從而最佳化查詢效率。 + +以下程式碼實現了一個包含懶刪除的開放定址(線性探查)雜湊表。為了更加充分地使用雜湊表的空間,我們將雜湊表看作一個“環形陣列”,當越過陣列尾部時,回到頭部繼續走訪。 + +```src +[file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{} +``` + +### 平方探測 + +平方探測與線性探查類似,都是開放定址的常見策略之一。當發生衝突時,平方探測不是簡單地跳過一個固定的步數,而是跳過“探測次數的平方”的步數,即 $1, 4, 9, \dots$ 步。 + +平方探測主要具有以下優勢。 + +- 平方探測透過跳過探測次數平方的距離,試圖緩解線性探查的聚集效應。 +- 平方探測會跳過更大的距離來尋找空位置,有助於資料分佈得更加均勻。 + +然而,平方探測並不是完美的。 + +- 仍然存在聚集現象,即某些位置比其他位置更容易被佔用。 +- 由於平方的增長,平方探測可能不會探測整個雜湊表,這意味著即使雜湊表中有空桶,平方探測也可能無法訪問到它。 + +### 多次雜湊 + +顧名思義,多次雜湊方法使用多個雜湊函式 $f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$ 進行探測。 + +- **插入元素**:若雜湊函式 $f_1(x)$ 出現衝突,則嘗試 $f_2(x)$ ,以此類推,直到找到空位後插入元素。 +- **查詢元素**:在相同的雜湊函式順序下進行查詢,直到找到目標元素時返回;若遇到空位或已嘗試所有雜湊函式,說明雜湊表中不存在該元素,則返回 `None` 。 + +與線性探查相比,多次雜湊方法不易產生聚集,但多個雜湊函式會帶來額外的計算量。 + +!!! tip + + 請注意,開放定址(線性探查、平方探測和多次雜湊)雜湊表都存在“不能直接刪除元素”的問題。 + +## 程式語言的選擇 + +各種程式語言採取了不同的雜湊表實現策略,下面舉幾個例子。 + +- Python 採用開放定址。字典 `dict` 使用偽隨機數進行探測。 +- Java 採用鏈式位址。自 JDK 1.8 以來,當 `HashMap` 內陣列長度達到 64 且鏈結串列長度達到 8 時,鏈結串列會轉換為紅黑樹以提升查詢效能。 +- Go 採用鏈式位址。Go 規定每個桶最多儲存 8 個鍵值對,超出容量則連線一個溢位桶;當溢位桶過多時,會執行一次特殊的等量擴容操作,以確保效能。 diff --git a/zh-hant/docs/chapter_hashing/hash_map.assets/hash_collision.png b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_collision.png new file mode 100644 index 000000000..eebe8c8f8 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_collision.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_map.assets/hash_function.png b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_function.png new file mode 100644 index 000000000..35d0a3ddf Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_function.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png new file mode 100644 index 000000000..4f71b61a8 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png new file mode 100644 index 000000000..cd6459d27 Binary files /dev/null and b/zh-hant/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png differ diff --git a/zh-hant/docs/chapter_hashing/hash_map.md b/zh-hant/docs/chapter_hashing/hash_map.md new file mode 100755 index 000000000..f181e98a5 --- /dev/null +++ b/zh-hant/docs/chapter_hashing/hash_map.md @@ -0,0 +1,578 @@ +# 雜湊表 + +雜湊表(hash table),又稱散列表,它透過建立鍵 `key` 與值 `value` 之間的對映,實現高效的元素查詢。具體而言,我們向雜湊表中輸入一個鍵 `key` ,則可以在 $O(1)$ 時間內獲取對應的值 `value` 。 + +如下圖所示,給定 $n$ 個學生,每個學生都有“姓名”和“學號”兩項資料。假如我們希望實現“輸入一個學號,返回對應的姓名”的查詢功能,則可以採用下圖所示的雜湊表來實現。 + +![雜湊表的抽象表示](hash_map.assets/hash_table_lookup.png) + +除雜湊表外,陣列和鏈結串列也可以實現查詢功能,它們的效率對比如下表所示。 + +- **新增元素**:僅需將元素新增至陣列(鏈結串列)的尾部即可,使用 $O(1)$ 時間。 +- **查詢元素**:由於陣列(鏈結串列)是亂序的,因此需要走訪其中的所有元素,使用 $O(n)$ 時間。 +- **刪除元素**:需要先查詢到元素,再從陣列(鏈結串列)中刪除,使用 $O(n)$ 時間。 + +

  元素查詢效率對比

+ +| | 陣列 | 鏈結串列 | 雜湊表 | +| -------- | ------ | ------ | ------ | +| 查詢元素 | $O(n)$ | $O(n)$ | $O(1)$ | +| 新增元素 | $O(1)$ | $O(1)$ | $O(1)$ | +| 刪除元素 | $O(n)$ | $O(n)$ | $O(1)$ | + +觀察發現,**在雜湊表中進行增刪查改的時間複雜度都是 $O(1)$** ,非常高效。 + +## 雜湊表常用操作 + +雜湊表的常見操作包括:初始化、查詢操作、新增鍵值對和刪除鍵值對等,示例程式碼如下: + +=== "Python" + + ```python title="hash_map.py" + # 初始化雜湊表 + hmap: dict = {} + + # 新增操作 + # 在雜湊表中新增鍵值對 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小囉" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鴨" + + # 查詢操作 + # 向雜湊表中輸入鍵 key ,得到值 value + name: str = hmap[15937] + + # 刪除操作 + # 在雜湊表中刪除鍵值對 (key, value) + hmap.pop(10583) + ``` + +=== "C++" + + ```cpp title="hash_map.cpp" + /* 初始化雜湊表 */ + unordered_map map; + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈"; + map[15937] = "小囉"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鴨"; + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map[15937]; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.erase(10583); + ``` + +=== "Java" + + ```java title="hash_map.java" + /* 初始化雜湊表 */ + Map map = new HashMap<>(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + 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); + ``` + +=== "C#" + + ```csharp title="hash_map.cs" + /* 初始化雜湊表 */ + Dictionary map = new() { + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + { 12836, "小哈" }, + { 15937, "小囉" }, + { 16750, "小算" }, + { 13276, "小法" }, + { 10583, "小鴨" } + }; + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + string name = map[15937]; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.Remove(10583); + ``` + +=== "Go" + + ```go title="hash_map_test.go" + /* 初始化雜湊表 */ + hmap := make(map[int]string) + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + hmap[12836] = "小哈" + hmap[15937] = "小囉" + hmap[16750] = "小算" + hmap[13276] = "小法" + hmap[10583] = "小鴨" + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + name := hmap[15937] + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + delete(hmap, 10583) + ``` + +=== "Swift" + + ```swift title="hash_map.swift" + /* 初始化雜湊表 */ + var map: [Int: String] = [:] + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈" + map[15937] = "小囉" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鴨" + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map[15937]! + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.removeValue(forKey: 10583) + ``` + +=== "JS" + + ```javascript title="hash_map.js" + /* 初始化雜湊表 */ + const map = new Map(); + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.set(12836, '小哈'); + map.set(15937, '小囉'); + map.set(16750, '小算'); + map.set(13276, '小法'); + map.set(10583, '小鴨'); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let name = map.get(15937); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.delete(10583); + ``` + +=== "TS" + + ```typescript title="hash_map.ts" + /* 初始化雜湊表 */ + const map = new Map(); + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.set(12836, '小哈'); + map.set(15937, '小囉'); + map.set(16750, '小算'); + map.set(13276, '小法'); + 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); + console.info('\n刪除 10583 後,雜湊表為\nKey -> Value'); + console.info(map); + ``` + +=== "Dart" + + ```dart title="hash_map.dart" + /* 初始化雜湊表 */ + Map map = {}; + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈"; + map[15937] = "小囉"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鴨"; + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + String name = map[15937]; + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583); + ``` + +=== "Rust" + + ```rust title="hash_map.rs" + use std::collections::HashMap; + + /* 初始化雜湊表 */ + let mut map: HashMap = HashMap::new(); + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map.insert(12836, "小哈".to_string()); + map.insert(15937, "小囉".to_string()); + map.insert(16750, "小算".to_string()); + map.insert(13279, "小法".to_string()); + map.insert(10583, "小鴨".to_string()); + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + let _name: Option<&String> = map.get(&15937); + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + let _removed_value: Option = map.remove(&10583); + ``` + +=== "C" + + ```c title="hash_map.c" + // C 未提供內建雜湊表 + ``` + +=== "Kotlin" + + ```kotlin title="hash_map.kt" + /* 初始化雜湊表 */ + val map = HashMap() + + /* 新增操作 */ + // 在雜湊表中新增鍵值對 (key, value) + map[12836] = "小哈" + map[15937] = "小囉" + map[16750] = "小算" + map[13276] = "小法" + map[10583] = "小鴨" + + /* 查詢操作 */ + // 向雜湊表中輸入鍵 key ,得到值 value + val name = map[15937] + + /* 刪除操作 */ + // 在雜湊表中刪除鍵值對 (key, value) + map.remove(10583) + ``` + +=== "Ruby" + + ```ruby title="hash_map.rb" + + ``` + +=== "Zig" + + ```zig title="hash_map.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +雜湊表有三種常用的走訪方式:走訪鍵值對、走訪鍵和走訪值。示例程式碼如下: + +=== "Python" + + ```python title="hash_map.py" + # 走訪雜湊表 + # 走訪鍵值對 key->value + for key, value in hmap.items(): + print(key, "->", value) + # 單獨走訪鍵 key + for key in hmap.keys(): + print(key) + # 單獨走訪值 value + for value in hmap.values(): + print(value) + ``` + +=== "C++" + + ```cpp title="hash_map.cpp" + /* 走訪雜湊表 */ + // 走訪鍵值對 key->value + for (auto kv: map) { + cout << kv.first << " -> " << kv.second << endl; + } + // 使用迭代器走訪 key->value + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; + } + ``` + +=== "Java" + + ```java title="hash_map.java" + /* 走訪雜湊表 */ + // 走訪鍵值對 key->value + for (Map.Entry kv: map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + // 單獨走訪鍵 key + for (int key: map.keySet()) { + System.out.println(key); + } + // 單獨走訪值 value + for (String val: map.values()) { + System.out.println(val); + } + ``` + +=== "C#" + + ```csharp title="hash_map.cs" + /* 走訪雜湊表 */ + // 走訪鍵值對 Key->Value + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + // 單獨走訪鍵 key + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + // 單獨走訪值 value + foreach (string val in map.Values) { + Console.WriteLine(val); + } + ``` + +=== "Go" + + ```go title="hash_map_test.go" + /* 走訪雜湊表 */ + // 走訪鍵值對 key->value + for key, value := range hmap { + fmt.Println(key, "->", value) + } + // 單獨走訪鍵 key + for key := range hmap { + fmt.Println(key) + } + // 單獨走訪值 value + for _, value := range hmap { + fmt.Println(value) + } + ``` + +=== "Swift" + + ```swift title="hash_map.swift" + /* 走訪雜湊表 */ + // 走訪鍵值對 Key->Value + for (key, value) in map { + print("\(key) -> \(value)") + } + // 單獨走訪鍵 Key + for key in map.keys { + print(key) + } + // 單獨走訪值 Value + for value in map.values { + print(value) + } + ``` + +=== "JS" + + ```javascript title="hash_map.js" + /* 走訪雜湊表 */ + console.info('\n走訪鍵值對 Key->Value'); + for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); + } + console.info('\n單獨走訪鍵 Key'); + for (const k of map.keys()) { + console.info(k); + } + console.info('\n單獨走訪值 Value'); + for (const v of map.values()) { + console.info(v); + } + ``` + +=== "TS" + + ```typescript title="hash_map.ts" + /* 走訪雜湊表 */ + console.info('\n走訪鍵值對 Key->Value'); + for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); + } + console.info('\n單獨走訪鍵 Key'); + for (const k of map.keys()) { + console.info(k); + } + console.info('\n單獨走訪值 Value'); + for (const v of map.values()) { + console.info(v); + } + ``` + +=== "Dart" + + ```dart title="hash_map.dart" + /* 走訪雜湊表 */ + // 走訪鍵值對 Key->Value + map.forEach((key, value) { + print('$key -> $value'); + }); + + // 單獨走訪鍵 Key + map.keys.forEach((key) { + print(key); + }); + + // 單獨走訪值 Value + map.values.forEach((value) { + print(value); + }); + ``` + +=== "Rust" + + ```rust title="hash_map.rs" + /* 走訪雜湊表 */ + // 走訪鍵值對 Key->Value + for (key, value) in &map { + println!("{key} -> {value}"); + } + + // 單獨走訪鍵 Key + for key in map.keys() { + println!("{key}"); + } + + // 單獨走訪值 Value + for value in map.values() { + println!("{value}"); + } + ``` + +=== "C" + + ```c title="hash_map.c" + // C 未提供內建雜湊表 + ``` + +=== "Kotlin" + + ```kotlin title="hash_map.kt" + /* 走訪雜湊表 */ + // 走訪鍵值對 key->value + for ((key, value) in map) { + println("$key -> $value") + } + // 單獨走訪鍵 key + for (key in map.keys) { + println(key) + } + // 單獨走訪值 value + for (_val in map.values) { + println(_val) + } + ``` + +=== "Ruby" + + ```ruby title="hash_map.rb" + + ``` + +=== "Zig" + + ```zig title="hash_map.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 雜湊表簡單實現 + +我們先考慮最簡單的情況,**僅用一個陣列來實現雜湊表**。在雜湊表中,我們將陣列中的每個空位稱為桶(bucket),每個桶可儲存一個鍵值對。因此,查詢操作就是找到 `key` 對應的桶,並在桶中獲取 `value` 。 + +那麼,如何基於 `key` 定位對應的桶呢?這是透過雜湊函式(hash function)實現的。雜湊函式的作用是將一個較大的輸入空間對映到一個較小的輸出空間。在雜湊表中,輸入空間是所有 `key` ,輸出空間是所有桶(陣列索引)。換句話說,輸入一個 `key` ,**我們可以透過雜湊函式得到該 `key` 對應的鍵值對在陣列中的儲存位置**。 + +輸入一個 `key` ,雜湊函式的計算過程分為以下兩步。 + +1. 透過某種雜湊演算法 `hash()` 計算得到雜湊值。 +2. 將雜湊值對桶數量(陣列長度)`capacity` 取模,從而獲取該 `key` 對應的陣列索引 `index` 。 + +```shell +index = hash(key) % capacity +``` + +隨後,我們就可以利用 `index` 在雜湊表中訪問對應的桶,從而獲取 `value` 。 + +設陣列長度 `capacity = 100`、雜湊演算法 `hash(key) = key` ,易得雜湊函式為 `key % 100` 。下圖以 `key` 學號和 `value` 姓名為例,展示了雜湊函式的工作原理。 + +![雜湊函式工作原理](hash_map.assets/hash_function.png) + +以下程式碼實現了一個簡單雜湊表。其中,我們將 `key` 和 `value` 封裝成一個類別 `Pair` ,以表示鍵值對。 + +```src +[file]{array_hash_map}-[class]{array_hash_map}-[func]{} +``` + +## 雜湊衝突與擴容 + +從本質上看,雜湊函式的作用是將所有 `key` 構成的輸入空間對映到陣列所有索引構成的輸出空間,而輸入空間往往遠大於輸出空間。因此,**理論上一定存在“多個輸入對應相同輸出”的情況**。 + +對於上述示例中的雜湊函式,當輸入的 `key` 後兩位相同時,雜湊函式的輸出結果也相同。例如,查詢學號為 12836 和 20336 的兩個學生時,我們得到: + +```shell +12836 % 100 = 36 +20336 % 100 = 36 +``` + +如下圖所示,兩個學號指向了同一個姓名,這顯然是不對的。我們將這種多個輸入對應同一輸出的情況稱為雜湊衝突(hash collision)。 + +![雜湊衝突示例](hash_map.assets/hash_collision.png) + +容易想到,雜湊表容量 $n$ 越大,多個 `key` 被分配到同一個桶中的機率就越低,衝突就越少。因此,**我們可以透過擴容雜湊表來減少雜湊衝突**。 + +如下圖所示,擴容前鍵值對 `(136, A)` 和 `(236, D)` 發生衝突,擴容後衝突消失。 + +![雜湊表擴容](hash_map.assets/hash_table_reshash.png) + +類似於陣列擴容,雜湊表擴容需將所有鍵值對從原雜湊表遷移至新雜湊表,非常耗時;並且由於雜湊表容量 `capacity` 改變,我們需要透過雜湊函式來重新計算所有鍵值對的儲存位置,這進一步增加了擴容過程的計算開銷。為此,程式語言通常會預留足夠大的雜湊表容量,防止頻繁擴容。 + +負載因子(load factor)是雜湊表的一個重要概念,其定義為雜湊表的元素數量除以桶數量,用於衡量雜湊衝突的嚴重程度,**也常作為雜湊表擴容的觸發條件**。例如在 Java 中,當負載因子超過 $0.75$ 時,系統會將雜湊表擴容至原先的 $2$ 倍。 diff --git a/zh-hant/docs/chapter_hashing/index.md b/zh-hant/docs/chapter_hashing/index.md new file mode 100644 index 000000000..f1f2fa24e --- /dev/null +++ b/zh-hant/docs/chapter_hashing/index.md @@ -0,0 +1,9 @@ +# 雜湊表 + +![雜湊表](../assets/covers/chapter_hashing.jpg) + +!!! abstract + + 在計算機世界中,雜湊表如同一位聰慧的圖書管理員。 + + 他知道如何計算索書號,從而可以快速找到目標圖書。 diff --git a/zh-hant/docs/chapter_hashing/summary.md b/zh-hant/docs/chapter_hashing/summary.md new file mode 100644 index 000000000..dfc193921 --- /dev/null +++ b/zh-hant/docs/chapter_hashing/summary.md @@ -0,0 +1,47 @@ +# 小結 + +### 重點回顧 + +- 輸入 `key` ,雜湊表能夠在 $O(1)$ 時間內查詢到 `value` ,效率非常高。 +- 常見的雜湊表操作包括查詢、新增鍵值對、刪除鍵值對和走訪雜湊表等。 +- 雜湊函式將 `key` 對映為陣列索引,從而訪問對應桶並獲取 `value` 。 +- 兩個不同的 `key` 可能在經過雜湊函式後得到相同的陣列索引,導致查詢結果出錯,這種現象被稱為雜湊衝突。 +- 雜湊表容量越大,雜湊衝突的機率就越低。因此可以透過擴容雜湊表來緩解雜湊衝突。與陣列擴容類似,雜湊表擴容操作的開銷很大。 +- 負載因子定義為雜湊表中元素數量除以桶數量,反映了雜湊衝突的嚴重程度,常用作觸發雜湊表擴容的條件。 +- 鏈式位址透過將單個元素轉化為鏈結串列,將所有衝突元素儲存在同一個鏈結串列中。然而,鏈結串列過長會降低查詢效率,可以透過進一步將鏈結串列轉換為紅黑樹來提高效率。 +- 開放定址透過多次探測來處理雜湊衝突。線性探查使用固定步長,缺點是不能刪除元素,且容易產生聚集。多次雜湊使用多個雜湊函式進行探測,相較線性探查更不易產生聚集,但多個雜湊函式增加了計算量。 +- 不同程式語言採取了不同的雜湊表實現。例如,Java 的 `HashMap` 使用鏈式位址,而 Python 的 `Dict` 採用開放定址。 +- 在雜湊表中,我們希望雜湊演算法具有確定性、高效率和均勻分佈的特點。在密碼學中,雜湊演算法還應該具備抗碰撞性和雪崩效應。 +- 雜湊演算法通常採用大質數作為模數,以最大化地保證雜湊值均勻分佈,減少雜湊衝突。 +- 常見的雜湊演算法包括 MD5、SHA-1、SHA-2 和 SHA-3 等。MD5 常用於校驗檔案完整性,SHA-2 常用於安全應用與協議。 +- 程式語言通常會為資料型別提供內建雜湊演算法,用於計算雜湊表中的桶索引。通常情況下,只有不可變物件是可雜湊的。 + +### Q & A + +**Q**:雜湊表的時間複雜度在什麼情況下是 $O(n)$ ? + +當雜湊衝突比較嚴重時,雜湊表的時間複雜度會退化至 $O(n)$ 。當雜湊函式設計得比較好、容量設定比較合理、衝突比較平均時,時間複雜度是 $O(1)$ 。我們使用程式語言內建的雜湊表時,通常認為時間複雜度是 $O(1)$ 。 + +**Q**:為什麼不使用雜湊函式 $f(x) = x$ 呢?這樣就不會有衝突了。 + +在 $f(x) = x$ 雜湊函式下,每個元素對應唯一的桶索引,這與陣列等價。然而,輸入空間通常遠大於輸出空間(陣列長度),因此雜湊函式的最後一步往往是對陣列長度取模。換句話說,雜湊表的目標是將一個較大的狀態空間對映到一個較小的空間,並提供 $O(1)$ 的查詢效率。 + +**Q**:雜湊表底層實現是陣列、鏈結串列、二元樹,但為什麼效率可以比它們更高呢? + +首先,雜湊表的時間效率變高,但空間效率變低了。雜湊表有相當一部分記憶體未使用。 + +其次,只是在特定使用場景下時間效率變高了。如果一個功能能夠在相同的時間複雜度下使用陣列或鏈結串列實現,那麼通常比雜湊表更快。這是因為雜湊函式計算需要開銷,時間複雜度的常數項更大。 + +最後,雜湊表的時間複雜度可能發生劣化。例如在鏈式位址中,我們採取在鏈結串列或紅黑樹中執行查詢操作,仍然有退化至 $O(n)$ 時間的風險。 + +**Q**:多次雜湊有不能直接刪除元素的缺陷嗎?標記為已刪除的空間還能再次使用嗎? + +多次雜湊是開放定址的一種,開放定址法都有不能直接刪除元素的缺陷,需要透過標記刪除。標記為已刪除的空間可以再次使用。當將新元素插入雜湊表,並且透過雜湊函式找到標記為已刪除的位置時,該位置可以被新元素使用。這樣做既能保持雜湊表的探測序列不變,又能保證雜湊表的空間使用率。 + +**Q**:為什麼線上性探查中,查詢元素的時候會出現雜湊衝突呢? + +查詢的時候透過雜湊函式找到對應的桶和鍵值對,發現 `key` 不匹配,這就代表有雜湊衝突。因此,線性探查法會根據預先設定的步長依次向下查詢,直至找到正確的鍵值對或無法找到跳出為止。 + +**Q**:為什麼雜湊表擴容能夠緩解雜湊衝突? + +雜湊函式的最後一步往往是對陣列長度 $n$ 取模(取餘),讓輸出值落在陣列索引範圍內;在擴容後,陣列長度 $n$ 發生變化,而 `key` 對應的索引也可能發生變化。原先落在同一個桶的多個 `key` ,在擴容後可能會被分配到多個桶中,從而實現雜湊衝突的緩解。 diff --git a/zh-hant/docs/chapter_heap/build_heap.assets/heapify_operations_count.png b/zh-hant/docs/chapter_heap/build_heap.assets/heapify_operations_count.png new file mode 100644 index 000000000..d1309c7db Binary files /dev/null and b/zh-hant/docs/chapter_heap/build_heap.assets/heapify_operations_count.png differ diff --git a/zh-hant/docs/chapter_heap/build_heap.md b/zh-hant/docs/chapter_heap/build_heap.md new file mode 100644 index 000000000..5b6c51049 --- /dev/null +++ b/zh-hant/docs/chapter_heap/build_heap.md @@ -0,0 +1,74 @@ +# 建堆積操作 + +在某些情況下,我們希望使用一個串列的所有元素來構建一個堆積,這個過程被稱為“建堆積操作”。 + +## 藉助入堆積操作實現 + +我們首先建立一個空堆積,然後走訪串列,依次對每個元素執行“入堆積操作”,即先將元素新增至堆積的尾部,再對該元素執行“從底至頂”堆積化。 + +每當一個元素入堆積,堆積的長度就加一。由於節點是從頂到底依次被新增進二元樹的,因此堆積是“自上而下”構建的。 + +設元素數量為 $n$ ,每個元素的入堆積操作使用 $O(\log{n})$ 時間,因此該建堆積方法的時間複雜度為 $O(n \log n)$ 。 + +## 透過走訪堆積化實現 + +實際上,我們可以實現一種更為高效的建堆積方法,共分為兩步。 + +1. 將串列所有元素原封不動地新增到堆積中,此時堆積的性質尚未得到滿足。 +2. 倒序走訪堆積(層序走訪的倒序),依次對每個非葉節點執行“從頂至底堆積化”。 + +**每當堆積化一個節點後,以該節點為根節點的子樹就形成一個合法的子堆積**。而由於是倒序走訪,因此堆積是“自下而上”構建的。 + +之所以選擇倒序走訪,是因為這樣能夠保證當前節點之下的子樹已經是合法的子堆積,這樣堆積化當前節點才是有效的。 + +值得說明的是,**由於葉節點沒有子節點,因此它們天然就是合法的子堆積,無須堆積化**。如以下程式碼所示,最後一個非葉節點是最後一個節點的父節點,我們從它開始倒序走訪並執行堆積化: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{__init__} +``` + +## 複雜度分析 + +下面,我們來嘗試推算第二種建堆積方法的時間複雜度。 + +- 假設完全二元樹的節點數量為 $n$ ,則葉節點數量為 $(n + 1) / 2$ ,其中 $/$ 為向下整除。因此需要堆積化的節點數量為 $(n - 1) / 2$ 。 +- 在從頂至底堆積化的過程中,每個節點最多堆積化到葉節點,因此最大迭代次數為二元樹高度 $\log n$ 。 + +將上述兩者相乘,可得到建堆積過程的時間複雜度為 $O(n \log n)$ 。**但這個估算結果並不準確,因為我們沒有考慮到二元樹底層節點數量遠多於頂層節點的性質**。 + +接下來我們來進行更為準確的計算。為了降低計算難度,假設給定一個節點數量為 $n$ 、高度為 $h$ 的“完美二元樹”,該假設不會影響計算結果的正確性。 + +![完美二元樹的各層節點數量](build_heap.assets/heapify_operations_count.png) + +如上圖所示,節點“從頂至底堆積化”的最大迭代次數等於該節點到葉節點的距離,而該距離正是“節點高度”。因此,我們可以對各層的“節點數量 $\times$ 節點高度”求和,**得到所有節點的堆積化迭代次數的總和**。 + +$$ +T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 +$$ + +化簡上式需要藉助中學的數列知識,先將 $T(h)$ 乘以 $2$ ,得到: + +$$ +\begin{aligned} +T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline +2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline +\end{aligned} +$$ + +使用錯位相減法,用下式 $2 T(h)$ 減去上式 $T(h)$ ,可得: + +$$ +2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h +$$ + +觀察上式,發現 $T(h)$ 是一個等比數列,可直接使用求和公式,得到時間複雜度為: + +$$ +\begin{aligned} +T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline +& = 2^{h+1} - h - 2 \newline +& = O(2^h) +\end{aligned} +$$ + +進一步,高度為 $h$ 的完美二元樹的節點數量為 $n = 2^{h+1} - 1$ ,易得複雜度為 $O(2^h) = O(n)$ 。以上推算表明,**輸入串列並建堆積的時間複雜度為 $O(n)$ ,非常高效**。 diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step1.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step1.png new file mode 100644 index 000000000..fd00bf20f Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step1.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step10.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step10.png new file mode 100644 index 000000000..83a650826 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step10.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step2.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step2.png new file mode 100644 index 000000000..d16378341 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step2.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step3.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step3.png new file mode 100644 index 000000000..7719b0038 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step3.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step4.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step4.png new file mode 100644 index 000000000..0ae3e06ea Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step4.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step5.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step5.png new file mode 100644 index 000000000..827a22bff Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step5.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step6.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step6.png new file mode 100644 index 000000000..6c63b044b Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step6.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step7.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step7.png new file mode 100644 index 000000000..f3176038b Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step7.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step8.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step8.png new file mode 100644 index 000000000..d075759af Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step8.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step9.png b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step9.png new file mode 100644 index 000000000..411413003 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_pop_step9.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step1.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step1.png new file mode 100644 index 000000000..17eb29a0f Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step1.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step2.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step2.png new file mode 100644 index 000000000..b4003f1f9 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step2.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step3.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step3.png new file mode 100644 index 000000000..a032d34b4 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step3.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step4.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step4.png new file mode 100644 index 000000000..4c78dc6d3 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step4.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step5.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step5.png new file mode 100644 index 000000000..c62ec270b Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step5.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step6.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step6.png new file mode 100644 index 000000000..604212203 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step6.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step7.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step7.png new file mode 100644 index 000000000..0bfb5a433 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step7.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step8.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step8.png new file mode 100644 index 000000000..5d820c7f4 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step8.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/heap_push_step9.png b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step9.png new file mode 100644 index 000000000..d2c23bbd0 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/heap_push_step9.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png b/zh-hant/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png new file mode 100644 index 000000000..0016f58e6 Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png differ diff --git a/zh-hant/docs/chapter_heap/heap.assets/representation_of_heap.png b/zh-hant/docs/chapter_heap/heap.assets/representation_of_heap.png new file mode 100644 index 000000000..e17e5aa0b Binary files /dev/null and b/zh-hant/docs/chapter_heap/heap.assets/representation_of_heap.png differ diff --git a/zh-hant/docs/chapter_heap/heap.md b/zh-hant/docs/chapter_heap/heap.md new file mode 100644 index 000000000..78cc8501c --- /dev/null +++ b/zh-hant/docs/chapter_heap/heap.md @@ -0,0 +1,538 @@ +# 堆積 + +堆積(heap)是一種滿足特定條件的完全二元樹,主要可分為兩種型別,如下圖所示。 + +- 小頂堆積(min heap):任意節點的值 $\leq$ 其子節點的值。 +- 大頂堆積(max heap):任意節點的值 $\geq$ 其子節點的值。 + +![小頂堆積與大頂堆積](heap.assets/min_heap_and_max_heap.png) + +堆積作為完全二元樹的一個特例,具有以下特性。 + +- 最底層節點靠左填充,其他層的節點都被填滿。 +- 我們將二元樹的根節點稱為“堆積頂”,將底層最靠右的節點稱為“堆積底”。 +- 對於大頂堆積(小頂堆積),堆積頂元素(根節點)的值是最大(最小)的。 + +## 堆積的常用操作 + +需要指出的是,許多程式語言提供的是優先佇列(priority queue),這是一種抽象的資料結構,定義為具有優先順序排序的佇列。 + +實際上,**堆積通常用於實現優先佇列,大頂堆積相當於元素按從大到小的順序出列的優先佇列**。從使用角度來看,我們可以將“優先佇列”和“堆積”看作等價的資料結構。因此,本書對兩者不做特別區分,統一稱作“堆積”。 + +堆積的常用操作見下表,方法名需要根據程式語言來確定。 + +

  堆積的操作效率

+ +| 方法名 | 描述 | 時間複雜度 | +| ----------- | ------------------------------------------------ | ----------- | +| `push()` | 元素入堆積 | $O(\log n)$ | +| `pop()` | 堆積頂元素出堆積 | $O(\log n)$ | +| `peek()` | 訪問堆積頂元素(對於大 / 小頂堆積分別為最大 / 小值) | $O(1)$ | +| `size()` | 獲取堆積的元素數量 | $O(1)$ | +| `isEmpty()` | 判斷堆積是否為空 | $O(1)$ | + +在實際應用中,我們可以直接使用程式語言提供的堆積類別(或優先佇列類別)。 + +類似於排序演算法中的“從小到大排列”和“從大到小排列”,我們可以透過設定一個 `flag` 或修改 `Comparator` 實現“小頂堆積”與“大頂堆積”之間的轉換。程式碼如下所示: + +=== "Python" + + ```python title="heap.py" + # 初始化小頂堆積 + min_heap, flag = [], 1 + # 初始化大頂堆積 + max_heap, flag = [], -1 + + # Python 的 heapq 模組預設實現小頂堆積 + # 考慮將“元素取負”後再入堆積,這樣就可以將大小關係顛倒,從而實現大頂堆積 + # 在本示例中,flag = 1 時對應小頂堆積,flag = -1 時對應大頂堆積 + + # 元素入堆積 + heapq.heappush(max_heap, flag * 1) + heapq.heappush(max_heap, flag * 3) + heapq.heappush(max_heap, flag * 2) + heapq.heappush(max_heap, flag * 5) + heapq.heappush(max_heap, flag * 4) + + # 獲取堆積頂元素 + peek: int = flag * max_heap[0] # 5 + + # 堆積頂元素出堆積 + # 出堆積元素會形成一個從大到小的序列 + val = flag * heapq.heappop(max_heap) # 5 + val = flag * heapq.heappop(max_heap) # 4 + val = flag * heapq.heappop(max_heap) # 3 + val = flag * heapq.heappop(max_heap) # 2 + val = flag * heapq.heappop(max_heap) # 1 + + # 獲取堆積大小 + size: int = len(max_heap) + + # 判斷堆積是否為空 + is_empty: bool = not max_heap + + # 輸入串列並建堆積 + min_heap: list[int] = [1, 3, 2, 5, 4] + heapq.heapify(min_heap) + ``` + +=== "C++" + + ```cpp title="heap.cpp" + /* 初始化堆積 */ + // 初始化小頂堆積 + priority_queue, greater> minHeap; + // 初始化大頂堆積 + priority_queue, less> maxHeap; + + /* 元素入堆積 */ + maxHeap.push(1); + maxHeap.push(3); + maxHeap.push(2); + maxHeap.push(5); + maxHeap.push(4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.top(); // 5 + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + maxHeap.pop(); // 5 + maxHeap.pop(); // 4 + maxHeap.pop(); // 3 + maxHeap.pop(); // 2 + maxHeap.pop(); // 1 + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.empty(); + + /* 輸入串列並建堆積 */ + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + ``` + +=== "Java" + + ```java title="heap.java" + /* 初始化堆積 */ + // 初始化小頂堆積 + Queue minHeap = new PriorityQueue<>(); + // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + /* 元素入堆積 */ + maxHeap.offer(1); + maxHeap.offer(3); + maxHeap.offer(2); + maxHeap.offer(5); + maxHeap.offer(4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.peek(); // 5 + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + peek = maxHeap.poll(); // 5 + peek = maxHeap.poll(); // 4 + peek = maxHeap.poll(); // 3 + peek = maxHeap.poll(); // 2 + peek = maxHeap.poll(); // 1 + + /* 獲取堆積大小 */ + int size = maxHeap.size(); + + /* 判斷堆積是否為空 */ + boolean isEmpty = maxHeap.isEmpty(); + + /* 輸入串列並建堆積 */ + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + ``` + +=== "C#" + + ```csharp title="heap.cs" + /* 初始化堆積 */ + // 初始化小頂堆積 + PriorityQueue minHeap = new(); + // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y - x)); + + /* 元素入堆積 */ + maxHeap.Enqueue(1, 1); + maxHeap.Enqueue(3, 3); + maxHeap.Enqueue(2, 2); + maxHeap.Enqueue(5, 5); + maxHeap.Enqueue(4, 4); + + /* 獲取堆積頂元素 */ + int peek = maxHeap.Peek();//5 + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + peek = maxHeap.Dequeue(); // 5 + peek = maxHeap.Dequeue(); // 4 + peek = maxHeap.Dequeue(); // 3 + peek = maxHeap.Dequeue(); // 2 + peek = maxHeap.Dequeue(); // 1 + + /* 獲取堆積大小 */ + int size = maxHeap.Count; + + /* 判斷堆積是否為空 */ + bool isEmpty = maxHeap.Count == 0; + + /* 輸入串列並建堆積 */ + minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); + ``` + +=== "Go" + + ```go title="heap.go" + // Go 語言中可以透過實現 heap.Interface 來構建整數大頂堆積 + // 實現 heap.Interface 需要同時實現 sort.Interface + type intHeap []any + + // Push heap.Interface 的方法,實現推入元素到堆積 + func (h *intHeap) Push(x any) { + // Push 和 Pop 使用 pointer receiver 作為參數 + // 因為它們不僅會對切片的內容進行調整,還會修改切片的長度。 + *h = append(*h, x.(int)) + } + + // Pop heap.Interface 的方法,實現彈出堆積頂元素 + func (h *intHeap) Pop() any { + // 待出堆積元素存放在最後 + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last + } + + // Len sort.Interface 的方法 + func (h *intHeap) Len() int { + return len(*h) + } + + // Less sort.Interface 的方法 + func (h *intHeap) Less(i, j int) bool { + // 如果實現小頂堆積,則需要調整為小於號 + return (*h)[i].(int) > (*h)[j].(int) + } + + // Swap sort.Interface 的方法 + func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] + } + + // Top 獲取堆積頂元素 + func (h *intHeap) Top() any { + return (*h)[0] + } + + /* Driver Code */ + func TestHeap(t *testing.T) { + /* 初始化堆積 */ + // 初始化大頂堆積 + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* 元素入堆積 */ + // 呼叫 heap.Interface 的方法,來新增元素 + heap.Push(maxHeap, 1) + heap.Push(maxHeap, 3) + heap.Push(maxHeap, 2) + heap.Push(maxHeap, 4) + heap.Push(maxHeap, 5) + + /* 獲取堆積頂元素 */ + top := maxHeap.Top() + fmt.Printf("堆積頂元素為 %d\n", top) + + /* 堆積頂元素出堆積 */ + // 呼叫 heap.Interface 的方法,來移除元素 + heap.Pop(maxHeap) // 5 + heap.Pop(maxHeap) // 4 + heap.Pop(maxHeap) // 3 + heap.Pop(maxHeap) // 2 + heap.Pop(maxHeap) // 1 + + /* 獲取堆積大小 */ + size := len(*maxHeap) + fmt.Printf("堆積元素數量為 %d\n", size) + + /* 判斷堆積是否為空 */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("堆積是否為空 %t\n", isEmpty) + } + ``` + +=== "Swift" + + ```swift title="heap.swift" + /* 初始化堆積 */ + // Swift 的 Heap 型別同時支持最大堆積和最小堆積,且需要引入 swift-collections + var heap = Heap() + + /* 元素入堆積 */ + heap.insert(1) + heap.insert(3) + heap.insert(2) + heap.insert(5) + heap.insert(4) + + /* 獲取堆積頂元素 */ + var peek = heap.max()! + + /* 堆積頂元素出堆積 */ + peek = heap.removeMax() // 5 + peek = heap.removeMax() // 4 + peek = heap.removeMax() // 3 + peek = heap.removeMax() // 2 + peek = heap.removeMax() // 1 + + /* 獲取堆積大小 */ + let size = heap.count + + /* 判斷堆積是否為空 */ + let isEmpty = heap.isEmpty + + /* 輸入串列並建堆積 */ + let heap2 = Heap([1, 3, 2, 5, 4]) + ``` + +=== "JS" + + ```javascript title="heap.js" + // JavaScript 未提供內建 Heap 類別 + ``` + +=== "TS" + + ```typescript title="heap.ts" + // TypeScript 未提供內建 Heap 類別 + ``` + +=== "Dart" + + ```dart title="heap.dart" + // Dart 未提供內建 Heap 類別 + ``` + +=== "Rust" + + ```rust title="heap.rs" + use std::collections::BinaryHeap; + use std::cmp::Reverse; + + /* 初始化堆積 */ + // 初始化小頂堆積 + let mut min_heap = BinaryHeap::>::new(); + // 初始化大頂堆積 + let mut max_heap = BinaryHeap::new(); + + /* 元素入堆積 */ + max_heap.push(1); + max_heap.push(3); + max_heap.push(2); + max_heap.push(5); + max_heap.push(4); + + /* 獲取堆積頂元素 */ + let peek = max_heap.peek().unwrap(); // 5 + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + let peek = max_heap.pop().unwrap(); // 5 + let peek = max_heap.pop().unwrap(); // 4 + let peek = max_heap.pop().unwrap(); // 3 + let peek = max_heap.pop().unwrap(); // 2 + let peek = max_heap.pop().unwrap(); // 1 + + /* 獲取堆積大小 */ + let size = max_heap.len(); + + /* 判斷堆積是否為空 */ + let is_empty = max_heap.is_empty(); + + /* 輸入串列並建堆積 */ + let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); + ``` + +=== "C" + + ```c title="heap.c" + // C 未提供內建 Heap 類別 + ``` + +=== "Kotlin" + + ```kotlin title="heap.kt" + /* 初始化堆積 */ + // 初始化小頂堆積 + var minHeap = PriorityQueue() + // 初始化大頂堆積(使用 lambda 表示式修改 Comparator 即可) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } + + /* 元素入堆積 */ + maxHeap.offer(1) + maxHeap.offer(3) + maxHeap.offer(2) + maxHeap.offer(5) + maxHeap.offer(4) + + /* 獲取堆積頂元素 */ + var peek = maxHeap.peek() // 5 + + /* 堆積頂元素出堆積 */ + // 出堆積元素會形成一個從大到小的序列 + peek = maxHeap.poll() // 5 + peek = maxHeap.poll() // 4 + peek = maxHeap.poll() // 3 + peek = maxHeap.poll() // 2 + peek = maxHeap.poll() // 1 + + /* 獲取堆積大小 */ + val size = maxHeap.size + + /* 判斷堆積是否為空 */ + val isEmpty = maxHeap.isEmpty() + + /* 輸入串列並建堆積 */ + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) + ``` + +=== "Ruby" + + ```ruby title="heap.rb" + + ``` + +=== "Zig" + + ```zig title="heap.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20min_heap,%20flag%20%3D%20%5B%5D,%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap,%20flag%20%3D%20%5B%5D,%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E5%9D%97%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%80%83%E8%99%91%E5%B0%86%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B4%9F%E2%80%9D%E5%90%8E%E5%86%8D%E5%85%A5%E5%A0%86%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%86%E5%A4%A7%E5%B0%8F%E5%85%B3%E7%B3%BB%E9%A2%A0%E5%80%92%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%B0%8F%E9%A1%B6%E5%A0%86%EF%BC%8Cflag%20%3D%20-1%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%201%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%203%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%202%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%205%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20*%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E5%85%83%E7%B4%A0%E4%BC%9A%E5%BD%A2%E6%88%90%E4%B8%80%E4%B8%AA%E4%BB%8E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%B9%B6%E5%BB%BA%E5%A0%86%0A%20%20%20%20min_heap%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 堆積的實現 + +下文實現的是大頂堆積。若要將其轉換為小頂堆積,只需將所有大小邏輯判斷取逆(例如,將 $\geq$ 替換為 $\leq$ )。感興趣的讀者可以自行實現。 + +### 堆積的儲存與表示 + +“二元樹”章節講過,完全二元樹非常適合用陣列來表示。由於堆積正是一種完全二元樹,**因此我們將採用陣列來儲存堆積**。 + +當使用陣列表示二元樹時,元素代表節點值,索引代表節點在二元樹中的位置。**節點指標透過索引對映公式來實現**。 + +如下圖所示,給定索引 $i$ ,其左子節點的索引為 $2i + 1$ ,右子節點的索引為 $2i + 2$ ,父節點的索引為 $(i - 1) / 2$(向下整除)。當索引越界時,表示空節點或節點不存在。 + +![堆積的表示與儲存](heap.assets/representation_of_heap.png) + +我們可以將索引對映公式封裝成函式,方便後續使用: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{parent} +``` + +### 訪問堆積頂元素 + +堆積頂元素即為二元樹的根節點,也就是串列的首個元素: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{peek} +``` + +### 元素入堆積 + +給定元素 `val` ,我們首先將其新增到堆積底。新增之後,由於 `val` 可能大於堆積中其他元素,堆積的成立條件可能已被破壞,**因此需要修復從插入節點到根節點的路徑上的各個節點**,這個操作被稱為堆積化(heapify)。 + +考慮從入堆積節點開始,**從底至頂執行堆積化**。如下圖所示,我們比較插入節點與其父節點的值,如果插入節點更大,則將它們交換。然後繼續執行此操作,從底至頂修復堆積中的各個節點,直至越過根節點或遇到無須交換的節點時結束。 + +=== "<1>" + ![元素入堆積步驟](heap.assets/heap_push_step1.png) + +=== "<2>" + ![heap_push_step2](heap.assets/heap_push_step2.png) + +=== "<3>" + ![heap_push_step3](heap.assets/heap_push_step3.png) + +=== "<4>" + ![heap_push_step4](heap.assets/heap_push_step4.png) + +=== "<5>" + ![heap_push_step5](heap.assets/heap_push_step5.png) + +=== "<6>" + ![heap_push_step6](heap.assets/heap_push_step6.png) + +=== "<7>" + ![heap_push_step7](heap.assets/heap_push_step7.png) + +=== "<8>" + ![heap_push_step8](heap.assets/heap_push_step8.png) + +=== "<9>" + ![heap_push_step9](heap.assets/heap_push_step9.png) + +設節點總數為 $n$ ,則樹的高度為 $O(\log n)$ 。由此可知,堆積化操作的迴圈輪數最多為 $O(\log n)$ ,**元素入堆積操作的時間複雜度為 $O(\log n)$** 。程式碼如下所示: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{sift_up} +``` + +### 堆積頂元素出堆積 + +堆積頂元素是二元樹的根節點,即串列首元素。如果我們直接從串列中刪除首元素,那麼二元樹中所有節點的索引都會發生變化,這將使得後續使用堆積化進行修復變得困難。為了儘量減少元素索引的變動,我們採用以下操作步驟。 + +1. 交換堆積頂元素與堆積底元素(交換根節點與最右葉節點)。 +2. 交換完成後,將堆積底從串列中刪除(注意,由於已經交換,因此實際上刪除的是原來的堆積頂元素)。 +3. 從根節點開始,**從頂至底執行堆積化**。 + +如下圖所示,**“從頂至底堆積化”的操作方向與“從底至頂堆積化”相反**,我們將根節點的值與其兩個子節點的值進行比較,將最大的子節點與根節點交換。然後迴圈執行此操作,直到越過葉節點或遇到無須交換的節點時結束。 + +=== "<1>" + ![堆積頂元素出堆積步驟](heap.assets/heap_pop_step1.png) + +=== "<2>" + ![heap_pop_step2](heap.assets/heap_pop_step2.png) + +=== "<3>" + ![heap_pop_step3](heap.assets/heap_pop_step3.png) + +=== "<4>" + ![heap_pop_step4](heap.assets/heap_pop_step4.png) + +=== "<5>" + ![heap_pop_step5](heap.assets/heap_pop_step5.png) + +=== "<6>" + ![heap_pop_step6](heap.assets/heap_pop_step6.png) + +=== "<7>" + ![heap_pop_step7](heap.assets/heap_pop_step7.png) + +=== "<8>" + ![heap_pop_step8](heap.assets/heap_pop_step8.png) + +=== "<9>" + ![heap_pop_step9](heap.assets/heap_pop_step9.png) + +=== "<10>" + ![heap_pop_step10](heap.assets/heap_pop_step10.png) + +與元素入堆積操作相似,堆積頂元素出堆積操作的時間複雜度也為 $O(\log n)$ 。程式碼如下所示: + +```src +[file]{my_heap}-[class]{max_heap}-[func]{sift_down} +``` + +## 堆積的常見應用 + +- **優先佇列**:堆積通常作為實現優先佇列的首選資料結構,其入列和出列操作的時間複雜度均為 $O(\log n)$ ,而建隊操作為 $O(n)$ ,這些操作都非常高效。 +- **堆積排序**:給定一組資料,我們可以用它們建立一個堆積,然後不斷地執行元素出堆積操作,從而得到有序資料。然而,我們通常會使用一種更優雅的方式實現堆積排序,詳見“堆積排序”章節。 +- **獲取最大的 $k$ 個元素**:這是一個經典的演算法問題,同時也是一種典型應用,例如選擇熱度前 10 的新聞作為微博熱搜,選取銷量前 10 的商品等。 diff --git a/zh-hant/docs/chapter_heap/index.md b/zh-hant/docs/chapter_heap/index.md new file mode 100644 index 000000000..af380781a --- /dev/null +++ b/zh-hant/docs/chapter_heap/index.md @@ -0,0 +1,9 @@ +# 堆積 + +![堆積](../assets/covers/chapter_heap.jpg) + +!!! abstract + + 堆積就像是山嶽峰巒,層疊起伏、形態各異。 + + 座座山峰高低錯落,而最高的山峰總是最先映入眼簾。 diff --git a/zh-hant/docs/chapter_heap/summary.md b/zh-hant/docs/chapter_heap/summary.md new file mode 100644 index 000000000..621abde81 --- /dev/null +++ b/zh-hant/docs/chapter_heap/summary.md @@ -0,0 +1,17 @@ +# 小結 + +### 重點回顧 + +- 堆積是一棵完全二元樹,根據成立條件可分為大頂堆積和小頂堆積。大(小)頂堆積的堆積頂元素是最大(小)的。 +- 優先佇列的定義是具有出列優先順序的佇列,通常使用堆積來實現。 +- 堆積的常用操作及其對應的時間複雜度包括:元素入堆積 $O(\log n)$、堆積頂元素出堆積 $O(\log n)$ 和訪問堆積頂元素 $O(1)$ 等。 +- 完全二元樹非常適合用陣列表示,因此我們通常使用陣列來儲存堆積。 +- 堆積化操作用於維護堆積的性質,在入堆積和出堆積操作中都會用到。 +- 輸入 $n$ 個元素並建堆積的時間複雜度可以最佳化至 $O(n)$ ,非常高效。 +- Top-k 是一個經典演算法問題,可以使用堆積資料結構高效解決,時間複雜度為 $O(n \log k)$ 。 + +### Q & A + +**Q**:資料結構的“堆積”與記憶體管理的“堆積”是同一個概念嗎? + +兩者不是同一個概念,只是碰巧都叫“堆積”。計算機系統記憶體中的堆積是動態記憶體分配的一部分,程式在執行時可以使用它來儲存資料。程式可以請求一定量的堆積記憶體,用於儲存如物件和陣列等複雜結構。當這些資料不再需要時,程式需要釋放這些記憶體,以防止記憶體流失。相較於堆疊記憶體,堆積記憶體的管理和使用需要更謹慎,使用不當可能會導致記憶體流失和野指標等問題。 diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step1.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step1.png new file mode 100644 index 000000000..9d399e34c Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step1.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step2.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step2.png new file mode 100644 index 000000000..d57b2d0f7 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step2.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step3.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step3.png new file mode 100644 index 000000000..f60076b62 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step3.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step4.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step4.png new file mode 100644 index 000000000..bf8b825c5 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step4.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step5.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step5.png new file mode 100644 index 000000000..952431780 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step5.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step6.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step6.png new file mode 100644 index 000000000..05d5720f7 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step6.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step7.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step7.png new file mode 100644 index 000000000..ebc8cd000 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step7.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step8.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step8.png new file mode 100644 index 000000000..0ee7ee0ae Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step8.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step9.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step9.png new file mode 100644 index 000000000..575e63bf2 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_heap_step9.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_sorting.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_sorting.png new file mode 100644 index 000000000..bc301db15 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_sorting.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.assets/top_k_traversal.png b/zh-hant/docs/chapter_heap/top_k.assets/top_k_traversal.png new file mode 100644 index 000000000..25d588984 Binary files /dev/null and b/zh-hant/docs/chapter_heap/top_k.assets/top_k_traversal.png differ diff --git a/zh-hant/docs/chapter_heap/top_k.md b/zh-hant/docs/chapter_heap/top_k.md new file mode 100644 index 000000000..beee1bb78 --- /dev/null +++ b/zh-hant/docs/chapter_heap/top_k.md @@ -0,0 +1,73 @@ +# Top-k 問題 + +!!! question + + 給定一個長度為 $n$ 的無序陣列 `nums` ,請返回陣列中最大的 $k$ 個元素。 + +對於該問題,我們先介紹兩種思路比較直接的解法,再介紹效率更高的堆積解法。 + +## 方法一:走訪選擇 + +我們可以進行下圖所示的 $k$ 輪走訪,分別在每輪中提取第 $1$、$2$、$\dots$、$k$ 大的元素,時間複雜度為 $O(nk)$ 。 + +此方法只適用於 $k \ll n$ 的情況,因為當 $k$ 與 $n$ 比較接近時,其時間複雜度趨向於 $O(n^2)$ ,非常耗時。 + +![走訪尋找最大的 k 個元素](top_k.assets/top_k_traversal.png) + +!!! tip + + 當 $k = n$ 時,我們可以得到完整的有序序列,此時等價於“選擇排序”演算法。 + +## 方法二:排序 + +如下圖所示,我們可以先對陣列 `nums` 進行排序,再返回最右邊的 $k$ 個元素,時間複雜度為 $O(n \log n)$ 。 + +顯然,該方法“超額”完成任務了,因為我們只需找出最大的 $k$ 個元素即可,而不需要排序其他元素。 + +![排序尋找最大的 k 個元素](top_k.assets/top_k_sorting.png) + +## 方法三:堆積 + +我們可以基於堆積更加高效地解決 Top-k 問題,流程如下圖所示。 + +1. 初始化一個小頂堆積,其堆積頂元素最小。 +2. 先將陣列的前 $k$ 個元素依次入堆積。 +3. 從第 $k + 1$ 個元素開始,若當前元素大於堆積頂元素,則將堆積頂元素出堆積,並將當前元素入堆積。 +4. 走訪完成後,堆積中儲存的就是最大的 $k$ 個元素。 + +=== "<1>" + ![基於堆積尋找最大的 k 個元素](top_k.assets/top_k_heap_step1.png) + +=== "<2>" + ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png) + +=== "<3>" + ![top_k_heap_step3](top_k.assets/top_k_heap_step3.png) + +=== "<4>" + ![top_k_heap_step4](top_k.assets/top_k_heap_step4.png) + +=== "<5>" + ![top_k_heap_step5](top_k.assets/top_k_heap_step5.png) + +=== "<6>" + ![top_k_heap_step6](top_k.assets/top_k_heap_step6.png) + +=== "<7>" + ![top_k_heap_step7](top_k.assets/top_k_heap_step7.png) + +=== "<8>" + ![top_k_heap_step8](top_k.assets/top_k_heap_step8.png) + +=== "<9>" + ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png) + +示例程式碼如下: + +```src +[file]{top_k}-[class]{}-[func]{top_k_heap} +``` + +總共執行了 $n$ 輪入堆積和出堆積,堆積的最大長度為 $k$ ,因此時間複雜度為 $O(n \log k)$ 。該方法的效率很高,當 $k$ 較小時,時間複雜度趨向 $O(n)$ ;當 $k$ 較大時,時間複雜度不會超過 $O(n \log n)$ 。 + +另外,該方法適用於動態資料流的使用場景。在不斷加入資料時,我們可以持續維護堆積內的元素,從而實現最大的 $k$ 個元素的動態更新。 diff --git a/zh-hant/docs/chapter_hello_algo/index.md b/zh-hant/docs/chapter_hello_algo/index.md new file mode 100644 index 000000000..ac04b5769 --- /dev/null +++ b/zh-hant/docs/chapter_hello_algo/index.md @@ -0,0 +1,30 @@ +--- +comments: true +icon: material/rocket-launch-outline +--- + +# 序 + +幾年前,我在力扣上分享了“劍指 Offer”系列題解,受到了許多讀者的鼓勵和支持。在與讀者交流期間,我最常被問的一個問題是“如何入門演算法”。逐漸地,我對這個問題產生了濃厚的興趣。 + +兩眼一抹黑地刷題似乎是最受歡迎的方法,簡單、直接且有效。然而刷題就如同玩“掃雷”遊戲,自學能力強的人能夠順利將地雷逐個排掉,而基礎不足的人很可能被炸得滿頭是包,並在挫折中步步退縮。通讀教材也是一種常見做法,但對於面向求職的人來說,畢業論文、投遞簡歷、準備筆試和面試已經消耗了大部分精力,啃厚重的書往往變成了一項艱鉅的挑戰。 + +如果你也面臨類似的困擾,那麼很幸運這本書“找”到了你。本書是我對這個問題給出的答案,即使不是最優解,也至少是一次積極的嘗試。本書雖然不足以讓你直接拿到 Offer,但會引導你探索資料結構與演算法的“知識地圖”,帶你瞭解不同“地雷”的形狀、大小和分佈位置,讓你掌握各種“排雷方法”。有了這些本領,相信你可以更加自如地刷題和閱讀文獻,逐步構建起完整的知識體系。 + +我深深贊同費曼教授所言:“Knowledge isn't free. You have to pay attention.”從這個意義上看,這本書並非完全“免費”。為了不辜負你為本書所付出的寶貴“注意力”,我會竭盡所能,投入最大的“注意力”來完成本書的創作。 + +本人自知學疏才淺,書中內容雖然已經過一段時間的打磨,但一定仍有許多錯誤,懇請各位老師和同學批評指正。 + +![Hello 演算法](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" } + +
+

Hello,演算法!

+
+ +計算機的出現給世界帶來了巨大變革,它憑藉高速的計算能力和出色的可程式設計性,成為了執行演算法與處理資料的理想媒介。無論是電子遊戲的逼真畫面、自動駕駛的智慧決策,還是 AlphaGo 的精彩棋局、ChatGPT 的自然互動,這些應用都是演算法在計算機上的精妙演繹。 + +事實上,在計算機問世之前,演算法和資料結構就已經存在於世界的各個角落。早期的演算法相對簡單,例如古代的計數方法和工具製作步驟等。隨著文明的進步,演算法逐漸變得更加精細和複雜。從巧奪天工的匠人技藝、到解放生產力的工業產品、再到宇宙執行的科學規律,幾乎每一件平凡或令人驚歎的事物背後,都隱藏著精妙的演算法思想。 + +同樣,資料結構無處不在:大到社會網路,小到地鐵線路,許多系統都可以建模為“圖”;大到一個國家,小到一個家庭,社會的主要組織形式呈現出“樹”的特徵;冬天的衣服就像“堆疊”,最先穿上的最後才能脫下;羽毛球筒則如同“佇列”,一端放入、另一端取出;字典就像一個“雜湊表”,能夠快速查詢目標詞條。 + +本書旨在透過清晰易懂的動畫圖解和可執行的程式碼示例,使讀者理解演算法和資料結構的核心概念,並能夠透過程式設計來實現它們。在此基礎上,本書致力於揭示演算法在複雜世界中的生動體現,展現演算法之美。希望本書能夠幫助到你! diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png new file mode 100644 index 000000000..acb6171ba Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png new file mode 100644 index 000000000..f23a9682d Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png new file mode 100644 index 000000000..e6832c86b Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png new file mode 100644 index 000000000..72778f922 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png new file mode 100644 index 000000000..9b0c910b4 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png new file mode 100644 index 000000000..2c57d7524 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png new file mode 100644 index 000000000..15daf3fe0 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png differ diff --git a/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.md b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.md new file mode 100644 index 000000000..56380625c --- /dev/null +++ b/zh-hant/docs/chapter_introduction/algorithms_are_everywhere.md @@ -0,0 +1,56 @@ +# 演算法無處不在 + +當我們聽到“演算法”這個詞時,很自然地會想到數學。然而實際上,許多演算法並不涉及複雜數學,而是更多地依賴基本邏輯,這些邏輯在我們的日常生活中處處可見。 + +在正式探討演算法之前,有一個有趣的事實值得分享:**你已經在不知不覺中學會了許多演算法,並習慣將它們應用到日常生活中了**。下面我將舉幾個具體的例子來證實這一點。 + +**例一:查字典**。在字典裡,每個漢字都對應一個拼音,而字典是按照拼音字母順序排列的。假設我們需要查詢一個拼音首字母為 $r$ 的字,通常會按照下圖所示的方式實現。 + +1. 翻開字典約一半的頁數,檢視該頁的首字母是什麼,假設首字母為 $m$ 。 +2. 由於在拼音字母表中 $r$ 位於 $m$ 之後,所以排除字典前半部分,查詢範圍縮小到後半部分。 +3. 不斷重複步驟 `1.` 和 步驟 `2.` ,直至找到拼音首字母為 $r$ 的頁碼為止。 + +=== "<1>" + ![查字典步驟](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) + +=== "<2>" + ![binary_search_dictionary_step2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) + +=== "<3>" + ![binary_search_dictionary_step3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) + +=== "<4>" + ![binary_search_dictionary_step4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) + +=== "<5>" + ![binary_search_dictionary_step5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) + +查字典這個小學生必備技能,實際上就是著名的“二分搜尋”演算法。從資料結構的角度,我們可以把字典視為一個已排序的“陣列”;從演算法的角度,我們可以將上述查字典的一系列操作看作“二分搜尋”。 + +**例二:整理撲克**。我們在打牌時,每局都需要整理手中的撲克牌,使其從小到大排列,實現流程如下圖所示。 + +1. 將撲克牌劃分為“有序”和“無序”兩部分,並假設初始狀態下最左 1 張撲克牌已經有序。 +2. 在無序部分抽出一張撲克牌,插入至有序部分的正確位置;完成後最左 2 張撲克已經有序。 +3. 不斷迴圈步驟 `2.` ,每一輪將一張撲克牌從無序部分插入至有序部分,直至所有撲克牌都有序。 + +![撲克排序步驟](algorithms_are_everywhere.assets/playing_cards_sorting.png) + +上述整理撲克牌的方法本質上是“插入排序”演算法,它在處理小型資料集時非常高效。許多程式語言的排序庫函式中都有插入排序的身影。 + +**例三:貨幣找零**。假設我們在超市購買了 $69$ 元的商品,給了收銀員 $100$ 元,則收銀員需要找我們 $31$ 元。他會很自然地完成如下圖所示的思考。 + +1. 可選項是比 $31$ 元面值更小的貨幣,包括 $1$ 元、$5$ 元、$10$ 元、$20$ 元。 +2. 從可選項中拿出最大的 $20$ 元,剩餘 $31 - 20 = 11$ 元。 +3. 從剩餘可選項中拿出最大的 $10$ 元,剩餘 $11 - 10 = 1$ 元。 +4. 從剩餘可選項中拿出最大的 $1$ 元,剩餘 $1 - 1 = 0$ 元。 +5. 完成找零,方案為 $20 + 10 + 1 = 31$ 元。 + +![貨幣找零過程](algorithms_are_everywhere.assets/greedy_change.png) + +在以上步驟中,我們每一步都採取當前看來最好的選擇(儘可能用大面額的貨幣),最終得到了可行的找零方案。從資料結構與演算法的角度看,這種方法本質上是“貪婪”演算法。 + +小到烹飪一道菜,大到星際航行,幾乎所有問題的解決都離不開演算法。計算機的出現使得我們能夠透過程式設計將資料結構儲存在記憶體中,同時編寫程式碼呼叫 CPU 和 GPU 執行演算法。這樣一來,我們就能把生活中的問題轉移到計算機上,以更高效的方式解決各種複雜問題。 + +!!! tip + + 如果你對資料結構、演算法、陣列和二分搜尋等概念仍感到一知半解,請繼續往下閱讀,本書將引導你邁入資料結構與演算法的知識殿堂。 diff --git a/zh-hant/docs/chapter_introduction/index.md b/zh-hant/docs/chapter_introduction/index.md new file mode 100644 index 000000000..f9257ddd7 --- /dev/null +++ b/zh-hant/docs/chapter_introduction/index.md @@ -0,0 +1,9 @@ +# 初識演算法 + +![初識演算法](../assets/covers/chapter_introduction.jpg) + +!!! abstract + + 一位少女翩翩起舞,與資料交織在一起,裙襬上飄揚著演算法的旋律。 + + 她邀請你共舞,請緊跟她的步伐,踏入充滿邏輯與美感的演算法世界。 diff --git a/zh-hant/docs/chapter_introduction/summary.md b/zh-hant/docs/chapter_introduction/summary.md new file mode 100644 index 000000000..d33fe6eb1 --- /dev/null +++ b/zh-hant/docs/chapter_introduction/summary.md @@ -0,0 +1,9 @@ +# 小結 + +- 演算法在日常生活中無處不在,並不是遙不可及的高深知識。實際上,我們已經在不知不覺中學會了許多演算法,用以解決生活中的大小問題。 +- 查字典的原理與二分搜尋演算法相一致。二分搜尋演算法體現了分而治之的重要演算法思想。 +- 整理撲克的過程與插入排序演算法非常類似。插入排序演算法適合排序小型資料集。 +- 貨幣找零的步驟本質上是貪婪演算法,每一步都採取當前看來最好的選擇。 +- 演算法是在有限時間內解決特定問題的一組指令或操作步驟,而資料結構是計算機中組織和儲存資料的方式。 +- 資料結構與演算法緊密相連。資料結構是演算法的基石,而演算法是資料結構發揮作用的舞臺。 +- 我們可以將資料結構與演算法類比為拼裝積木,積木代表資料,積木的形狀和連線方式等代表資料結構,拼裝積木的步驟則對應演算法。 diff --git a/zh-hant/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png b/zh-hant/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png new file mode 100644 index 000000000..ff209eb73 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png differ diff --git a/zh-hant/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png b/zh-hant/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png new file mode 100644 index 000000000..42d5ac456 Binary files /dev/null and b/zh-hant/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png differ diff --git a/zh-hant/docs/chapter_introduction/what_is_dsa.md b/zh-hant/docs/chapter_introduction/what_is_dsa.md new file mode 100644 index 000000000..953b0b7a6 --- /dev/null +++ b/zh-hant/docs/chapter_introduction/what_is_dsa.md @@ -0,0 +1,53 @@ +# 演算法是什麼 + +## 演算法定義 + +演算法(algorithm)是在有限時間內解決特定問題的一組指令或操作步驟,它具有以下特性。 + +- 問題是明確的,包含清晰的輸入和輸出定義。 +- 具有可行性,能夠在有限步驟、時間和記憶體空間下完成。 +- 各步驟都有確定的含義,在相同的輸入和執行條件下,輸出始終相同。 + +## 資料結構定義 + +資料結構(data structure)是計算機中組織和儲存資料的方式,具有以下設計目標。 + +- 空間佔用儘量少,以節省計算機記憶體。 +- 資料操作儘可能快速,涵蓋資料訪問、新增、刪除、更新等。 +- 提供簡潔的資料表示和邏輯資訊,以便演算法高效執行。 + +**資料結構設計是一個充滿權衡的過程**。如果想在某方面取得提升,往往需要在另一方面作出妥協。下面舉兩個例子。 + +- 鏈結串列相較於陣列,在資料新增和刪除操作上更加便捷,但犧牲了資料訪問速度。 +- 圖相較於鏈結串列,提供了更豐富的邏輯資訊,但需要佔用更大的記憶體空間。 + +## 資料結構與演算法的關係 + +如下圖所示,資料結構與演算法高度相關、緊密結合,具體表現在以下三個方面。 + +- 資料結構是演算法的基石。資料結構為演算法提供了結構化儲存的資料,以及操作資料的方法。 +- 演算法是資料結構發揮作用的舞臺。資料結構本身僅儲存資料資訊,結合演算法才能解決特定問題。 +- 演算法通常可以基於不同的資料結構實現,但執行效率可能相差很大,選擇合適的資料結構是關鍵。 + +![資料結構與演算法的關係](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) + +資料結構與演算法猶如下圖所示的拼裝積木。一套積木,除了包含許多零件之外,還附有詳細的組裝說明書。我們按照說明書一步步操作,就能組裝出精美的積木模型。 + +![拼裝積木](what_is_dsa.assets/assembling_blocks.png) + +兩者的詳細對應關係如下表所示。 + +

  將資料結構與演算法類比為拼裝積木

+ +| 資料結構與演算法 | 拼裝積木 | +| -------------- | ---------------------------------------- | +| 輸入資料 | 未拼裝的積木 | +| 資料結構 | 積木組織形式,包括形狀、大小、連線方式等 | +| 演算法 | 把積木拼成目標形態的一系列操作步驟 | +| 輸出資料 | 積木模型 | + +值得說明的是,資料結構與演算法是獨立於程式語言的。正因如此,本書得以提供基於多種程式語言的實現。 + +!!! tip "約定俗成的簡稱" + + 在實際討論時,我們通常會將“資料結構與演算法”簡稱為“演算法”。比如眾所周知的 LeetCode 演算法題目,實際上同時考查資料結構和演算法兩方面的知識。 diff --git a/zh-hant/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png b/zh-hant/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png new file mode 100644 index 000000000..f29eb8f8f Binary files /dev/null and b/zh-hant/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png differ diff --git a/zh-hant/docs/chapter_preface/about_the_book.md b/zh-hant/docs/chapter_preface/about_the_book.md new file mode 100644 index 000000000..38484877c --- /dev/null +++ b/zh-hant/docs/chapter_preface/about_the_book.md @@ -0,0 +1,50 @@ +# 關於本書 + +本專案旨在建立一本開源、免費、對新手友好的資料結構與演算法入門教程。 + +- 全書採用動畫圖解,結構化地講解資料結構與演算法知識,內容清晰易懂,學習曲線平滑。 +- 演算法源程式碼皆可一鍵執行,支持 Python、C++、Java、C#、Go、Swift、JavaScript、TypeScript、Dart、Rust、C 和 Zig 等語言。 +- 鼓勵讀者在線上章節評論區互幫互助、共同進步,提問與評論通常可在兩日內得到回覆。 + +## 讀者物件 + +若你是演算法初學者,從未接觸過演算法,或者已經有一些刷題經驗,對資料結構與演算法有模糊的認識,在會與不會之間反覆橫跳,那麼本書正是為你量身定製的! + +如果你已經積累一定的刷題量,熟悉大部分題型,那麼本書可助你回顧與梳理演算法知識體系,倉庫源程式碼可以當作“刷題工具庫”或“演算法字典”來使用。 + +若你是演算法“大神”,我們期待收到你的寶貴建議,或者[一起參與創作](https://www.hello-algo.com/chapter_appendix/contribution/)。 + +!!! success "前置條件" + + 你需要至少具備任一語言的程式設計基礎,能夠閱讀和編寫簡單程式碼。 + +## 內容結構 + +本書的主要內容如下圖所示。 + +- **複雜度分析**:資料結構和演算法的評價維度與方法。時間複雜度和空間複雜度的推算方法、常見型別、示例等。 +- **資料結構**:基本資料型別和資料結構的分類方法。陣列、鏈結串列、堆疊、佇列、雜湊表、樹、堆積、圖等資料結構的定義、優缺點、常用操作、常見型別、典型應用、實現方法等。 +- **演算法**:搜尋、排序、分治、回溯、動態規劃、貪婪等演算法的定義、優缺點、效率、應用場景、解題步驟和示例問題等。 + +![本書主要內容](about_the_book.assets/hello_algo_mindmap.png) + +## 致謝 + +本書在開源社群眾多貢獻者的共同努力下不斷完善。感謝每一位投入時間與精力的撰稿人,他們是(按照 GitHub 自動生成的順序):krahets、codingonion、nuomi1、Gonglja、Reanon、justin-tse、danielsss、hpstory、S-N-O-R-L-A-X、night-cruise、msk397、gvenusleo、RiverTwilight、gyt95、zhuoqinyue、Zuoxun、Xia-Sang、mingXta、FangYuan33、GN-Yu、IsChristina、xBLACKICEx、guowei-gong、Cathay-Chen、mgisr、JoseHung、qualifier1024、pengchzn、Guanngxu、longsizhuo、L-Super、what-is-me、yuan0221、lhxsm、Slone123c、WSL0809、longranger2、theNefelibatas、xiongsp、JeffersonHuang、hongyun-robot、K3v123、yuelinxin、a16su、gaofer、malone6、Wonderdch、xjr7670、DullSword、Horbin-Magician、NI-SW、reeswell、XC-Zero、XiaChuerwu、yd-j、iron-irax、huawuque404、MolDuM、Nigh、KorsChen、foursevenlove、52coder、bubble9um、youshaoXG、curly210102、gltianwen、fanchenggang、Transmigration-zhou、FloranceYeh、FreddieLi、ShiMaRing、lipusheng、Javesun99、JackYang-hellobobo、shanghai-Jerry、0130w、Keynman、psychelzh、logan-qiu、ZnYang2018、MwumLi、1ch0、Phoenix0415、qingpeng9802、Richard-Zhang1019、QiLOL、Suremotoo、Turing-1024-Lee、Evilrabbit520、GaochaoZhu、ZJKung、linzeyan、hezhizhen、ZongYangL、beintentional、czruby、coderlef、dshlstarr、szu17dmy、fbigm、gledfish、hts0000、boloboloda、iStig、jiaxianhua、wenjianmin、keshida、kilikilikid、lclc6、lwbaptx、liuxjerry、lucaswangdev、lyl625760、chadyi、noobcodemaker、selear、siqyka、syd168、4yDX3906、tao363、wangwang105、weibk、yabo083、yi427、yishangzhang、zhouLion、baagod、ElaBosak233、xb534、luluxia、yanedie、thomasq0、YangXuanyi 和 th1nk3r-ing 。 + +本書的程式碼審閱工作由 codingonion、Gonglja、gvenusleo、hpstory、justin-tse、krahets、night-cruise、nuomi1 和 Reanon 完成(按照首字母順序排列)。感謝他們付出的時間與精力,正是他們確保了各語言程式碼的規範與統一。 + +在本書的創作過程中,我得到了許多人的幫助。 + +- 感謝我在公司的導師李汐博士,在一次暢談中你鼓勵我“快行動起來”,堅定了我寫這本書的決心; +- 感謝我的女朋友泡泡作為本書的首位讀者,從演算法小白的角度提出許多寶貴建議,使得本書更適合新手閱讀; +- 感謝騰寶、琦寶、飛寶為本書起了一個富有創意的名字,喚起大家寫下第一行程式碼“Hello World!”的美好回憶; +- 感謝校銓在智慧財產權方面提供的專業幫助,這對本開源書的完善起到了重要作用; +- 感謝蘇潼為本書設計了精美的封面和 logo ,並在我的強迫症的驅使下多次耐心修改; +- 感謝 @squidfunk 提供的排版建議,以及他開發的開源文件主題 [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master) 。 + +在寫作過程中,我閱讀了許多關於資料結構與演算法的教材和文章。這些作品為本書提供了優秀的範本,確保了本書內容的準確性與品質。在此感謝所有老師和前輩的傑出貢獻! + +本書倡導手腦並用的學習方式,在這一點上我深受[《動手學深度學習》](https://github.com/d2l-ai/d2l-zh)的啟發。在此向各位讀者強烈推薦這本優秀的著作。 + +**衷心感謝我的父母,正是你們一直以來的支持與鼓勵,讓我有機會做這件富有趣味的事**。 diff --git a/zh-hant/docs/chapter_preface/index.md b/zh-hant/docs/chapter_preface/index.md new file mode 100644 index 000000000..ad378bf01 --- /dev/null +++ b/zh-hant/docs/chapter_preface/index.md @@ -0,0 +1,9 @@ +# 前言 + +![前言](../assets/covers/chapter_preface.jpg) + +!!! abstract + + 演算法猶如美妙的交響樂,每一行程式碼都像韻律般流淌。 + + 願這本書在你的腦海中輕輕響起,留下獨特而深刻的旋律。 diff --git a/zh-hant/docs/chapter_preface/suggestions.assets/code_md_to_repo.png b/zh-hant/docs/chapter_preface/suggestions.assets/code_md_to_repo.png new file mode 100644 index 000000000..a6fea53b2 Binary files /dev/null and b/zh-hant/docs/chapter_preface/suggestions.assets/code_md_to_repo.png differ diff --git a/zh-hant/docs/chapter_preface/suggestions.assets/download_code.png b/zh-hant/docs/chapter_preface/suggestions.assets/download_code.png new file mode 100644 index 000000000..af909b4f6 Binary files /dev/null and b/zh-hant/docs/chapter_preface/suggestions.assets/download_code.png differ diff --git a/zh-hant/docs/chapter_preface/suggestions.assets/learning_route.png b/zh-hant/docs/chapter_preface/suggestions.assets/learning_route.png new file mode 100644 index 000000000..e6ea5b604 Binary files /dev/null and b/zh-hant/docs/chapter_preface/suggestions.assets/learning_route.png differ diff --git a/zh-hant/docs/chapter_preface/suggestions.assets/pythontutor_example.png b/zh-hant/docs/chapter_preface/suggestions.assets/pythontutor_example.png new file mode 100644 index 000000000..92f67fc0a Binary files /dev/null and b/zh-hant/docs/chapter_preface/suggestions.assets/pythontutor_example.png differ diff --git a/zh-hant/docs/chapter_preface/suggestions.md b/zh-hant/docs/chapter_preface/suggestions.md new file mode 100644 index 000000000..066324265 --- /dev/null +++ b/zh-hant/docs/chapter_preface/suggestions.md @@ -0,0 +1,247 @@ +# 如何使用本書 + +!!! tip + + 為了獲得最佳的閱讀體驗,建議你通讀本節內容。 + +## 行文風格約定 + +- 標題後標註 `*` 的是選讀章節,內容相對困難。如果你的時間有限,可以先跳過。 +- 專業術語會使用黑體(紙質版和 PDF 版)或新增下劃線(網頁版),例如陣列(array)。建議記住它們,以便閱讀文獻。 +- 重點內容和總結性語句會 **加粗**,這類文字值得特別關注。 +- 有特指含義的詞句會使用“引號”標註,以避免歧義。 +- 當涉及程式語言之間不一致的名詞時,本書均以 Python 為準,例如使用 `None` 來表示“空”。 +- 本書部分放棄了程式語言的註釋規範,以換取更加緊湊的內容排版。註釋主要分為三種類型:標題註釋、內容註釋、多行註釋。 + +=== "Python" + + ```python title="" + """標題註釋,用於標註函式、類別、測試樣例等""" + + # 內容註釋,用於詳解程式碼 + + """ + 多行 + 註釋 + """ + ``` + +=== "C++" + + ```cpp title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Java" + + ```java title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "C#" + + ```csharp title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Go" + + ```go title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Swift" + + ```swift title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "JS" + + ```javascript title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "TS" + + ```typescript title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Dart" + + ```dart title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Rust" + + ```rust title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "C" + + ```c title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 標題註釋,用於標註函式、類別、測試樣例等 */ + + // 內容註釋,用於詳解程式碼 + + /** + * 多行 + * 註釋 + */ + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + // 標題註釋,用於標註函式、類別、測試樣例等 + + // 內容註釋,用於詳解程式碼 + + // 多行 + // 註釋 + ``` + +## 在動畫圖解中高效學習 + +相較於文字,影片和圖片具有更高的資訊密度和結構化程度,更易於理解。在本書中,**重點和難點知識將主要透過動畫以圖解形式展示**,而文字則作為解釋與補充。 + +如果你在閱讀本書時,發現某段內容提供瞭如下圖所示的動畫圖解,**請以圖為主、以文字為輔**,綜合兩者來理解內容。 + +![動畫圖解示例](../index.assets/animation.gif) + +## 在程式碼實踐中加深理解 + +本書的配套程式碼託管在 [GitHub 倉庫](https://github.com/krahets/hello-algo)。如下圖所示,**源程式碼附有測試樣例,可一鍵執行**。 + +如果時間允許,**建議你參照程式碼自行敲一遍**。如果學習時間有限,請至少通讀並執行所有程式碼。 + +與閱讀程式碼相比,編寫程式碼的過程往往能帶來更多收穫。**動手學,才是真的學**。 + +![執行程式碼示例](../index.assets/running_code.gif) + +執行程式碼的前置工作主要分為三步。 + +**第一步:安裝本地程式設計環境**。請參照附錄所示的[教程](https://www.hello-algo.com/chapter_appendix/installation/)進行安裝,如果已安裝,則可跳過此步驟。 + +**第二步:克隆或下載程式碼倉庫**。前往 [GitHub 倉庫](https://github.com/krahets/hello-algo)。如果已經安裝 [Git](https://git-scm.com/downloads) ,可以透過以下命令克隆本倉庫: + +```shell +git clone https://github.com/krahets/hello-algo.git +``` + +當然,你也可以在下圖所示的位置,點選“Download ZIP”按鈕直接下載程式碼壓縮包,然後在本地解壓即可。 + +![克隆倉庫與下載程式碼](suggestions.assets/download_code.png) + +**第三步:執行源程式碼**。如下圖所示,對於頂部標有檔案名稱的程式碼塊,我們可以在倉庫的 `codes` 檔案夾內找到對應的源程式碼檔案。源程式碼檔案可一鍵執行,將幫助你節省不必要的除錯時間,讓你能夠專注於學習內容。 + +![程式碼塊與對應的源程式碼檔案](suggestions.assets/code_md_to_repo.png) + +除了本地執行程式碼,**網頁版還支持 Python 程式碼的視覺化執行**(基於 [pythontutor](https://pythontutor.com/) 實現)。如下圖所示,你可以點選程式碼塊下方的“視覺化執行”來展開檢視,觀察演算法程式碼的執行過程;也可以點選“全屏觀看”,以獲得更好的閱覽體驗。 + +![Python 程式碼的視覺化執行](suggestions.assets/pythontutor_example.png) + +## 在提問討論中共同成長 + +在閱讀本書時,請不要輕易跳過那些沒學明白的知識點。**歡迎在評論區提出你的問題**,我和小夥伴們將竭誠為你解答,一般情況下可在兩天內回覆。 + +如下圖所示,網頁版每個章節的底部都配有評論區。希望你能多關注評論區的內容。一方面,你可以瞭解大家遇到的問題,從而查漏補缺,激發更深入的思考。另一方面,期待你能慷慨地回答其他小夥伴的問題,分享你的見解,幫助他人進步。 + +![評論區示例](../index.assets/comment.gif) + +## 演算法學習路線 + +從總體上看,我們可以將學習資料結構與演算法的過程劃分為三個階段。 + +1. **階段一:演算法入門**。我們需要熟悉各種資料結構的特點和用法,學習不同演算法的原理、流程、用途和效率等方面的內容。 +2. **階段二:刷演算法題**。建議從熱門題目開刷,先積累至少 100 道題目,熟悉主流的演算法問題。初次刷題時,“知識遺忘”可能是一個挑戰,但請放心,這是很正常的。我們可以按照“艾賓浩斯遺忘曲線”來複習題目,通常在進行 3~5 輪的重複後,就能將其牢記在心。推薦的題單和刷題計劃請見此 [GitHub 倉庫](https://github.com/krahets/LeetCode-Book)。 +3. **階段三:搭建知識體系**。在學習方面,我們可以閱讀演算法專欄文章、解題框架和演算法教材,以不斷豐富知識體系。在刷題方面,可以嘗試採用進階刷題策略,如按專題分類、一題多解、一解多題等,相關的刷題心得可以在各個社群找到。 + +如下圖所示,本書內容主要涵蓋“階段一”,旨在幫助你更高效地展開階段二和階段三的學習。 + +![演算法學習路線](suggestions.assets/learning_route.png) diff --git a/zh-hant/docs/chapter_preface/summary.md b/zh-hant/docs/chapter_preface/summary.md new file mode 100644 index 000000000..03d3c6098 --- /dev/null +++ b/zh-hant/docs/chapter_preface/summary.md @@ -0,0 +1,8 @@ +# 小結 + +- 本書的主要受眾是演算法初學者。如果你已有一定基礎,本書能幫助你系統回顧演算法知識,書中源程式碼也可作為“刷題工具庫”使用。 +- 書中內容主要包括複雜度分析、資料結構和演算法三部分,涵蓋了該領域的大部分主題。 +- 對於演算法新手,在初學階段閱讀一本入門書至關重要,可以少走許多彎路。 +- 書中的動畫圖解通常用於介紹重點和難點知識。閱讀本書時,應給予這些內容更多關注。 +- 實踐乃學習程式設計之最佳途徑。強烈建議執行源程式碼並親自敲程式碼。 +- 本書網頁版的每個章節都設有評論區,歡迎隨時分享你的疑惑與見解。 diff --git a/zh-hant/docs/chapter_reference/index.md b/zh-hant/docs/chapter_reference/index.md new file mode 100644 index 000000000..b75d606cb --- /dev/null +++ b/zh-hant/docs/chapter_reference/index.md @@ -0,0 +1,25 @@ +--- +icon: material/bookshelf +--- + +# 參考文獻 + +[1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition). + +[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition). + +[3] Robert Sedgewick, et al. Algorithms (4th Edition). + +[4] 嚴蔚敏. 資料結構(C 語言版). + +[5] 鄧俊輝. 資料結構(C++ 語言版,第三版). + +[6] 馬克 艾倫 維斯著,陳越譯. 資料結構與演算法分析:Java語言描述(第三版). + +[7] 程傑. 大話資料結構. + +[8] 王爭. 資料結構與演算法之美. + +[9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). + +[10] Aston Zhang, et al. Dive into Deep Learning. diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_example.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_example.png new file mode 100644 index 000000000..290d25b26 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_example.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_ranges.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_ranges.png new file mode 100644 index 000000000..ed8e67cc1 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_ranges.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step1.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step1.png new file mode 100644 index 000000000..9c91225b1 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step1.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step2.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step2.png new file mode 100644 index 000000000..f1c96e924 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step2.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step3.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step3.png new file mode 100644 index 000000000..8a9f56048 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step3.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step4.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step4.png new file mode 100644 index 000000000..6544debd3 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step4.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step5.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step5.png new file mode 100644 index 000000000..a4df911a9 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step5.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step6.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step6.png new file mode 100644 index 000000000..148161b83 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step6.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step7.png b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step7.png new file mode 100644 index 000000000..ad07cb620 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search.assets/binary_search_step7.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search.md b/zh-hant/docs/chapter_searching/binary_search.md new file mode 100755 index 000000000..be2b6dd54 --- /dev/null +++ b/zh-hant/docs/chapter_searching/binary_search.md @@ -0,0 +1,83 @@ +# 二分搜尋 + +二分搜尋(binary search)是一種基於分治策略的高效搜尋演算法。它利用資料的有序性,每輪縮小一半搜尋範圍,直至找到目標元素或搜尋區間為空為止。 + +!!! question + + 給定一個長度為 $n$ 的陣列 `nums` ,元素按從小到大的順序排列且不重複。請查詢並返回元素 `target` 在該陣列中的索引。若陣列不包含該元素,則返回 $-1$ 。示例如下圖所示。 + +![二分搜尋示例資料](binary_search.assets/binary_search_example.png) + +如下圖所示,我們先初始化指標 $i = 0$ 和 $j = n - 1$ ,分別指向陣列首元素和尾元素,代表搜尋區間 $[0, n - 1]$ 。請注意,中括號表示閉區間,其包含邊界值本身。 + +接下來,迴圈執行以下兩步。 + +1. 計算中點索引 $m = \lfloor {(i + j) / 2} \rfloor$ ,其中 $\lfloor \: \rfloor$ 表示向下取整操作。 +2. 判斷 `nums[m]` 和 `target` 的大小關係,分為以下三種情況。 + 1. 當 `nums[m] < target` 時,說明 `target` 在區間 $[m + 1, j]$ 中,因此執行 $i = m + 1$ 。 + 2. 當 `nums[m] > target` 時,說明 `target` 在區間 $[i, m - 1]$ 中,因此執行 $j = m - 1$ 。 + 3. 當 `nums[m] = target` 時,說明找到 `target` ,因此返回索引 $m$ 。 + +若陣列不包含目標元素,搜尋區間最終會縮小為空。此時返回 $-1$ 。 + +=== "<1>" + ![二分搜尋流程](binary_search.assets/binary_search_step1.png) + +=== "<2>" + ![binary_search_step2](binary_search.assets/binary_search_step2.png) + +=== "<3>" + ![binary_search_step3](binary_search.assets/binary_search_step3.png) + +=== "<4>" + ![binary_search_step4](binary_search.assets/binary_search_step4.png) + +=== "<5>" + ![binary_search_step5](binary_search.assets/binary_search_step5.png) + +=== "<6>" + ![binary_search_step6](binary_search.assets/binary_search_step6.png) + +=== "<7>" + ![binary_search_step7](binary_search.assets/binary_search_step7.png) + +值得注意的是,由於 $i$ 和 $j$ 都是 `int` 型別,**因此 $i + j$ 可能會超出 `int` 型別的取值範圍**。為了避免大數越界,我們通常採用公式 $m = \lfloor {i + (j - i) / 2} \rfloor$ 來計算中點。 + +程式碼如下所示: + +```src +[file]{binary_search}-[class]{}-[func]{binary_search} +``` + +**時間複雜度為 $O(\log n)$** :在二分迴圈中,區間每輪縮小一半,因此迴圈次數為 $\log_2 n$ 。 + +**空間複雜度為 $O(1)$** :指標 $i$ 和 $j$ 使用常數大小空間。 + +## 區間表示方法 + +除了上述雙閉區間外,常見的區間表示還有“左閉右開”區間,定義為 $[0, n)$ ,即左邊界包含自身,右邊界不包含自身。在該表示下,區間 $[i, j)$ 在 $i = j$ 時為空。 + +我們可以基於該表示實現具有相同功能的二分搜尋演算法: + +```src +[file]{binary_search}-[class]{}-[func]{binary_search_lcro} +``` + +如下圖所示,在兩種區間表示下,二分搜尋演算法的初始化、迴圈條件和縮小區間操作皆有所不同。 + +由於“雙閉區間”表示中的左右邊界都被定義為閉區間,因此透過指標 $i$ 和指標 $j$ 縮小區間的操作也是對稱的。這樣更不容易出錯,**因此一般建議採用“雙閉區間”的寫法**。 + +![兩種區間定義](binary_search.assets/binary_search_ranges.png) + +## 優點與侷限性 + +二分搜尋在時間和空間方面都有較好的效能。 + +- 二分搜尋的時間效率高。在大資料量下,對數階的時間複雜度具有顯著優勢。例如,當資料大小 $n = 2^{20}$ 時,線性查詢需要 $2^{20} = 1048576$ 輪迴圈,而二分搜尋僅需 $\log_2 2^{20} = 20$ 輪迴圈。 +- 二分搜尋無須額外空間。相較於需要藉助額外空間的搜尋演算法(例如雜湊查詢),二分搜尋更加節省空間。 + +然而,二分搜尋並非適用於所有情況,主要有以下原因。 + +- 二分搜尋僅適用於有序資料。若輸入資料無序,為了使用二分搜尋而專門進行排序,得不償失。因為排序演算法的時間複雜度通常為 $O(n \log n)$ ,比線性查詢和二分搜尋都更高。對於頻繁插入元素的場景,為保持陣列有序性,需要將元素插入到特定位置,時間複雜度為 $O(n)$ ,也是非常昂貴的。 +- 二分搜尋僅適用於陣列。二分搜尋需要跳躍式(非連續地)訪問元素,而在鏈結串列中執行跳躍式訪問的效率較低,因此不適合應用在鏈結串列或基於鏈結串列實現的資料結構。 +- 小資料量下,線性查詢效能更佳。線上性查詢中,每輪只需 1 次判斷操作;而在二分搜尋中,需要 1 次加法、1 次除法、1 ~ 3 次判斷操作、1 次加法(減法),共 4 ~ 6 個單元操作;因此,當資料量 $n$ 較小時,線性查詢反而比二分搜尋更快。 diff --git a/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png b/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png new file mode 100644 index 000000000..c9789c656 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png b/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png new file mode 100644 index 000000000..8f9379bd9 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_edge.md b/zh-hant/docs/chapter_searching/binary_search_edge.md new file mode 100644 index 000000000..b015e0cef --- /dev/null +++ b/zh-hant/docs/chapter_searching/binary_search_edge.md @@ -0,0 +1,56 @@ +# 二分搜尋邊界 + +## 查詢左邊界 + +!!! question + + 給定一個長度為 $n$ 的有序陣列 `nums` ,其中可能包含重複元素。請返回陣列中最左一個元素 `target` 的索引。若陣列中不包含該元素,則返回 $-1$ 。 + +回憶二分搜尋插入點的方法,搜尋完成後 $i$ 指向最左一個 `target` ,**因此查詢插入點本質上是在查詢最左一個 `target` 的索引**。 + +考慮透過查詢插入點的函式實現查詢左邊界。請注意,陣列中可能不包含 `target` ,這種情況可能導致以下兩種結果。 + +- 插入點的索引 $i$ 越界。 +- 元素 `nums[i]` 與 `target` 不相等。 + +當遇到以上兩種情況時,直接返回 $-1$ 即可。程式碼如下所示: + +```src +[file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge} +``` + +## 查詢右邊界 + +那麼如何查詢最右一個 `target` 呢?最直接的方式是修改程式碼,替換在 `nums[m] == target` 情況下的指標收縮操作。程式碼在此省略,有興趣的讀者可以自行實現。 + +下面我們介紹兩種更加取巧的方法。 + +### 複用查詢左邊界 + +實際上,我們可以利用查詢最左元素的函式來查詢最右元素,具體方法為:**將查詢最右一個 `target` 轉化為查詢最左一個 `target + 1`**。 + +如下圖所示,查詢完成後,指標 $i$ 指向最左一個 `target + 1`(如果存在),而 $j$ 指向最右一個 `target` ,**因此返回 $j$ 即可**。 + +![將查詢右邊界轉化為查詢左邊界](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) + +請注意,返回的插入點是 $i$ ,因此需要將其減 $1$ ,從而獲得 $j$ : + +```src +[file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge} +``` + +### 轉化為查詢元素 + +我們知道,當陣列不包含 `target` 時,最終 $i$ 和 $j$ 會分別指向首個大於、小於 `target` 的元素。 + +因此,如下圖所示,我們可以構造一個陣列中不存在的元素,用於查詢左右邊界。 + +- 查詢最左一個 `target` :可以轉化為查詢 `target - 0.5` ,並返回指標 $i$ 。 +- 查詢最右一個 `target` :可以轉化為查詢 `target + 0.5` ,並返回指標 $j$ 。 + +![將查詢邊界轉化為查詢元素](binary_search_edge.assets/binary_search_edge_by_element.png) + +程式碼在此省略,以下兩點值得注意。 + +- 給定陣列不包含小數,這意味著我們無須關心如何處理相等的情況。 +- 因為該方法引入了小數,所以需要將函式中的變數 `target` 改為浮點數型別(Python 無須改動)。 diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png new file mode 100644 index 000000000..9bd1ce0bb Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png new file mode 100644 index 000000000..ac31d0243 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png new file mode 100644 index 000000000..41d3b06f6 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png new file mode 100644 index 000000000..769f0800c Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png new file mode 100644 index 000000000..1c73ca071 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png new file mode 100644 index 000000000..9b2ea6223 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png new file mode 100644 index 000000000..5be8add50 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png new file mode 100644 index 000000000..a98887a04 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png new file mode 100644 index 000000000..9f5b2089b Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png new file mode 100644 index 000000000..ad1543612 Binary files /dev/null and b/zh-hant/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png differ diff --git a/zh-hant/docs/chapter_searching/binary_search_insertion.md b/zh-hant/docs/chapter_searching/binary_search_insertion.md new file mode 100644 index 000000000..bfc0d5181 --- /dev/null +++ b/zh-hant/docs/chapter_searching/binary_search_insertion.md @@ -0,0 +1,91 @@ +# 二分搜尋插入點 + +二分搜尋不僅可用於搜尋目標元素,還可用於解決許多變種問題,比如搜尋目標元素的插入位置。 + +## 無重複元素的情況 + +!!! question + + 給定一個長度為 $n$ 的有序陣列 `nums` 和一個元素 `target` ,陣列不存在重複元素。現將 `target` 插入陣列 `nums` 中,並保持其有序性。若陣列中已存在元素 `target` ,則插入到其左方。請返回插入後 `target` 在陣列中的索引。示例如下圖所示。 + +![二分搜尋插入點示例資料](binary_search_insertion.assets/binary_search_insertion_example.png) + +如果想複用上一節的二分搜尋程式碼,則需要回答以下兩個問題。 + +**問題一**:當陣列中包含 `target` 時,插入點的索引是否是該元素的索引? + +題目要求將 `target` 插入到相等元素的左邊,這意味著新插入的 `target` 替換了原來 `target` 的位置。也就是說,**當陣列包含 `target` 時,插入點的索引就是該 `target` 的索引**。 + +**問題二**:當陣列中不存在 `target` 時,插入點是哪個元素的索引? + +進一步思考二分搜尋過程:當 `nums[m] < target` 時 $i$ 移動,這意味著指標 $i$ 在向大於等於 `target` 的元素靠近。同理,指標 $j$ 始終在向小於等於 `target` 的元素靠近。 + +因此二分結束時一定有:$i$ 指向首個大於 `target` 的元素,$j$ 指向首個小於 `target` 的元素。**易得當陣列不包含 `target` 時,插入索引為 $i$** 。程式碼如下所示: + +```src +[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} +``` + +## 存在重複元素的情況 + +!!! question + + 在上一題的基礎上,規定陣列可能包含重複元素,其餘不變。 + +假設陣列中存在多個 `target` ,則普通二分搜尋只能返回其中一個 `target` 的索引,**而無法確定該元素的左邊和右邊還有多少 `target`**。 + +題目要求將目標元素插入到最左邊,**所以我們需要查詢陣列中最左一個 `target` 的索引**。初步考慮透過下圖所示的步驟實現。 + +1. 執行二分搜尋,得到任意一個 `target` 的索引,記為 $k$ 。 +2. 從索引 $k$ 開始,向左進行線性走訪,當找到最左邊的 `target` 時返回。 + +![線性查詢重複元素的插入點](binary_search_insertion.assets/binary_search_insertion_naive.png) + +此方法雖然可用,但其包含線性查詢,因此時間複雜度為 $O(n)$ 。當陣列中存在很多重複的 `target` 時,該方法效率很低。 + +現考慮拓展二分搜尋程式碼。如下圖所示,整體流程保持不變,每輪先計算中點索引 $m$ ,再判斷 `target` 和 `nums[m]` 的大小關係,分為以下幾種情況。 + +- 當 `nums[m] < target` 或 `nums[m] > target` 時,說明還沒有找到 `target` ,因此採用普通二分搜尋的縮小區間操作,**從而使指標 $i$ 和 $j$ 向 `target` 靠近**。 +- 當 `nums[m] == target` 時,說明小於 `target` 的元素在區間 $[i, m - 1]$ 中,因此採用 $j = m - 1$ 來縮小區間,**從而使指標 $j$ 向小於 `target` 的元素靠近**。 + +迴圈完成後,$i$ 指向最左邊的 `target` ,$j$ 指向首個小於 `target` 的元素,**因此索引 $i$ 就是插入點**。 + +=== "<1>" + ![二分搜尋重複元素的插入點的步驟](binary_search_insertion.assets/binary_search_insertion_step1.png) + +=== "<2>" + ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) + +=== "<3>" + ![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png) + +=== "<4>" + ![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png) + +=== "<5>" + ![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png) + +=== "<6>" + ![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png) + +=== "<7>" + ![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png) + +=== "<8>" + ![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png) + +觀察以下程式碼,判斷分支 `nums[m] > target` 和 `nums[m] == target` 的操作相同,因此兩者可以合併。 + +即便如此,我們仍然可以將判斷條件保持展開,因為其邏輯更加清晰、可讀性更好。 + +```src +[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} +``` + +!!! tip + + 本節的程式碼都是“雙閉區間”寫法。有興趣的讀者可以自行實現“左閉右開”寫法。 + +總的來看,二分搜尋無非就是給指標 $i$ 和 $j$ 分別設定搜尋目標,目標可能是一個具體的元素(例如 `target` ),也可能是一個元素範圍(例如小於 `target` 的元素)。 + +在不斷的迴圈二分中,指標 $i$ 和 $j$ 都逐漸逼近預先設定的目標。最終,它們或是成功找到答案,或是越過邊界後停止。 diff --git a/zh-hant/docs/chapter_searching/index.md b/zh-hant/docs/chapter_searching/index.md new file mode 100644 index 000000000..83a2e62ec --- /dev/null +++ b/zh-hant/docs/chapter_searching/index.md @@ -0,0 +1,9 @@ +# 搜尋 + +![搜尋](../assets/covers/chapter_searching.jpg) + +!!! abstract + + 搜尋是一場未知的冒險,我們或許需要走遍神秘空間的每個角落,又或許可以快速鎖定目標。 + + 在這場尋覓之旅中,每一次探索都可能得到一個未曾料想的答案。 diff --git a/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png new file mode 100644 index 000000000..558b8381c Binary files /dev/null and b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png differ diff --git a/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png new file mode 100644 index 000000000..04bade145 Binary files /dev/null and b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png differ diff --git a/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png new file mode 100644 index 000000000..1485e2525 Binary files /dev/null and b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png differ diff --git a/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png new file mode 100644 index 000000000..b406aaa35 Binary files /dev/null and b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png differ diff --git a/zh-hant/docs/chapter_searching/replace_linear_by_hashing.md b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.md new file mode 100755 index 000000000..b59cb3534 --- /dev/null +++ b/zh-hant/docs/chapter_searching/replace_linear_by_hashing.md @@ -0,0 +1,47 @@ +# 雜湊最佳化策略 + +在演算法題中,**我們常透過將線性查詢替換為雜湊查詢來降低演算法的時間複雜度**。我們藉助一個演算法題來加深理解。 + +!!! question + + 給定一個整數陣列 `nums` 和一個目標元素 `target` ,請在陣列中搜索“和”為 `target` 的兩個元素,並返回它們的陣列索引。返回任意一個解即可。 + +## 線性查詢:以時間換空間 + +考慮直接走訪所有可能的組合。如下圖所示,我們開啟一個兩層迴圈,在每輪中判斷兩個整數的和是否為 `target` ,若是,則返回它們的索引。 + +![線性查詢求解兩數之和](replace_linear_by_hashing.assets/two_sum_brute_force.png) + +程式碼如下所示: + +```src +[file]{two_sum}-[class]{}-[func]{two_sum_brute_force} +``` + +此方法的時間複雜度為 $O(n^2)$ ,空間複雜度為 $O(1)$ ,在大資料量下非常耗時。 + +## 雜湊查詢:以空間換時間 + +考慮藉助一個雜湊表,鍵值對分別為陣列元素和元素索引。迴圈走訪陣列,每輪執行下圖所示的步驟。 + +1. 判斷數字 `target - nums[i]` 是否在雜湊表中,若是,則直接返回這兩個元素的索引。 +2. 將鍵值對 `nums[i]` 和索引 `i` 新增進雜湊表。 + +=== "<1>" + ![輔助雜湊表求解兩數之和](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) + +=== "<2>" + ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) + +=== "<3>" + ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) + +實現程式碼如下所示,僅需單層迴圈即可: + +```src +[file]{two_sum}-[class]{}-[func]{two_sum_hash_table} +``` + +此方法透過雜湊查詢將時間複雜度從 $O(n^2)$ 降至 $O(n)$ ,大幅提升執行效率。 + +由於需要維護一個額外的雜湊表,因此空間複雜度為 $O(n)$ 。**儘管如此,該方法的整體時空效率更為均衡,因此它是本題的最優解法**。 diff --git a/zh-hant/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png b/zh-hant/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png new file mode 100644 index 000000000..6e84b0837 Binary files /dev/null and b/zh-hant/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png differ diff --git a/zh-hant/docs/chapter_searching/searching_algorithm_revisited.md b/zh-hant/docs/chapter_searching/searching_algorithm_revisited.md new file mode 100644 index 000000000..0ce25d722 --- /dev/null +++ b/zh-hant/docs/chapter_searching/searching_algorithm_revisited.md @@ -0,0 +1,84 @@ +# 重識搜尋演算法 + +搜尋演算法(searching algorithm)用於在資料結構(例如陣列、鏈結串列、樹或圖)中搜索一個或一組滿足特定條件的元素。 + +搜尋演算法可根據實現思路分為以下兩類。 + +- **透過走訪資料結構來定位目標元素**,例如陣列、鏈結串列、樹和圖的走訪等。 +- **利用資料組織結構或資料包含的先驗資訊,實現高效元素查詢**,例如二分搜尋、雜湊查詢和二元搜尋樹查詢等。 + +不難發現,這些知識點都已在前面的章節中介紹過,因此搜尋演算法對於我們來說並不陌生。在本節中,我們將從更加系統的視角切入,重新審視搜尋演算法。 + +## 暴力搜尋 + +暴力搜尋透過走訪資料結構的每個元素來定位目標元素。 + +- “線性搜尋”適用於陣列和鏈結串列等線性資料結構。它從資料結構的一端開始,逐個訪問元素,直到找到目標元素或到達另一端仍沒有找到目標元素為止。 +- “廣度優先搜尋”和“深度優先搜尋”是圖和樹的兩種走訪策略。廣度優先搜尋從初始節點開始逐層搜尋,由近及遠地訪問各個節點。深度優先搜尋從初始節點開始,沿著一條路徑走到頭,再回溯並嘗試其他路徑,直到走訪完整個資料結構。 + +暴力搜尋的優點是簡單且通用性好,**無須對資料做預處理和藉助額外的資料結構**。 + +然而,**此類演算法的時間複雜度為 $O(n)$** ,其中 $n$ 為元素數量,因此在資料量較大的情況下效能較差。 + +## 自適應搜尋 + +自適應搜尋利用資料的特有屬性(例如有序性)來最佳化搜尋過程,從而更高效地定位目標元素。 + +- “二分搜尋”利用資料的有序性實現高效查詢,僅適用於陣列。 +- “雜湊查詢”利用雜湊表將搜尋資料和目標資料建立為鍵值對對映,從而實現查詢操作。 +- “樹查詢”在特定的樹結構(例如二元搜尋樹)中,基於比較節點值來快速排除節點,從而定位目標元素。 + +此類演算法的優點是效率高,**時間複雜度可達到 $O(\log n)$ 甚至 $O(1)$** 。 + +然而,**使用這些演算法往往需要對資料進行預處理**。例如,二分搜尋需要預先對陣列進行排序,雜湊查詢和樹查詢都需要藉助額外的資料結構,維護這些資料結構也需要額外的時間和空間開銷。 + +!!! tip + + 自適應搜尋演算法常被稱為查詢演算法,**主要用於在特定資料結構中快速檢索目標元素**。 + +## 搜尋方法選取 + +給定大小為 $n$ 的一組資料,我們可以使用線性搜尋、二分搜尋、樹查詢、雜湊查詢等多種方法從中搜索目標元素。各個方法的工作原理如下圖所示。 + +![多種搜尋策略](searching_algorithm_revisited.assets/searching_algorithms.png) + +上述幾種方法的操作效率與特性如下表所示。 + +

  查詢演算法效率對比

+ +| | 線性搜尋 | 二分搜尋 | 樹查詢 | 雜湊查詢 | +| ------------ | -------- | ------------------ | ------------------ | --------------- | +| 查詢元素 | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | +| 插入元素 | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| 刪除元素 | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| 額外空間 | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | +| 資料預處理 | / | 排序 $O(n \log n)$ | 建樹 $O(n \log n)$ | 建雜湊表 $O(n)$ | +| 資料是否有序 | 無序 | 有序 | 有序 | 無序 | + +搜尋演算法的選擇還取決於資料體量、搜尋效能要求、資料查詢與更新頻率等。 + +**線性搜尋** + +- 通用性較好,無須任何資料預處理操作。假如我們僅需查詢一次資料,那麼其他三種方法的資料預處理的時間比線性搜尋的時間還要更長。 +- 適用於體量較小的資料,此情況下時間複雜度對效率影響較小。 +- 適用於資料更新頻率較高的場景,因為該方法不需要對資料進行任何額外維護。 + +**二分搜尋** + +- 適用於大資料量的情況,效率表現穩定,最差時間複雜度為 $O(\log n)$ 。 +- 資料量不能過大,因為儲存陣列需要連續的記憶體空間。 +- 不適用於高頻增刪資料的場景,因為維護有序陣列的開銷較大。 + +**雜湊查詢** + +- 適合對查詢效能要求很高的場景,平均時間複雜度為 $O(1)$ 。 +- 不適合需要有序資料或範圍查詢的場景,因為雜湊表無法維護資料的有序性。 +- 對雜湊函式和雜湊衝突處理策略的依賴性較高,具有較大的效能劣化風險。 +- 不適合資料量過大的情況,因為雜湊表需要額外空間來最大程度地減少衝突,從而提供良好的查詢效能。 + +**樹查詢** + +- 適用於海量資料,因為樹節點在記憶體中是分散儲存的。 +- 適合需要維護有序資料或範圍查詢的場景。 +- 在持續增刪節點的過程中,二元搜尋樹可能產生傾斜,時間複雜度劣化至 $O(n)$ 。 +- 若使用 AVL 樹或紅黑樹,則各項操作可在 $O(\log n)$ 效率下穩定執行,但維護樹平衡的操作會增加額外的開銷。 diff --git a/zh-hant/docs/chapter_searching/summary.md b/zh-hant/docs/chapter_searching/summary.md new file mode 100644 index 000000000..964158c9a --- /dev/null +++ b/zh-hant/docs/chapter_searching/summary.md @@ -0,0 +1,8 @@ +# 小結 + +- 二分搜尋依賴資料的有序性,透過迴圈逐步縮減一半搜尋區間來進行查詢。它要求輸入資料有序,且僅適用於陣列或基於陣列實現的資料結構。 +- 暴力搜尋透過走訪資料結構來定位資料。線性搜尋適用於陣列和鏈結串列,廣度優先搜尋和深度優先搜尋適用於圖和樹。此類演算法通用性好,無須對資料進行預處理,但時間複雜度 $O(n)$ 較高。 +- 雜湊查詢、樹查詢和二分搜尋屬於高效搜尋方法,可在特定資料結構中快速定位目標元素。此類演算法效率高,時間複雜度可達 $O(\log n)$ 甚至 $O(1)$ ,但通常需要藉助額外資料結構。 +- 實際中,我們需要對資料體量、搜尋效能要求、資料查詢和更新頻率等因素進行具體分析,從而選擇合適的搜尋方法。 +- 線性搜尋適用於小型或頻繁更新的資料;二分搜尋適用於大型、排序的資料;雜湊查詢適用於對查詢效率要求較高且無須範圍查詢的資料;樹查詢適用於需要維護順序和支持範圍查詢的大型動態資料。 +- 用雜湊查詢替換線性查詢是一種常用的最佳化執行時間的策略,可將時間複雜度從 $O(n)$ 降至 $O(1)$ 。 diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png new file mode 100644 index 000000000..905f952e0 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png new file mode 100644 index 000000000..f91050308 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png new file mode 100644 index 000000000..0572d132b Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png new file mode 100644 index 000000000..a89622735 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png new file mode 100644 index 000000000..d82c85a8a Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png new file mode 100644 index 000000000..0cd31f5f9 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png new file mode 100644 index 000000000..46c3f0746 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png new file mode 100644 index 000000000..e21825b6d Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/bubble_sort.md b/zh-hant/docs/chapter_sorting/bubble_sort.md new file mode 100755 index 000000000..162394d5d --- /dev/null +++ b/zh-hant/docs/chapter_sorting/bubble_sort.md @@ -0,0 +1,59 @@ +# 泡沫排序 + +泡沫排序(bubble sort)透過連續地比較與交換相鄰元素實現排序。這個過程就像氣泡從底部升到頂部一樣,因此得名泡沫排序。 + +如下圖所示,冒泡過程可以利用元素交換操作來模擬:從陣列最左端開始向右走訪,依次比較相鄰元素大小,如果“左元素 > 右元素”就交換二者。走訪完成後,最大的元素會被移動到陣列的最右端。 + +=== "<1>" + ![利用元素交換操作模擬冒泡](bubble_sort.assets/bubble_operation_step1.png) + +=== "<2>" + ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) + +=== "<3>" + ![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png) + +=== "<4>" + ![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png) + +=== "<5>" + ![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png) + +=== "<6>" + ![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png) + +=== "<7>" + ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) + +## 演算法流程 + +設陣列的長度為 $n$ ,泡沫排序的步驟如下圖所示。 + +1. 首先,對 $n$ 個元素執行“冒泡”,**將陣列的最大元素交換至正確位置**。 +2. 接下來,對剩餘 $n - 1$ 個元素執行“冒泡”,**將第二大元素交換至正確位置**。 +3. 以此類推,經過 $n - 1$ 輪“冒泡”後,**前 $n - 1$ 大的元素都被交換至正確位置**。 +4. 僅剩的一個元素必定是最小元素,無須排序,因此陣列排序完成。 + +![泡沫排序流程](bubble_sort.assets/bubble_sort_overview.png) + +示例程式碼如下: + +```src +[file]{bubble_sort}-[class]{}-[func]{bubble_sort} +``` + +## 效率最佳化 + +我們發現,如果某輪“冒泡”中沒有執行任何交換操作,說明陣列已經完成排序,可直接返回結果。因此,可以增加一個標誌位 `flag` 來監測這種情況,一旦出現就立即返回。 + +經過最佳化,泡沫排序的最差時間複雜度和平均時間複雜度仍為 $O(n^2)$ ;但當輸入陣列完全有序時,可達到最佳時間複雜度 $O(n)$ 。 + +```src +[file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n^2)$、自適應排序**:各輪“冒泡”走訪的陣列長度依次為 $n - 1$、$n - 2$、$\dots$、$2$、$1$ ,總和為 $(n - 1) n / 2$ 。在引入 `flag` 最佳化後,最佳時間複雜度可達到 $O(n)$ 。 +- **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。 +- **穩定排序**:由於在“冒泡”中遇到相等元素不交換。 diff --git a/zh-hant/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png b/zh-hant/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png new file mode 100644 index 000000000..d78b1e042 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png b/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png new file mode 100644 index 000000000..4697b20c7 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png differ diff --git a/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png b/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png new file mode 100644 index 000000000..8049782aa Binary files /dev/null and b/zh-hant/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png differ diff --git a/zh-hant/docs/chapter_sorting/bucket_sort.md b/zh-hant/docs/chapter_sorting/bucket_sort.md new file mode 100644 index 000000000..93853a129 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/bucket_sort.md @@ -0,0 +1,46 @@ +# 桶排序 + +前述幾種排序演算法都屬於“基於比較的排序演算法”,它們透過比較元素間的大小來實現排序。此類排序演算法的時間複雜度無法超越 $O(n \log n)$ 。接下來,我們將探討幾種“非比較排序演算法”,它們的時間複雜度可以達到線性階。 + +桶排序(bucket sort)是分治策略的一個典型應用。它透過設定一些具有大小順序的桶,每個桶對應一個數據範圍,將資料平均分配到各個桶中;然後,在每個桶內部分別執行排序;最終按照桶的順序將所有資料合併。 + +## 演算法流程 + +考慮一個長度為 $n$ 的陣列,其元素是範圍 $[0, 1)$ 內的浮點數。桶排序的流程如下圖所示。 + +1. 初始化 $k$ 個桶,將 $n$ 個元素分配到 $k$ 個桶中。 +2. 對每個桶分別執行排序(這裡採用程式語言的內建排序函式)。 +3. 按照桶從小到大的順序合併結果。 + +![桶排序演算法流程](bucket_sort.assets/bucket_sort_overview.png) + +程式碼如下所示: + +```src +[file]{bucket_sort}-[class]{}-[func]{bucket_sort} +``` + +## 演算法特性 + +桶排序適用於處理體量很大的資料。例如,輸入資料包含 100 萬個元素,由於空間限制,系統記憶體無法一次性載入所有資料。此時,可以將資料分成 1000 個桶,然後分別對每個桶進行排序,最後將結果合併。 + +- **時間複雜度為 $O(n + k)$** :假設元素在各個桶內平均分佈,那麼每個桶內的元素數量為 $\frac{n}{k}$ 。假設排序單個桶使用 $O(\frac{n}{k} \log\frac{n}{k})$ 時間,則排序所有桶使用 $O(n \log\frac{n}{k})$ 時間。**當桶數量 $k$ 比較大時,時間複雜度則趨向於 $O(n)$** 。合併結果時需要走訪所有桶和元素,花費 $O(n + k)$ 時間。 +- **自適應排序**:在最差情況下,所有資料被分配到一個桶中,且排序該桶使用 $O(n^2)$ 時間。 +- **空間複雜度為 $O(n + k)$、非原地排序**:需要藉助 $k$ 個桶和總共 $n$ 個元素的額外空間。 +- 桶排序是否穩定取決於排序桶內元素的演算法是否穩定。 + +## 如何實現平均分配 + +桶排序的時間複雜度理論上可以達到 $O(n)$ ,**關鍵在於將元素均勻分配到各個桶中**,因為實際資料往往不是均勻分佈的。例如,我們想要將淘寶上的所有商品按價格範圍平均分配到 10 個桶中,但商品價格分佈不均,低於 100 元的非常多,高於 1000 元的非常少。若將價格區間平均劃分為 10 個,各個桶中的商品數量差距會非常大。 + +為實現平均分配,我們可以先設定一條大致的分界線,將資料粗略地分到 3 個桶中。**分配完畢後,再將商品較多的桶繼續劃分為 3 個桶,直至所有桶中的元素數量大致相等**。 + +如下圖所示,這種方法本質上是建立一棵遞迴樹,目標是讓葉節點的值儘可能平均。當然,不一定要每輪將資料劃分為 3 個桶,具體劃分方式可根據資料特點靈活選擇。 + +![遞迴劃分桶](bucket_sort.assets/scatter_in_buckets_recursively.png) + +如果我們提前知道商品價格的機率分佈,**則可以根據資料機率分佈設定每個桶的價格分界線**。值得注意的是,資料分佈並不一定需要特意統計,也可以根據資料特點採用某種機率模型進行近似。 + +如下圖所示,我們假設商品價格服從正態分佈,這樣就可以合理地設定價格區間,從而將商品平均分配到各個桶中。 + +![根據機率分佈劃分桶](bucket_sort.assets/scatter_in_buckets_distribution.png) diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png new file mode 100644 index 000000000..cbd2be3ca Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png new file mode 100644 index 000000000..17169225b Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png new file mode 100644 index 000000000..478fcc82d Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png new file mode 100644 index 000000000..4d6cc3aec Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png new file mode 100644 index 000000000..fb0865d85 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png new file mode 100644 index 000000000..52b85568f Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png new file mode 100644 index 000000000..649df0ce9 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png new file mode 100644 index 000000000..9b77a0d02 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png new file mode 100644 index 000000000..1acbf18a8 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png differ diff --git a/zh-hant/docs/chapter_sorting/counting_sort.md b/zh-hant/docs/chapter_sorting/counting_sort.md new file mode 100644 index 000000000..17501c48a --- /dev/null +++ b/zh-hant/docs/chapter_sorting/counting_sort.md @@ -0,0 +1,84 @@ +# 計數排序 + +計數排序(counting sort)透過統計元素數量來實現排序,通常應用於整數陣列。 + +## 簡單實現 + +先來看一個簡單的例子。給定一個長度為 $n$ 的陣列 `nums` ,其中的元素都是“非負整數”,計數排序的整體流程如下圖所示。 + +1. 走訪陣列,找出其中的最大數字,記為 $m$ ,然後建立一個長度為 $m + 1$ 的輔助陣列 `counter` 。 +2. **藉助 `counter` 統計 `nums` 中各數字的出現次數**,其中 `counter[num]` 對應數字 `num` 的出現次數。統計方法很簡單,只需走訪 `nums`(設當前數字為 `num`),每輪將 `counter[num]` 增加 $1$ 即可。 +3. **由於 `counter` 的各個索引天然有序,因此相當於所有數字已經排序好了**。接下來,我們走訪 `counter` ,根據各數字出現次數從小到大的順序填入 `nums` 即可。 + +![計數排序流程](counting_sort.assets/counting_sort_overview.png) + +程式碼如下所示: + +```src +[file]{counting_sort}-[class]{}-[func]{counting_sort_naive} +``` + +!!! note "計數排序與桶排序的關聯" + + 從桶排序的角度看,我們可以將計數排序中的計數陣列 `counter` 的每個索引視為一個桶,將統計數量的過程看作將各個元素分配到對應的桶中。本質上,計數排序是桶排序在整型資料下的一個特例。 + +## 完整實現 + +細心的讀者可能發現了,**如果輸入資料是物件,上述步驟 `3.` 就失效了**。假設輸入資料是商品物件,我們想按照商品價格(類別的成員變數)對商品進行排序,而上述演算法只能給出價格的排序結果。 + +那麼如何才能得到原資料的排序結果呢?我們首先計算 `counter` 的“前綴和”。顧名思義,索引 `i` 處的前綴和 `prefix[i]` 等於陣列前 `i` 個元素之和: + +$$ +\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} +$$ + +**前綴和具有明確的意義,`prefix[num] - 1` 代表元素 `num` 在結果陣列 `res` 中最後一次出現的索引**。這個資訊非常關鍵,因為它告訴我們各個元素應該出現在結果陣列的哪個位置。接下來,我們倒序走訪原陣列 `nums` 的每個元素 `num` ,在每輪迭代中執行以下兩步。 + +1. 將 `num` 填入陣列 `res` 的索引 `prefix[num] - 1` 處。 +2. 令前綴和 `prefix[num]` 減小 $1$ ,從而得到下次放置 `num` 的索引。 + +走訪完成後,陣列 `res` 中就是排序好的結果,最後使用 `res` 覆蓋原陣列 `nums` 即可。下圖展示了完整的計數排序流程。 + +=== "<1>" + ![計數排序步驟](counting_sort.assets/counting_sort_step1.png) + +=== "<2>" + ![counting_sort_step2](counting_sort.assets/counting_sort_step2.png) + +=== "<3>" + ![counting_sort_step3](counting_sort.assets/counting_sort_step3.png) + +=== "<4>" + ![counting_sort_step4](counting_sort.assets/counting_sort_step4.png) + +=== "<5>" + ![counting_sort_step5](counting_sort.assets/counting_sort_step5.png) + +=== "<6>" + ![counting_sort_step6](counting_sort.assets/counting_sort_step6.png) + +=== "<7>" + ![counting_sort_step7](counting_sort.assets/counting_sort_step7.png) + +=== "<8>" + ![counting_sort_step8](counting_sort.assets/counting_sort_step8.png) + +計數排序的實現程式碼如下所示: + +```src +[file]{counting_sort}-[class]{}-[func]{counting_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n + m)$** :涉及走訪 `nums` 和走訪 `counter` ,都使用線性時間。一般情況下 $n \gg m$ ,時間複雜度趨於 $O(n)$ 。 +- **空間複雜度為 $O(n + m)$、非原地排序**:藉助了長度分別為 $n$ 和 $m$ 的陣列 `res` 和 `counter` 。 +- **穩定排序**:由於向 `res` 中填充元素的順序是“從右向左”的,因此倒序走訪 `nums` 可以避免改變相等元素之間的相對位置,從而實現穩定排序。實際上,正序走訪 `nums` 也可以得到正確的排序結果,但結果是非穩定的。 + +## 侷限性 + +看到這裡,你也許會覺得計數排序非常巧妙,僅透過統計數量就可以實現高效的排序。然而,使用計數排序的前置條件相對較為嚴格。 + +**計數排序只適用於非負整數**。若想將其用於其他型別的資料,需要確保這些資料可以轉換為非負整數,並且在轉換過程中不能改變各個元素之間的相對大小關係。例如,對於包含負數的整數陣列,可以先給所有數字加上一個常數,將全部數字轉化為正數,排序完成後再轉換回去。 + +**計數排序適用於資料量大但資料範圍較小的情況**。比如,在上述示例中 $m$ 不能太大,否則會佔用過多空間。而當 $n \ll m$ 時,計數排序使用 $O(m)$ 時間,可能比 $O(n \log n)$ 的排序演算法還要慢。 diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png new file mode 100644 index 000000000..7ee3be24e Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png new file mode 100644 index 000000000..769e12d24 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png new file mode 100644 index 000000000..7d5751462 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png new file mode 100644 index 000000000..1809c3b5b Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png new file mode 100644 index 000000000..70364f2f4 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png new file mode 100644 index 000000000..f9b3c7c42 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png new file mode 100644 index 000000000..1da60687f Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png new file mode 100644 index 000000000..32eac7962 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png new file mode 100644 index 000000000..f4cd7165e Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png new file mode 100644 index 000000000..f9b0bc7ff Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png new file mode 100644 index 000000000..8c0ee3d37 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png new file mode 100644 index 000000000..1cf905eaf Binary files /dev/null and b/zh-hant/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png differ diff --git a/zh-hant/docs/chapter_sorting/heap_sort.md b/zh-hant/docs/chapter_sorting/heap_sort.md new file mode 100644 index 000000000..81ca4452b --- /dev/null +++ b/zh-hant/docs/chapter_sorting/heap_sort.md @@ -0,0 +1,73 @@ +# 堆積排序 + +!!! tip + + 閱讀本節前,請確保已學完“堆積“章節。 + +堆積排序(heap sort)是一種基於堆積資料結構實現的高效排序演算法。我們可以利用已經學過的“建堆積操作”和“元素出堆積操作”實現堆積排序。 + +1. 輸入陣列並建立小頂堆積,此時最小元素位於堆積頂。 +2. 不斷執行出堆積操作,依次記錄出堆積元素,即可得到從小到大排序的序列。 + +以上方法雖然可行,但需要藉助一個額外陣列來儲存彈出的元素,比較浪費空間。在實際中,我們通常使用一種更加優雅的實現方式。 + +## 演算法流程 + +設陣列的長度為 $n$ ,堆積排序的流程如下圖所示。 + +1. 輸入陣列並建立大頂堆積。完成後,最大元素位於堆積頂。 +2. 將堆積頂元素(第一個元素)與堆積底元素(最後一個元素)交換。完成交換後,堆積的長度減 $1$ ,已排序元素數量加 $1$ 。 +3. 從堆積頂元素開始,從頂到底執行堆積化操作(sift down)。完成堆積化後,堆積的性質得到修復。 +4. 迴圈執行第 `2.` 步和第 `3.` 步。迴圈 $n - 1$ 輪後,即可完成陣列排序。 + +!!! tip + + 實際上,元素出堆積操作中也包含第 `2.` 步和第 `3.` 步,只是多了一個彈出元素的步驟。 + +=== "<1>" + ![堆積排序步驟](heap_sort.assets/heap_sort_step1.png) + +=== "<2>" + ![heap_sort_step2](heap_sort.assets/heap_sort_step2.png) + +=== "<3>" + ![heap_sort_step3](heap_sort.assets/heap_sort_step3.png) + +=== "<4>" + ![heap_sort_step4](heap_sort.assets/heap_sort_step4.png) + +=== "<5>" + ![heap_sort_step5](heap_sort.assets/heap_sort_step5.png) + +=== "<6>" + ![heap_sort_step6](heap_sort.assets/heap_sort_step6.png) + +=== "<7>" + ![heap_sort_step7](heap_sort.assets/heap_sort_step7.png) + +=== "<8>" + ![heap_sort_step8](heap_sort.assets/heap_sort_step8.png) + +=== "<9>" + ![heap_sort_step9](heap_sort.assets/heap_sort_step9.png) + +=== "<10>" + ![heap_sort_step10](heap_sort.assets/heap_sort_step10.png) + +=== "<11>" + ![heap_sort_step11](heap_sort.assets/heap_sort_step11.png) + +=== "<12>" + ![heap_sort_step12](heap_sort.assets/heap_sort_step12.png) + +在程式碼實現中,我們使用了與“堆積”章節相同的從頂至底堆積化 `sift_down()` 函式。值得注意的是,由於堆積的長度會隨著提取最大元素而減小,因此我們需要給 `sift_down()` 函式新增一個長度參數 $n$ ,用於指定堆積的當前有效長度。程式碼如下所示: + +```src +[file]{heap_sort}-[class]{}-[func]{heap_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n \log n)$、非自適應排序**:建堆積操作使用 $O(n)$ 時間。從堆積中提取最大元素的時間複雜度為 $O(\log n)$ ,共迴圈 $n - 1$ 輪。 +- **空間複雜度為 $O(1)$、原地排序**:幾個指標變數使用 $O(1)$ 空間。元素交換和堆積化操作都是在原陣列上進行的。 +- **非穩定排序**:在交換堆積頂元素和堆積底元素時,相等元素的相對位置可能發生變化。 diff --git a/zh-hant/docs/chapter_sorting/index.md b/zh-hant/docs/chapter_sorting/index.md new file mode 100644 index 000000000..a125ec36a --- /dev/null +++ b/zh-hant/docs/chapter_sorting/index.md @@ -0,0 +1,9 @@ +# 排序 + +![排序](../assets/covers/chapter_sorting.jpg) + +!!! abstract + + 排序猶如一把將混亂變為秩序的魔法鑰匙,使我們能以更高效的方式理解與處理資料。 + + 無論是簡單的升序,還是複雜的分類排列,排序都向我們展示了資料的和諧美感。 diff --git a/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png b/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png new file mode 100644 index 000000000..eed064b55 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png differ diff --git a/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png b/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png new file mode 100644 index 000000000..0ca14ff2c Binary files /dev/null and b/zh-hant/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/insertion_sort.md b/zh-hant/docs/chapter_sorting/insertion_sort.md new file mode 100755 index 000000000..ab1573e9b --- /dev/null +++ b/zh-hant/docs/chapter_sorting/insertion_sort.md @@ -0,0 +1,46 @@ +# 插入排序 + +插入排序(insertion sort)是一種簡單的排序演算法,它的工作原理與手動整理一副牌的過程非常相似。 + +具體來說,我們在未排序區間選擇一個基準元素,將該元素與其左側已排序區間的元素逐一比較大小,並將該元素插入到正確的位置。 + +下圖展示了陣列插入元素的操作流程。設基準元素為 `base` ,我們需要將從目標索引到 `base` 之間的所有元素向右移動一位,然後將 `base` 賦值給目標索引。 + +![單次插入操作](insertion_sort.assets/insertion_operation.png) + +## 演算法流程 + +插入排序的整體流程如下圖所示。 + +1. 初始狀態下,陣列的第 1 個元素已完成排序。 +2. 選取陣列的第 2 個元素作為 `base` ,將其插入到正確位置後,**陣列的前 2 個元素已排序**。 +3. 選取第 3 個元素作為 `base` ,將其插入到正確位置後,**陣列的前 3 個元素已排序**。 +4. 以此類推,在最後一輪中,選取最後一個元素作為 `base` ,將其插入到正確位置後,**所有元素均已排序**。 + +![插入排序流程](insertion_sort.assets/insertion_sort_overview.png) + +示例程式碼如下: + +```src +[file]{insertion_sort}-[class]{}-[func]{insertion_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n^2)$、自適應排序**:在最差情況下,每次插入操作分別需要迴圈 $n - 1$、$n-2$、$\dots$、$2$、$1$ 次,求和得到 $(n - 1) n / 2$ ,因此時間複雜度為 $O(n^2)$ 。在遇到有序資料時,插入操作會提前終止。當輸入陣列完全有序時,插入排序達到最佳時間複雜度 $O(n)$ 。 +- **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。 +- **穩定排序**:在插入操作過程中,我們會將元素插入到相等元素的右側,不會改變它們的順序。 + +## 插入排序的優勢 + +插入排序的時間複雜度為 $O(n^2)$ ,而我們即將學習的快速排序的時間複雜度為 $O(n \log n)$ 。儘管插入排序的時間複雜度更高,**但在資料量較小的情況下,插入排序通常更快**。 + +這個結論與線性查詢和二分搜尋的適用情況的結論類似。快速排序這類 $O(n \log n)$ 的演算法屬於基於分治策略的排序演算法,往往包含更多單元計算操作。而在資料量較小時,$n^2$ 和 $n \log n$ 的數值比較接近,複雜度不佔主導地位,每輪中的單元操作數量起到決定性作用。 + +實際上,許多程式語言(例如 Java)的內建排序函式採用了插入排序,大致思路為:對於長陣列,採用基於分治策略的排序演算法,例如快速排序;對於短陣列,直接使用插入排序。 + +雖然泡沫排序、選擇排序和插入排序的時間複雜度都為 $O(n^2)$ ,但在實際情況中,**插入排序的使用頻率顯著高於泡沫排序和選擇排序**,主要有以下原因。 + +- 泡沫排序基於元素交換實現,需要藉助一個臨時變數,共涉及 3 個單元操作;插入排序基於元素賦值實現,僅需 1 個單元操作。因此,**泡沫排序的計算開銷通常比插入排序更高**。 +- 選擇排序在任何情況下的時間複雜度都為 $O(n^2)$ 。**如果給定一組部分有序的資料,插入排序通常比選擇排序效率更高**。 +- 選擇排序不穩定,無法應用於多級排序。 diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png new file mode 100644 index 000000000..c59a14e57 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png new file mode 100644 index 000000000..7ff1c4ec4 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png new file mode 100644 index 000000000..ca2f4c5d2 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png new file mode 100644 index 000000000..da1a589c3 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png new file mode 100644 index 000000000..2e74aaa4e Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png new file mode 100644 index 000000000..9cef20beb Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png new file mode 100644 index 000000000..933ba2a2c Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png new file mode 100644 index 000000000..11596becf Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png new file mode 100644 index 000000000..0dd27d530 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png new file mode 100644 index 000000000..d8b93e591 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png new file mode 100644 index 000000000..582dc7bd7 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png differ diff --git a/zh-hant/docs/chapter_sorting/merge_sort.md b/zh-hant/docs/chapter_sorting/merge_sort.md new file mode 100755 index 000000000..bdfca92f9 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/merge_sort.md @@ -0,0 +1,73 @@ +# 合併排序 + +合併排序(merge sort)是一種基於分治策略的排序演算法,包含下圖所示的“劃分”和“合併”階段。 + +1. **劃分階段**:透過遞迴不斷地將陣列從中點處分開,將長陣列的排序問題轉換為短陣列的排序問題。 +2. **合併階段**:當子陣列長度為 1 時終止劃分,開始合併,持續地將左右兩個較短的有序陣列合併為一個較長的有序陣列,直至結束。 + +![合併排序的劃分與合併階段](merge_sort.assets/merge_sort_overview.png) + +## 演算法流程 + +如下圖所示,“劃分階段”從頂至底遞迴地將陣列從中點切分為兩個子陣列。 + +1. 計算陣列中點 `mid` ,遞迴劃分左子陣列(區間 `[left, mid]` )和右子陣列(區間 `[mid + 1, right]` )。 +2. 遞迴執行步驟 `1.` ,直至子陣列區間長度為 1 時終止。 + +“合併階段”從底至頂地將左子陣列和右子陣列合併為一個有序陣列。需要注意的是,從長度為 1 的子陣列開始合併,合併階段中的每個子陣列都是有序的。 + +=== "<1>" + ![合併排序步驟](merge_sort.assets/merge_sort_step1.png) + +=== "<2>" + ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) + +=== "<3>" + ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) + +=== "<4>" + ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) + +=== "<5>" + ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) + +=== "<6>" + ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) + +=== "<7>" + ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) + +=== "<8>" + ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) + +=== "<9>" + ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) + +=== "<10>" + ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) + +觀察發現,合併排序與二元樹後序走訪的遞迴順序是一致的。 + +- **後序走訪**:先遞迴左子樹,再遞迴右子樹,最後處理根節點。 +- **合併排序**:先遞迴左子陣列,再遞迴右子陣列,最後處理合併。 + +合併排序的實現如以下程式碼所示。請注意,`nums` 的待合併區間為 `[left, right]` ,而 `tmp` 的對應區間為 `[0, right - left]` 。 + +```src +[file]{merge_sort}-[class]{}-[func]{merge_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n \log n)$、非自適應排序**:劃分產生高度為 $\log n$ 的遞迴樹,每層合併的總操作數量為 $n$ ,因此總體時間複雜度為 $O(n \log n)$ 。 +- **空間複雜度為 $O(n)$、非原地排序**:遞迴深度為 $\log n$ ,使用 $O(\log n)$ 大小的堆疊幀空間。合併操作需要藉助輔助陣列實現,使用 $O(n)$ 大小的額外空間。 +- **穩定排序**:在合併過程中,相等元素的次序保持不變。 + +## 鏈結串列排序 + +對於鏈結串列,合併排序相較於其他排序演算法具有顯著優勢,**可以將鏈結串列排序任務的空間複雜度最佳化至 $O(1)$** 。 + +- **劃分階段**:可以使用“迭代”替代“遞迴”來實現鏈結串列劃分工作,從而省去遞迴使用的堆疊幀空間。 +- **合併階段**:在鏈結串列中,節點增刪操作僅需改變引用(指標)即可實現,因此合併階段(將兩個短有序鏈結串列合併為一個長有序鏈結串列)無須建立額外鏈結串列。 + +具體實現細節比較複雜,有興趣的讀者可以查閱相關資料進行學習。 diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png new file mode 100644 index 000000000..ac8919a33 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png new file mode 100644 index 000000000..7443da0d6 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png new file mode 100644 index 000000000..52be3adf3 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png new file mode 100644 index 000000000..10376ff23 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png new file mode 100644 index 000000000..0ea384c7c Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png new file mode 100644 index 000000000..25ed36767 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png new file mode 100644 index 000000000..123c18d17 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png new file mode 100644 index 000000000..47ffe9975 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png new file mode 100644 index 000000000..51dfd6d62 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png b/zh-hant/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png new file mode 100644 index 000000000..c227d3838 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/quick_sort.md b/zh-hant/docs/chapter_sorting/quick_sort.md new file mode 100755 index 000000000..e6b49b08b --- /dev/null +++ b/zh-hant/docs/chapter_sorting/quick_sort.md @@ -0,0 +1,100 @@ +# 快速排序 + +快速排序(quick sort)是一種基於分治策略的排序演算法,執行高效,應用廣泛。 + +快速排序的核心操作是“哨兵劃分”,其目標是:選擇陣列中的某個元素作為“基準數”,將所有小於基準數的元素移到其左側,而大於基準數的元素移到其右側。具體來說,哨兵劃分的流程如下圖所示。 + +1. 選取陣列最左端元素作為基準數,初始化兩個指標 `i` 和 `j` 分別指向陣列的兩端。 +2. 設定一個迴圈,在每輪中使用 `i`(`j`)分別尋找第一個比基準數大(小)的元素,然後交換這兩個元素。 +3. 迴圈執行步驟 `2.` ,直到 `i` 和 `j` 相遇時停止,最後將基準數交換至兩個子陣列的分界線。 + +=== "<1>" + ![哨兵劃分步驟](quick_sort.assets/pivot_division_step1.png) + +=== "<2>" + ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) + +=== "<3>" + ![pivot_division_step3](quick_sort.assets/pivot_division_step3.png) + +=== "<4>" + ![pivot_division_step4](quick_sort.assets/pivot_division_step4.png) + +=== "<5>" + ![pivot_division_step5](quick_sort.assets/pivot_division_step5.png) + +=== "<6>" + ![pivot_division_step6](quick_sort.assets/pivot_division_step6.png) + +=== "<7>" + ![pivot_division_step7](quick_sort.assets/pivot_division_step7.png) + +=== "<8>" + ![pivot_division_step8](quick_sort.assets/pivot_division_step8.png) + +=== "<9>" + ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) + +哨兵劃分完成後,原陣列被劃分成三部分:左子陣列、基準數、右子陣列,且滿足“左子陣列任意元素 $\leq$ 基準數 $\leq$ 右子陣列任意元素”。因此,我們接下來只需對這兩個子陣列進行排序。 + +!!! note "快速排序的分治策略" + + 哨兵劃分的實質是將一個較長陣列的排序問題簡化為兩個較短陣列的排序問題。 + +```src +[file]{quick_sort}-[class]{quick_sort}-[func]{partition} +``` + +## 演算法流程 + +快速排序的整體流程如下圖所示。 + +1. 首先,對原陣列執行一次“哨兵劃分”,得到未排序的左子陣列和右子陣列。 +2. 然後,對左子陣列和右子陣列分別遞迴執行“哨兵劃分”。 +3. 持續遞迴,直至子陣列長度為 1 時終止,從而完成整個陣列的排序。 + +![快速排序流程](quick_sort.assets/quick_sort_overview.png) + +```src +[file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n \log n)$、自適應排序**:在平均情況下,哨兵劃分的遞迴層數為 $\log n$ ,每層中的總迴圈數為 $n$ ,總體使用 $O(n \log n)$ 時間。在最差情況下,每輪哨兵劃分操作都將長度為 $n$ 的陣列劃分為長度為 $0$ 和 $n - 1$ 的兩個子陣列,此時遞迴層數達到 $n$ ,每層中的迴圈數為 $n$ ,總體使用 $O(n^2)$ 時間。 +- **空間複雜度為 $O(n)$、原地排序**:在輸入陣列完全倒序的情況下,達到最差遞迴深度 $n$ ,使用 $O(n)$ 堆疊幀空間。排序操作是在原陣列上進行的,未藉助額外陣列。 +- **非穩定排序**:在哨兵劃分的最後一步,基準數可能會被交換至相等元素的右側。 + +## 快速排序為什麼快 + +從名稱上就能看出,快速排序在效率方面應該具有一定的優勢。儘管快速排序的平均時間複雜度與“合併排序”和“堆積排序”相同,但通常快速排序的效率更高,主要有以下原因。 + +- **出現最差情況的機率很低**:雖然快速排序的最差時間複雜度為 $O(n^2)$ ,沒有合併排序穩定,但在絕大多數情況下,快速排序能在 $O(n \log n)$ 的時間複雜度下執行。 +- **快取使用效率高**:在執行哨兵劃分操作時,系統可將整個子陣列載入到快取,因此訪問元素的效率較高。而像“堆積排序”這類演算法需要跳躍式訪問元素,從而缺乏這一特性。 +- **複雜度的常數係數小**:在上述三種演算法中,快速排序的比較、賦值、交換等操作的總數量最少。這與“插入排序”比“泡沫排序”更快的原因類似。 + +## 基準數最佳化 + +**快速排序在某些輸入下的時間效率可能降低**。舉一個極端例子,假設輸入陣列是完全倒序的,由於我們選擇最左端元素作為基準數,那麼在哨兵劃分完成後,基準數被交換至陣列最右端,導致左子陣列長度為 $n - 1$、右子陣列長度為 $0$ 。如此遞迴下去,每輪哨兵劃分後都有一個子陣列的長度為 $0$ ,分治策略失效,快速排序退化為“泡沫排序”的近似形式。 + +為了儘量避免這種情況發生,**我們可以最佳化哨兵劃分中的基準數的選取策略**。例如,我們可以隨機選取一個元素作為基準數。然而,如果運氣不佳,每次都選到不理想的基準數,效率仍然不盡如人意。 + +需要注意的是,程式語言通常生成的是“偽隨機數”。如果我們針對偽隨機數序列構建一個特定的測試樣例,那麼快速排序的效率仍然可能劣化。 + +為了進一步改進,我們可以在陣列中選取三個候選元素(通常為陣列的首、尾、中點元素),**並將這三個候選元素的中位數作為基準數**。這樣一來,基準數“既不太小也不太大”的機率將大幅提升。當然,我們還可以選取更多候選元素,以進一步提高演算法的穩健性。採用這種方法後,時間複雜度劣化至 $O(n^2)$ 的機率大大降低。 + +示例程式碼如下: + +```src +[file]{quick_sort}-[class]{quick_sort_median}-[func]{partition} +``` + +## 尾遞迴最佳化 + +**在某些輸入下,快速排序可能佔用空間較多**。以完全有序的輸入陣列為例,設遞迴中的子陣列長度為 $m$ ,每輪哨兵劃分操作都將產生長度為 $0$ 的左子陣列和長度為 $m - 1$ 的右子陣列,這意味著每一層遞迴呼叫減少的問題規模非常小(只減少一個元素),遞迴樹的高度會達到 $n - 1$ ,此時需要佔用 $O(n)$ 大小的堆疊幀空間。 + +為了防止堆疊幀空間的累積,我們可以在每輪哨兵排序完成後,比較兩個子陣列的長度,**僅對較短的子陣列進行遞迴**。由於較短子陣列的長度不會超過 $n / 2$ ,因此這種方法能確保遞迴深度不超過 $\log n$ ,從而將最差空間複雜度最佳化至 $O(\log n)$ 。程式碼如下所示: + +```src +[file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort} +``` diff --git a/zh-hant/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png b/zh-hant/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png new file mode 100644 index 000000000..616a3b407 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png differ diff --git a/zh-hant/docs/chapter_sorting/radix_sort.md b/zh-hant/docs/chapter_sorting/radix_sort.md new file mode 100644 index 000000000..aae68bee5 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/radix_sort.md @@ -0,0 +1,41 @@ +# 基數排序 + +上一節介紹了計數排序,它適用於資料量 $n$ 較大但資料範圍 $m$ 較小的情況。假設我們需要對 $n = 10^6$ 個學號進行排序,而學號是一個 $8$ 位數字,這意味著資料範圍 $m = 10^8$ 非常大,使用計數排序需要分配大量記憶體空間,而基數排序可以避免這種情況。 + +基數排序(radix sort)的核心思想與計數排序一致,也透過統計個數來實現排序。在此基礎上,基數排序利用數字各位之間的遞進關係,依次對每一位進行排序,從而得到最終的排序結果。 + +## 演算法流程 + +以學號資料為例,假設數字的最低位是第 $1$ 位,最高位是第 $8$ 位,基數排序的流程如下圖所示。 + +1. 初始化位數 $k = 1$ 。 +2. 對學號的第 $k$ 位執行“計數排序”。完成後,資料會根據第 $k$ 位從小到大排序。 +3. 將 $k$ 增加 $1$ ,然後返回步驟 `2.` 繼續迭代,直到所有位都排序完成後結束。 + +![基數排序演算法流程](radix_sort.assets/radix_sort_overview.png) + +下面剖析程式碼實現。對於一個 $d$ 進位制的數字 $x$ ,要獲取其第 $k$ 位 $x_k$ ,可以使用以下計算公式: + +$$ +x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d +$$ + +其中 $\lfloor a \rfloor$ 表示對浮點數 $a$ 向下取整,而 $\bmod \: d$ 表示對 $d$ 取模(取餘)。對於學號資料,$d = 10$ 且 $k \in [1, 8]$ 。 + +此外,我們需要小幅改動計數排序程式碼,使之可以根據數字的第 $k$ 位進行排序: + +```src +[file]{radix_sort}-[class]{}-[func]{radix_sort} +``` + +!!! question "為什麼從最低位開始排序?" + + 在連續的排序輪次中,後一輪排序會覆蓋前一輪排序的結果。舉例來說,如果第一輪排序結果 $a < b$ ,而第二輪排序結果 $a > b$ ,那麼第二輪的結果將取代第一輪的結果。由於數字的高位優先順序高於低位,因此應該先排序低位再排序高位。 + +## 演算法特性 + +相較於計數排序,基數排序適用於數值範圍較大的情況,**但前提是資料必須可以表示為固定位數的格式,且位數不能過大**。例如,浮點數不適合使用基數排序,因為其位數 $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(n + d)$、非原地排序**:與計數排序相同,基數排序需要藉助長度為 $n$ 和 $d$ 的陣列 `res` 和 `counter` 。 +- **穩定排序**:當計數排序穩定時,基數排序也穩定;當計數排序不穩定時,基數排序無法保證得到正確的排序結果。 diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png new file mode 100644 index 000000000..2065aa4ab Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png new file mode 100644 index 000000000..a07f6b4d5 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png new file mode 100644 index 000000000..f223d7211 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png new file mode 100644 index 000000000..7b5aa96fd Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png new file mode 100644 index 000000000..7e4bf210b Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png new file mode 100644 index 000000000..9a0c6cffa Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png new file mode 100644 index 000000000..9d0866d01 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png new file mode 100644 index 000000000..1faf50d0e Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png new file mode 100644 index 000000000..c7815fca8 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png new file mode 100644 index 000000000..9ea07a3e2 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png new file mode 100644 index 000000000..035a9d033 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png new file mode 100644 index 000000000..0fca09f34 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png differ diff --git a/zh-hant/docs/chapter_sorting/selection_sort.md b/zh-hant/docs/chapter_sorting/selection_sort.md new file mode 100644 index 000000000..911a9b476 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/selection_sort.md @@ -0,0 +1,58 @@ +# 選擇排序 + +選擇排序(selection sort)的工作原理非常簡單:開啟一個迴圈,每輪從未排序區間選擇最小的元素,將其放到已排序區間的末尾。 + +設陣列的長度為 $n$ ,選擇排序的演算法流程如下圖所示。 + +1. 初始狀態下,所有元素未排序,即未排序(索引)區間為 $[0, n-1]$ 。 +2. 選取區間 $[0, n-1]$ 中的最小元素,將其與索引 $0$ 處的元素交換。完成後,陣列前 1 個元素已排序。 +3. 選取區間 $[1, n-1]$ 中的最小元素,將其與索引 $1$ 處的元素交換。完成後,陣列前 2 個元素已排序。 +4. 以此類推。經過 $n - 1$ 輪選擇與交換後,陣列前 $n - 1$ 個元素已排序。 +5. 僅剩的一個元素必定是最大元素,無須排序,因此陣列排序完成。 + +=== "<1>" + ![選擇排序步驟](selection_sort.assets/selection_sort_step1.png) + +=== "<2>" + ![selection_sort_step2](selection_sort.assets/selection_sort_step2.png) + +=== "<3>" + ![selection_sort_step3](selection_sort.assets/selection_sort_step3.png) + +=== "<4>" + ![selection_sort_step4](selection_sort.assets/selection_sort_step4.png) + +=== "<5>" + ![selection_sort_step5](selection_sort.assets/selection_sort_step5.png) + +=== "<6>" + ![selection_sort_step6](selection_sort.assets/selection_sort_step6.png) + +=== "<7>" + ![selection_sort_step7](selection_sort.assets/selection_sort_step7.png) + +=== "<8>" + ![selection_sort_step8](selection_sort.assets/selection_sort_step8.png) + +=== "<9>" + ![selection_sort_step9](selection_sort.assets/selection_sort_step9.png) + +=== "<10>" + ![selection_sort_step10](selection_sort.assets/selection_sort_step10.png) + +=== "<11>" + ![selection_sort_step11](selection_sort.assets/selection_sort_step11.png) + +在程式碼中,我們用 $k$ 來記錄未排序區間內的最小元素: + +```src +[file]{selection_sort}-[class]{}-[func]{selection_sort} +``` + +## 演算法特性 + +- **時間複雜度為 $O(n^2)$、非自適應排序**:外迴圈共 $n - 1$ 輪,第一輪的未排序區間長度為 $n$ ,最後一輪的未排序區間長度為 $2$ ,即各輪外迴圈分別包含 $n$、$n - 1$、$\dots$、$3$、$2$ 輪內迴圈,求和為 $\frac{(n - 1)(n + 2)}{2}$ 。 +- **空間複雜度為 $O(1)$、原地排序**:指標 $i$ 和 $j$ 使用常數大小的額外空間。 +- **非穩定排序**:如下圖所示,元素 `nums[i]` 有可能被交換至與其相等的元素的右邊,導致兩者的相對順序發生改變。 + +![選擇排序非穩定示例](selection_sort.assets/selection_sort_instability.png) diff --git a/zh-hant/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png b/zh-hant/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png new file mode 100644 index 000000000..1b8d7b295 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png differ diff --git a/zh-hant/docs/chapter_sorting/sorting_algorithm.md b/zh-hant/docs/chapter_sorting/sorting_algorithm.md new file mode 100644 index 000000000..26a3559ae --- /dev/null +++ b/zh-hant/docs/chapter_sorting/sorting_algorithm.md @@ -0,0 +1,48 @@ +# 排序演算法 + +排序演算法(sorting algorithm)用於對一組資料按照特定順序進行排列。排序演算法有著廣泛的應用,因為有序資料通常能夠被更高效地查詢、分析和處理。 + +如下圖所示,排序演算法中的資料型別可以是整數、浮點數、字元或字串等。排序的判斷規則可根據需求設定,如數字大小、字元 ASCII 碼順序或自定義規則。 + +![資料型別和判斷規則示例](sorting_algorithm.assets/sorting_examples.png) + +## 評價維度 + +**執行效率**:我們期望排序演算法的時間複雜度儘量低,且總體操作數量較少(時間複雜度中的常數項變小)。對於大資料量的情況,執行效率顯得尤為重要。 + +**就地性**:顧名思義,原地排序透過在原陣列上直接操作實現排序,無須藉助額外的輔助陣列,從而節省記憶體。通常情況下,原地排序的資料搬運操作較少,執行速度也更快。 + +**穩定性**:穩定排序在完成排序後,相等元素在陣列中的相對順序不發生改變。 + +穩定排序是多級排序場景的必要條件。假設我們有一個儲存學生資訊的表格,第 1 列和第 2 列分別是姓名和年齡。在這種情況下,非穩定排序可能導致輸入資料的有序性喪失: + +```shell +# 輸入資料是按照姓名排序好的 +# (name, age) + ('A', 19) + ('B', 18) + ('C', 21) + ('D', 19) + ('E', 23) + +# 假設使用非穩定排序演算法按年齡排序串列, +# 結果中 ('D', 19) 和 ('A', 19) 的相對位置改變, +# 輸入資料按姓名排序的性質丟失 + ('B', 18) + ('D', 19) + ('A', 19) + ('C', 21) + ('E', 23) +``` + +**自適應性**:自適應排序的時間複雜度會受輸入資料的影響,即最佳時間複雜度、最差時間複雜度、平均時間複雜度並不完全相等。 + +自適應性需要根據具體情況來評估。如果最差時間複雜度差於平均時間複雜度,說明排序演算法在某些資料下效能可能劣化,因此被視為負面屬性;而如果最佳時間複雜度優於平均時間複雜度,則被視為正面屬性。 + +**是否基於比較**:基於比較的排序依賴比較運算子($<$、$=$、$>$)來判斷元素的相對順序,從而排序整個陣列,理論最優時間複雜度為 $O(n \log n)$ 。而非比較排序不使用比較運算子,時間複雜度可達 $O(n)$ ,但其通用性相對較差。 + +## 理想排序演算法 + +**執行快、原地、穩定、正向自適應、通用性好**。顯然,迄今為止尚未發現兼具以上所有特性的排序演算法。因此,在選擇排序演算法時,需要根據具體的資料特點和問題需求來決定。 + +接下來,我們將共同學習各種排序演算法,並基於上述評價維度對各個排序演算法的優缺點進行分析。 diff --git a/zh-hant/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png b/zh-hant/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png new file mode 100644 index 000000000..92fa08fc8 Binary files /dev/null and b/zh-hant/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png differ diff --git a/zh-hant/docs/chapter_sorting/summary.md b/zh-hant/docs/chapter_sorting/summary.md new file mode 100644 index 000000000..b1bde36f7 --- /dev/null +++ b/zh-hant/docs/chapter_sorting/summary.md @@ -0,0 +1,47 @@ +# 小結 + +### 重點回顧 + +- 泡沫排序透過交換相鄰元素來實現排序。透過新增一個標誌位來實現提前返回,我們可以將泡沫排序的最佳時間複雜度最佳化到 $O(n)$ 。 +- 插入排序每輪將未排序區間內的元素插入到已排序區間的正確位置,從而完成排序。雖然插入排序的時間複雜度為 $O(n^2)$ ,但由於單元操作相對較少,因此在小資料量的排序任務中非常受歡迎。 +- 快速排序基於哨兵劃分操作實現排序。在哨兵劃分中,有可能每次都選取到最差的基準數,導致時間複雜度劣化至 $O(n^2)$ 。引入中位數基準數或隨機基準數可以降低這種劣化的機率。尾遞迴方法可以有效地減少遞迴深度,將空間複雜度最佳化到 $O(\log n)$ 。 +- 合併排序包括劃分和合並兩個階段,典型地體現了分治策略。在合併排序中,排序陣列需要建立輔助陣列,空間複雜度為 $O(n)$ ;然而排序鏈結串列的空間複雜度可以最佳化至 $O(1)$ 。 +- 桶排序包含三個步驟:資料分桶、桶內排序和合並結果。它同樣體現了分治策略,適用於資料體量很大的情況。桶排序的關鍵在於對資料進行平均分配。 +- 計數排序是桶排序的一個特例,它透過統計資料出現的次數來實現排序。計數排序適用於資料量大但資料範圍有限的情況,並且要求資料能夠轉換為正整數。 +- 基數排序透過逐位排序來實現資料排序,要求資料能夠表示為固定位數的數字。 +- 總的來說,我們希望找到一種排序演算法,具有高效率、穩定、原地以及正向自適應性等優點。然而,正如其他資料結構和演算法一樣,沒有一種排序演算法能夠同時滿足所有這些條件。在實際應用中,我們需要根據資料的特性來選擇合適的排序演算法。 +- 下圖對比了主流排序演算法的效率、穩定性、就地性和自適應性等。 + +![排序演算法對比](summary.assets/sorting_algorithms_comparison.png) + +### Q & A + +**Q**:排序演算法穩定性在什麼情況下是必需的? + +在現實中,我們有可能基於物件的某個屬性進行排序。例如,學生有姓名和身高兩個屬性,我們希望實現一個多級排序:先按照姓名進行排序,得到 `(A, 180) (B, 185) (C, 170) (D, 170)` ;再對身高進行排序。由於排序演算法不穩定,因此可能得到 `(D, 170) (C, 170) (A, 180) (B, 185)` 。 + +可以發現,學生 D 和 C 的位置發生了交換,姓名的有序性被破壞了,而這是我們不希望看到的。 + +**Q**:哨兵劃分中“從右往左查詢”與“從左往右查詢”的順序可以交換嗎? + +不行,當我們以最左端元素為基準數時,必須先“從右往左查詢”再“從左往右查詢”。這個結論有些反直覺,我們來剖析一下原因。 + +哨兵劃分 `partition()` 的最後一步是交換 `nums[left]` 和 `nums[i]` 。完成交換後,基準數左邊的元素都 `<=` 基準數,**這就要求最後一步交換前 `nums[left] >= nums[i]` 必須成立**。假設我們先“從左往右查詢”,那麼如果找不到比基準數更大的元素,**則會在 `i == j` 時跳出迴圈,此時可能 `nums[j] == nums[i] > nums[left]`**。也就是說,此時最後一步交換操作會把一個比基準數更大的元素交換至陣列最左端,導致哨兵劃分失敗。 + +舉個例子,給定陣列 `[0, 0, 0, 0, 1]` ,如果先“從左向右查詢”,哨兵劃分後陣列為 `[1, 0, 0, 0, 0]` ,這個結果是不正確的。 + +再深入思考一下,如果我們選擇 `nums[right]` 為基準數,那麼正好反過來,必須先“從左往右查詢”。 + +**Q**:關於尾遞迴最佳化,為什麼選短的陣列能保證遞迴深度不超過 $\log n$ ? + +遞迴深度就是當前未返回的遞迴方法的數量。每輪哨兵劃分我們將原陣列劃分為兩個子陣列。在尾遞迴最佳化後,向下遞迴的子陣列長度最大為原陣列長度的一半。假設最差情況,一直為一半長度,那麼最終的遞迴深度就是 $\log n$ 。 + +回顧原始的快速排序,我們有可能會連續地遞迴長度較大的陣列,最差情況下為 $n$、$n - 1$、$\dots$、$2$、$1$ ,遞迴深度為 $n$ 。尾遞迴最佳化可以避免這種情況出現。 + +**Q**:當陣列中所有元素都相等時,快速排序的時間複雜度是 $O(n^2)$ 嗎?該如何處理這種退化情況? + +是的。對於這種情況,可以考慮透過哨兵劃分將陣列劃分為三個部分:小於、等於、大於基準數。僅向下遞迴小於和大於的兩部分。在該方法下,輸入元素全部相等的陣列,僅一輪哨兵劃分即可完成排序。 + +**Q**:桶排序的最差時間複雜度為什麼是 $O(n^2)$ ? + +最差情況下,所有元素被分至同一個桶中。如果我們採用一個 $O(n^2)$ 演算法來排序這些元素,則時間複雜度為 $O(n^2)$ 。 diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png new file mode 100644 index 000000000..b7d8a70d8 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png new file mode 100644 index 000000000..76e23962a Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png new file mode 100644 index 000000000..fa8ef7577 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png new file mode 100644 index 000000000..4702a982d Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png new file mode 100644 index 000000000..4a3a21055 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/deque_operations.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/deque_operations.png new file mode 100644 index 000000000..ae2239638 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/deque_operations.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png new file mode 100644 index 000000000..2a07ab3b0 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png new file mode 100644 index 000000000..caf1425fc Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png new file mode 100644 index 000000000..50b3dc340 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png new file mode 100644 index 000000000..b50bb6170 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png new file mode 100644 index 000000000..e54d59ea1 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/deque.md b/zh-hant/docs/chapter_stack_and_queue/deque.md new file mode 100644 index 000000000..14e4d444f --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/deque.md @@ -0,0 +1,433 @@ +# 雙向佇列 + +在佇列中,我們僅能刪除頭部元素或在尾部新增元素。如下圖所示,雙向佇列(double-ended queue)提供了更高的靈活性,允許在頭部和尾部執行元素的新增或刪除操作。 + +![雙向佇列的操作](deque.assets/deque_operations.png) + +## 雙向佇列常用操作 + +雙向佇列的常用操作如下表所示,具體的方法名稱需要根據所使用的程式語言來確定。 + +

  雙向佇列操作效率

+ +| 方法名 | 描述 | 時間複雜度 | +| -------------- | ---------------- | ---------- | +| `push_first()` | 將元素新增至佇列首 | $O(1)$ | +| `push_last()` | 將元素新增至佇列尾 | $O(1)$ | +| `pop_first()` | 刪除佇列首元素 | $O(1)$ | +| `pop_last()` | 刪除佇列尾元素 | $O(1)$ | +| `peek_first()` | 訪問佇列首元素 | $O(1)$ | +| `peek_last()` | 訪問佇列尾元素 | $O(1)$ | + +同樣地,我們可以直接使用程式語言中已實現的雙向佇列類別: + +=== "Python" + + ```python title="deque.py" + from collections import deque + + # 初始化雙向佇列 + deque: deque[int] = deque() + + # 元素入列 + deque.append(2) # 新增至佇列尾 + deque.append(5) + deque.append(4) + deque.appendleft(3) # 新增至佇列首 + deque.appendleft(1) + + # 訪問元素 + front: int = deque[0] # 佇列首元素 + rear: int = deque[-1] # 佇列尾元素 + + # 元素出列 + pop_front: int = deque.popleft() # 佇列首元素出列 + pop_rear: int = deque.pop() # 佇列尾元素出列 + + # 獲取雙向佇列的長度 + size: int = len(deque) + + # 判斷雙向佇列是否為空 + is_empty: bool = len(deque) == 0 + ``` + +=== "C++" + + ```cpp title="deque.cpp" + /* 初始化雙向佇列 */ + deque deque; + + /* 元素入列 */ + deque.push_back(2); // 新增至佇列尾 + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); // 新增至佇列首 + deque.push_front(1); + + /* 訪問元素 */ + int front = deque.front(); // 佇列首元素 + int back = deque.back(); // 佇列尾元素 + + /* 元素出列 */ + deque.pop_front(); // 佇列首元素出列 + deque.pop_back(); // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + + /* 判斷雙向佇列是否為空 */ + bool empty = deque.empty(); + ``` + +=== "Java" + + ```java title="deque.java" + /* 初始化雙向佇列 */ + Deque deque = new LinkedList<>(); + + /* 元素入列 */ + deque.offerLast(2); // 新增至佇列尾 + deque.offerLast(5); + deque.offerLast(4); + deque.offerFirst(3); // 新增至佇列首 + deque.offerFirst(1); + + /* 訪問元素 */ + int peekFirst = deque.peekFirst(); // 佇列首元素 + int peekLast = deque.peekLast(); // 佇列尾元素 + + /* 元素出列 */ + int popFirst = deque.pollFirst(); // 佇列首元素出列 + int popLast = deque.pollLast(); // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + int size = deque.size(); + + /* 判斷雙向佇列是否為空 */ + boolean isEmpty = deque.isEmpty(); + ``` + +=== "C#" + + ```csharp title="deque.cs" + /* 初始化雙向佇列 */ + // 在 C# 中,將鏈結串列 LinkedList 看作雙向佇列來使用 + LinkedList deque = new(); + + /* 元素入列 */ + deque.AddLast(2); // 新增至佇列尾 + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // 新增至佇列首 + deque.AddFirst(1); + + /* 訪問元素 */ + int peekFirst = deque.First.Value; // 佇列首元素 + int peekLast = deque.Last.Value; // 佇列尾元素 + + /* 元素出列 */ + deque.RemoveFirst(); // 佇列首元素出列 + deque.RemoveLast(); // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + int size = deque.Count; + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.Count == 0; + ``` + +=== "Go" + + ```go title="deque_test.go" + /* 初始化雙向佇列 */ + // 在 Go 中,將 list 作為雙向佇列使用 + deque := list.New() + + /* 元素入列 */ + deque.PushBack(2) // 新增至佇列尾 + deque.PushBack(5) + deque.PushBack(4) + deque.PushFront(3) // 新增至佇列首 + deque.PushFront(1) + + /* 訪問元素 */ + front := deque.Front() // 佇列首元素 + rear := deque.Back() // 佇列尾元素 + + /* 元素出列 */ + deque.Remove(front) // 佇列首元素出列 + deque.Remove(rear) // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + size := deque.Len() + + /* 判斷雙向佇列是否為空 */ + isEmpty := deque.Len() == 0 + ``` + +=== "Swift" + + ```swift title="deque.swift" + /* 初始化雙向佇列 */ + // Swift 沒有內建的雙向佇列類別,可以把 Array 當作雙向佇列來使用 + var deque: [Int] = [] + + /* 元素入列 */ + deque.append(2) // 新增至佇列尾 + deque.append(5) + deque.append(4) + deque.insert(3, at: 0) // 新增至佇列首 + deque.insert(1, at: 0) + + /* 訪問元素 */ + let peekFirst = deque.first! // 佇列首元素 + let peekLast = deque.last! // 佇列尾元素 + + /* 元素出列 */ + // 使用 Array 模擬時 popFirst 的複雜度為 O(n) + let popFirst = deque.removeFirst() // 佇列首元素出列 + let popLast = deque.removeLast() // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + let size = deque.count + + /* 判斷雙向佇列是否為空 */ + let isEmpty = deque.isEmpty + ``` + +=== "JS" + + ```javascript title="deque.js" + /* 初始化雙向佇列 */ + // JavaScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 + const deque = []; + + /* 元素入列 */ + deque.push(2); + deque.push(5); + deque.push(4); + // 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) + deque.unshift(3); + deque.unshift(1); + + /* 訪問元素 */ + const peekFirst = deque[0]; + const peekLast = deque[deque.length - 1]; + + /* 元素出列 */ + // 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) + const popFront = deque.shift(); + const popBack = deque.pop(); + + /* 獲取雙向佇列的長度 */ + const size = deque.length; + + /* 判斷雙向佇列是否為空 */ + const isEmpty = size === 0; + ``` + +=== "TS" + + ```typescript title="deque.ts" + /* 初始化雙向佇列 */ + // TypeScript 沒有內建的雙端佇列,只能把 Array 當作雙端佇列來使用 + const deque: number[] = []; + + /* 元素入列 */ + deque.push(2); + deque.push(5); + deque.push(4); + // 請注意,由於是陣列,unshift() 方法的時間複雜度為 O(n) + deque.unshift(3); + deque.unshift(1); + + /* 訪問元素 */ + const peekFirst: number = deque[0]; + const peekLast: number = deque[deque.length - 1]; + + /* 元素出列 */ + // 請注意,由於是陣列,shift() 方法的時間複雜度為 O(n) + const popFront: number = deque.shift() as number; + const popBack: number = deque.pop() as number; + + /* 獲取雙向佇列的長度 */ + const size: number = deque.length; + + /* 判斷雙向佇列是否為空 */ + const isEmpty: boolean = size === 0; + ``` + +=== "Dart" + + ```dart title="deque.dart" + /* 初始化雙向佇列 */ + // 在 Dart 中,Queue 被定義為雙向佇列 + Queue deque = Queue(); + + /* 元素入列 */ + deque.addLast(2); // 新增至佇列尾 + deque.addLast(5); + deque.addLast(4); + deque.addFirst(3); // 新增至佇列首 + deque.addFirst(1); + + /* 訪問元素 */ + int peekFirst = deque.first; // 佇列首元素 + int peekLast = deque.last; // 佇列尾元素 + + /* 元素出列 */ + int popFirst = deque.removeFirst(); // 佇列首元素出列 + int popLast = deque.removeLast(); // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + int size = deque.length; + + /* 判斷雙向佇列是否為空 */ + bool isEmpty = deque.isEmpty; + ``` + +=== "Rust" + + ```rust title="deque.rs" + /* 初始化雙向佇列 */ + let mut deque: VecDeque = VecDeque::new(); + + /* 元素入列 */ + deque.push_back(2); // 新增至佇列尾 + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); // 新增至佇列首 + deque.push_front(1); + + /* 訪問元素 */ + if let Some(front) = deque.front() { // 佇列首元素 + } + if let Some(rear) = deque.back() { // 佇列尾元素 + } + + /* 元素出列 */ + if let Some(pop_front) = deque.pop_front() { // 佇列首元素出列 + } + if let Some(pop_rear) = deque.pop_back() { // 佇列尾元素出列 + } + + /* 獲取雙向佇列的長度 */ + let size = deque.len(); + + /* 判斷雙向佇列是否為空 */ + let is_empty = deque.is_empty(); + ``` + +=== "C" + + ```c title="deque.c" + // C 未提供內建雙向佇列 + ``` + +=== "Kotlin" + + ```kotlin title="deque.kt" + /* 初始化雙向佇列 */ + val deque = LinkedList() + + /* 元素入列 */ + deque.offerLast(2) // 新增至佇列尾 + deque.offerLast(5) + deque.offerLast(4) + deque.offerFirst(3) // 新增至佇列首 + deque.offerFirst(1) + + /* 訪問元素 */ + val peekFirst = deque.peekFirst() // 佇列首元素 + val peekLast = deque.peekLast() // 佇列尾元素 + + /* 元素出列 */ + val popFirst = deque.pollFirst() // 佇列首元素出列 + val popLast = deque.pollLast() // 佇列尾元素出列 + + /* 獲取雙向佇列的長度 */ + val size = deque.size + + /* 判斷雙向佇列是否為空 */ + val isEmpty = deque.isEmpty() + ``` + +=== "Ruby" + + ```ruby title="deque.rb" + + ``` + +=== "Zig" + + ```zig title="deque.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 雙向佇列實現 * + +雙向佇列的實現與佇列類似,可以選擇鏈結串列或陣列作為底層資料結構。 + +### 基於雙向鏈結串列的實現 + +回顧上一節內容,我們使用普通單向鏈結串列來實現佇列,因為它可以方便地刪除頭節點(對應出列操作)和在尾節點後新增新節點(對應入列操作)。 + +對於雙向佇列而言,頭部和尾部都可以執行入列和出列操作。換句話說,雙向佇列需要實現另一個對稱方向的操作。為此,我們採用“雙向鏈結串列”作為雙向佇列的底層資料結構。 + +如下圖所示,我們將雙向鏈結串列的頭節點和尾節點視為雙向佇列的佇列首和佇列尾,同時實現在兩端新增和刪除節點的功能。 + +=== "LinkedListDeque" + ![基於鏈結串列實現雙向佇列的入列出列操作](deque.assets/linkedlist_deque_step1.png) + +=== "push_last()" + ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png) + +=== "push_first()" + ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png) + +=== "pop_last()" + ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png) + +=== "pop_first()" + ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png) + +實現程式碼如下所示: + +```src +[file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} +``` + +### 基於陣列的實現 + +如下圖所示,與基於陣列實現佇列類似,我們也可以使用環形陣列來實現雙向佇列。 + +=== "ArrayDeque" + ![基於陣列實現雙向佇列的入列出列操作](deque.assets/array_deque_step1.png) + +=== "push_last()" + ![array_deque_push_last](deque.assets/array_deque_step2_push_last.png) + +=== "push_first()" + ![array_deque_push_first](deque.assets/array_deque_step3_push_first.png) + +=== "pop_last()" + ![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png) + +=== "pop_first()" + ![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png) + +在佇列的實現基礎上,僅需增加“佇列首入列”和“佇列尾出列”的方法: + +```src +[file]{array_deque}-[class]{array_deque}-[func]{} +``` + +## 雙向佇列應用 + +雙向佇列兼具堆疊與佇列的邏輯,**因此它可以實現這兩者的所有應用場景,同時提供更高的自由度**。 + +我們知道,軟體的“撤銷”功能通常使用堆疊來實現:系統將每次更改操作 `push` 到堆疊中,然後透過 `pop` 實現撤銷。然而,考慮到系統資源的限制,軟體通常會限制撤銷的步數(例如僅允許儲存 $50$ 步)。當堆疊的長度超過 $50$ 時,軟體需要在堆疊底(佇列首)執行刪除操作。**但堆疊無法實現該功能,此時就需要使用雙向佇列來替代堆疊**。請注意,“撤銷”的核心邏輯仍然遵循堆疊的先入後出原則,只是雙向佇列能夠更加靈活地實現一些額外邏輯。 diff --git a/zh-hant/docs/chapter_stack_and_queue/index.md b/zh-hant/docs/chapter_stack_and_queue/index.md new file mode 100644 index 000000000..298a5ec20 --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/index.md @@ -0,0 +1,9 @@ +# 堆疊與佇列 + +![堆疊與佇列](../assets/covers/chapter_stack_and_queue.jpg) + +!!! abstract + + 堆疊如同疊貓貓,而佇列就像貓貓排隊。 + + 兩者分別代表先入後出和先入先出的邏輯關係。 diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png new file mode 100644 index 000000000..d2ef0e175 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png new file mode 100644 index 000000000..2cc4d9b04 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png new file mode 100644 index 000000000..dfde9e5a2 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png new file mode 100644 index 000000000..074f34c5e Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png new file mode 100644 index 000000000..42bd02e57 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png new file mode 100644 index 000000000..6a811a3b9 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.assets/queue_operations.png b/zh-hant/docs/chapter_stack_and_queue/queue.assets/queue_operations.png new file mode 100644 index 000000000..79b482ef9 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/queue.assets/queue_operations.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/queue.md b/zh-hant/docs/chapter_stack_and_queue/queue.md new file mode 100755 index 000000000..650d16bbf --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/queue.md @@ -0,0 +1,407 @@ +# 佇列 + +佇列(queue)是一種遵循先入先出規則的線性資料結構。顧名思義,佇列模擬了排隊現象,即新來的人不斷加入佇列尾部,而位於佇列頭部的人逐個離開。 + +如下圖所示,我們將佇列頭部稱為“佇列首”,尾部稱為“佇列尾”,將把元素加入列尾的操作稱為“入列”,刪除佇列首元素的操作稱為“出列”。 + +![佇列的先入先出規則](queue.assets/queue_operations.png) + +## 佇列常用操作 + +佇列的常見操作如下表所示。需要注意的是,不同程式語言的方法名稱可能會有所不同。我們在此採用與堆疊相同的方法命名。 + +

  佇列操作效率

+ +| 方法名 | 描述 | 時間複雜度 | +| -------- | ---------------------------- | ---------- | +| `push()` | 元素入列,即將元素新增至佇列尾 | $O(1)$ | +| `pop()` | 佇列首元素出列 | $O(1)$ | +| `peek()` | 訪問佇列首元素 | $O(1)$ | + +我們可以直接使用程式語言中現成的佇列類別: + +=== "Python" + + ```python title="queue.py" + from collections import deque + + # 初始化佇列 + # 在 Python 中,我們一般將雙向佇列類別 deque 當作佇列使用 + # 雖然 queue.Queue() 是純正的佇列類別,但不太好用,因此不推薦 + que: deque[int] = deque() + + # 元素入列 + que.append(1) + que.append(3) + que.append(2) + que.append(5) + que.append(4) + + # 訪問佇列首元素 + front: int = que[0] + + # 元素出列 + pop: int = que.popleft() + + # 獲取佇列的長度 + size: int = len(que) + + # 判斷佇列是否為空 + is_empty: bool = len(que) == 0 + ``` + +=== "C++" + + ```cpp title="queue.cpp" + /* 初始化佇列 */ + queue queue; + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* 訪問佇列首元素 */ + int front = queue.front(); + + /* 元素出列 */ + queue.pop(); + + /* 獲取佇列的長度 */ + int size = queue.size(); + + /* 判斷佇列是否為空 */ + bool empty = queue.empty(); + ``` + +=== "Java" + + ```java title="queue.java" + /* 初始化佇列 */ + Queue queue = new LinkedList<>(); + + /* 元素入列 */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + queue.offer(4); + + /* 訪問佇列首元素 */ + int peek = queue.peek(); + + /* 元素出列 */ + int pop = queue.poll(); + + /* 獲取佇列的長度 */ + int size = queue.size(); + + /* 判斷佇列是否為空 */ + boolean isEmpty = queue.isEmpty(); + ``` + +=== "C#" + + ```csharp title="queue.cs" + /* 初始化佇列 */ + Queue queue = new(); + + /* 元素入列 */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + + /* 訪問佇列首元素 */ + int peek = queue.Peek(); + + /* 元素出列 */ + int pop = queue.Dequeue(); + + /* 獲取佇列的長度 */ + int size = queue.Count; + + /* 判斷佇列是否為空 */ + bool isEmpty = queue.Count == 0; + ``` + +=== "Go" + + ```go title="queue_test.go" + /* 初始化佇列 */ + // 在 Go 中,將 list 作為佇列來使用 + queue := list.New() + + /* 元素入列 */ + queue.PushBack(1) + queue.PushBack(3) + queue.PushBack(2) + queue.PushBack(5) + queue.PushBack(4) + + /* 訪問佇列首元素 */ + peek := queue.Front() + + /* 元素出列 */ + pop := queue.Front() + queue.Remove(pop) + + /* 獲取佇列的長度 */ + size := queue.Len() + + /* 判斷佇列是否為空 */ + isEmpty := queue.Len() == 0 + ``` + +=== "Swift" + + ```swift title="queue.swift" + /* 初始化佇列 */ + // Swift 沒有內建的佇列類別,可以把 Array 當作佇列來使用 + var queue: [Int] = [] + + /* 元素入列 */ + queue.append(1) + queue.append(3) + queue.append(2) + queue.append(5) + queue.append(4) + + /* 訪問佇列首元素 */ + let peek = queue.first! + + /* 元素出列 */ + // 由於是陣列,因此 removeFirst 的複雜度為 O(n) + let pool = queue.removeFirst() + + /* 獲取佇列的長度 */ + let size = queue.count + + /* 判斷佇列是否為空 */ + let isEmpty = queue.isEmpty + ``` + +=== "JS" + + ```javascript title="queue.js" + /* 初始化佇列 */ + // JavaScript 沒有內建的佇列,可以把 Array 當作佇列來使用 + const queue = []; + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* 訪問佇列首元素 */ + const peek = queue[0]; + + /* 元素出列 */ + // 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) + const pop = queue.shift(); + + /* 獲取佇列的長度 */ + const size = queue.length; + + /* 判斷佇列是否為空 */ + const empty = queue.length === 0; + ``` + +=== "TS" + + ```typescript title="queue.ts" + /* 初始化佇列 */ + // TypeScript 沒有內建的佇列,可以把 Array 當作佇列來使用 + const queue: number[] = []; + + /* 元素入列 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + + /* 訪問佇列首元素 */ + const peek = queue[0]; + + /* 元素出列 */ + // 底層是陣列,因此 shift() 方法的時間複雜度為 O(n) + const pop = queue.shift(); + + /* 獲取佇列的長度 */ + const size = queue.length; + + /* 判斷佇列是否為空 */ + const empty = queue.length === 0; + ``` + +=== "Dart" + + ```dart title="queue.dart" + /* 初始化佇列 */ + // 在 Dart 中,佇列類別 Qeque 是雙向佇列,也可作為佇列使用 + Queue queue = Queue(); + + /* 元素入列 */ + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); + + /* 訪問佇列首元素 */ + int peek = queue.first; + + /* 元素出列 */ + int pop = queue.removeFirst(); + + /* 獲取佇列的長度 */ + int size = queue.length; + + /* 判斷佇列是否為空 */ + bool isEmpty = queue.isEmpty; + ``` + +=== "Rust" + + ```rust title="queue.rs" + /* 初始化雙向佇列 */ + // 在 Rust 中使用雙向佇列作為普通佇列來使用 + let mut deque: VecDeque = VecDeque::new(); + + /* 元素入列 */ + deque.push_back(1); + deque.push_back(3); + deque.push_back(2); + deque.push_back(5); + deque.push_back(4); + + /* 訪問佇列首元素 */ + if let Some(front) = deque.front() { + } + + /* 元素出列 */ + if let Some(pop) = deque.pop_front() { + } + + /* 獲取佇列的長度 */ + let size = deque.len(); + + /* 判斷佇列是否為空 */ + let is_empty = deque.is_empty(); + ``` + +=== "C" + + ```c title="queue.c" + // C 未提供內建佇列 + ``` + +=== "Kotlin" + + ```kotlin title="queue.kt" + /* 初始化佇列 */ + val queue = LinkedList() + + /* 元素入列 */ + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) + + /* 訪問佇列首元素 */ + val peek = queue.peek() + + /* 元素出列 */ + val pop = queue.poll() + + /* 獲取佇列的長度 */ + val size = queue.size + + /* 判斷佇列是否為空 */ + val isEmpty = queue.isEmpty() + ``` + +=== "Ruby" + + ```ruby title="queue.rb" + + ``` + +=== "Zig" + + ```zig title="queue.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E4%B8%80%E8%88%AC%E5%B0%86%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%B1%BB%20deque%20%E7%9C%8B%E4%BD%9C%E9%98%9F%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E8%99%BD%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%BA%AF%E6%AD%A3%E7%9A%84%E9%98%9F%E5%88%97%E7%B1%BB%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 佇列實現 + +為了實現佇列,我們需要一種資料結構,可以在一端新增元素,並在另一端刪除元素,鏈結串列和陣列都符合要求。 + +### 基於鏈結串列的實現 + +如下圖所示,我們可以將鏈結串列的“頭節點”和“尾節點”分別視為“佇列首”和“佇列尾”,規定佇列尾僅可新增節點,佇列首僅可刪除節點。 + +=== "LinkedListQueue" + ![基於鏈結串列實現佇列的入列出列操作](queue.assets/linkedlist_queue_step1.png) + +=== "push()" + ![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png) + +=== "pop()" + ![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png) + +以下是用鏈結串列實現佇列的程式碼: + +```src +[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} +``` + +### 基於陣列的實現 + +在陣列中刪除首元素的時間複雜度為 $O(n)$ ,這會導致出列操作效率較低。然而,我們可以採用以下巧妙方法來避免這個問題。 + +我們可以使用一個變數 `front` 指向佇列首元素的索引,並維護一個變數 `size` 用於記錄佇列長度。定義 `rear = front + size` ,這個公式計算出的 `rear` 指向佇列尾元素之後的下一個位置。 + +基於此設計,**陣列中包含元素的有效區間為 `[front, rear - 1]`**,各種操作的實現方法如下圖所示。 + +- 入列操作:將輸入元素賦值給 `rear` 索引處,並將 `size` 增加 1 。 +- 出列操作:只需將 `front` 增加 1 ,並將 `size` 減少 1 。 + +可以看到,入列和出列操作都只需進行一次操作,時間複雜度均為 $O(1)$ 。 + +=== "ArrayQueue" + ![基於陣列實現佇列的入列出列操作](queue.assets/array_queue_step1.png) + +=== "push()" + ![array_queue_push](queue.assets/array_queue_step2_push.png) + +=== "pop()" + ![array_queue_pop](queue.assets/array_queue_step3_pop.png) + +你可能會發現一個問題:在不斷進行入列和出列的過程中,`front` 和 `rear` 都在向右移動,**當它們到達陣列尾部時就無法繼續移動了**。為了解決此問題,我們可以將陣列視為首尾相接的“環形陣列”。 + +對於環形陣列,我們需要讓 `front` 或 `rear` 在越過陣列尾部時,直接回到陣列頭部繼續走訪。這種週期性規律可以透過“取餘操作”來實現,程式碼如下所示: + +```src +[file]{array_queue}-[class]{array_queue}-[func]{} +``` + +以上實現的佇列仍然具有侷限性:其長度不可變。然而,這個問題不難解決,我們可以將陣列替換為動態陣列,從而引入擴容機制。有興趣的讀者可以嘗試自行實現。 + +兩種實現的對比結論與堆疊一致,在此不再贅述。 + +## 佇列典型應用 + +- **淘寶訂單**。購物者下單後,訂單將加入列列中,系統隨後會根據順序處理佇列中的訂單。在雙十一期間,短時間內會產生海量訂單,高併發成為工程師們需要重點攻克的問題。 +- **各類待辦事項**。任何需要實現“先來後到”功能的場景,例如印表機的任務佇列、餐廳的出餐佇列等,佇列在這些場景中可以有效地維護處理順序。 diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png new file mode 100644 index 000000000..fb0dbc11d Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png new file mode 100644 index 000000000..dc5392bd0 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png new file mode 100644 index 000000000..1f7c41b2b Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png new file mode 100644 index 000000000..9ddbc6127 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png new file mode 100644 index 000000000..8b20c5cf1 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png new file mode 100644 index 000000000..87ea39369 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.assets/stack_operations.png b/zh-hant/docs/chapter_stack_and_queue/stack.assets/stack_operations.png new file mode 100644 index 000000000..3cb18a9c6 Binary files /dev/null and b/zh-hant/docs/chapter_stack_and_queue/stack.assets/stack_operations.png differ diff --git a/zh-hant/docs/chapter_stack_and_queue/stack.md b/zh-hant/docs/chapter_stack_and_queue/stack.md new file mode 100755 index 000000000..918a9c1be --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/stack.md @@ -0,0 +1,415 @@ +# 堆疊 + +堆疊(stack)是一種遵循先入後出邏輯的線性資料結構。 + +我們可以將堆疊類比為桌面上的一疊盤子,如果想取出底部的盤子,則需要先將上面的盤子依次移走。我們將盤子替換為各種型別的元素(如整數、字元、物件等),就得到了堆疊這種資料結構。 + +如下圖所示,我們把堆積疊元素的頂部稱為“堆疊頂”,底部稱為“堆疊底”。將把元素新增到堆疊頂的操作叫作“入堆疊”,刪除堆疊頂元素的操作叫作“出堆疊”。 + +![堆疊的先入後出規則](stack.assets/stack_operations.png) + +## 堆疊的常用操作 + +堆疊的常用操作如下表所示,具體的方法名需要根據所使用的程式語言來確定。在此,我們以常見的 `push()`、`pop()`、`peek()` 命名為例。 + +

  堆疊的操作效率

+ +| 方法 | 描述 | 時間複雜度 | +| -------- | ---------------------- | ---------- | +| `push()` | 元素入堆疊(新增至堆疊頂) | $O(1)$ | +| `pop()` | 堆疊頂元素出堆疊 | $O(1)$ | +| `peek()` | 訪問堆疊頂元素 | $O(1)$ | + +通常情況下,我們可以直接使用程式語言內建的堆疊類別。然而,某些語言可能沒有專門提供堆疊類別,這時我們可以將該語言的“陣列”或“鏈結串列”當作堆疊來使用,並在程式邏輯上忽略與堆疊無關的操作。 + +=== "Python" + + ```python title="stack.py" + # 初始化堆疊 + # Python 沒有內建的堆疊類別,可以把 list 當作堆疊來使用 + stack: list[int] = [] + + # 元素入堆疊 + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + + # 訪問堆疊頂元素 + peek: int = stack[-1] + + # 元素出堆疊 + pop: int = stack.pop() + + # 獲取堆疊的長度 + size: int = len(stack) + + # 判斷是否為空 + is_empty: bool = len(stack) == 0 + ``` + +=== "C++" + + ```cpp title="stack.cpp" + /* 初始化堆疊 */ + stack stack; + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* 訪問堆疊頂元素 */ + int top = stack.top(); + + /* 元素出堆疊 */ + stack.pop(); // 無返回值 + + /* 獲取堆疊的長度 */ + int size = stack.size(); + + /* 判斷是否為空 */ + bool empty = stack.empty(); + ``` + +=== "Java" + + ```java title="stack.java" + /* 初始化堆疊 */ + Stack stack = new Stack<>(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* 訪問堆疊頂元素 */ + int peek = stack.peek(); + + /* 元素出堆疊 */ + int pop = stack.pop(); + + /* 獲取堆疊的長度 */ + int size = stack.size(); + + /* 判斷是否為空 */ + boolean isEmpty = stack.isEmpty(); + ``` + +=== "C#" + + ```csharp title="stack.cs" + /* 初始化堆疊 */ + Stack stack = new(); + + /* 元素入堆疊 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + + /* 訪問堆疊頂元素 */ + int peek = stack.Peek(); + + /* 元素出堆疊 */ + int pop = stack.Pop(); + + /* 獲取堆疊的長度 */ + int size = stack.Count; + + /* 判斷是否為空 */ + bool isEmpty = stack.Count == 0; + ``` + +=== "Go" + + ```go title="stack_test.go" + /* 初始化堆疊 */ + // 在 Go 中,推薦將 Slice 當作堆疊來使用 + var stack []int + + /* 元素入堆疊 */ + stack = append(stack, 1) + stack = append(stack, 3) + stack = append(stack, 2) + stack = append(stack, 5) + stack = append(stack, 4) + + /* 訪問堆疊頂元素 */ + peek := stack[len(stack)-1] + + /* 元素出堆疊 */ + pop := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + /* 獲取堆疊的長度 */ + size := len(stack) + + /* 判斷是否為空 */ + isEmpty := len(stack) == 0 + ``` + +=== "Swift" + + ```swift title="stack.swift" + /* 初始化堆疊 */ + // Swift 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 + var stack: [Int] = [] + + /* 元素入堆疊 */ + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + + /* 訪問堆疊頂元素 */ + let peek = stack.last! + + /* 元素出堆疊 */ + let pop = stack.removeLast() + + /* 獲取堆疊的長度 */ + let size = stack.count + + /* 判斷是否為空 */ + let isEmpty = stack.isEmpty + ``` + +=== "JS" + + ```javascript title="stack.js" + /* 初始化堆疊 */ + // JavaScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 + const stack = []; + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* 訪問堆疊頂元素 */ + const peek = stack[stack.length-1]; + + /* 元素出堆疊 */ + const pop = stack.pop(); + + /* 獲取堆疊的長度 */ + const size = stack.length; + + /* 判斷是否為空 */ + const is_empty = stack.length === 0; + ``` + +=== "TS" + + ```typescript title="stack.ts" + /* 初始化堆疊 */ + // TypeScript 沒有內建的堆疊類別,可以把 Array 當作堆疊來使用 + const stack: number[] = []; + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* 訪問堆疊頂元素 */ + const peek = stack[stack.length - 1]; + + /* 元素出堆疊 */ + const pop = stack.pop(); + + /* 獲取堆疊的長度 */ + const size = stack.length; + + /* 判斷是否為空 */ + const is_empty = stack.length === 0; + ``` + +=== "Dart" + + ```dart title="stack.dart" + /* 初始化堆疊 */ + // Dart 沒有內建的堆疊類別,可以把 List 當作堆疊來使用 + List stack = []; + + /* 元素入堆疊 */ + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + + /* 訪問堆疊頂元素 */ + int peek = stack.last; + + /* 元素出堆疊 */ + int pop = stack.removeLast(); + + /* 獲取堆疊的長度 */ + int size = stack.length; + + /* 判斷是否為空 */ + bool isEmpty = stack.isEmpty; + ``` + +=== "Rust" + + ```rust title="stack.rs" + /* 初始化堆疊 */ + // 把 Vec 當作堆疊來使用 + let mut stack: Vec = Vec::new(); + + /* 元素入堆疊 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + + /* 訪問堆疊頂元素 */ + let top = stack.last().unwrap(); + + /* 元素出堆疊 */ + let pop = stack.pop().unwrap(); + + /* 獲取堆疊的長度 */ + let size = stack.len(); + + /* 判斷是否為空 */ + let is_empty = stack.is_empty(); + ``` + +=== "C" + + ```c title="stack.c" + // C 未提供內建堆疊 + ``` + +=== "Kotlin" + + ```kotlin title="stack.kt" + /* 初始化堆疊 */ + val stack = Stack() + + /* 元素入堆疊 */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + + /* 訪問堆疊頂元素 */ + val peek = stack.peek() + + /* 元素出堆疊 */ + val pop = stack.pop() + + /* 獲取堆疊的長度 */ + val size = stack.size + + /* 判斷是否為空 */ + val isEmpty = stack.isEmpty() + ``` + +=== "Ruby" + + ```ruby title="stack.rb" + + ``` + +=== "Zig" + + ```zig title="stack.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## 堆疊的實現 + +為了深入瞭解堆疊的執行機制,我們來嘗試自己實現一個堆疊類別。 + +堆疊遵循先入後出的原則,因此我們只能在堆疊頂新增或刪除元素。然而,陣列和鏈結串列都可以在任意位置新增和刪除元素,**因此堆疊可以視為一種受限制的陣列或鏈結串列**。換句話說,我們可以“遮蔽”陣列或鏈結串列的部分無關操作,使其對外表現的邏輯符合堆疊的特性。 + +### 基於鏈結串列的實現 + +使用鏈結串列實現堆疊時,我們可以將鏈結串列的頭節點視為堆疊頂,尾節點視為堆疊底。 + +如下圖所示,對於入堆疊操作,我們只需將元素插入鏈結串列頭部,這種節點插入方法被稱為“頭插法”。而對於出堆疊操作,只需將頭節點從鏈結串列中刪除即可。 + +=== "LinkedListStack" + ![基於鏈結串列實現堆疊的入堆疊出堆疊操作](stack.assets/linkedlist_stack_step1.png) + +=== "push()" + ![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png) + +=== "pop()" + ![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png) + +以下是基於鏈結串列實現堆疊的示例程式碼: + +```src +[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} +``` + +### 基於陣列的實現 + +使用陣列實現堆疊時,我們可以將陣列的尾部作為堆疊頂。如下圖所示,入堆疊與出堆疊操作分別對應在陣列尾部新增元素與刪除元素,時間複雜度都為 $O(1)$ 。 + +=== "ArrayStack" + ![基於陣列實現堆疊的入堆疊出堆疊操作](stack.assets/array_stack_step1.png) + +=== "push()" + ![array_stack_push](stack.assets/array_stack_step2_push.png) + +=== "pop()" + ![array_stack_pop](stack.assets/array_stack_step3_pop.png) + +由於入堆疊的元素可能會源源不斷地增加,因此我們可以使用動態陣列,這樣就無須自行處理陣列擴容問題。以下為示例程式碼: + +```src +[file]{array_stack}-[class]{array_stack}-[func]{} +``` + +## 兩種實現對比 + +**支持操作** + +兩種實現都支持堆疊定義中的各項操作。陣列實現額外支持隨機訪問,但這已超出了堆疊的定義範疇,因此一般不會用到。 + +**時間效率** + +在基於陣列的實現中,入堆疊和出堆疊操作都在預先分配好的連續記憶體中進行,具有很好的快取本地性,因此效率較高。然而,如果入堆疊時超出陣列容量,會觸發擴容機制,導致該次入堆疊操作的時間複雜度變為 $O(n)$ 。 + +在基於鏈結串列的實現中,鏈結串列的擴容非常靈活,不存在上述陣列擴容時效率降低的問題。但是,入堆疊操作需要初始化節點物件並修改指標,因此效率相對較低。不過,如果入堆疊元素本身就是節點物件,那麼可以省去初始化步驟,從而提高效率。 + +綜上所述,當入堆疊與出堆疊操作的元素是基本資料型別時,例如 `int` 或 `double` ,我們可以得出以下結論。 + +- 基於陣列實現的堆疊在觸發擴容時效率會降低,但由於擴容是低頻操作,因此平均效率更高。 +- 基於鏈結串列實現的堆疊可以提供更加穩定的效率表現。 + +**空間效率** + +在初始化串列時,系統會為串列分配“初始容量”,該容量可能超出實際需求;並且,擴容機制通常是按照特定倍率(例如 2 倍)進行擴容的,擴容後的容量也可能超出實際需求。因此,**基於陣列實現的堆疊可能造成一定的空間浪費**。 + +然而,由於鏈結串列節點需要額外儲存指標,**因此鏈結串列節點佔用的空間相對較大**。 + +綜上,我們不能簡單地確定哪種實現更加節省記憶體,需要針對具體情況進行分析。 + +## 堆疊的典型應用 + +- **瀏覽器中的後退與前進、軟體中的撤銷與反撤銷**。每當我們開啟新的網頁,瀏覽器就會對上一個網頁執行入堆疊,這樣我們就可以通過後退操作回到上一個網頁。後退操作實際上是在執行出堆疊。如果要同時支持後退和前進,那麼需要兩個堆疊來配合實現。 +- **程式記憶體管理**。每次呼叫函式時,系統都會在堆疊頂新增一個堆疊幀,用於記錄函式的上下文資訊。在遞迴函式中,向下遞推階段會不斷執行入堆疊操作,而向上回溯階段則會不斷執行出堆疊操作。 diff --git a/zh-hant/docs/chapter_stack_and_queue/summary.md b/zh-hant/docs/chapter_stack_and_queue/summary.md new file mode 100644 index 000000000..128f7d7dd --- /dev/null +++ b/zh-hant/docs/chapter_stack_and_queue/summary.md @@ -0,0 +1,31 @@ +# 小結 + +### 重點回顧 + +- 堆疊是一種遵循先入後出原則的資料結構,可透過陣列或鏈結串列來實現。 +- 在時間效率方面,堆疊的陣列實現具有較高的平均效率,但在擴容過程中,單次入堆疊操作的時間複雜度會劣化至 $O(n)$ 。相比之下,堆疊的鏈結串列實現具有更為穩定的效率表現。 +- 在空間效率方面,堆疊的陣列實現可能導致一定程度的空間浪費。但需要注意的是,鏈結串列節點所佔用的記憶體空間比陣列元素更大。 +- 佇列是一種遵循先入先出原則的資料結構,同樣可以透過陣列或鏈結串列來實現。在時間效率和空間效率的對比上,佇列的結論與前述堆疊的結論相似。 +- 雙向佇列是一種具有更高自由度的佇列,它允許在兩端進行元素的新增和刪除操作。 + +### Q & A + +**Q**:瀏覽器的前進後退是否是雙向鏈結串列實現? + +瀏覽器的前進後退功能本質上是“堆疊”的體現。當用戶訪問一個新頁面時,該頁面會被新增到堆疊頂;當用戶點選後退按鈕時,該頁面會從堆疊頂彈出。使用雙向佇列可以方便地實現一些額外操作,這個在“雙向佇列”章節有提到。 + +**Q**:在出堆疊後,是否需要釋放出堆疊節點的記憶體? + +如果後續仍需要使用彈出節點,則不需要釋放記憶體。若之後不需要用到,`Java` 和 `Python` 等語言擁有自動垃圾回收機制,因此不需要手動釋放記憶體;在 `C` 和 `C++` 中需要手動釋放記憶體。 + +**Q**:雙向佇列像是兩個堆疊拼接在了一起,它的用途是什麼? + +雙向佇列就像是堆疊和佇列的組合或兩個堆疊拼在了一起。它表現的是堆疊 + 佇列的邏輯,因此可以實現堆疊與佇列的所有應用,並且更加靈活。 + +**Q**:撤銷(undo)和反撤銷(redo)具體是如何實現的? + +使用兩個堆疊,堆疊 `A` 用於撤銷,堆疊 `B` 用於反撤銷。 + +1. 每當使用者執行一個操作,將這個操作壓入堆疊 `A` ,並清空堆疊 `B` 。 +2. 當用戶執行“撤銷”時,從堆疊 `A` 中彈出最近的操作,並將其壓入堆疊 `B` 。 +3. 當用戶執行“反撤銷”時,從堆疊 `B` 中彈出最近的操作,並將其壓入堆疊 `A` 。 diff --git a/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png new file mode 100644 index 000000000..6ce09d052 Binary files /dev/null and b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png new file mode 100644 index 000000000..0e5dfd5d3 Binary files /dev/null and b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png new file mode 100644 index 000000000..4ef80b50f Binary files /dev/null and b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png differ diff --git a/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png new file mode 100644 index 000000000..46345f506 Binary files /dev/null and b/zh-hant/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png differ diff --git a/zh-hant/docs/chapter_tree/array_representation_of_tree.md b/zh-hant/docs/chapter_tree/array_representation_of_tree.md new file mode 100644 index 000000000..527c6c0f0 --- /dev/null +++ b/zh-hant/docs/chapter_tree/array_representation_of_tree.md @@ -0,0 +1,164 @@ +# 二元樹陣列表示 + +在鏈結串列表示下,二元樹的儲存單元為節點 `TreeNode` ,節點之間透過指標相連線。上一節介紹了鏈結串列表示下的二元樹的各項基本操作。 + +那麼,我們能否用陣列來表示二元樹呢?答案是肯定的。 + +## 表示完美二元樹 + +先分析一個簡單案例。給定一棵完美二元樹,我們將所有節點按照層序走訪的順序儲存在一個陣列中,則每個節點都對應唯一的陣列索引。 + +根據層序走訪的特性,我們可以推導出父節點索引與子節點索引之間的“對映公式”:**若某節點的索引為 $i$ ,則該節點的左子節點索引為 $2i + 1$ ,右子節點索引為 $2i + 2$** 。下圖展示了各個節點索引之間的對映關係。 + +![完美二元樹的陣列表示](array_representation_of_tree.assets/array_representation_binary_tree.png) + +**對映公式的角色相當於鏈結串列中的節點引用(指標)**。給定陣列中的任意一個節點,我們都可以透過對映公式來訪問它的左(右)子節點。 + +## 表示任意二元樹 + +完美二元樹是一個特例,在二元樹的中間層通常存在許多 `None` 。由於層序走訪序列並不包含這些 `None` ,因此我們無法僅憑該序列來推測 `None` 的數量和分佈位置。**這意味著存在多種二元樹結構都符合該層序走訪序列**。 + +如下圖所示,給定一棵非完美二元樹,上述陣列表示方法已經失效。 + +![層序走訪序列對應多種二元樹可能性](array_representation_of_tree.assets/array_representation_without_empty.png) + +為了解決此問題,**我們可以考慮在層序走訪序列中顯式地寫出所有 `None`** 。如下圖所示,這樣處理後,層序走訪序列就可以唯一表示二元樹了。示例程式碼如下: + +=== "Python" + + ```python title="" + # 二元樹的陣列表示 + # 使用 None 來表示空位 + tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + ``` + +=== "C++" + + ```cpp title="" + /* 二元樹的陣列表示 */ + // 使用 int 最大值 INT_MAX 標記空位 + vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Java" + + ```java title="" + /* 二元樹的陣列表示 */ + // 使用 int 的包裝類別 Integer ,就可以使用 null 來標記空位 + Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; + ``` + +=== "C#" + + ```csharp title="" + /* 二元樹的陣列表示 */ + // 使用 int? 可空型別 ,就可以使用 null 來標記空位 + int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Go" + + ```go title="" + /* 二元樹的陣列表示 */ + // 使用 any 型別的切片, 就可以使用 nil 來標記空位 + tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + ``` + +=== "Swift" + + ```swift title="" + /* 二元樹的陣列表示 */ + // 使用 Int? 可空型別 ,就可以使用 nil 來標記空位 + let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + ``` + +=== "JS" + + ```javascript title="" + /* 二元樹的陣列表示 */ + // 使用 null 來表示空位 + let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "TS" + + ```typescript title="" + /* 二元樹的陣列表示 */ + // 使用 null 來表示空位 + let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Dart" + + ```dart title="" + /* 二元樹的陣列表示 */ + // 使用 int? 可空型別 ,就可以使用 null 來標記空位 + List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + ``` + +=== "Rust" + + ```rust title="" + /* 二元樹的陣列表示 */ + // 使用 None 來標記空位 + let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; + ``` + +=== "C" + + ```c title="" + /* 二元樹的陣列表示 */ + // 使用 int 最大值標記空位,因此要求節點值不能為 INT_MAX + int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 二元樹的陣列表示 */ + // 使用 null 來表示空位 + val tree = mutableListOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +![任意型別二元樹的陣列表示](array_representation_of_tree.assets/array_representation_with_empty.png) + +值得說明的是,**完全二元樹非常適合使用陣列來表示**。回顧完全二元樹的定義,`None` 只出現在最底層且靠右的位置,**因此所有 `None` 一定出現在層序走訪序列的末尾**。 + +這意味著使用陣列表示完全二元樹時,可以省略儲存所有 `None` ,非常方便。下圖給出了一個例子。 + +![完全二元樹的陣列表示](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) + +以下程式碼實現了一棵基於陣列表示的二元樹,包括以下幾種操作。 + +- 給定某節點,獲取它的值、左(右)子節點、父節點。 +- 獲取前序走訪、中序走訪、後序走訪、層序走訪序列。 + +```src +[file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} +``` + +## 優點與侷限性 + +二元樹的陣列表示主要有以下優點。 + +- 陣列儲存在連續的記憶體空間中,對快取友好,訪問與走訪速度較快。 +- 不需要儲存指標,比較節省空間。 +- 允許隨機訪問節點。 + +然而,陣列表示也存在一些侷限性。 + +- 陣列儲存需要連續記憶體空間,因此不適合儲存資料量過大的樹。 +- 增刪節點需要透過陣列插入與刪除操作實現,效率較低。 +- 當二元樹中存在大量 `None` 時,陣列中包含的節點資料比重較低,空間利用率較低。 diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png new file mode 100644 index 000000000..440b5bc48 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png new file mode 100644 index 000000000..60c6d28bb Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png new file mode 100644 index 000000000..aaf0f100b Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png new file mode 100644 index 000000000..6b1250932 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png new file mode 100644 index 000000000..3aadd08ac Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png new file mode 100644 index 000000000..59a0d3bc3 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png new file mode 100644 index 000000000..75e49e0f7 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png new file mode 100644 index 000000000..65fd045b4 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png new file mode 100644 index 000000000..5ad7cf5f1 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png new file mode 100644 index 000000000..2d233f9f5 Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png new file mode 100644 index 000000000..5db9c0f0e Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png new file mode 100644 index 000000000..b6ff6805d Binary files /dev/null and b/zh-hant/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png differ diff --git a/zh-hant/docs/chapter_tree/avl_tree.md b/zh-hant/docs/chapter_tree/avl_tree.md new file mode 100644 index 000000000..eb289330d --- /dev/null +++ b/zh-hant/docs/chapter_tree/avl_tree.md @@ -0,0 +1,353 @@ +# AVL 樹 * + +在“二元搜尋樹”章節中我們提到,在多次插入和刪除操作後,二元搜尋樹可能退化為鏈結串列。在這種情況下,所有操作的時間複雜度將從 $O(\log n)$ 劣化為 $O(n)$ 。 + +如下圖所示,經過兩次刪除節點操作,這棵二元搜尋樹便會退化為鏈結串列。 + +![AVL 樹在刪除節點後發生退化](avl_tree.assets/avltree_degradation_from_removing_node.png) + +再例如,在下圖所示的完美二元樹中插入兩個節點後,樹將嚴重向左傾斜,查詢操作的時間複雜度也隨之劣化。 + +![AVL 樹在插入節點後發生退化](avl_tree.assets/avltree_degradation_from_inserting_node.png) + +1962 年 G. M. Adelson-Velsky 和 E. M. Landis 在論文“An algorithm for the organization of information”中提出了 AVL 樹。論文中詳細描述了一系列操作,確保在持續新增和刪除節點後,AVL 樹不會退化,從而使得各種操作的時間複雜度保持在 $O(\log n)$ 級別。換句話說,在需要頻繁進行增刪查改操作的場景中,AVL 樹能始終保持高效的資料操作效能,具有很好的應用價值。 + +## AVL 樹常見術語 + +AVL 樹既是二元搜尋樹,也是平衡二元樹,同時滿足這兩類二元樹的所有性質,因此是一種平衡二元搜尋樹(balanced binary search tree)。 + +### 節點高度 + +由於 AVL 樹的相關操作需要獲取節點高度,因此我們需要為節點類別新增 `height` 變數: + +=== "Python" + + ```python title="" + class TreeNode: + """AVL 樹節點類別""" + def __init__(self, val: int): + self.val: int = val # 節點值 + self.height: int = 0 # 節點高度 + self.left: TreeNode | None = None # 左子節點引用 + self.right: TreeNode | None = None # 右子節點引用 + ``` + +=== "C++" + + ```cpp title="" + /* AVL 樹節點類別 */ + struct TreeNode { + int val{}; // 節點值 + int height = 0; // 節點高度 + TreeNode *left{}; // 左子節點 + TreeNode *right{}; // 右子節點 + TreeNode() = default; + explicit TreeNode(int x) : val(x){} + }; + ``` + +=== "Java" + + ```java title="" + /* AVL 樹節點類別 */ + class TreeNode { + public int val; // 節點值 + public int height; // 節點高度 + public TreeNode left; // 左子節點 + public TreeNode right; // 右子節點 + public TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* AVL 樹節點類別 */ + class TreeNode(int? x) { + public int? val = x; // 節點值 + public int height; // 節點高度 + public TreeNode? left; // 左子節點引用 + public TreeNode? right; // 右子節點引用 + } + ``` + +=== "Go" + + ```go title="" + /* AVL 樹節點結構體 */ + type TreeNode struct { + Val int // 節點值 + Height int // 節點高度 + Left *TreeNode // 左子節點引用 + Right *TreeNode // 右子節點引用 + } + ``` + +=== "Swift" + + ```swift title="" + /* AVL 樹節點類別 */ + class TreeNode { + var val: Int // 節點值 + var height: Int // 節點高度 + var left: TreeNode? // 左子節點 + var right: TreeNode? // 右子節點 + + init(x: Int) { + val = x + height = 0 + } + } + ``` + +=== "JS" + + ```javascript title="" + /* AVL 樹節點類別 */ + class TreeNode { + val; // 節點值 + height; //節點高度 + left; // 左子節點指標 + right; // 右子節點指標 + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "TS" + + ```typescript title="" + /* AVL 樹節點類別 */ + class TreeNode { + val: number; // 節點值 + height: number; // 節點高度 + left: TreeNode | null; // 左子節點指標 + 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; + } + } + ``` + +=== "Dart" + + ```dart title="" + /* AVL 樹節點類別 */ + class TreeNode { + int val; // 節點值 + int height; // 節點高度 + TreeNode? left; // 左子節點 + TreeNode? right; // 右子節點 + TreeNode(this.val, [this.height = 0, this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* AVL 樹節點結構體 */ + struct TreeNode { + val: i32, // 節點值 + height: i32, // 節點高度 + left: Option>>, // 左子節點 + right: Option>>, // 右子節點 + } + + impl TreeNode { + /* 建構子 */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + left: None, + right: None + })) + } + } + ``` + +=== "C" + + ```c title="" + /* AVL 樹節點結構體 */ + TreeNode struct TreeNode { + int val; + int height; + struct TreeNode *left; + struct TreeNode *right; + } TreeNode; + + /* 建構子 */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* AVL 樹節點類別 */ + class TreeNode(val _val: Int) { // 節點值 + val height: Int = 0 // 節點高度 + val left: TreeNode? = null // 左子節點 + val right: TreeNode? = null // 右子節點 + } + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +“節點高度”是指從該節點到它的最遠葉節點的距離,即所經過的“邊”的數量。需要特別注意的是,葉節點的高度為 $0$ ,而空節點的高度為 $-1$ 。我們將建立兩個工具函式,分別用於獲取和更新節點的高度: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{update_height} +``` + +### 節點平衡因子 + +節點的平衡因子(balance factor)定義為節點左子樹的高度減去右子樹的高度,同時規定空節點的平衡因子為 $0$ 。我們同樣將獲取節點平衡因子的功能封裝成函式,方便後續使用: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor} +``` + +!!! note + + 設平衡因子為 $f$ ,則一棵 AVL 樹的任意節點的平衡因子皆滿足 $-1 \le f \le 1$ 。 + +## AVL 樹旋轉 + +AVL 樹的特點在於“旋轉”操作,它能夠在不影響二元樹的中序走訪序列的前提下,使失衡節點重新恢復平衡。換句話說,**旋轉操作既能保持“二元搜尋樹”的性質,也能使樹重新變為“平衡二元樹”**。 + +我們將平衡因子絕對值 $> 1$ 的節點稱為“失衡節點”。根據節點失衡情況的不同,旋轉操作分為四種:右旋、左旋、先右旋後左旋、先左旋後右旋。下面詳細介紹這些旋轉操作。 + +### 右旋 + +如下圖所示,節點下方為平衡因子。從底至頂看,二元樹中首個失衡節點是“節點 3”。我們關注以該失衡節點為根節點的子樹,將該節點記為 `node` ,其左子節點記為 `child` ,執行“右旋”操作。完成右旋後,子樹恢復平衡,並且仍然保持二元搜尋樹的性質。 + +=== "<1>" + ![右旋操作步驟](avl_tree.assets/avltree_right_rotate_step1.png) + +=== "<2>" + ![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png) + +=== "<3>" + ![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png) + +=== "<4>" + ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png) + +如下圖所示,當節點 `child` 有右子節點(記為 `grand_child` )時,需要在右旋中新增一步:將 `grand_child` 作為 `node` 的左子節點。 + +![有 grand_child 的右旋操作](avl_tree.assets/avltree_right_rotate_with_grandchild.png) + +“向右旋轉”是一種形象化的說法,實際上需要透過修改節點指標來實現,程式碼如下所示: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate} +``` + +### 左旋 + +相應地,如果考慮上述失衡二元樹的“映象”,則需要執行下圖所示的“左旋”操作。 + +![左旋操作](avl_tree.assets/avltree_left_rotate.png) + +同理,如下圖所示,當節點 `child` 有左子節點(記為 `grand_child` )時,需要在左旋中新增一步:將 `grand_child` 作為 `node` 的右子節點。 + +![有 grand_child 的左旋操作](avl_tree.assets/avltree_left_rotate_with_grandchild.png) + +可以觀察到,**右旋和左旋操作在邏輯上是映象對稱的,它們分別解決的兩種失衡情況也是對稱的**。基於對稱性,我們只需將右旋的實現程式碼中的所有的 `left` 替換為 `right` ,將所有的 `right` 替換為 `left` ,即可得到左旋的實現程式碼: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate} +``` + +### 先左旋後右旋 + +對於下圖中的失衡節點 3 ,僅使用左旋或右旋都無法使子樹恢復平衡。此時需要先對 `child` 執行“左旋”,再對 `node` 執行“右旋”。 + +![先左旋後右旋](avl_tree.assets/avltree_left_right_rotate.png) + +### 先右旋後左旋 + +如下圖所示,對於上述失衡二元樹的映象情況,需要先對 `child` 執行“右旋”,再對 `node` 執行“左旋”。 + +![先右旋後左旋](avl_tree.assets/avltree_right_left_rotate.png) + +### 旋轉的選擇 + +下圖展示的四種失衡情況與上述案例逐個對應,分別需要採用右旋、先左旋後右旋、先右旋後左旋、左旋的操作。 + +![AVL 樹的四種旋轉情況](avl_tree.assets/avltree_rotation_cases.png) + +如下表所示,我們透過判斷失衡節點的平衡因子以及較高一側子節點的平衡因子的正負號,來確定失衡節點屬於上圖中的哪種情況。 + +

  四種旋轉情況的選擇條件

+ +| 失衡節點的平衡因子 | 子節點的平衡因子 | 應採用的旋轉方法 | +| ------------------ | ---------------- | ---------------- | +| $> 1$ (左偏樹) | $\geq 0$ | 右旋 | +| $> 1$ (左偏樹) | $<0$ | 先左旋後右旋 | +| $< -1$ (右偏樹) | $\leq 0$ | 左旋 | +| $< -1$ (右偏樹) | $>0$ | 先右旋後左旋 | + +為了便於使用,我們將旋轉操作封裝成一個函式。**有了這個函式,我們就能對各種失衡情況進行旋轉,使失衡節點重新恢復平衡**。程式碼如下所示: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{rotate} +``` + +## AVL 樹常用操作 + +### 插入節點 + +AVL 樹的節點插入操作與二元搜尋樹在主體上類似。唯一的區別在於,在 AVL 樹中插入節點後,從該節點到根節點的路徑上可能會出現一系列失衡節點。因此,**我們需要從這個節點開始,自底向上執行旋轉操作,使所有失衡節點恢復平衡**。程式碼如下所示: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper} +``` + +### 刪除節點 + +類似地,在二元搜尋樹的刪除節點方法的基礎上,需要從底至頂執行旋轉操作,使所有失衡節點恢復平衡。程式碼如下所示: + +```src +[file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper} +``` + +### 查詢節點 + +AVL 樹的節點查詢操作與二元搜尋樹一致,在此不再贅述。 + +## AVL 樹典型應用 + +- 組織和儲存大型資料,適用於高頻查詢、低頻增刪的場景。 +- 用於構建資料庫中的索引系統。 +- 紅黑樹也是一種常見的平衡二元搜尋樹。相較於 AVL 樹,紅黑樹的平衡條件更寬鬆,插入與刪除節點所需的旋轉操作更少,節點增刪操作的平均效率更高。 diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png new file mode 100644 index 000000000..7dc800de4 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png new file mode 100644 index 000000000..50c278539 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png new file mode 100644 index 000000000..a91ff1e5e Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_insert.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_insert.png new file mode 100644 index 000000000..33f5f4861 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_insert.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png new file mode 100644 index 000000000..c67a7d8df Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png new file mode 100644 index 000000000..ee38a6b8b Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png new file mode 100644 index 000000000..a3bb84145 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png new file mode 100644 index 000000000..9f0c27270 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png new file mode 100644 index 000000000..b0280008a Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png new file mode 100644 index 000000000..3c09a41f0 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png new file mode 100644 index 000000000..1afee7e87 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png new file mode 100644 index 000000000..465251e05 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png new file mode 100644 index 000000000..7935fe720 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png new file mode 100644 index 000000000..e29f7dcad Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png differ diff --git a/zh-hant/docs/chapter_tree/binary_search_tree.md b/zh-hant/docs/chapter_tree/binary_search_tree.md new file mode 100755 index 000000000..0ffaa6139 --- /dev/null +++ b/zh-hant/docs/chapter_tree/binary_search_tree.md @@ -0,0 +1,129 @@ +# 二元搜尋樹 + +如下圖所示,二元搜尋樹(binary search tree)滿足以下條件。 + +1. 對於根節點,左子樹中所有節點的值 $<$ 根節點的值 $<$ 右子樹中所有節點的值。 +2. 任意節點的左、右子樹也是二元搜尋樹,即同樣滿足條件 `1.` 。 + +![二元搜尋樹](binary_search_tree.assets/binary_search_tree.png) + +## 二元搜尋樹的操作 + +我們將二元搜尋樹封裝為一個類別 `BinarySearchTree` ,並宣告一個成員變數 `root` ,指向樹的根節點。 + +### 查詢節點 + +給定目標節點值 `num` ,可以根據二元搜尋樹的性質來查詢。如下圖所示,我們宣告一個節點 `cur` ,從二元樹的根節點 `root` 出發,迴圈比較節點值 `cur.val` 和 `num` 之間的大小關係。 + +- 若 `cur.val < num` ,說明目標節點在 `cur` 的右子樹中,因此執行 `cur = cur.right` 。 +- 若 `cur.val > num` ,說明目標節點在 `cur` 的左子樹中,因此執行 `cur = cur.left` 。 +- 若 `cur.val = num` ,說明找到目標節點,跳出迴圈並返回該節點。 + +=== "<1>" + ![二元搜尋樹查詢節點示例](binary_search_tree.assets/bst_search_step1.png) + +=== "<2>" + ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png) + +=== "<3>" + ![bst_search_step3](binary_search_tree.assets/bst_search_step3.png) + +=== "<4>" + ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png) + +二元搜尋樹的查詢操作與二分搜尋演算法的工作原理一致,都是每輪排除一半情況。迴圈次數最多為二元樹的高度,當二元樹平衡時,使用 $O(\log n)$ 時間。示例程式碼如下: + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} +``` + +### 插入節點 + +給定一個待插入元素 `num` ,為了保持二元搜尋樹“左子樹 < 根節點 < 右子樹”的性質,插入操作流程如下圖所示。 + +1. **查詢插入位置**:與查詢操作相似,從根節點出發,根據當前節點值和 `num` 的大小關係迴圈向下搜尋,直到越過葉節點(走訪至 `None` )時跳出迴圈。 +2. **在該位置插入節點**:初始化節點 `num` ,將該節點置於 `None` 的位置。 + +![在二元搜尋樹中插入節點](binary_search_tree.assets/bst_insert.png) + +在程式碼實現中,需要注意以下兩點。 + +- 二元搜尋樹不允許存在重複節點,否則將違反其定義。因此,若待插入節點在樹中已存在,則不執行插入,直接返回。 +- 為了實現插入節點,我們需要藉助節點 `pre` 儲存上一輪迴圈的節點。這樣在走訪至 `None` 時,我們可以獲取到其父節點,從而完成節點插入操作。 + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} +``` + +與查詢節點相同,插入節點使用 $O(\log n)$ 時間。 + +### 刪除節點 + +先在二元樹中查詢到目標節點,再將其刪除。與插入節點類似,我們需要保證在刪除操作完成後,二元搜尋樹的“左子樹 < 根節點 < 右子樹”的性質仍然滿足。因此,我們根據目標節點的子節點數量,分 0、1 和 2 三種情況,執行對應的刪除節點操作。 + +如下圖所示,當待刪除節點的度為 $0$ 時,表示該節點是葉節點,可以直接刪除。 + +![在二元搜尋樹中刪除節點(度為 0 )](binary_search_tree.assets/bst_remove_case1.png) + +如下圖所示,當待刪除節點的度為 $1$ 時,將待刪除節點替換為其子節點即可。 + +![在二元搜尋樹中刪除節點(度為 1 )](binary_search_tree.assets/bst_remove_case2.png) + +當待刪除節點的度為 $2$ 時,我們無法直接刪除它,而需要使用一個節點替換該節點。由於要保持二元搜尋樹“左子樹 $<$ 根節點 $<$ 右子樹”的性質,**因此這個節點可以是右子樹的最小節點或左子樹的最大節點**。 + +假設我們選擇右子樹的最小節點(中序走訪的下一個節點),則刪除操作流程如下圖所示。 + +1. 找到待刪除節點在“中序走訪序列”中的下一個節點,記為 `tmp` 。 +2. 用 `tmp` 的值覆蓋待刪除節點的值,並在樹中遞迴刪除節點 `tmp` 。 + +=== "<1>" + ![在二元搜尋樹中刪除節點(度為 2 )](binary_search_tree.assets/bst_remove_case3_step1.png) + +=== "<2>" + ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png) + +=== "<3>" + ![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png) + +=== "<4>" + ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png) + +刪除節點操作同樣使用 $O(\log n)$ 時間,其中查詢待刪除節點需要 $O(\log n)$ 時間,獲取中序走訪後繼節點需要 $O(\log n)$ 時間。示例程式碼如下: + +```src +[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} +``` + +### 中序走訪有序 + +如下圖所示,二元樹的中序走訪遵循“左 $\rightarrow$ 根 $\rightarrow$ 右”的走訪順序,而二元搜尋樹滿足“左子節點 $<$ 根節點 $<$ 右子節點”的大小關係。 + +這意味著在二元搜尋樹中進行中序走訪時,總是會優先走訪下一個最小節點,從而得出一個重要性質:**二元搜尋樹的中序走訪序列是升序的**。 + +利用中序走訪升序的性質,我們在二元搜尋樹中獲取有序資料僅需 $O(n)$ 時間,無須進行額外的排序操作,非常高效。 + +![二元搜尋樹的中序走訪序列](binary_search_tree.assets/bst_inorder_traversal.png) + +## 二元搜尋樹的效率 + +給定一組資料,我們考慮使用陣列或二元搜尋樹儲存。觀察下表,二元搜尋樹的各項操作的時間複雜度都是對數階,具有穩定且高效的效能。只有在高頻新增、低頻查詢刪除資料的場景下,陣列比二元搜尋樹的效率更高。 + +

  陣列與搜尋樹的效率對比

+ +| | 無序陣列 | 二元搜尋樹 | +| -------- | -------- | ----------- | +| 查詢元素 | $O(n)$ | $O(\log n)$ | +| 插入元素 | $O(1)$ | $O(\log n)$ | +| 刪除元素 | $O(n)$ | $O(\log n)$ | + +在理想情況下,二元搜尋樹是“平衡”的,這樣就可以在 $\log n$ 輪迴圈內查詢任意節點。 + +然而,如果我們在二元搜尋樹中不斷地插入和刪除節點,可能導致二元樹退化為下圖所示的鏈結串列,這時各種操作的時間複雜度也會退化為 $O(n)$ 。 + +![二元搜尋樹退化](binary_search_tree.assets/bst_degradation.png) + +## 二元搜尋樹常見應用 + +- 用作系統中的多級索引,實現高效的查詢、插入、刪除操作。 +- 作為某些搜尋演算法的底層資料結構。 +- 用於儲存資料流,以保持其有序狀態。 diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png b/zh-hant/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png new file mode 100644 index 000000000..6e1b1e9ef Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png new file mode 100644 index 000000000..66132c3aa Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png new file mode 100644 index 000000000..f146f9e17 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png new file mode 100644 index 000000000..c70d6cc03 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png new file mode 100644 index 000000000..41e3c3554 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png b/zh-hant/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png new file mode 100644 index 000000000..66bbe8bd1 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/full_binary_tree.png b/zh-hant/docs/chapter_tree/binary_tree.assets/full_binary_tree.png new file mode 100644 index 000000000..7c30ff4f4 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/full_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png b/zh-hant/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png new file mode 100644 index 000000000..ac629b035 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree.md b/zh-hant/docs/chapter_tree/binary_tree.md new file mode 100644 index 000000000..2c361cf68 --- /dev/null +++ b/zh-hant/docs/chapter_tree/binary_tree.md @@ -0,0 +1,662 @@ +# 二元樹 + +二元樹(binary tree)是一種非線性資料結構,代表“祖先”與“後代”之間的派生關係,體現了“一分為二”的分治邏輯。與鏈結串列類似,二元樹的基本單元是節點,每個節點包含值、左子節點引用和右子節點引用。 + +=== "Python" + + ```python title="" + class TreeNode: + """二元樹節點類別""" + def __init__(self, val: int): + self.val: int = val # 節點值 + self.left: TreeNode | None = None # 左子節點引用 + self.right: TreeNode | None = None # 右子節點引用 + ``` + +=== "C++" + + ```cpp title="" + /* 二元樹節點結構體 */ + struct TreeNode { + int val; // 節點值 + TreeNode *left; // 左子節點指標 + TreeNode *right; // 右子節點指標 + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + }; + ``` + +=== "Java" + + ```java title="" + /* 二元樹節點類別 */ + class TreeNode { + int val; // 節點值 + TreeNode left; // 左子節點引用 + TreeNode right; // 右子節點引用 + TreeNode(int x) { val = x; } + } + ``` + +=== "C#" + + ```csharp title="" + /* 二元樹節點類別 */ + class TreeNode(int? x) { + public int? val = x; // 節點值 + public TreeNode? left; // 左子節點引用 + public TreeNode? right; // 右子節點引用 + } + ``` + +=== "Go" + + ```go title="" + /* 二元樹節點結構體 */ + type TreeNode struct { + Val int + Left *TreeNode + Right *TreeNode + } + /* 建構子 */ + func NewTreeNode(v int) *TreeNode { + return &TreeNode{ + Left: nil, // 左子節點指標 + Right: nil, // 右子節點指標 + Val: v, // 節點值 + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 二元樹節點類別 */ + class TreeNode { + var val: Int // 節點值 + var left: TreeNode? // 左子節點引用 + var right: TreeNode? // 右子節點引用 + + init(x: Int) { + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 二元樹節點類別 */ + class TreeNode { + val; // 節點值 + left; // 左子節點指標 + right; // 右子節點指標 + constructor(val, left, right) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } + } + ``` + +=== "TS" + + ```typescript title="" + /* 二元樹節點類別 */ + class TreeNode { + 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; // 左子節點引用 + this.right = right === undefined ? null : right; // 右子節點引用 + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 二元樹節點類別 */ + class TreeNode { + int val; // 節點值 + TreeNode? left; // 左子節點引用 + TreeNode? right; // 右子節點引用 + TreeNode(this.val, [this.left, this.right]); + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + + /* 二元樹節點結構體 */ + struct TreeNode { + val: i32, // 節點值 + left: Option>>, // 左子節點引用 + right: Option>>, // 右子節點引用 + } + + impl TreeNode { + /* 建構子 */ + fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + left: None, + right: None + })) + } + } + ``` + +=== "C" + + ```c title="" + /* 二元樹節點結構體 */ + typedef struct TreeNode { + int val; // 節點值 + int height; // 節點高度 + struct TreeNode *left; // 左子節點指標 + struct TreeNode *right; // 右子節點指標 + } TreeNode; + + /* 建構子 */ + TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; + } + ``` + +=== "Kotlin" + + ```kotlin title="" + /* 二元樹節點類別 */ + class TreeNode(val _val: Int) { // 節點值 + val left: TreeNode? = null // 左子節點引用 + val right: TreeNode? = null // 右子節點引用 + } + ``` + +=== "Ruby" + + ```ruby title="" + + ``` + +=== "Zig" + + ```zig title="" + + ``` + +每個節點都有兩個引用(指標),分別指向左子節點(left-child node)右子節點(right-child node),該節點被稱為這兩個子節點的父節點(parent node)。當給定一個二元樹的節點時,我們將該節點的左子節點及其以下節點形成的樹稱為該節點的左子樹(left subtree),同理可得右子樹(right subtree)。 + +**在二元樹中,除葉節點外,其他所有節點都包含子節點和非空子樹**。如下圖所示,如果將“節點 2”視為父節點,則其左子節點和右子節點分別是“節點 4”和“節點 5”,左子樹是“節點 4 及其以下節點形成的樹”,右子樹是“節點 5 及其以下節點形成的樹”。 + +![父節點、子節點、子樹](binary_tree.assets/binary_tree_definition.png) + +## 二元樹常見術語 + +二元樹的常用術語如下圖所示。 + +- 根節點(root node):位於二元樹頂層的節點,沒有父節點。 +- 葉節點(leaf node):沒有子節點的節點,其兩個指標均指向 `None` 。 +- 邊(edge):連線兩個節點的線段,即節點引用(指標)。 +- 節點所在的層(level):從頂至底遞增,根節點所在層為 1 。 +- 節點的度(degree):節點的子節點的數量。在二元樹中,度的取值範圍是 0、1、2 。 +- 二元樹的高度(height):從根節點到最遠葉節點所經過的邊的數量。 +- 節點的深度(depth):從根節點到該節點所經過的邊的數量。 +- 節點的高度(height):從距離該節點最遠的葉節點到該節點所經過的邊的數量。 + +![二元樹的常用術語](binary_tree.assets/binary_tree_terminology.png) + +!!! tip + + 請注意,我們通常將“高度”和“深度”定義為“經過的邊的數量”,但有些題目或教材可能會將其定義為“經過的節點的數量”。在這種情況下,高度和深度都需要加 1 。 + +## 二元樹基本操作 + +### 初始化二元樹 + +與鏈結串列類似,首先初始化節點,然後構建引用(指標)。 + +=== "Python" + + ```python title="binary_tree.py" + # 初始化二元樹 + # 初始化節點 + 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 + ``` + +=== "C++" + + ```cpp title="binary_tree.cpp" + /* 初始化二元樹 */ + // 初始化節點 + TreeNode* n1 = new TreeNode(1); + TreeNode* n2 = new TreeNode(2); + TreeNode* n3 = new TreeNode(3); + TreeNode* n4 = new TreeNode(4); + TreeNode* n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Java" + + ```java title="binary_tree.java" + // 初始化節點 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* 初始化二元樹 */ + // 初始化節點 + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Go" + + ```go title="binary_tree.go" + /* 初始化二元樹 */ + // 初始化節點 + n1 := NewTreeNode(1) + n2 := NewTreeNode(2) + n3 := NewTreeNode(3) + n4 := NewTreeNode(4) + n5 := NewTreeNode(5) + // 構建節點之間的引用(指標) + n1.Left = n2 + n1.Right = n3 + n2.Left = n4 + n2.Right = n5 + ``` + +=== "Swift" + + ```swift title="binary_tree.swift" + // 初始化節點 + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* 初始化二元樹 */ + // 初始化節點 + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "TS" + + ```typescript title="binary_tree.ts" + /* 初始化二元樹 */ + // 初始化節點 + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Dart" + + ```dart title="binary_tree.dart" + /* 初始化二元樹 */ + // 初始化節點 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // 構建節點之間的引用(指標) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + ``` + +=== "Rust" + + ```rust title="binary_tree.rs" + // 初始化節點 + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // 構建節點之間的引用(指標) + n1.borrow_mut().left = Some(n2.clone()); + n1.borrow_mut().right = Some(n3); + n2.borrow_mut().left = Some(n4); + n2.borrow_mut().right = Some(n5); + ``` + +=== "C" + + ```c title="binary_tree.c" + /* 初始化二元樹 */ + // 初始化節點 + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // 構建節點之間的引用(指標) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree.kt" + // 初始化節點 + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // 構建節點之間的引用(指標) + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + ``` + +=== "Ruby" + + ```ruby title="binary_tree.rb" + + ``` + +=== "Zig" + + ```zig title="binary_tree.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### 插入與刪除節點 + +與鏈結串列類似,在二元樹中插入與刪除節點可以透過修改指標來實現。下圖給出了一個示例。 + +![在二元樹中插入與刪除節點](binary_tree.assets/binary_tree_add_remove.png) + +=== "Python" + + ```python title="binary_tree.py" + # 插入與刪除節點 + p = TreeNode(0) + # 在 n1 -> n2 中間插入節點 P + n1.left = p + p.left = n2 + # 刪除節點 P + n1.left = n2 + ``` + +=== "C++" + + ```cpp title="binary_tree.cpp" + /* 插入與刪除節點 */ + TreeNode* P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1->left = P; + P->left = n2; + // 刪除節點 P + n1->left = n2; + ``` + +=== "Java" + + ```java title="binary_tree.java" + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + // 刪除節點 P + n1.left = n2; + ``` + +=== "C#" + + ```csharp title="binary_tree.cs" + /* 插入與刪除節點 */ + TreeNode P = new(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + // 刪除節點 P + n1.left = n2; + ``` + +=== "Go" + + ```go title="binary_tree.go" + /* 插入與刪除節點 */ + // 在 n1 -> n2 中間插入節點 P + p := NewTreeNode(0) + n1.Left = p + p.Left = n2 + // 刪除節點 P + n1.Left = n2 + ``` + +=== "Swift" + + ```swift title="binary_tree.swift" + let P = TreeNode(x: 0) + // 在 n1 -> n2 中間插入節點 P + n1.left = P + P.left = n2 + // 刪除節點 P + n1.left = n2 + ``` + +=== "JS" + + ```javascript title="binary_tree.js" + /* 插入與刪除節點 */ + let P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + // 刪除節點 P + n1.left = n2; + ``` + +=== "TS" + + ```typescript title="binary_tree.ts" + /* 插入與刪除節點 */ + const P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + // 刪除節點 P + n1.left = n2; + ``` + +=== "Dart" + + ```dart title="binary_tree.dart" + /* 插入與刪除節點 */ + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1.left = P; + P.left = n2; + // 刪除節點 P + n1.left = n2; + ``` + +=== "Rust" + + ```rust title="binary_tree.rs" + let p = TreeNode::new(0); + // 在 n1 -> n2 中間插入節點 P + n1.borrow_mut().left = Some(p.clone()); + p.borrow_mut().left = Some(n2.clone()); + // 刪除節點 p + n1.borrow_mut().left = Some(n2); + ``` + +=== "C" + + ```c title="binary_tree.c" + /* 插入與刪除節點 */ + TreeNode *P = newTreeNode(0); + // 在 n1 -> n2 中間插入節點 P + n1->left = P; + P->left = n2; + // 刪除節點 P + n1->left = n2; + ``` + +=== "Kotlin" + + ```kotlin title="binary_tree.kt" + val P = TreeNode(0) + // 在 n1 -> n2 中間插入節點 P + n1.left = P + P.left = n2 + // 刪除節點 P + n1.left = n2 + ``` + +=== "Ruby" + + ```ruby title="binary_tree.rb" + + ``` + +=== "Zig" + + ```zig title="binary_tree.zig" + + ``` + +??? pythontutor "視覺化執行" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%B8%8E%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +!!! note + + 需要注意的是,插入節點可能會改變二元樹的原有邏輯結構,而刪除節點通常意味著刪除該節點及其所有子樹。因此,在二元樹中,插入與刪除通常是由一套操作配合完成的,以實現有實際意義的操作。 + +## 常見二元樹型別 + +### 完美二元樹 + +如下圖所示,完美二元樹(perfect binary tree)所有層的節點都被完全填滿。在完美二元樹中,葉節點的度為 $0$ ,其餘所有節點的度都為 $2$ ;若樹的高度為 $h$ ,則節點總數為 $2^{h+1} - 1$ ,呈現標準的指數級關係,反映了自然界中常見的細胞分裂現象。 + +!!! tip + + 請注意,在中文社群中,完美二元樹常被稱為滿二元樹。 + +![完美二元樹](binary_tree.assets/perfect_binary_tree.png) + +### 完全二元樹 + +如下圖所示,完全二元樹(complete binary tree)只有最底層的節點未被填滿,且最底層節點儘量靠左填充。 + +![完全二元樹](binary_tree.assets/complete_binary_tree.png) + +### 完滿二元樹 + +如下圖所示,完滿二元樹(full binary tree)除了葉節點之外,其餘所有節點都有兩個子節點。 + +![完滿二元樹](binary_tree.assets/full_binary_tree.png) + +### 平衡二元樹 + +如下圖所示,平衡二元樹(balanced binary tree)中任意節點的左子樹和右子樹的高度之差的絕對值不超過 1 。 + +![平衡二元樹](binary_tree.assets/balanced_binary_tree.png) + +## 二元樹的退化 + +下圖展示了二元樹的理想結構與退化結構。當二元樹的每層節點都被填滿時,達到“完美二元樹”;而當所有節點都偏向一側時,二元樹退化為“鏈結串列”。 + +- 完美二元樹是理想情況,可以充分發揮二元樹“分治”的優勢。 +- 鏈結串列則是另一個極端,各項操作都變為線性操作,時間複雜度退化至 $O(n)$ 。 + +![二元樹的最佳結構與最差結構](binary_tree.assets/binary_tree_best_worst_cases.png) + +如下表所示,在最佳結構和最差結構下,二元樹的葉節點數量、節點總數、高度等達到極大值或極小值。 + +

  二元樹的最佳結構與最差結構

+ +| | 完美二元樹 | 鏈結串列 | +| --------------------------- | ------------------ | ------- | +| 第 $i$ 層的節點數量 | $2^{i-1}$ | $1$ | +| 高度為 $h$ 的樹的葉節點數量 | $2^h$ | $1$ | +| 高度為 $h$ 的樹的節點總數 | $2^{h+1} - 1$ | $h + 1$ | +| 節點總數為 $n$ 的樹的高度 | $\log_2 (n+1) - 1$ | $n - 1$ | diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png new file mode 100644 index 000000000..5909c8987 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png new file mode 100644 index 000000000..41c70bb40 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png new file mode 100644 index 000000000..5edef67ae Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png new file mode 100644 index 000000000..a9d5963de Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png new file mode 100644 index 000000000..f9d93414b Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png new file mode 100644 index 000000000..3f13799bf Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png new file mode 100644 index 000000000..73a0837d2 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png new file mode 100644 index 000000000..35cea3e7e Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png new file mode 100644 index 000000000..6ec8b4330 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png new file mode 100644 index 000000000..8aad23d85 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png new file mode 100644 index 000000000..16e587f77 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png new file mode 100644 index 000000000..e7f389931 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png new file mode 100644 index 000000000..056f21f05 Binary files /dev/null and b/zh-hant/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png differ diff --git a/zh-hant/docs/chapter_tree/binary_tree_traversal.md b/zh-hant/docs/chapter_tree/binary_tree_traversal.md new file mode 100755 index 000000000..f2a8dbfa0 --- /dev/null +++ b/zh-hant/docs/chapter_tree/binary_tree_traversal.md @@ -0,0 +1,89 @@ +# 二元樹走訪 + +從物理結構的角度來看,樹是一種基於鏈結串列的資料結構,因此其走訪方式是透過指標逐個訪問節點。然而,樹是一種非線性資料結構,這使得走訪樹比走訪鏈結串列更加複雜,需要藉助搜尋演算法來實現。 + +二元樹常見的走訪方式包括層序走訪、前序走訪、中序走訪和後序走訪等。 + +## 層序走訪 + +如下圖所示,層序走訪(level-order traversal)從頂部到底部逐層走訪二元樹,並在每一層按照從左到右的順序訪問節點。 + +層序走訪本質上屬於廣度優先走訪(breadth-first traversal),也稱廣度優先搜尋(breadth-first search, BFS),它體現了一種“一圈一圈向外擴展”的逐層走訪方式。 + +![二元樹的層序走訪](binary_tree_traversal.assets/binary_tree_bfs.png) + +### 程式碼實現 + +廣度優先走訪通常藉助“佇列”來實現。佇列遵循“先進先出”的規則,而廣度優先走訪則遵循“逐層推進”的規則,兩者背後的思想是一致的。實現程式碼如下: + +```src +[file]{binary_tree_bfs}-[class]{}-[func]{level_order} +``` + +### 複雜度分析 + +- **時間複雜度為 $O(n)$** :所有節點被訪問一次,使用 $O(n)$ 時間,其中 $n$ 為節點數量。 +- **空間複雜度為 $O(n)$** :在最差情況下,即滿二元樹時,走訪到最底層之前,佇列中最多同時存在 $(n + 1) / 2$ 個節點,佔用 $O(n)$ 空間。 + +## 前序、中序、後序走訪 + +相應地,前序、中序和後序走訪都屬於深度優先走訪(depth-first traversal),也稱深度優先搜尋(depth-first search, DFS),它體現了一種“先走到盡頭,再回溯繼續”的走訪方式。 + +下圖展示了對二元樹進行深度優先走訪的工作原理。**深度優先走訪就像是繞著整棵二元樹的外圍“走”一圈**,在每個節點都會遇到三個位置,分別對應前序走訪、中序走訪和後序走訪。 + +![二元搜尋樹的前序、中序、後序走訪](binary_tree_traversal.assets/binary_tree_dfs.png) + +### 程式碼實現 + +深度優先搜尋通常基於遞迴實現: + +```src +[file]{binary_tree_dfs}-[class]{}-[func]{post_order} +``` + +!!! tip + + 深度優先搜尋也可以基於迭代實現,有興趣的讀者可以自行研究。 + +下圖展示了前序走訪二元樹的遞迴過程,其可分為“遞”和“迴”兩個逆向的部分。 + +1. “遞”表示開啟新方法,程式在此過程中訪問下一個節點。 +2. “迴”表示函式返回,代表當前節點已經訪問完畢。 + +=== "<1>" + ![前序走訪的遞迴過程](binary_tree_traversal.assets/preorder_step1.png) + +=== "<2>" + ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) + +=== "<3>" + ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) + +=== "<4>" + ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) + +=== "<5>" + ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) + +=== "<6>" + ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) + +=== "<7>" + ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) + +=== "<8>" + ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) + +=== "<9>" + ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) + +=== "<10>" + ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) + +=== "<11>" + ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) + +### 複雜度分析 + +- **時間複雜度為 $O(n)$** :所有節點被訪問一次,使用 $O(n)$ 時間。 +- **空間複雜度為 $O(n)$** :在最差情況下,即樹退化為鏈結串列時,遞迴深度達到 $n$ ,系統佔用 $O(n)$ 堆疊幀空間。 diff --git a/zh-hant/docs/chapter_tree/index.md b/zh-hant/docs/chapter_tree/index.md new file mode 100644 index 000000000..a4c096227 --- /dev/null +++ b/zh-hant/docs/chapter_tree/index.md @@ -0,0 +1,9 @@ +# 樹 + +![樹](../assets/covers/chapter_tree.jpg) + +!!! abstract + + 參天大樹充滿生命力,根深葉茂,分枝扶疏。 + + 它為我們展現了資料分治的生動形態。 diff --git a/zh-hant/docs/chapter_tree/summary.md b/zh-hant/docs/chapter_tree/summary.md new file mode 100644 index 000000000..d2304bd13 --- /dev/null +++ b/zh-hant/docs/chapter_tree/summary.md @@ -0,0 +1,54 @@ +# 小結 + +### 重點回顧 + +- 二元樹是一種非線性資料結構,體現“一分為二”的分治邏輯。每個二元樹節點包含一個值以及兩個指標,分別指向其左子節點和右子節點。 +- 對於二元樹中的某個節點,其左(右)子節點及其以下形成的樹被稱為該節點的左(右)子樹。 +- 二元樹的相關術語包括根節點、葉節點、層、度、邊、高度和深度等。 +- 二元樹的初始化、節點插入和節點刪除操作與鏈結串列操作方法類似。 +- 常見的二元樹型別有完美二元樹、完全二元樹、完滿二元樹和平衡二元樹。完美二元樹是最理想的狀態,而鏈結串列是退化後的最差狀態。 +- 二元樹可以用陣列表示,方法是將節點值和空位按層序走訪順序排列,並根據父節點與子節點之間的索引對映關係來實現指標。 +- 二元樹的層序走訪是一種廣度優先搜尋方法,它體現了“一圈一圈向外擴展”的逐層走訪方式,通常透過佇列來實現。 +- 前序、中序、後序走訪皆屬於深度優先搜尋,它們體現了“先走到盡頭,再回溯繼續”的走訪方式,通常使用遞迴來實現。 +- 二元搜尋樹是一種高效的元素查詢資料結構,其查詢、插入和刪除操作的時間複雜度均為 $O(\log n)$ 。當二元搜尋樹退化為鏈結串列時,各項時間複雜度會劣化至 $O(n)$ 。 +- AVL 樹,也稱平衡二元搜尋樹,它透過旋轉操作確保在不斷插入和刪除節點後樹仍然保持平衡。 +- AVL 樹的旋轉操作包括右旋、左旋、先右旋再左旋、先左旋再右旋。在插入或刪除節點後,AVL 樹會從底向頂執行旋轉操作,使樹重新恢復平衡。 + +### Q & A + +**Q**:對於只有一個節點的二元樹,樹的高度和根節點的深度都是 $0$ 嗎? + +是的,因為高度和深度通常定義為“經過的邊的數量”。 + +**Q**:二元樹中的插入與刪除一般由一套操作配合完成,這裡的“一套操作”指什麼呢?可以理解為資源的子節點的資源釋放嗎? + +拿二元搜尋樹來舉例,刪除節點操作要分三種情況處理,其中每種情況都需要進行多個步驟的節點操作。 + +**Q**:為什麼 DFS 走訪二元樹有前、中、後三種順序,分別有什麼用呢? + +與順序和逆序走訪陣列類似,前序、中序、後序走訪是三種二元樹走訪方法,我們可以使用它們得到一個特定順序的走訪結果。例如在二元搜尋樹中,由於節點大小滿足 `左子節點值 < 根節點值 < 右子節點值` ,因此我們只要按照“左 $\rightarrow$ 根 $\rightarrow$ 右”的優先順序走訪樹,就可以獲得有序的節點序列。 + +**Q**:右旋操作是處理失衡節點 `node`、`child`、`grand_child` 之間的關係,那 `node` 的父節點和 `node` 原來的連線不需要維護嗎?右旋操作後豈不是斷掉了? + +我們需要從遞迴的視角來看這個問題。右旋操作 `right_rotate(root)` 傳入的是子樹的根節點,最終 `return child` 返回旋轉之後的子樹的根節點。子樹的根節點和其父節點的連線是在該函式返回後完成的,不屬於右旋操作的維護範圍。 + +**Q**:在 C++ 中,函式被劃分到 `private` 和 `public` 中,這方面有什麼考量嗎?為什麼要將 `height()` 函式和 `updateHeight()` 函式分別放在 `public` 和 `private` 中呢? + +主要看方法的使用範圍,如果方法只在類別內部使用,那麼就設計為 `private` 。例如,使用者單獨呼叫 `updateHeight()` 是沒有意義的,它只是插入、刪除操作中的一步。而 `height()` 是訪問節點高度,類似於 `vector.size()` ,因此設定成 `public` 以便使用。 + +**Q**:如何從一組輸入資料構建一棵二元搜尋樹?根節點的選擇是不是很重要? + +是的,構建樹的方法已在二元搜尋樹程式碼中的 `build_tree()` 方法中給出。至於根節點的選擇,我們通常會將輸入資料排序,然後將中點元素作為根節點,再遞迴地構建左右子樹。這樣做可以最大程度保證樹的平衡性。 + +**Q**:在 Java 中,字串對比是否一定要用 `equals()` 方法? + +在 Java 中,對於基本資料型別,`==` 用於對比兩個變數的值是否相等。對於引用型別,兩種符號的工作原理是不同的。 + +- `==` :用來比較兩個變數是否指向同一個物件,即它們在記憶體中的位置是否相同。 +- `equals()`:用來對比兩個物件的值是否相等。 + +因此,如果要對比值,我們應該使用 `equals()` 。然而,透過 `String a = "hi"; String b = "hi";` 初始化的字串都儲存在字串常數池中,它們指向同一個物件,因此也可以用 `a == b` 來比較兩個字串的內容。 + +**Q**:廣度優先走訪到最底層之前,佇列中的節點數量是 $2^h$ 嗎? + +是的,例如高度 $h = 2$ 的滿二元樹,其節點總數 $n = 7$ ,則底層節點數量 $4 = 2^h = (n + 1) / 2$ 。 diff --git a/zh-hant/docs/index.assets/animation.gif b/zh-hant/docs/index.assets/animation.gif new file mode 100644 index 000000000..641e677ef Binary files /dev/null and b/zh-hant/docs/index.assets/animation.gif differ diff --git a/zh-hant/docs/index.assets/animation_dark.gif b/zh-hant/docs/index.assets/animation_dark.gif new file mode 100644 index 000000000..a05aedd70 Binary files /dev/null and b/zh-hant/docs/index.assets/animation_dark.gif differ diff --git a/zh-hant/docs/index.assets/btn_download_pdf.svg b/zh-hant/docs/index.assets/btn_download_pdf.svg new file mode 100644 index 000000000..2d52ece69 --- /dev/null +++ b/zh-hant/docs/index.assets/btn_download_pdf.svg @@ -0,0 +1 @@ +下載PDF \ No newline at end of file diff --git a/zh-hant/docs/index.assets/btn_download_pdf_dark.svg b/zh-hant/docs/index.assets/btn_download_pdf_dark.svg new file mode 100644 index 000000000..71ae42ec0 --- /dev/null +++ b/zh-hant/docs/index.assets/btn_download_pdf_dark.svg @@ -0,0 +1 @@ +下載PDF \ No newline at end of file diff --git a/zh-hant/docs/index.assets/btn_read_online.svg b/zh-hant/docs/index.assets/btn_read_online.svg new file mode 100644 index 000000000..7ff5c3f26 --- /dev/null +++ b/zh-hant/docs/index.assets/btn_read_online.svg @@ -0,0 +1 @@ +線上閱讀 \ No newline at end of file diff --git a/zh-hant/docs/index.assets/btn_read_online_dark.svg b/zh-hant/docs/index.assets/btn_read_online_dark.svg new file mode 100644 index 000000000..4bc3134b5 --- /dev/null +++ b/zh-hant/docs/index.assets/btn_read_online_dark.svg @@ -0,0 +1 @@ +線上閱讀 \ No newline at end of file diff --git a/zh-hant/docs/index.assets/comment.gif b/zh-hant/docs/index.assets/comment.gif new file mode 100644 index 000000000..a6c5b8444 Binary files /dev/null and b/zh-hant/docs/index.assets/comment.gif differ diff --git a/zh-hant/docs/index.assets/hello_algo_header.png b/zh-hant/docs/index.assets/hello_algo_header.png new file mode 100644 index 000000000..51e21c54e Binary files /dev/null and b/zh-hant/docs/index.assets/hello_algo_header.png differ diff --git a/zh-hant/docs/index.assets/hello_algo_mindmap_tp.png b/zh-hant/docs/index.assets/hello_algo_mindmap_tp.png new file mode 100644 index 000000000..9ba521717 Binary files /dev/null and b/zh-hant/docs/index.assets/hello_algo_mindmap_tp.png differ diff --git a/zh-hant/docs/index.assets/running_code.gif b/zh-hant/docs/index.assets/running_code.gif new file mode 100644 index 000000000..5c77187f7 Binary files /dev/null and b/zh-hant/docs/index.assets/running_code.gif differ diff --git a/zh-hant/docs/index.assets/running_code_dark.gif b/zh-hant/docs/index.assets/running_code_dark.gif new file mode 100644 index 000000000..5d56a0184 Binary files /dev/null and b/zh-hant/docs/index.assets/running_code_dark.gif differ diff --git a/zh-hant/docs/index.html b/zh-hant/docs/index.html new file mode 100644 index 000000000..01c3b858a --- /dev/null +++ b/zh-hant/docs/index.html @@ -0,0 +1,357 @@ + +
+ + + + + +
+ +
+

+ 動畫圖解、一鍵執行的資料結構與演算法教程 +

+ + + + + 開始閱讀 + + + + + + + 程式碼倉庫 + +
+ +
+ + + +
+
+
+ + +
+
+ Preview +
+ + + + + + + + + + + + +
+

500 幅動畫圖解、12 種程式語言程式碼、3000 條社群問答,助你快速入門資料結構與演算法

+
+
+ + +
+ +
+ + +
+
+

推薦語

+
+
+

“一本通俗易懂的資料結構與演算法入門書,引導讀者手腦並用地學習,強烈推薦演算法初學者閱讀。”

+

—— 鄧俊輝,清華大學計算機系教授

+
+
+

“如果我當年學資料結構與演算法的時候有《Hello 演算法》,學起來應該會簡單 10 倍!”

+

—— 李沐,亞馬遜資深首席科學家

+
+
+
+
+ + +
+
+
+
+
+
+ + + +

動畫圖解

+
+

內容清晰易懂,學習曲線平滑

+

"A picture is worth a thousand words."
“一圖勝千言”

+
+
+ Animation example +
+ +
+ Running code example +
+
+
+ + + +

一鍵執行

+
+

十餘種程式語言,程式碼視覺化執行

+

"Talk is cheap. Show me the code."
“少吹牛,看程式碼”

+
+
+
+ +
+
+
+
+ + + +

互助學習

+
+

歡迎討論與提問,讀者間攜手共進

+

"Learning by teaching."
“教學相長”

+
+
+ Comments example +
+ +
+
+ + +
+
+ +
+

作者

+ +
+ + + + + +
+

貢獻者

+

本書在開源社群 140 多位貢獻者的共同努力下不斷完善,感謝他們付出的時間與精力!

+ + Contributors + +
+
+
\ No newline at end of file diff --git a/zh-hant/docs/index.md b/zh-hant/docs/index.md new file mode 100644 index 000000000..402b9b2af --- /dev/null +++ b/zh-hant/docs/index.md @@ -0,0 +1,9 @@ +--- +comments: false +glightbox: false +hide: + - footer + - toc + - edit + - navigation +--- diff --git a/zh-hant/mkdocs.yml b/zh-hant/mkdocs.yml new file mode 100644 index 000000000..ac96cda27 --- /dev/null +++ b/zh-hant/mkdocs.yml @@ -0,0 +1,178 @@ +# Config inheritance +INHERIT: ../mkdocs.yml + +# Project information +site_name: Hello 演算法 +site_url: https://www.hello-algo.com/zh-hant/ +site_description: "動畫圖解、一鍵執行的資料結構與演算法教程" +docs_dir: ../build/zh-hant/docs +site_dir: ../site/zh-hant +# Repository +edit_uri: tree/main/zh-hant/docs +version: 1.0.0 + +# Configuration +theme: + custom_dir: ../build/overrides + language: zh-Hant + palette: + - scheme: default + primary: white + accent: teal + toggle: + icon: material/theme-light-dark + name: 深色模式 + - scheme: slate + primary: black + accent: teal + toggle: + icon: material/theme-light-dark + name: 淺色模式 + +extra: + status: + new: 最近新增 + +# Page tree +nav: + - 序: + - chapter_hello_algo/index.md + - 第 0 章   前言: + # [icon: material/book-open-outline] + - chapter_preface/index.md + - 0.1   關於本書: chapter_preface/about_the_book.md + - 0.2   如何使用本書: chapter_preface/suggestions.md + - 0.3   小結: chapter_preface/summary.md + - 第 1 章   初識演算法: + # [icon: material/calculator-variant-outline] + - chapter_introduction/index.md + - 1.1   演算法無處不在: chapter_introduction/algorithms_are_everywhere.md + - 1.2   演算法是什麼: chapter_introduction/what_is_dsa.md + - 1.3   小結: chapter_introduction/summary.md + - 第 2 章   複雜度分析: + # [icon: material/timer-sand] + - chapter_computational_complexity/index.md + - 2.1   演算法效率評估: chapter_computational_complexity/performance_evaluation.md + - 2.2   迭代與遞迴: chapter_computational_complexity/iteration_and_recursion.md + - 2.3   時間複雜度: chapter_computational_complexity/time_complexity.md + - 2.4   空間複雜度: chapter_computational_complexity/space_complexity.md + - 2.5   小結: chapter_computational_complexity/summary.md + - 第 3 章   資料結構: + # [icon: material/shape-outline] + - chapter_data_structure/index.md + - 3.1   資料結構分類: chapter_data_structure/classification_of_data_structure.md + - 3.2   基本資料型別: chapter_data_structure/basic_data_types.md + - 3.3   數字編碼 *: chapter_data_structure/number_encoding.md + - 3.4   字元編碼 *: chapter_data_structure/character_encoding.md + - 3.5   小結: chapter_data_structure/summary.md + - 第 4 章   陣列與鏈結串列: + # [icon: material/view-list-outline] + - chapter_array_and_linkedlist/index.md + - 4.1   陣列: chapter_array_and_linkedlist/array.md + - 4.2   鏈結串列: chapter_array_and_linkedlist/linked_list.md + - 4.3   串列: chapter_array_and_linkedlist/list.md + # [status: new] + - 4.4   記憶體與快取 *: chapter_array_and_linkedlist/ram_and_cache.md + - 4.5   小結: chapter_array_and_linkedlist/summary.md + - 第 5 章   堆疊與佇列: + # [icon: material/stack-overflow] + - chapter_stack_and_queue/index.md + - 5.1   堆疊: chapter_stack_and_queue/stack.md + - 5.2   佇列: chapter_stack_and_queue/queue.md + - 5.3   雙向佇列: chapter_stack_and_queue/deque.md + - 5.4   小結: chapter_stack_and_queue/summary.md + - 第 6 章   雜湊表: + # [icon: material/table-search] + - chapter_hashing/index.md + - 6.1   雜湊表: chapter_hashing/hash_map.md + - 6.2   雜湊衝突: chapter_hashing/hash_collision.md + - 6.3   雜湊演算法: chapter_hashing/hash_algorithm.md + - 6.4   小結: chapter_hashing/summary.md + - 第 7 章   樹: + # [icon: material/graph-outline] + - chapter_tree/index.md + - 7.1   二元樹: chapter_tree/binary_tree.md + - 7.2   二元樹走訪: chapter_tree/binary_tree_traversal.md + - 7.3   二元樹陣列表示: chapter_tree/array_representation_of_tree.md + - 7.4   二元搜尋樹: chapter_tree/binary_search_tree.md + - 7.5   AVL *: chapter_tree/avl_tree.md + - 7.6   小結: chapter_tree/summary.md + - 第 8 章   堆積: + # [icon: material/family-tree] + - chapter_heap/index.md + - 8.1   堆積: chapter_heap/heap.md + - 8.2   建堆積操作: chapter_heap/build_heap.md + - 8.3   Top-k 問題: chapter_heap/top_k.md + - 8.4   小結: chapter_heap/summary.md + - 第 9 章   圖: + # [icon: material/graphql] + - chapter_graph/index.md + - 9.1   圖: chapter_graph/graph.md + - 9.2   圖基礎操作: chapter_graph/graph_operations.md + - 9.3   圖的走訪: chapter_graph/graph_traversal.md + - 9.4   小結: chapter_graph/summary.md + - 第 10 章   搜尋: + # [icon: material/text-search] + - chapter_searching/index.md + - 10.1   二分搜尋: chapter_searching/binary_search.md + - 10.2   二分搜尋插入點: chapter_searching/binary_search_insertion.md + - 10.3   二分搜尋邊界: chapter_searching/binary_search_edge.md + - 10.4   雜湊最佳化策略: chapter_searching/replace_linear_by_hashing.md + - 10.5   重識搜尋演算法: chapter_searching/searching_algorithm_revisited.md + - 10.6   小結: chapter_searching/summary.md + - 第 11 章   排序: + # [icon: material/sort-ascending] + - chapter_sorting/index.md + - 11.1   排序演算法: chapter_sorting/sorting_algorithm.md + - 11.2   選擇排序: chapter_sorting/selection_sort.md + - 11.3   泡沫排序: chapter_sorting/bubble_sort.md + - 11.4   插入排序: chapter_sorting/insertion_sort.md + - 11.5   快速排序: chapter_sorting/quick_sort.md + - 11.6   合併排序: chapter_sorting/merge_sort.md + - 11.7   堆積排序: chapter_sorting/heap_sort.md + - 11.8   桶排序: chapter_sorting/bucket_sort.md + - 11.9   計數排序: chapter_sorting/counting_sort.md + - 11.10   基數排序: chapter_sorting/radix_sort.md + - 11.11   小結: chapter_sorting/summary.md + - 第 12 章   分治: + # [icon: material/set-split] + - chapter_divide_and_conquer/index.md + - 12.1   分治演算法: chapter_divide_and_conquer/divide_and_conquer.md + - 12.2   分治搜尋策略: chapter_divide_and_conquer/binary_search_recur.md + - 12.3   構建樹問題: chapter_divide_and_conquer/build_binary_tree_problem.md + - 12.4   河內塔問題: chapter_divide_and_conquer/hanota_problem.md + - 12.5   小結: chapter_divide_and_conquer/summary.md + - 第 13 章   回溯: + # [icon: material/map-marker-path] + - chapter_backtracking/index.md + - 13.1   回溯演算法: chapter_backtracking/backtracking_algorithm.md + - 13.2   全排列問題: chapter_backtracking/permutations_problem.md + - 13.3   子集和問題: chapter_backtracking/subset_sum_problem.md + - 13.4   N 皇后問題: chapter_backtracking/n_queens_problem.md + - 13.5   小結: chapter_backtracking/summary.md + - 第 14 章   動態規劃: + # [icon: material/table-pivot] + - chapter_dynamic_programming/index.md + - 14.1   初探動態規劃: chapter_dynamic_programming/intro_to_dynamic_programming.md + - 14.2   DP 問題特性: chapter_dynamic_programming/dp_problem_features.md + - 14.3   DP 解題思路: chapter_dynamic_programming/dp_solution_pipeline.md + - 14.4   0-1 背包問題: chapter_dynamic_programming/knapsack_problem.md + - 14.5   完全背包問題: chapter_dynamic_programming/unbounded_knapsack_problem.md + - 14.6   編輯距離問題: chapter_dynamic_programming/edit_distance_problem.md + - 14.7   小結: chapter_dynamic_programming/summary.md + - 第 15 章   貪婪: + # [icon: material/head-heart-outline] + - chapter_greedy/index.md + - 15.1   貪婪演算法: chapter_greedy/greedy_algorithm.md + - 15.2   分數背包問題: chapter_greedy/fractional_knapsack_problem.md + - 15.3   最大容量問題: chapter_greedy/max_capacity_problem.md + - 15.4   最大切分乘積問題: chapter_greedy/max_product_cutting_problem.md + - 15.5   小結: chapter_greedy/summary.md + - 第 16 章   附錄: + # [icon: material/help-circle-outline] + - chapter_appendix/index.md + - 16.1   程式設計環境安裝: chapter_appendix/installation.md + - 16.2   一起參與創作: chapter_appendix/contribution.md + - 16.3   術語表: chapter_appendix/terminology.md + - 參考文獻: + - chapter_reference/index.md