diff --git a/.gitignore b/.gitignore index 3ff30549..c960a1bb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # Editor .vscode/ .idea/ +hello-algo.iml # mkdocs files site/ @@ -13,6 +14,3 @@ docs/overrides/ # python files __pycache__ - -# iml -hello-algo.iml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..5a938ce1 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "useTabs": false +} diff --git a/codes/c/chapter_array_and_linkedlist/array.c b/codes/c/chapter_array_and_linkedlist/array.c new file mode 100644 index 00000000..33dc9990 --- /dev/null +++ b/codes/c/chapter_array_and_linkedlist/array.c @@ -0,0 +1,112 @@ +/** + * File: array.c + * Created Time: 2022-12-20 + * Author: MolDuM (moldum@163.com) + */ + +#include "../include/include.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 处元素 */ +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++; + } +} + +/* 在数组中查找指定元素 */ +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[5] = { 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); + + return 0; +} diff --git a/codes/c/chapter_computational_complexity/time_complexity.c b/codes/c/chapter_computational_complexity/time_complexity.c new file mode 100644 index 00000000..b8821ec6 --- /dev/null +++ b/codes/c/chapter_computational_complexity/time_complexity.c @@ -0,0 +1,174 @@ +/** + * File: time_complexity.c + * Created Time: 2023-01-03 + * Author: sjinzh (sjinzh@gmail.com) + */ + +#include "../include/include.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; + // 循环次数与数组长度成平方关系 + 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; // 计数器 + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (int i = n - 1; i > 0; i--) { + // 内循环:冒泡操作 + for (int j = 0; j < i; j++) { + // 交换 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; + // cell 每轮一分为二,形成数列 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(float n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 对数阶(递归实现) */ +int logRecur(float n) { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +/* 线性对数阶 */ +int linearLogRecur(float 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/codes/c/chapter_computational_complexity/worst_best_time_complexity.c b/codes/c/chapter_computational_complexity/worst_best_time_complexity.c new file mode 100644 index 00000000..2570ff3c --- /dev/null +++ b/codes/c/chapter_computational_complexity/worst_best_time_complexity.c @@ -0,0 +1,54 @@ +/** + * File: worst_best_time_complexity.c + * Created Time: 2023-01-03 + * Author: sjinzh (sjinzh@gmail.com) + */ + +#include "../include/include.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++) { + 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; + } + } + getchar(); + return 0; +} diff --git a/codes/c/chapter_sorting/bubble_sort.c b/codes/c/chapter_sorting/bubble_sort.c index 6b67bd72..5c74d9e4 100644 --- a/codes/c/chapter_sorting/bubble_sort.c +++ b/codes/c/chapter_sorting/bubble_sort.c @@ -7,8 +7,7 @@ #include "../include/include.h" /* 冒泡排序 */ -void bubble_sort(int nums[], int size) -{ +void bubble_sort(int nums[], int size) { // 外循环:待排序元素数量为 n-1, n-2, ..., 1 for (int i = 0; i < size - 1; i++) { @@ -26,8 +25,7 @@ void bubble_sort(int nums[], int size) } /* 冒泡排序(标志优化)*/ -void bubble_sort_with_flag(int nums[], int size) -{ +void bubble_sort_with_flag(int nums[], int size) { // 外循环:待排序元素数量为 n-1, n-2, ..., 1 for (int i = 0; i < size - 1; i++) { @@ -50,8 +48,7 @@ void bubble_sort_with_flag(int nums[], int size) /* Driver Code */ -int main() -{ +int main() { int nums[6] = {4, 1, 3, 1, 5, 2}; printf("冒泡排序后:\n"); bubble_sort(nums, 6); @@ -69,4 +66,4 @@ int main() printf("\n"); return 0; -} \ No newline at end of file +} diff --git a/codes/c/chapter_sorting/insertion_sort.c b/codes/c/chapter_sorting/insertion_sort.c new file mode 100644 index 00000000..80e8b127 --- /dev/null +++ b/codes/c/chapter_sorting/insertion_sort.c @@ -0,0 +1,39 @@ +/** + * File: insertion_sort.c + * Created Time: 2022-12-29 + * Author: Listening (https://github.com/L-Super) + */ + +#include "../include/include.h" + +/* 插入排序 */ +void insertionSort(int nums[], int size) { + // 外循环:base = nums[1], nums[2], ..., nums[n-1] + for (int i = 1; i < size; i++) + { + int base = nums[i], j = i - 1; + // 内循环:将 base 插入到左边的正确位置 + while (j >= 0 && nums[j] > base) + { + // 1. 将 nums[j] 向右移动一位 + nums[j + 1] = nums[j]; + j--; + } + // 2. 将 base 赋值到正确位置 + nums[j + 1] = base; + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + insertionSort(nums, 6); + printf("插入排序完成后 nums = \n"); + for (int i = 0; i < 6; i++) + { + printf("%d ", nums[i]); + } + printf("\n"); + + return 0; +} diff --git a/codes/c/include/PrintUtil.h b/codes/c/include/PrintUtil.h new file mode 100644 index 00000000..59a8eac1 --- /dev/null +++ b/codes/c/include/PrintUtil.h @@ -0,0 +1,28 @@ +/** + * File: PrintUtil.h + * Created Time: 2022-12-21 + * Author: MolDum (moldum@163.com) + */ + +#include +#include +#include + +// #include "ListNode.h" +// #include "TreeNode.h" + +/** + * @brief Print an Array + * + * @param arr + * @param n + */ + +static void printArray(int* arr, int n) +{ + printf("["); + for (int i = 0; i < n - 1; i++) { + printf("%d, ", arr[i]); + } + printf("%d]\n", arr[n-1]); +} diff --git a/codes/c/include/include.h b/codes/c/include/include.h index 44843254..2c4fd925 100644 --- a/codes/c/include/include.h +++ b/codes/c/include/include.h @@ -1,2 +1,13 @@ +/** + * File: include.h + * Created Time: 2022-12-20 + * Author: MolDuM (moldum@163.com) + */ + #include -#include \ No newline at end of file +#include +#include +#include +#include + +#include "PrintUtil.h" diff --git a/codes/cpp/chapter_array_and_linkedlist/array.cpp b/codes/cpp/chapter_array_and_linkedlist/array.cpp index 22c5e50d..6cf4919b 100644 --- a/codes/cpp/chapter_array_and_linkedlist/array.cpp +++ b/codes/cpp/chapter_array_and_linkedlist/array.cpp @@ -1,4 +1,4 @@ -/* +/** * File: array.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp b/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp index 46116d29..5e976a89 100644 --- a/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp +++ b/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp @@ -1,4 +1,4 @@ -/* +/** * File: linked_list.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -21,14 +21,16 @@ void remove(ListNode* n0) { 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++) { - head = head->next; if (head == nullptr) return nullptr; + head = head->next; } return head; } diff --git a/codes/cpp/chapter_array_and_linkedlist/list.cpp b/codes/cpp/chapter_array_and_linkedlist/list.cpp index 287cae1b..44bbf88c 100644 --- a/codes/cpp/chapter_array_and_linkedlist/list.cpp +++ b/codes/cpp/chapter_array_and_linkedlist/list.cpp @@ -1,4 +1,4 @@ -/* +/** * File: list.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_array_and_linkedlist/my_list.cpp b/codes/cpp/chapter_array_and_linkedlist/my_list.cpp index f64bd27c..0b550475 100644 --- a/codes/cpp/chapter_array_and_linkedlist/my_list.cpp +++ b/codes/cpp/chapter_array_and_linkedlist/my_list.cpp @@ -1,4 +1,4 @@ -/* +/** * File: my_list.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_computational_complexity/leetcode_two_sum.cpp b/codes/cpp/chapter_computational_complexity/leetcode_two_sum.cpp index 9df3b0a4..dd561d54 100644 --- a/codes/cpp/chapter_computational_complexity/leetcode_two_sum.cpp +++ b/codes/cpp/chapter_computational_complexity/leetcode_two_sum.cpp @@ -1,4 +1,4 @@ -/* +/** * File: leetcode_two_sum.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_computational_complexity/space_complexity.cpp b/codes/cpp/chapter_computational_complexity/space_complexity.cpp index 136e85bf..6352b9d4 100644 --- a/codes/cpp/chapter_computational_complexity/space_complexity.cpp +++ b/codes/cpp/chapter_computational_complexity/space_complexity.cpp @@ -1,4 +1,4 @@ -/* +/** * File: space_complexity.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_computational_complexity/time_complexity.cpp b/codes/cpp/chapter_computational_complexity/time_complexity.cpp index 68408bcc..cb1a2077 100644 --- a/codes/cpp/chapter_computational_complexity/time_complexity.cpp +++ b/codes/cpp/chapter_computational_complexity/time_complexity.cpp @@ -1,4 +1,4 @@ -/* +/** * File: time_complexity.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp b/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp index 9b215cf8..c2916cb4 100644 --- a/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp +++ b/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp @@ -1,4 +1,4 @@ -/* +/** * File: worst_best_time_complexity.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_hashing/array_hash_map.cpp b/codes/cpp/chapter_hashing/array_hash_map.cpp index 394e3681..8b92174e 100644 --- a/codes/cpp/chapter_hashing/array_hash_map.cpp +++ b/codes/cpp/chapter_hashing/array_hash_map.cpp @@ -1,4 +1,4 @@ -/* +/** * File: array_hash_map.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) diff --git a/codes/cpp/chapter_hashing/hash_map.cpp b/codes/cpp/chapter_hashing/hash_map.cpp index 898f679a..829265d6 100644 --- a/codes/cpp/chapter_hashing/hash_map.cpp +++ b/codes/cpp/chapter_hashing/hash_map.cpp @@ -1,4 +1,4 @@ -/* +/** * File: hash_map.cpp * Created Time: 2022-12-14 * Author: msk397 (machangxinq@gmail.com) diff --git a/codes/cpp/chapter_searching/binary_search.cpp b/codes/cpp/chapter_searching/binary_search.cpp index 9729128a..8d727ab4 100644 --- a/codes/cpp/chapter_searching/binary_search.cpp +++ b/codes/cpp/chapter_searching/binary_search.cpp @@ -1,4 +1,4 @@ -/* +/** * File: binary_search.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_searching/hashing_search.cpp b/codes/cpp/chapter_searching/hashing_search.cpp index 674376ff..ebc2fb01 100644 --- a/codes/cpp/chapter_searching/hashing_search.cpp +++ b/codes/cpp/chapter_searching/hashing_search.cpp @@ -1,4 +1,4 @@ -/* +/** * File: hashing_search.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_searching/linear_search.cpp b/codes/cpp/chapter_searching/linear_search.cpp index e1f2a567..7b509276 100644 --- a/codes/cpp/chapter_searching/linear_search.cpp +++ b/codes/cpp/chapter_searching/linear_search.cpp @@ -1,4 +1,4 @@ -/* +/** * File: linear_search.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_sorting/bubble_sort.cpp b/codes/cpp/chapter_sorting/bubble_sort.cpp index 87ca305d..27427cae 100644 --- a/codes/cpp/chapter_sorting/bubble_sort.cpp +++ b/codes/cpp/chapter_sorting/bubble_sort.cpp @@ -1,4 +1,4 @@ -/* +/** * File: bubble_sort.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_sorting/insertion_sort.cpp b/codes/cpp/chapter_sorting/insertion_sort.cpp index 9ae5988c..a40e4a5f 100644 --- a/codes/cpp/chapter_sorting/insertion_sort.cpp +++ b/codes/cpp/chapter_sorting/insertion_sort.cpp @@ -1,4 +1,4 @@ -/* +/** * File: insertion_sort.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_sorting/merge_sort.cpp b/codes/cpp/chapter_sorting/merge_sort.cpp index 7c9a41c8..89b5b09c 100644 --- a/codes/cpp/chapter_sorting/merge_sort.cpp +++ b/codes/cpp/chapter_sorting/merge_sort.cpp @@ -1,4 +1,4 @@ -/* +/** * File: merge_sort.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -25,10 +25,10 @@ void merge(vector& nums, int left, int mid, int right) { // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ else if (j > rightEnd || tmp[i] <= tmp[j]) nums[k] = tmp[i++]; - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ else nums[k] = tmp[j++]; } diff --git a/codes/cpp/chapter_sorting/quick_sort.cpp b/codes/cpp/chapter_sorting/quick_sort.cpp index 4e4430e7..c298f5be 100644 --- a/codes/cpp/chapter_sorting/quick_sort.cpp +++ b/codes/cpp/chapter_sorting/quick_sort.cpp @@ -1,4 +1,4 @@ -/* +/** * File: quick_sort.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_stack_and_queue/array_queue.cpp b/codes/cpp/chapter_stack_and_queue/array_queue.cpp index 387ffd14..de5e780f 100644 --- a/codes/cpp/chapter_stack_and_queue/array_queue.cpp +++ b/codes/cpp/chapter_stack_and_queue/array_queue.cpp @@ -1,4 +1,4 @@ -/* +/** * File: array_queue.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -50,11 +50,10 @@ public: } /* 出队 */ - int poll() { + void poll() { int num = peek(); // 队头指针向后移动一位,若越过尾部则返回到数组头部 front = (front + 1) % capacity(); - return num; } /* 访问队首元素 */ @@ -98,8 +97,8 @@ int main() { cout << "队首元素 peek = " << peek << endl; /* 元素出队 */ - int poll = queue->poll(); - cout << "出队元素 poll = " << poll << ",出队后 queue = "; + queue->poll(); + cout << "出队元素 poll = " << peek << ",出队后 queue = "; PrintUtil::printVector(queue->toVector()); /* 获取队列的长度 */ diff --git a/codes/cpp/chapter_stack_and_queue/array_stack.cpp b/codes/cpp/chapter_stack_and_queue/array_stack.cpp index 517a18a4..e6159e8c 100644 --- a/codes/cpp/chapter_stack_and_queue/array_stack.cpp +++ b/codes/cpp/chapter_stack_and_queue/array_stack.cpp @@ -1,4 +1,4 @@ -/* +/** * File: array_stack.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) @@ -28,10 +28,9 @@ public: } /* 出栈 */ - int pop() { + void pop() { int oldTop = top(); stack.pop_back(); - return oldTop; } /* 访问栈顶元素 */ @@ -67,8 +66,8 @@ int main() { cout << "栈顶元素 top = " << top << endl; /* 元素出栈 */ - int pop = stack->pop(); - cout << "出栈元素 pop = " << pop << ",出栈后 stack = "; + stack->pop(); + cout << "出栈元素 pop = " << top << ",出栈后 stack = "; PrintUtil::printVector(stack->toVector()); /* 获取栈的长度 */ diff --git a/codes/cpp/chapter_stack_and_queue/deque.cpp b/codes/cpp/chapter_stack_and_queue/deque.cpp index 96cf5811..e60af11e 100644 --- a/codes/cpp/chapter_stack_and_queue/deque.cpp +++ b/codes/cpp/chapter_stack_and_queue/deque.cpp @@ -1,4 +1,4 @@ -/* +/** * File: deque.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp b/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp index 17625322..ae22740d 100644 --- a/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp +++ b/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp @@ -1,4 +1,4 @@ -/* +/** * File: linkedlist_queue.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -47,12 +47,14 @@ public: } /* 出队 */ - int poll() { + void poll() { int num = peek(); // 删除头结点 + ListNode *tmp = front; front = front->next; + // 释放内存 + delete tmp; queSize--; - return num; } /* 访问队首元素 */ @@ -94,8 +96,8 @@ int main() { cout << "队首元素 peek = " << peek << endl; /* 元素出队 */ - int poll = queue->poll(); - cout << "出队元素 poll = " << poll << ",出队后 queue = "; + queue->poll(); + cout << "出队元素 poll = " << peek << ",出队后 queue = "; PrintUtil::printVector(queue->toVector()); /* 获取队列的长度 */ diff --git a/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp b/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp index 1bb7c2a3..495f2660 100644 --- a/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp +++ b/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp @@ -1,4 +1,4 @@ -/* +/** * File: linkedlist_stack.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) @@ -37,11 +37,13 @@ public: } /* 出栈 */ - int pop() { + void pop() { int num = top(); + ListNode *tmp = stackTop; stackTop = stackTop->next; + // 释放内存 + delete tmp; stkSize--; - return num; } /* 访问栈顶元素 */ @@ -83,8 +85,8 @@ int main() { cout << "栈顶元素 top = " << top << endl; /* 元素出栈 */ - int pop = stack->pop(); - cout << "出栈元素 pop = " << pop << ",出栈后 stack = "; + stack->pop(); + cout << "出栈元素 pop = " << top << ",出栈后 stack = "; PrintUtil::printVector(stack->toVector()); /* 获取栈的长度 */ diff --git a/codes/cpp/chapter_stack_and_queue/queue.cpp b/codes/cpp/chapter_stack_and_queue/queue.cpp index 2ca504a2..6bc37302 100644 --- a/codes/cpp/chapter_stack_and_queue/queue.cpp +++ b/codes/cpp/chapter_stack_and_queue/queue.cpp @@ -1,4 +1,4 @@ -/* +/** * File: queue.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) diff --git a/codes/cpp/chapter_stack_and_queue/stack.cpp b/codes/cpp/chapter_stack_and_queue/stack.cpp index cc027697..7bbfd6b9 100644 --- a/codes/cpp/chapter_stack_and_queue/stack.cpp +++ b/codes/cpp/chapter_stack_and_queue/stack.cpp @@ -1,4 +1,4 @@ -/* +/** * File: stack.cpp * Created Time: 2022-11-28 * Author: qualifier1024 (2539244001@qq.com) diff --git a/codes/cpp/chapter_tree/avl_tree.cpp b/codes/cpp/chapter_tree/avl_tree.cpp deleted file mode 100644 index b816f975..00000000 --- a/codes/cpp/chapter_tree/avl_tree.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/* - * File: avl_tree.cpp - * Created Time: 2022-12-2 - * Author: mgisr (maguagua0706@gmail.com) - */ - -#include "../include/include.hpp" - -class AvlTree { -private: - TreeNode *root{}; - static bool isBalance(const TreeNode *p); - static int getBalanceFactor(const TreeNode *p); - static void updateHeight(TreeNode *p); - void fixBalance(TreeNode *p); - static bool isLeftChild(const TreeNode *p); - static TreeNode *&fromParentTo(TreeNode *node); -public: - AvlTree() = default; - AvlTree(const AvlTree &p) = default; - const TreeNode *search(int val); - bool insert(int val); - bool remove(int val); - void printTree(); -}; - -// 判断该结点是否平衡 -bool AvlTree::isBalance(const TreeNode *p) { - int balance_factor = getBalanceFactor(p); - if (-1 <= balance_factor && balance_factor <= 1) { return true; } - else { return false; } -} - -// 获取当前结点的平衡因子 -int AvlTree::getBalanceFactor(const TreeNode *p) { - if (p->left == nullptr && p->right == nullptr) { return 0; } - else if (p->left == nullptr) { return (-1 - p->right->height); } - else if (p->right == nullptr) { return p->left->height + 1; } - else { return p->left->height - p->right->height; } -} - -// 更新结点高度 -void AvlTree::updateHeight(TreeNode *p) { - if (p->left == nullptr && p->right == nullptr) { p->height = 0; } - else if (p->left == nullptr) { p->height = p->right->height + 1; } - else if (p->right == nullptr) { p->height = p->left->height + 1; } - else { p->height = std::max(p->left->height, p->right->height) + 1; } -} - -void AvlTree::fixBalance(TreeNode *p) { - // 左旋操作 - auto rotate_left = [&](TreeNode *node) -> TreeNode * { - TreeNode *temp = node->right; - temp->parent = p->parent; - node->right = temp->left; - if (temp->left != nullptr) { - temp->left->parent = node; - } - temp->left = node; - node->parent = temp; - updateHeight(node); - updateHeight(temp); - return temp; - }; - // 右旋操作 - auto rotate_right = [&](TreeNode *node) -> TreeNode * { - TreeNode *temp = node->left; - temp->parent = p->parent; - node->left = temp->right; - if (temp->right != nullptr) { - temp->right->parent = node; - } - temp->right = node; - node->parent = temp; - updateHeight(node); - updateHeight(temp); - return temp; - }; - // 根据规则选取旋转方式 - if (getBalanceFactor(p) > 1) { - if (getBalanceFactor(p->left) > 0) { - if (p->parent == nullptr) { root = rotate_right(p); } - else { fromParentTo(p) = rotate_right(p); } - } else { - p->left = rotate_left(p->left); - if (p->parent == nullptr) { root = rotate_right(p); } - else { fromParentTo(p) = rotate_right(p); } - } - } else { - if (getBalanceFactor(p->right) < 0) { - if (p->parent == nullptr) { root = rotate_left(p); } - else { fromParentTo(p) = rotate_left(p); } - } else { - p->right = rotate_right(p->right); - if (p->parent == nullptr) { root = rotate_left(p); } - else { fromParentTo(p) = rotate_left(p); } - } - } -} - -// 判断当前结点是否为其父节点的左孩子 -bool AvlTree::isLeftChild(const TreeNode *p) { - if (p->parent == nullptr) { return false; } - return (p->parent->left == p); -} - -// 返回父节点指向当前结点指针的引用 -TreeNode *&AvlTree::fromParentTo(TreeNode *node) { - if (isLeftChild(node)) { return node->parent->left; } - else { return node->parent->right; } -} - -const TreeNode *AvlTree::search(int val) { - TreeNode *p = root; - while (p != nullptr) { - if (p->val == val) { return p; } - else if (p->val > val) { p = p->left; } - else { p = p->right; } - } - return nullptr; -} - -bool AvlTree::insert(int val) { - TreeNode *p = root; - if (p == nullptr) { - root = new TreeNode(val); - return true; - } - for (;;) { - if (p->val == val) { return false; } - else if (p->val > val) { - if (p->left == nullptr) { - p->left = new TreeNode(val, p); - break; - } else { - p = p->left; - } - } else { - if (p->right == nullptr) { - p->right = new TreeNode(val, p); - break; - } else { - p = p->right; - } - } - } - for (; p != nullptr; p = p->parent) { - if (!isBalance(p)) { - fixBalance(p); - break; - } else { updateHeight(p); } - } - return true; -} - -bool AvlTree::remove(int val) { - TreeNode *p = root; - if (p == nullptr) { return false; } - while (p != nullptr) { - if (p->val == val) { - TreeNode *real_delete_node = p; - TreeNode *next_node; - if (p->left == nullptr) { - next_node = p->right; - if (p->parent == nullptr) { root = next_node; } - else { fromParentTo(p) = next_node; } - } else if (p->right == nullptr) { - next_node = p->left; - if (p->parent == nullptr) { root = next_node; } - else { fromParentTo(p) = next_node; } - } else { - while (real_delete_node->left != nullptr) { - real_delete_node = real_delete_node->left; - } - std::swap(p->val, real_delete_node->val); - next_node = real_delete_node->right; - if (real_delete_node->parent == p) { p->right = next_node; } - else { real_delete_node->parent->left = next_node; } - } - if (next_node != nullptr) { - next_node->parent = real_delete_node->parent; - } - for (p = real_delete_node; p != nullptr; p = p->parent) { - if (!isBalance(p)) { fixBalance(p); } - updateHeight(p); - } - delete real_delete_node; - return true; - } else if (p->val > val) { - p = p->left; - } else { - p = p->right; - } - } - return false; -} - -void inOrder(const TreeNode *root) { - if (root == nullptr) return; - inOrder(root->left); - cout << root->val << ' '; - inOrder(root->right); -} - -void AvlTree::printTree() { - inOrder(root); - cout << endl; -} - -int main() { - AvlTree tree = AvlTree(); - // tree.insert(13); - // tree.insert(24); - // tree.insert(37); - // tree.insert(90); - // tree.insert(53); - - tree.insert(53); - tree.insert(90); - tree.insert(37); - tree.insert(24); - tree.insert(13); - tree.remove(90); - tree.printTree(); - const TreeNode *p = tree.search(37); - cout << p->val; - return 0; -} \ No newline at end of file diff --git a/codes/cpp/chapter_tree/binary_search_tree.cpp b/codes/cpp/chapter_tree/binary_search_tree.cpp index 648e194a..006d7d30 100644 --- a/codes/cpp/chapter_tree/binary_search_tree.cpp +++ b/codes/cpp/chapter_tree/binary_search_tree.cpp @@ -1,4 +1,4 @@ -/* +/** * File: binary_search_tree.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -96,11 +96,13 @@ public: // 删除结点 cur if (pre->left == cur) pre->left = child; else pre->right = child; + // 释放内存 + delete cur; } // 子结点数量 = 2 else { // 获取中序遍历中 cur 的下一个结点 - TreeNode* nex = min(cur->right); + TreeNode* nex = getInOrderNext(cur->right); int tmp = nex->val; // 递归删除结点 nex remove(nex->val); @@ -110,8 +112,8 @@ public: return cur; } - /* 获取最小结点 */ - TreeNode* min(TreeNode* root) { + /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + TreeNode* getInOrderNext(TreeNode* root) { if (root == nullptr) return root; // 循环访问左子结点,直到叶结点时为最小结点,跳出 while (root->left != nullptr) { diff --git a/codes/cpp/chapter_tree/binary_tree.cpp b/codes/cpp/chapter_tree/binary_tree.cpp index 3258d9cf..cd06778a 100644 --- a/codes/cpp/chapter_tree/binary_tree.cpp +++ b/codes/cpp/chapter_tree/binary_tree.cpp @@ -1,4 +1,4 @@ -/* +/** * File: binary_tree.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -33,6 +33,7 @@ int main() { PrintUtil::printTree(n1); // 删除结点 P n1->left = n2; + delete P; // 释放内存 cout << endl << "删除结点 P 后\n" << endl; PrintUtil::printTree(n1); diff --git a/codes/cpp/chapter_tree/binary_tree_bfs.cpp b/codes/cpp/chapter_tree/binary_tree_bfs.cpp index 30c2d600..ffc2e372 100644 --- a/codes/cpp/chapter_tree/binary_tree_bfs.cpp +++ b/codes/cpp/chapter_tree/binary_tree_bfs.cpp @@ -1,4 +1,4 @@ -/* +/** * File: binary_tree_bfs.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -30,8 +30,7 @@ vector hierOrder(TreeNode* root) { int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode* root = vecToTree(vector - { 1, 2, 3, 4, 5, 6, 7, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX }); + TreeNode* root = vecToTree(vector { 1, 2, 3, 4, 5, 6, 7 }); cout << endl << "初始化二叉树\n" << endl; PrintUtil::printTree(root); diff --git a/codes/cpp/chapter_tree/binary_tree_dfs.cpp b/codes/cpp/chapter_tree/binary_tree_dfs.cpp index 08a0a331..5ed5b78f 100644 --- a/codes/cpp/chapter_tree/binary_tree_dfs.cpp +++ b/codes/cpp/chapter_tree/binary_tree_dfs.cpp @@ -1,4 +1,4 @@ -/* +/** * File: binary_tree_dfs.cpp * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -41,8 +41,7 @@ void postOrder(TreeNode* root) { int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode* root = vecToTree(vector - { 1, 2, 3, 4, 5, 6, 7, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX}); + TreeNode* root = vecToTree(vector { 1, 2, 3, 4, 5, 6, 7 }); cout << endl << "初始化二叉树\n" << endl; PrintUtil::printTree(root); diff --git a/codes/cpp/include/ListNode.hpp b/codes/cpp/include/ListNode.hpp index bd91f255..9e6bd069 100644 --- a/codes/cpp/include/ListNode.hpp +++ b/codes/cpp/include/ListNode.hpp @@ -1,4 +1,4 @@ -/* +/** * File: PrintUtil.hpp * Created Time: 2021-12-19 * Author: Krahets (krahets@163.com) diff --git a/codes/cpp/include/PrintUtil.hpp b/codes/cpp/include/PrintUtil.hpp index 544ee85d..e788d165 100644 --- a/codes/cpp/include/PrintUtil.hpp +++ b/codes/cpp/include/PrintUtil.hpp @@ -1,4 +1,4 @@ -/* +/** * File: PrintUtil.hpp * Created Time: 2021-12-19 * Author: Krahets (krahets@163.com), msk397 (machangxinq@gmail.com) @@ -9,6 +9,7 @@ #include #include #include +#include #include "ListNode.hpp" #include "TreeNode.hpp" diff --git a/codes/cpp/include/TreeNode.hpp b/codes/cpp/include/TreeNode.hpp index b3b27a65..82f2ce98 100644 --- a/codes/cpp/include/TreeNode.hpp +++ b/codes/cpp/include/TreeNode.hpp @@ -1,4 +1,4 @@ -/* +/** * File: PrintUtil.hpp * Created Time: 2021-12-19 * Author: Krahets (krahets@163.com) @@ -27,23 +27,24 @@ struct TreeNode { * @return TreeNode* */ TreeNode *vecToTree(vector list) { - if (list.empty()) { + if (list.empty()) return nullptr; - } auto *root = new TreeNode(list[0]); queue que; - size_t n = list.size(), index = 1; - while (index < n) { + que.emplace(root); + size_t n = list.size(), index = 0; + while (!que.empty()) { auto node = que.front(); que.pop(); - + if (++index >= n) break; if (index < n) { - node->left = new TreeNode(list[index++]); + node->left = new TreeNode(list[index]); que.emplace(node->left); } + if (++index >= n) break; if (index < n) { - node->right = new TreeNode(list[index++]); + node->right = new TreeNode(list[index]); que.emplace(node->right); } } diff --git a/codes/cpp/include/include.hpp b/codes/cpp/include/include.hpp index 28eab2cd..8e4a8070 100644 --- a/codes/cpp/include/include.hpp +++ b/codes/cpp/include/include.hpp @@ -1,4 +1,4 @@ -/* +/** * File: PrintUtil.hpp * Created Time: 2021-12-19 * Author: Krahets (krahets@163.com) diff --git a/codes/csharp/chapter_array_and_linkedlist/array.cs b/codes/csharp/chapter_array_and_linkedlist/array.cs index 95479574..97a37f04 100644 --- a/codes/csharp/chapter_array_and_linkedlist/array.cs +++ b/codes/csharp/chapter_array_and_linkedlist/array.cs @@ -8,9 +8,7 @@ namespace hello_algo.chapter_array_and_linkedlist { public class Array { - /// - /// 随机返回一个数组元素 - /// + /* 随机返回一个数组元素 */ public static int RandomAccess(int[] nums) { Random random = new(); @@ -19,9 +17,7 @@ namespace hello_algo.chapter_array_and_linkedlist return randomNum; } - /// - /// 扩展数组长度 - /// + /* 扩展数组长度 */ public static int[] Extend(int[] nums, int enlarge) { // 初始化一个扩展长度后的数组 @@ -35,9 +31,7 @@ namespace hello_algo.chapter_array_and_linkedlist return res; } - /// - /// 在数组的索引 index 处插入元素 num - /// + /* 在数组的索引 index 处插入元素 num */ public static void Insert(int[] nums, int num, int index) { // 把索引 index 以及之后的所有元素向后移动一位 @@ -49,9 +43,7 @@ namespace hello_algo.chapter_array_and_linkedlist nums[index] = num; } - /// - /// 删除索引 index 处元素 - /// + /* 删除索引 index 处元素 */ public static void Remove(int[] nums, int index) { // 把索引 index 之后的所有元素向前移动一位 @@ -61,9 +53,7 @@ namespace hello_algo.chapter_array_and_linkedlist } } - /// - /// 遍历数组 - /// + /* 遍历数组 */ public static void Traverse(int[] nums) { int count = 0; @@ -79,9 +69,7 @@ namespace hello_algo.chapter_array_and_linkedlist } } - /// - /// 在数组中查找指定元素 - /// + /* 在数组中查找指定元素 */ public static int Find(int[] nums, int target) { for (int i = 0; i < nums.Length; i++) @@ -92,15 +80,13 @@ namespace hello_algo.chapter_array_and_linkedlist return -1; } - /// - /// 辅助函数,数组转字符串 - /// + /* 辅助函数,数组转字符串 */ public static string ToString(int[] nums) { return string.Join(",", nums); } - // Driver Code + [Test] public static void Test() { diff --git a/codes/csharp/chapter_array_and_linkedlist/linked_list.cs b/codes/csharp/chapter_array_and_linkedlist/linked_list.cs index d700a5ae..81e1499c 100644 --- a/codes/csharp/chapter_array_and_linkedlist/linked_list.cs +++ b/codes/csharp/chapter_array_and_linkedlist/linked_list.cs @@ -9,9 +9,7 @@ namespace hello_algo.chapter_array_and_linkedlist { public class linked_list { - /// - /// 在链表的结点 n0 之后插入结点 P - /// + /* 在链表的结点 n0 之后插入结点 P */ public static void Insert(ListNode n0, ListNode P) { ListNode? n1 = n0.next; @@ -19,9 +17,7 @@ namespace hello_algo.chapter_array_and_linkedlist P.next = n1; } - /// - /// 删除链表的结点 n0 之后的首个结点 - /// + /* 删除链表的结点 n0 之后的首个结点 */ public static void Remove(ListNode n0) { if (n0.next == null) @@ -32,23 +28,19 @@ namespace hello_algo.chapter_array_and_linkedlist n0.next = n1; } - /// - /// 访问链表中索引为 index 的结点 - /// + /* 访问链表中索引为 index 的结点 */ public static ListNode? Access(ListNode head, int index) { for (int i = 0; i < index; i++) { - head = head.next; if (head == null) return null; + head = head.next; } return head; } - /// - /// 在链表中查找值为 target 的首个结点 - /// + /* 在链表中查找值为 target 的首个结点 */ public static int Find(ListNode head, int target) { int index = 0; @@ -62,7 +54,7 @@ namespace hello_algo.chapter_array_and_linkedlist return -1; } - // Driver Code + [Test] public void Test() { diff --git a/codes/csharp/chapter_sorting/merge_sort.cs b/codes/csharp/chapter_sorting/merge_sort.cs index 04ac8acd..e66ae5e2 100644 --- a/codes/csharp/chapter_sorting/merge_sort.cs +++ b/codes/csharp/chapter_sorting/merge_sort.cs @@ -31,10 +31,10 @@ namespace hello_algo.chapter_sorting // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ else if (j > rightEnd || tmp[i] <= tmp[j]) nums[k] = tmp[i++]; - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ else nums[k] = tmp[j++]; } diff --git a/codes/csharp/chapter_stack_and_queue/deque.cs b/codes/csharp/chapter_stack_and_queue/deque.cs new file mode 100644 index 00000000..d9f7a509 --- /dev/null +++ b/codes/csharp/chapter_stack_and_queue/deque.cs @@ -0,0 +1,49 @@ +/** + * File: deque.cs + * Created Time: 2022-12-30 + * Author: moonache (microin1301@outlook.com) + */ + +using NUnit.Framework; + +namespace hello_algo.chapter_stack_and_queue +{ + public class deque + { + [Test] + public void Test() + { + /* 初始化双向队列 */ + // 在 C# 中,将链表 LinkedList 看作双向队列来使用 + LinkedList deque = new LinkedList(); + + /* 元素入队 */ + deque.AddLast(2); // 添加至队尾 + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // 添加至队首 + deque.AddFirst(1); + Console.WriteLine("双向队列 deque = " + String.Join(",", deque.ToArray())); + + /* 访问元素 */ + 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.ToArray())); + deque.RemoveLast(); // 队尾元素出队 + Console.WriteLine("队尾元素出队后 deque = " + String.Join(",", deque.ToArray())); + + /* 获取双向队列的长度 */ + int size = deque.Count; + Console.WriteLine("双向队列长度 size = " + size); + + /* 判断双向队列是否为空 */ + bool isEmpty = deque.Count == 0; + Console.WriteLine("双向队列是否为空 = " + isEmpty); + } + } +} diff --git a/codes/csharp/chapter_tree/avl_tree.cs b/codes/csharp/chapter_tree/avl_tree.cs index 99ec17f6..6bcb559b 100644 --- a/codes/csharp/chapter_tree/avl_tree.cs +++ b/codes/csharp/chapter_tree/avl_tree.cs @@ -162,7 +162,7 @@ namespace hello_algo.chapter_tree else { // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode? temp = minNode(node.right); + TreeNode? temp = getInOrderNext(node.right); node.right = removeHelper(node.right, temp.val); node.val = temp.val; } @@ -174,8 +174,8 @@ namespace hello_algo.chapter_tree return node; } - /* 获取最小结点 */ - private TreeNode? minNode(TreeNode? node) + /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + private TreeNode? getInOrderNext(TreeNode? node) { if (node == null) return node; // 循环访问左子结点,直到叶结点时为最小结点,跳出 diff --git a/codes/csharp/chapter_tree/binary_search_tree.cs b/codes/csharp/chapter_tree/binary_search_tree.cs index 3cb99148..e12cdd42 100644 --- a/codes/csharp/chapter_tree/binary_search_tree.cs +++ b/codes/csharp/chapter_tree/binary_search_tree.cs @@ -35,11 +35,7 @@ namespace hello_algo.chapter_tree return root; } - /// - /// 查找结点 - /// - /// - /// + /* 查找结点 */ public TreeNode? search(int num) { TreeNode? cur = root; @@ -125,7 +121,7 @@ namespace hello_algo.chapter_tree else { // 获取中序遍历中 cur 的下一个结点 - TreeNode? nex = min(cur.right); + TreeNode? nex = getInOrderNext(cur.right); if (nex != null) { int tmp = nex.val; @@ -138,8 +134,8 @@ namespace hello_algo.chapter_tree return cur; } - /* 获取最小结点 */ - private TreeNode? min(TreeNode? root) + /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + private TreeNode? getInOrderNext(TreeNode? root) { if (root == null) return root; // 循环访问左子结点,直到叶结点时为最小结点,跳出 diff --git a/codes/csharp/chapter_tree/binary_tree_bfs.cs b/codes/csharp/chapter_tree/binary_tree_bfs.cs index 31f70724..9a57dbc1 100644 --- a/codes/csharp/chapter_tree/binary_tree_bfs.cs +++ b/codes/csharp/chapter_tree/binary_tree_bfs.cs @@ -12,11 +12,7 @@ namespace hello_algo.chapter_tree public class binary_tree_bfs { - /// - /// 层序遍历 - /// - /// - /// + /* 层序遍历 */ public List hierOrder(TreeNode root) { // 初始化队列,加入根结点 @@ -41,8 +37,7 @@ namespace hello_algo.chapter_tree { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode? root = TreeNode.ArrToTree(new int?[] { - 1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null}); + TreeNode? root = TreeNode.ArrToTree(new int?[] { 1, 2, 3, 4, 5, 6, 7 }); Console.WriteLine("\n初始化二叉树\n"); PrintUtil.PrintTree(root); diff --git a/codes/csharp/chapter_tree/binary_tree_dfs.cs b/codes/csharp/chapter_tree/binary_tree_dfs.cs index 6e38ebe4..f8669d78 100644 --- a/codes/csharp/chapter_tree/binary_tree_dfs.cs +++ b/codes/csharp/chapter_tree/binary_tree_dfs.cs @@ -13,10 +13,7 @@ namespace hello_algo.chapter_tree { List list = new(); - /// - /// 前序遍历 - /// - /// + /* 前序遍历 */ void preOrder(TreeNode? root) { if (root == null) return; @@ -26,10 +23,7 @@ namespace hello_algo.chapter_tree preOrder(root.right); } - /// - /// 中序遍历 - /// - /// + /* 中序遍历 */ void inOrder(TreeNode? root) { if (root == null) return; @@ -39,10 +33,7 @@ namespace hello_algo.chapter_tree inOrder(root.right); } - /// - /// 后序遍历 - /// - /// + /* 后序遍历 */ void postOrder(TreeNode? root) { if (root == null) return; @@ -57,8 +48,7 @@ namespace hello_algo.chapter_tree { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode? root = TreeNode.ArrToTree(new int?[] { - 1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null}); + TreeNode? root = TreeNode.ArrToTree(new int?[] { 1, 2, 3, 4, 5, 6, 7 }); Console.WriteLine("\n初始化二叉树\n"); PrintUtil.PrintTree(root); diff --git a/codes/csharp/hello-algo.csproj b/codes/csharp/hello-algo.csproj index 2ef4da85..d96d728b 100644 --- a/codes/csharp/hello-algo.csproj +++ b/codes/csharp/hello-algo.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/codes/csharp/include/TreeNode.cs b/codes/csharp/include/TreeNode.cs index e5cacd59..9cd0ac92 100644 --- a/codes/csharp/include/TreeNode.cs +++ b/codes/csharp/include/TreeNode.cs @@ -19,7 +19,7 @@ namespace hello_algo.include } /** - * Generate a binary tree with an array + * Generate a binary tree given an array * @param arr * @return */ @@ -31,22 +31,22 @@ namespace hello_algo.include TreeNode root = new TreeNode((int) arr[0]); Queue queue = new Queue(); queue.Enqueue(root); - int i = 1; - while (queue.Count!=0) + int i = 0; + while (queue.Count != 0) { TreeNode node = queue.Dequeue(); + if (++i >= arr.Length) break; if (arr[i] != null) { node.left = new TreeNode((int) arr[i]); queue.Enqueue(node.left); } - i++; + if (++i >= arr.Length) break; if (arr[i] != null) { node.right = new TreeNode((int) arr[i]); queue.Enqueue(node.right); } - i++; } return root; } diff --git a/codes/go/chapter_array_and_linkedlist/array.go b/codes/go/chapter_array_and_linkedlist/array.go new file mode 100644 index 00000000..0cf0d5d8 --- /dev/null +++ b/codes/go/chapter_array_and_linkedlist/array.go @@ -0,0 +1,74 @@ +// 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++ + } + count = 0 + // 直接遍历数组 + for range nums { + count++ + } +} + +/* 在数组中查找指定元素 */ +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/codes/go/chapter_array_and_linkedlist/array_test.go b/codes/go/chapter_array_and_linkedlist/array_test.go new file mode 100644 index 00000000..3d4e8f88 --- /dev/null +++ b/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/codes/go/chapter_array_and_linkedlist/linked_list.go b/codes/go/chapter_array_and_linkedlist/linked_list.go new file mode 100644 index 00000000..8a60fd18 --- /dev/null +++ b/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 + n0.Next = P + P.Next = n1 +} + +/* 删除链表的结点 n0 之后的首个结点 */ +func removeNode(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/codes/go/chapter_array_and_linkedlist/linked_list_test.go b/codes/go/chapter_array_and_linkedlist/linked_list_test.go new file mode 100644 index 00000000..466bde4f --- /dev/null +++ b/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 TestLikedList(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) + + /* 删除结点 */ + removeNode(n0) + fmt.Println("删除结点后的链表为") + PrintLinkedList(n0) + + /* 访问结点 */ + node := access(n0, 3) + fmt.Println("链表中索引 3 处的结点的值 =", node) + + /* 查找结点 */ + index := findNode(n0, 2) + fmt.Println("链表中值为 2 的结点的索引 =", index) +} diff --git a/codes/go/chapter_array_and_linkedlist/my_list.go b/codes/go/chapter_array_and_linkedlist/my_list.go index 8f13630b..134096d0 100644 --- a/codes/go/chapter_array_and_linkedlist/my_list.go +++ b/codes/go/chapter_array_and_linkedlist/my_list.go @@ -5,7 +5,7 @@ package chapter_array_and_linkedlist /* 列表类简易实现 */ -type MyList struct { +type myList struct { numsCapacity int nums []int numsSize int @@ -13,8 +13,8 @@ type MyList struct { } /* 构造函数 */ -func newMyList() *MyList { - return &MyList{ +func newMyList() *myList { + return &myList{ numsCapacity: 10, // 列表容量 nums: make([]int, 10), // 数组(存储列表元素) numsSize: 0, // 列表长度(即当前元素数量) @@ -23,17 +23,17 @@ func newMyList() *MyList { } /* 获取列表长度(即当前元素数量) */ -func (l *MyList) size() int { +func (l *myList) size() int { return l.numsSize } /* 获取列表容量 */ -func (l *MyList) capacity() int { +func (l *myList) capacity() int { return l.numsCapacity } /* 访问元素 */ -func (l *MyList) get(index int) int { +func (l *myList) get(index int) int { // 索引如果越界则抛出异常,下同 if index >= l.numsSize { panic("索引越界") @@ -42,7 +42,7 @@ func (l *MyList) get(index int) int { } /* 更新元素 */ -func (l *MyList) set(num, index int) { +func (l *myList) set(num, index int) { if index >= l.numsSize { panic("索引越界") } @@ -50,7 +50,7 @@ func (l *MyList) set(num, index int) { } /* 尾部添加元素 */ -func (l *MyList) add(num int) { +func (l *myList) add(num int) { // 元素数量超出容量时,触发扩容机制 if l.numsSize == l.numsCapacity { l.extendCapacity() @@ -61,7 +61,7 @@ func (l *MyList) add(num int) { } /* 中间插入元素 */ -func (l *MyList) insert(num, index int) { +func (l *myList) insert(num, index int) { if index >= l.numsSize { panic("索引越界") } @@ -79,7 +79,7 @@ func (l *MyList) insert(num, index int) { } /* 删除元素 */ -func (l *MyList) remove(index int) int { +func (l *myList) remove(index int) int { if index >= l.numsSize { panic("索引越界") } @@ -95,7 +95,7 @@ func (l *MyList) remove(index int) int { } /* 列表扩容 */ -func (l *MyList) extendCapacity() { +func (l *myList) extendCapacity() { // 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组 l.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...) // 更新列表容量 @@ -103,7 +103,7 @@ func (l *MyList) extendCapacity() { } /* 返回有效长度的列表 */ -func (l *MyList) toArray() []int { +func (l *myList) toArray() []int { // 仅转换有效长度范围内的列表元素 return l.nums[:l.numsSize] } diff --git a/codes/go/chapter_computational_complexity/space_complexity.go b/codes/go/chapter_computational_complexity/space_complexity.go index eb3ebec9..93f5b338 100644 --- a/codes/go/chapter_computational_complexity/space_complexity.go +++ b/codes/go/chapter_computational_complexity/space_complexity.go @@ -9,31 +9,31 @@ import ( "strconv" ) -/* Node 结构体 */ -type Node struct { +/* 结构体 */ +type node struct { val int - next *Node + next *node } -/* TreeNode 二叉树 */ -type TreeNode struct { +/* treeNode 二叉树 */ +type treeNode struct { val int - left *TreeNode - right *TreeNode + left *treeNode + right *treeNode } -/* 创建 Node 结构体 */ -func newNode(val int) *Node { - return &Node{val: val} +/* 创建 node 结构体 */ +func newNode(val int) *node { + return &node{val: val} } -/* 创建 TreeNode 结构体 */ -func newTreeNode(val int) *TreeNode { - return &TreeNode{val: val} +/* 创建 treeNode 结构体 */ +func newTreeNode(val int) *treeNode { + return &treeNode{val: val} } /* 输出二叉树 */ -func printTree(root *TreeNode) { +func printTree(root *treeNode) { if root == nil { return } @@ -72,7 +72,7 @@ func spaceLinear(n int) { // 长度为 n 的数组占用 O(n) 空间 _ = make([]int, n) // 长度为 n 的列表占用 O(n) 空间 - var nodes []*Node + var nodes []*node for i := 0; i < n; i++ { nodes = append(nodes, newNode(i)) } @@ -112,7 +112,7 @@ func spaceQuadraticRecur(n int) int { } /* 指数阶(建立满二叉树) */ -func buildTree(n int) *TreeNode { +func buildTree(n int) *treeNode { if n == 0 { return nil } diff --git a/codes/go/chapter_hashing/array_hash_map.go b/codes/go/chapter_hashing/array_hash_map.go index a962b6a4..5fb1480c 100644 --- a/codes/go/chapter_hashing/array_hash_map.go +++ b/codes/go/chapter_hashing/array_hash_map.go @@ -7,30 +7,30 @@ package chapter_hashing import "fmt" /* 键值对 int->String */ -type Entry struct { +type entry struct { key int val string } /* 基于数组简易实现的哈希表 */ -type ArrayHashMap struct { - bucket []*Entry +type arrayHashMap struct { + bucket []*entry } -func newArrayHashMap() *ArrayHashMap { +func newArrayHashMap() *arrayHashMap { // 初始化一个长度为 100 的桶(数组) - bucket := make([]*Entry, 100) - return &ArrayHashMap{bucket: bucket} + bucket := make([]*entry, 100) + return &arrayHashMap{bucket: bucket} } /* 哈希函数 */ -func (a *ArrayHashMap) hashFunc(key int) int { +func (a *arrayHashMap) hashFunc(key int) int { index := key % 100 return index } /* 查询操作 */ -func (a *ArrayHashMap) get(key int) string { +func (a *arrayHashMap) get(key int) string { index := a.hashFunc(key) pair := a.bucket[index] if pair == nil { @@ -40,22 +40,22 @@ func (a *ArrayHashMap) get(key int) string { } /* 添加操作 */ -func (a *ArrayHashMap) put(key int, val string) { - pair := &Entry{key: key, val: val} +func (a *arrayHashMap) put(key int, val string) { + pair := &entry{key: key, val: val} index := a.hashFunc(key) a.bucket[index] = pair } /* 删除操作 */ -func (a *ArrayHashMap) remove(key int) { +func (a *arrayHashMap) remove(key int) { index := a.hashFunc(key) // 置为 nil ,代表删除 a.bucket[index] = nil } /* 获取所有键对 */ -func (a *ArrayHashMap) entrySet() []*Entry { - var pairs []*Entry +func (a *arrayHashMap) entrySet() []*entry { + var pairs []*entry for _, pair := range a.bucket { if pair != nil { pairs = append(pairs, pair) @@ -65,7 +65,7 @@ func (a *ArrayHashMap) entrySet() []*Entry { } /* 获取所有键 */ -func (a *ArrayHashMap) keySet() []int { +func (a *arrayHashMap) keySet() []int { var keys []int for _, pair := range a.bucket { if pair != nil { @@ -76,7 +76,7 @@ func (a *ArrayHashMap) keySet() []int { } /* 获取所有值 */ -func (a *ArrayHashMap) valueSet() []string { +func (a *arrayHashMap) valueSet() []string { var values []string for _, pair := range a.bucket { if pair != nil { @@ -87,7 +87,7 @@ func (a *ArrayHashMap) valueSet() []string { } /* 打印哈希表 */ -func (a *ArrayHashMap) print() { +func (a *arrayHashMap) print() { for _, pair := range a.bucket { if pair != nil { fmt.Println(pair.key, "->", pair.val) diff --git a/codes/go/chapter_searching/hashing_search_test.go b/codes/go/chapter_searching/hashing_search_test.go index 4bccd038..18cf5c65 100644 --- a/codes/go/chapter_searching/hashing_search_test.go +++ b/codes/go/chapter_searching/hashing_search_test.go @@ -6,8 +6,9 @@ package chapter_searching import ( "fmt" - . "github.com/krahets/hello-algo/pkg" "testing" + + . "github.com/krahets/hello-algo/pkg" ) func TestHashingSearch(t *testing.T) { diff --git a/codes/go/chapter_sorting/bubble_sort/bubble_sort.go b/codes/go/chapter_sorting/bubble_sort.go similarity index 97% rename from codes/go/chapter_sorting/bubble_sort/bubble_sort.go rename to codes/go/chapter_sorting/bubble_sort.go index a51d1992..ff1ff9ee 100644 --- a/codes/go/chapter_sorting/bubble_sort/bubble_sort.go +++ b/codes/go/chapter_sorting/bubble_sort.go @@ -2,7 +2,7 @@ // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) -package bubble_sort +package chapter_sorting /* 冒泡排序 */ func bubbleSort(nums []int) { diff --git a/codes/go/chapter_sorting/bubble_sort/bubble_sort_test.go b/codes/go/chapter_sorting/bubble_sort_test.go similarity index 94% rename from codes/go/chapter_sorting/bubble_sort/bubble_sort_test.go rename to codes/go/chapter_sorting/bubble_sort_test.go index bab85139..53a7d055 100644 --- a/codes/go/chapter_sorting/bubble_sort/bubble_sort_test.go +++ b/codes/go/chapter_sorting/bubble_sort_test.go @@ -2,7 +2,7 @@ // Created Time: 2022-12-06 // Author: Slone123c (274325721@qq.com) -package bubble_sort +package chapter_sorting import ( "fmt" diff --git a/codes/go/chapter_sorting/insertion_sort/insertion_sort.go b/codes/go/chapter_sorting/insertion_sort.go similarity index 95% rename from codes/go/chapter_sorting/insertion_sort/insertion_sort.go rename to codes/go/chapter_sorting/insertion_sort.go index 2be78bcf..24101e96 100644 --- a/codes/go/chapter_sorting/insertion_sort/insertion_sort.go +++ b/codes/go/chapter_sorting/insertion_sort.go @@ -2,7 +2,7 @@ // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) -package insertion_sort +package chapter_sorting func insertionSort(nums []int) { // 外循环:待排序元素数量为 n-1, n-2, ..., 1 diff --git a/codes/go/chapter_sorting/insertion_sort/insertion_sort_test.go b/codes/go/chapter_sorting/insertion_sort_test.go similarity index 92% rename from codes/go/chapter_sorting/insertion_sort/insertion_sort_test.go rename to codes/go/chapter_sorting/insertion_sort_test.go index 07a96b8f..9802b406 100644 --- a/codes/go/chapter_sorting/insertion_sort/insertion_sort_test.go +++ b/codes/go/chapter_sorting/insertion_sort_test.go @@ -2,7 +2,7 @@ // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) -package insertion_sort +package chapter_sorting import ( "fmt" diff --git a/codes/go/chapter_sorting/merge_sort/merge_sort.go b/codes/go/chapter_sorting/merge_sort.go similarity index 69% rename from codes/go/chapter_sorting/merge_sort/merge_sort.go rename to codes/go/chapter_sorting/merge_sort.go index 830e2dfa..43aff01a 100644 --- a/codes/go/chapter_sorting/merge_sort/merge_sort.go +++ b/codes/go/chapter_sorting/merge_sort.go @@ -2,34 +2,34 @@ // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) -package merge_sort +package chapter_sorting // 合并左子数组和右子数组 // 左子数组区间 [left, mid] // 右子数组区间 [mid + 1, right] func merge(nums []int, left, mid, right int) { - // 初始化辅助数组 借助 copy模块 + // 初始化辅助数组 借助 copy 模块 tmp := make([]int, right-left+1) for i := left; i <= right; i++ { tmp[i-left] = nums[i] } // 左子数组的起始索引和结束索引 - left_start, left_end := left-left, mid-left + leftStart, leftEnd := left-left, mid-left // 右子数组的起始索引和结束索引 - right_start, right_end := mid+1-left, right-left + rightStart, rightEnd := mid+1-left, right-left // i, j 分别指向左子数组、右子数组的首元素 - i, j := left_start, right_start + i, j := leftStart, rightStart // 通过覆盖原数组 nums 来合并左子数组和右子数组 for k := left; k <= right; k++ { // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > left_end { + if i > leftEnd { nums[k] = tmp[j] j++ - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ - } else if j > right_end || tmp[i] <= tmp[j] { + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ + } else if j > rightEnd || tmp[i] <= tmp[j] { nums[k] = tmp[i] i++ - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ } else { nums[k] = tmp[j] j++ diff --git a/codes/go/chapter_sorting/merge_sort/merge_sort_test.go b/codes/go/chapter_sorting/merge_sort_test.go similarity index 92% rename from codes/go/chapter_sorting/merge_sort/merge_sort_test.go rename to codes/go/chapter_sorting/merge_sort_test.go index 04abd443..3ece175a 100644 --- a/codes/go/chapter_sorting/merge_sort/merge_sort_test.go +++ b/codes/go/chapter_sorting/merge_sort_test.go @@ -1,8 +1,9 @@ -package merge_sort // File: merge_sort_test.go // Created Time: 2022-12-13 // Author: msk397 (machangxinq@gmail.com) +package chapter_sorting + import ( "fmt" "testing" diff --git a/codes/go/chapter_sorting/quick_sort/quick_sort.go b/codes/go/chapter_sorting/quick_sort.go similarity index 85% rename from codes/go/chapter_sorting/quick_sort/quick_sort.go rename to codes/go/chapter_sorting/quick_sort.go index d42eb0ab..853bce45 100644 --- a/codes/go/chapter_sorting/quick_sort/quick_sort.go +++ b/codes/go/chapter_sorting/quick_sort.go @@ -2,19 +2,19 @@ // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) -package quick_sort +package chapter_sorting // 快速排序 -type QuickSort struct{} +type quickSort struct{} // 快速排序(中位基准数优化) -type QuickSortMedian struct{} +type quickSortMedian struct{} // 快速排序(尾递归优化) -type QuickSortTailCall struct{} +type quickSortTailCall struct{} /* 哨兵划分 */ -func (q *QuickSort) partition(nums []int, left, right int) int { +func (q *quickSort) partition(nums []int, left, right int) int { // 以 nums[left] 作为基准数 i, j := left, right for i < j { @@ -33,7 +33,7 @@ func (q *QuickSort) partition(nums []int, left, right int) int { } /* 快速排序 */ -func (q *QuickSort) quickSort(nums []int, left, right int) { +func (q *quickSort) quickSort(nums []int, left, right int) { // 子数组长度为 1 时终止递归 if left >= right { return @@ -46,7 +46,7 @@ func (q *QuickSort) quickSort(nums []int, left, right int) { } /* 选取三个元素的中位数 */ -func (q *QuickSortMedian) medianThree(nums []int, left, mid, right int) int { +func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { if (nums[left] > nums[mid]) != (nums[left] > nums[right]) { return left } else if (nums[mid] < nums[left]) != (nums[mid] > nums[right]) { @@ -56,7 +56,7 @@ func (q *QuickSortMedian) medianThree(nums []int, left, mid, right int) int { } /* 哨兵划分(三数取中值)*/ -func (q *QuickSortMedian) partition(nums []int, left, right int) int { +func (q *quickSortMedian) partition(nums []int, left, right int) int { // 以 nums[left] 作为基准数 med := q.medianThree(nums, left, (left+right)/2, right) // 将中位数交换至数组最左端 @@ -79,7 +79,7 @@ func (q *QuickSortMedian) partition(nums []int, left, right int) int { } /* 快速排序 */ -func (q *QuickSortMedian) quickSort(nums []int, left, right int) { +func (q *quickSortMedian) quickSort(nums []int, left, right int) { // 子数组长度为 1 时终止递归 if left >= right { return @@ -92,7 +92,7 @@ func (q *QuickSortMedian) quickSort(nums []int, left, right int) { } /* 哨兵划分 */ -func (q *QuickSortTailCall) partition(nums []int, left, right int) int { +func (q *quickSortTailCall) partition(nums []int, left, right int) int { // 以 nums[left] 作为基准数 i, j := left, right for i < j { @@ -111,7 +111,7 @@ func (q *QuickSortTailCall) partition(nums []int, left, right int) int { } /* 快速排序(尾递归优化)*/ -func (q *QuickSortTailCall) quickSort(nums []int, left, right int) { +func (q *quickSortTailCall) quickSort(nums []int, left, right int) { // 子数组长度为 1 时终止 for left < right { // 哨兵划分操作 diff --git a/codes/go/chapter_sorting/quick_sort/quick_sort_test.go b/codes/go/chapter_sorting/quick_sort_test.go similarity index 89% rename from codes/go/chapter_sorting/quick_sort/quick_sort_test.go rename to codes/go/chapter_sorting/quick_sort_test.go index 0a0a6d44..d780663e 100644 --- a/codes/go/chapter_sorting/quick_sort/quick_sort_test.go +++ b/codes/go/chapter_sorting/quick_sort_test.go @@ -2,7 +2,7 @@ // Created Time: 2022-12-12 // Author: msk397 (machangxinq@gmail.com) -package quick_sort +package chapter_sorting import ( "fmt" @@ -11,7 +11,7 @@ import ( // 快速排序 func TestQuickSort(t *testing.T) { - q := QuickSort{} + q := quickSort{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序完成后 nums = ", nums) @@ -19,7 +19,7 @@ func TestQuickSort(t *testing.T) { // 快速排序(中位基准数优化) func TestQuickSortMedian(t *testing.T) { - q := QuickSortMedian{} + q := quickSortMedian{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序(中位基准数优化)完成后 nums = ", nums) @@ -27,7 +27,7 @@ func TestQuickSortMedian(t *testing.T) { // 快速排序(尾递归优化) func TestQuickSortTailCall(t *testing.T) { - q := QuickSortTailCall{} + q := quickSortTailCall{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序(尾递归优化)完成后 nums = ", nums) diff --git a/codes/go/chapter_stack_and_queue/array_queue.go b/codes/go/chapter_stack_and_queue/array_queue.go index 863768ba..7d0367df 100644 --- a/codes/go/chapter_stack_and_queue/array_queue.go +++ b/codes/go/chapter_stack_and_queue/array_queue.go @@ -5,16 +5,16 @@ package chapter_stack_and_queue /* 基于环形数组实现的队列 */ -type ArrayQueue struct { +type arrayQueue struct { data []int // 用于存储队列元素的数组 capacity int // 队列容量(即最多容量的元素个数) front int // 头指针,指向队首 rear int // 尾指针,指向队尾 + 1 } -// NewArrayQueue 基于环形数组实现的队列 -func NewArrayQueue(capacity int) *ArrayQueue { - return &ArrayQueue{ +// newArrayQueue 基于环形数组实现的队列 +func newArrayQueue(capacity int) *arrayQueue { + return &arrayQueue{ data: make([]int, capacity), capacity: capacity, front: 0, @@ -22,21 +22,21 @@ func NewArrayQueue(capacity int) *ArrayQueue { } } -// Size 获取队列的长度 -func (q *ArrayQueue) Size() int { +// size 获取队列的长度 +func (q *arrayQueue) size() int { size := (q.capacity + q.rear - q.front) % q.capacity return size } -// IsEmpty 判断队列是否为空 -func (q *ArrayQueue) IsEmpty() bool { +// isEmpty 判断队列是否为空 +func (q *arrayQueue) isEmpty() bool { return q.rear-q.front == 0 } -// Offer 入队 -func (q *ArrayQueue) Offer(v int) { +// offer 入队 +func (q *arrayQueue) offer(v int) { // 当 rear == capacity 表示队列已满 - if q.Size() == q.capacity { + if q.size() == q.capacity { return } // 尾结点后添加 @@ -45,9 +45,9 @@ func (q *ArrayQueue) Offer(v int) { q.rear = (q.rear + 1) % q.capacity } -// Poll 出队 -func (q *ArrayQueue) Poll() any { - if q.IsEmpty() { +// poll 出队 +func (q *arrayQueue) poll() any { + if q.isEmpty() { return nil } v := q.data[q.front] @@ -56,9 +56,9 @@ func (q *ArrayQueue) Poll() any { return v } -// Peek 访问队首元素 -func (q *ArrayQueue) Peek() any { - if q.IsEmpty() { +// peek 访问队首元素 +func (q *arrayQueue) peek() any { + if q.isEmpty() { return nil } v := q.data[q.front] @@ -66,6 +66,6 @@ func (q *ArrayQueue) Peek() any { } // 获取 Slice 用于打印 -func (s *ArrayQueue) toSlice() []int { - return s.data[s.front:s.rear] +func (q *arrayQueue) toSlice() []int { + return q.data[q.front:q.rear] } diff --git a/codes/go/chapter_stack_and_queue/array_stack.go b/codes/go/chapter_stack_and_queue/array_stack.go index dca97404..ef32ebe4 100644 --- a/codes/go/chapter_stack_and_queue/array_stack.go +++ b/codes/go/chapter_stack_and_queue/array_stack.go @@ -5,47 +5,47 @@ package chapter_stack_and_queue /* 基于数组实现的栈 */ -type ArrayStack struct { +type arrayStack struct { data []int // 数据 } -func NewArrayStack() *ArrayStack { - return &ArrayStack{ +func newArrayStack() *arrayStack { + return &arrayStack{ // 设置栈的长度为 0,容量为 16 data: make([]int, 0, 16), } } -// Size 栈的长度 -func (s *ArrayStack) Size() int { +// size 栈的长度 +func (s *arrayStack) size() int { return len(s.data) } -// IsEmpty 栈是否为空 -func (s *ArrayStack) IsEmpty() bool { - return s.Size() == 0 +// isEmpty 栈是否为空 +func (s *arrayStack) isEmpty() bool { + return s.size() == 0 } -// Push 入栈 -func (s *ArrayStack) Push(v int) { +// push 入栈 +func (s *arrayStack) push(v int) { // 切片会自动扩容 s.data = append(s.data, v) } -// Pop 出栈 -func (s *ArrayStack) Pop() any { +// pop 出栈 +func (s *arrayStack) pop() any { // 弹出栈前,先判断是否为空 - if s.IsEmpty() { + if s.isEmpty() { return nil } - val := s.Peek() + val := s.peek() s.data = s.data[:len(s.data)-1] return val } -// Peek 获取栈顶元素 -func (s *ArrayStack) Peek() any { - if s.IsEmpty() { +// peek 获取栈顶元素 +func (s *arrayStack) peek() any { + if s.isEmpty() { return nil } val := s.data[len(s.data)-1] @@ -53,6 +53,6 @@ func (s *ArrayStack) Peek() any { } // 获取 Slice 用于打印 -func (s *ArrayStack) toSlice() []int { +func (s *arrayStack) toSlice() []int { return s.data } diff --git a/codes/go/chapter_stack_and_queue/deque_test.go b/codes/go/chapter_stack_and_queue/deque_test.go index 647ac642..4c0f2f53 100644 --- a/codes/go/chapter_stack_and_queue/deque_test.go +++ b/codes/go/chapter_stack_and_queue/deque_test.go @@ -51,48 +51,48 @@ func TestDeque(t *testing.T) { func TestLinkedListDeque(t *testing.T) { // 初始化队列 - deque := NewLinkedListDeque() + deque := newLinkedListDeque() // 元素入队 - deque.OfferLast(2) - deque.OfferLast(5) - deque.OfferLast(4) - deque.OfferFirst(3) - deque.OfferFirst(1) + deque.offerLast(2) + deque.offerLast(5) + deque.offerLast(4) + deque.offerFirst(3) + deque.offerFirst(1) fmt.Print("队列 deque = ") PrintList(deque.toList()) // 访问队首元素 - front := deque.PeekFirst() + front := deque.peekFirst() fmt.Println("队首元素 front =", front) - rear := deque.PeekLast() + rear := deque.peekLast() fmt.Println("队尾元素 rear =", rear) // 元素出队 - pollFirst := deque.PollFirst() + pollFirst := deque.pollFirst() fmt.Print("队首出队元素 pollFirst = ", pollFirst, ",队首出队后 deque = ") PrintList(deque.toList()) - pollLast := deque.PollLast() + pollLast := deque.pollLast() fmt.Print("队尾出队元素 pollLast = ", pollLast, ",队尾出队后 deque = ") PrintList(deque.toList()) // 获取队的长度 - size := deque.Size() + size := deque.size() fmt.Println("队的长度 size =", size) // 判断是否为空 - isEmpty := deque.IsEmpty() + isEmpty := deque.isEmpty() fmt.Println("队是否为空 =", isEmpty) } // BenchmarkArrayQueue 67.92 ns/op in Mac M1 Pro func BenchmarkLinkedListDeque(b *testing.B) { - stack := NewLinkedListDeque() + stack := newLinkedListDeque() // use b.N for looping for i := 0; i < b.N; i++ { - stack.OfferLast(777) + stack.offerLast(777) } for i := 0; i < b.N; i++ { - stack.PollFirst() + stack.pollFirst() } } diff --git a/codes/go/chapter_stack_and_queue/linkedlist_deque.go b/codes/go/chapter_stack_and_queue/linkedlist_deque.go index 019aa778..b1db77ca 100644 --- a/codes/go/chapter_stack_and_queue/linkedlist_deque.go +++ b/codes/go/chapter_stack_and_queue/linkedlist_deque.go @@ -8,31 +8,31 @@ import ( "container/list" ) -// LinkedListDeque 基于链表实现的双端队列, 使用内置包 list 来实现栈 -type LinkedListDeque struct { +// linkedListDeque 基于链表实现的双端队列, 使用内置包 list 来实现栈 +type linkedListDeque struct { data *list.List } -// NewLinkedListDeque 初始化双端队列 -func NewLinkedListDeque() *LinkedListDeque { - return &LinkedListDeque{ +// newLinkedListDeque 初始化双端队列 +func newLinkedListDeque() *linkedListDeque { + return &linkedListDeque{ data: list.New(), } } -// OfferFirst 队首元素入队 -func (s *LinkedListDeque) OfferFirst(value any) { +// offerFirst 队首元素入队 +func (s *linkedListDeque) offerFirst(value any) { s.data.PushFront(value) } -// OfferLast 队尾元素入队 -func (s *LinkedListDeque) OfferLast(value any) { +// offerLast 队尾元素入队 +func (s *linkedListDeque) offerLast(value any) { s.data.PushBack(value) } -// PollFirst 队首元素出队 -func (s *LinkedListDeque) PollFirst() any { - if s.IsEmpty() { +// pollFirst 队首元素出队 +func (s *linkedListDeque) pollFirst() any { + if s.isEmpty() { return nil } e := s.data.Front() @@ -40,9 +40,9 @@ func (s *LinkedListDeque) PollFirst() any { return e.Value } -// PollLast 队尾元素出队 -func (s *LinkedListDeque) PollLast() any { - if s.IsEmpty() { +// pollLast 队尾元素出队 +func (s *linkedListDeque) pollLast() any { + if s.isEmpty() { return nil } e := s.data.Back() @@ -50,35 +50,35 @@ func (s *LinkedListDeque) PollLast() any { return e.Value } -// PeekFirst 访问队首元素 -func (s *LinkedListDeque) PeekFirst() any { - if s.IsEmpty() { +// peekFirst 访问队首元素 +func (s *linkedListDeque) peekFirst() any { + if s.isEmpty() { return nil } e := s.data.Front() return e.Value } -// PeekLast 访问队尾元素 -func (s *LinkedListDeque) PeekLast() any { - if s.IsEmpty() { +// peekLast 访问队尾元素 +func (s *linkedListDeque) peekLast() any { + if s.isEmpty() { return nil } e := s.data.Back() return e.Value } -// Size 获取队列的长度 -func (s *LinkedListDeque) Size() int { +// size 获取队列的长度 +func (s *linkedListDeque) size() int { return s.data.Len() } -// IsEmpty 判断队列是否为空 -func (s *LinkedListDeque) IsEmpty() bool { +// isEmpty 判断队列是否为空 +func (s *linkedListDeque) isEmpty() bool { return s.data.Len() == 0 } // 获取 List 用于打印 -func (s *LinkedListDeque) toList() *list.List { +func (s *linkedListDeque) toList() *list.List { return s.data } diff --git a/codes/go/chapter_stack_and_queue/linkedlist_queue.go b/codes/go/chapter_stack_and_queue/linkedlist_queue.go index d3046180..f5d164a8 100644 --- a/codes/go/chapter_stack_and_queue/linkedlist_queue.go +++ b/codes/go/chapter_stack_and_queue/linkedlist_queue.go @@ -9,26 +9,26 @@ import ( ) /* 基于链表实现的队列 */ -type LinkedListQueue struct { +type linkedListQueue struct { // 使用内置包 list 来实现队列 data *list.List } -// NewLinkedListQueue 初始化链表 -func NewLinkedListQueue() *LinkedListQueue { - return &LinkedListQueue{ +// newLinkedListQueue 初始化链表 +func newLinkedListQueue() *linkedListQueue { + return &linkedListQueue{ data: list.New(), } } -// Offer 入队 -func (s *LinkedListQueue) Offer(value any) { +// offer 入队 +func (s *linkedListQueue) offer(value any) { s.data.PushBack(value) } -// Poll 出队 -func (s *LinkedListQueue) Poll() any { - if s.IsEmpty() { +// poll 出队 +func (s *linkedListQueue) poll() any { + if s.isEmpty() { return nil } e := s.data.Front() @@ -36,26 +36,26 @@ func (s *LinkedListQueue) Poll() any { return e.Value } -// Peek 访问队首元素 -func (s *LinkedListQueue) Peek() any { - if s.IsEmpty() { +// peek 访问队首元素 +func (s *linkedListQueue) peek() any { + if s.isEmpty() { return nil } e := s.data.Front() return e.Value } -// Size 获取队列的长度 -func (s *LinkedListQueue) Size() int { +// size 获取队列的长度 +func (s *linkedListQueue) size() int { return s.data.Len() } -// IsEmpty 判断队列是否为空 -func (s *LinkedListQueue) IsEmpty() bool { +// isEmpty 判断队列是否为空 +func (s *linkedListQueue) isEmpty() bool { return s.data.Len() == 0 } // 获取 List 用于打印 -func (s *LinkedListQueue) toList() *list.List { +func (s *linkedListQueue) toList() *list.List { return s.data } diff --git a/codes/go/chapter_stack_and_queue/linkedlist_stack.go b/codes/go/chapter_stack_and_queue/linkedlist_stack.go index ab8a0628..8509c2b8 100644 --- a/codes/go/chapter_stack_and_queue/linkedlist_stack.go +++ b/codes/go/chapter_stack_and_queue/linkedlist_stack.go @@ -9,26 +9,26 @@ import ( ) /* 基于链表实现的栈 */ -type LinkedListStack struct { +type linkedListStack struct { // 使用内置包 list 来实现栈 data *list.List } -// NewLinkedListStack 初始化链表 -func NewLinkedListStack() *LinkedListStack { - return &LinkedListStack{ +// newLinkedListStack 初始化链表 +func newLinkedListStack() *linkedListStack { + return &linkedListStack{ data: list.New(), } } -// Push 入栈 -func (s *LinkedListStack) Push(value int) { +// push 入栈 +func (s *linkedListStack) push(value int) { s.data.PushBack(value) } -// Pop 出栈 -func (s *LinkedListStack) Pop() any { - if s.IsEmpty() { +// pop 出栈 +func (s *linkedListStack) pop() any { + if s.isEmpty() { return nil } e := s.data.Back() @@ -36,26 +36,26 @@ func (s *LinkedListStack) Pop() any { return e.Value } -// Peek 访问栈顶元素 -func (s *LinkedListStack) Peek() any { - if s.IsEmpty() { +// peek 访问栈顶元素 +func (s *linkedListStack) peek() any { + if s.isEmpty() { return nil } e := s.data.Back() return e.Value } -// Size 获取栈的长度 -func (s *LinkedListStack) Size() int { +// size 获取栈的长度 +func (s *linkedListStack) size() int { return s.data.Len() } -// IsEmpty 判断栈是否为空 -func (s *LinkedListStack) IsEmpty() bool { +// isEmpty 判断栈是否为空 +func (s *linkedListStack) isEmpty() bool { return s.data.Len() == 0 } // 获取 List 用于打印 -func (s *LinkedListStack) toList() *list.List { +func (s *linkedListStack) toList() *list.List { return s.data } diff --git a/codes/go/chapter_stack_and_queue/queue_test.go b/codes/go/chapter_stack_and_queue/queue_test.go index 368becbc..b9ec79df 100644 --- a/codes/go/chapter_stack_and_queue/queue_test.go +++ b/codes/go/chapter_stack_and_queue/queue_test.go @@ -48,87 +48,87 @@ func TestQueue(t *testing.T) { func TestArrayQueue(t *testing.T) { // 初始化队列,使用队列的通用接口 capacity := 10 - queue := NewArrayQueue(capacity) + queue := newArrayQueue(capacity) // 元素入队 - queue.Offer(1) - queue.Offer(3) - queue.Offer(2) - queue.Offer(5) - queue.Offer(4) + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) fmt.Print("队列 queue = ") PrintSlice(queue.toSlice()) // 访问队首元素 - peek := queue.Peek() + peek := queue.peek() fmt.Println("队首元素 peek =", peek) // 元素出队 - poll := queue.Poll() + poll := queue.poll() fmt.Print("出队元素 poll = ", poll, ", 出队后 queue = ") PrintSlice(queue.toSlice()) // 获取队的长度 - size := queue.Size() + size := queue.size() fmt.Println("队的长度 size =", size) // 判断是否为空 - isEmpty := queue.IsEmpty() + isEmpty := queue.isEmpty() fmt.Println("队是否为空 =", isEmpty) } func TestLinkedListQueue(t *testing.T) { // 初始化队 - queue := NewLinkedListQueue() + queue := newLinkedListQueue() // 元素入队 - queue.Offer(1) - queue.Offer(3) - queue.Offer(2) - queue.Offer(5) - queue.Offer(4) + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) fmt.Print("队列 queue = ") PrintList(queue.toList()) // 访问队首元素 - peek := queue.Peek() + peek := queue.peek() fmt.Println("队首元素 peek =", peek) // 元素出队 - poll := queue.Poll() + poll := queue.poll() fmt.Print("出队元素 poll = ", poll, ", 出队后 queue = ") PrintList(queue.toList()) // 获取队的长度 - size := queue.Size() + size := queue.size() fmt.Println("队的长度 size =", size) // 判断是否为空 - isEmpty := queue.IsEmpty() + isEmpty := queue.isEmpty() fmt.Println("队是否为空 =", isEmpty) } // BenchmarkArrayQueue 8 ns/op in Mac M1 Pro func BenchmarkArrayQueue(b *testing.B) { capacity := 1000 - stack := NewArrayQueue(capacity) + stack := newArrayQueue(capacity) // use b.N for looping for i := 0; i < b.N; i++ { - stack.Offer(777) + stack.offer(777) } for i := 0; i < b.N; i++ { - stack.Poll() + stack.poll() } } // BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro func BenchmarkLinkedQueue(b *testing.B) { - stack := NewLinkedListQueue() + stack := newLinkedListQueue() // use b.N for looping for i := 0; i < b.N; i++ { - stack.Offer(777) + stack.offer(777) } for i := 0; i < b.N; i++ { - stack.Poll() + stack.poll() } } diff --git a/codes/go/chapter_stack_and_queue/stack_test.go b/codes/go/chapter_stack_and_queue/stack_test.go index 9dc97e69..a4cf338b 100644 --- a/codes/go/chapter_stack_and_queue/stack_test.go +++ b/codes/go/chapter_stack_and_queue/stack_test.go @@ -46,85 +46,85 @@ func TestStack(t *testing.T) { func TestArrayStack(t *testing.T) { // 初始化栈, 使用接口承接 - stack := NewArrayStack() + stack := newArrayStack() // 元素入栈 - stack.Push(1) - stack.Push(3) - stack.Push(2) - stack.Push(5) - stack.Push(4) + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) fmt.Print("栈 stack = ") PrintSlice(stack.toSlice()) // 访问栈顶元素 - peek := stack.Peek() + peek := stack.peek() fmt.Println("栈顶元素 peek =", peek) // 元素出栈 - pop := stack.Pop() + pop := stack.pop() fmt.Print("出栈元素 pop = ", pop, ", 出栈后 stack = ") PrintSlice(stack.toSlice()) // 获取栈的长度 - size := stack.Size() + size := stack.size() fmt.Println("栈的长度 size =", size) // 判断是否为空 - isEmpty := stack.IsEmpty() + isEmpty := stack.isEmpty() fmt.Println("栈是否为空 =", isEmpty) } func TestLinkedListStack(t *testing.T) { // 初始化栈 - stack := NewLinkedListStack() + stack := newLinkedListStack() // 元素入栈 - stack.Push(1) - stack.Push(3) - stack.Push(2) - stack.Push(5) - stack.Push(4) + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) fmt.Print("栈 stack = ") PrintList(stack.toList()) // 访问栈顶元素 - peek := stack.Peek() + peek := stack.peek() fmt.Println("栈顶元素 peek =", peek) // 元素出栈 - pop := stack.Pop() + pop := stack.pop() fmt.Print("出栈元素 pop = ", pop, ", 出栈后 stack = ") PrintList(stack.toList()) // 获取栈的长度 - size := stack.Size() + size := stack.size() fmt.Println("栈的长度 size =", size) // 判断是否为空 - isEmpty := stack.IsEmpty() + isEmpty := stack.isEmpty() fmt.Println("栈是否为空 =", isEmpty) } // BenchmarkArrayStack 8 ns/op in Mac M1 Pro func BenchmarkArrayStack(b *testing.B) { - stack := NewArrayStack() + stack := newArrayStack() // use b.N for looping for i := 0; i < b.N; i++ { - stack.Push(777) + stack.push(777) } for i := 0; i < b.N; i++ { - stack.Pop() + stack.pop() } } // BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro func BenchmarkLinkedListStack(b *testing.B) { - stack := NewLinkedListStack() + stack := newLinkedListStack() // use b.N for looping for i := 0; i < b.N; i++ { - stack.Push(777) + stack.push(777) } for i := 0; i < b.N; i++ { - stack.Pop() + stack.pop() } } diff --git a/codes/go/chapter_tree/avl_tree.go b/codes/go/chapter_tree/avl_tree.go new file mode 100644 index 00000000..e83b2882 --- /dev/null +++ b/codes/go/chapter_tree/avl_tree.go @@ -0,0 +1,211 @@ +// 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 Tree*/ +type avlTree struct { + // 根节点 + root *TreeNode +} + +func newAVLTree() *avlTree { + return &avlTree{root: nil} +} + +/* 获取结点高度 */ +func height(node *TreeNode) int { + // 空结点高度为 -1 ,叶结点高度为 0 + if node != nil { + return node.Height + } + return -1 +} + +/* 更新结点高度 */ +func updateHeight(node *TreeNode) { + lh := height(node.Left) + rh := height(node.Right) + // 结点高度等于最高子树高度 + 1 + if lh > rh { + node.Height = lh + 1 + } else { + node.Height = rh + 1 + } +} + +/* 获取平衡因子 */ +func balanceFactor(node *TreeNode) int { + // 空结点平衡因子为 0 + if node == nil { + return 0 + } + // 结点平衡因子 = 左子树高度 - 右子树高度 + return height(node.Left) - height(node.Right) +} + +/* 右旋操作 */ +func rightRotate(node *TreeNode) *TreeNode { + child := node.Left + grandChild := child.Right + // 以 child 为原点,将 node 向右旋转 + child.Right = node + node.Left = grandChild + // 更新结点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child +} + +/* 左旋操作 */ +func leftRotate(node *TreeNode) *TreeNode { + child := node.Right + grandChild := child.Left + // 以 child 为原点,将 node 向左旋转 + child.Left = node + node.Right = grandChild + // 更新结点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child +} + +/* 执行旋转操作,使该子树重新恢复平衡 */ +func rotate(node *TreeNode) *TreeNode { + // 获取结点 node 的平衡因子 + // Go 推荐短变量,这里 bf 指代 balanceFactor + 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 +} + +/* 插入结点 */ +func (t *avlTree) insert(val int) *TreeNode { + t.root = insertHelper(t.root, val) + return t.root +} + +/* 递归插入结点(辅助函数) */ +func insertHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + 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 +} + +/* 删除结点 */ +func (t *avlTree) remove(val int) *TreeNode { + root := removeHelper(t.root, val) + return root +} + +/* 递归删除结点(辅助函数) */ +func removeHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return nil + } + /* 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 == nil || node.Right == nil { + child := node.Left + if node.Right != nil { + child = node.Right + } + // 子结点数量 = 0 ,直接删除 node 并返回 + if child == nil { + return nil + } else { + // 子结点数量 = 1 ,直接删除 node + node = child + } + } else { + // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 + temp := getInOrderNext(node.Right) + node.Right = removeHelper(node.Right, temp.Val) + node.Val = temp.Val + } + } + // 更新结点高度 + updateHeight(node) + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node) + // 返回子树的根节点 + return node +} + +/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ +func getInOrderNext(node *TreeNode) *TreeNode { + if node == nil { + return node + } + // 循环访问左子结点,直到叶结点时为最小结点,跳出 + for node.Left != nil { + node = node.Left + } + return node +} + +/* 查找结点 */ +func (t *avlTree) search(val int) *TreeNode { + cur := t.root + // 循环查找,越过叶结点后跳出 + for cur != nil { + // 目标结点在 root 的右子树中 + if cur.Val < val { + cur = cur.Right + } else if cur.Val > val { + // 目标结点在 root 的左子树中 + cur = cur.Left + } else { + // 找到目标结点,跳出循环 + break + } + } + // 返回目标结点 + return cur +} diff --git a/codes/go/chapter_tree/avl_tree_test.go b/codes/go/chapter_tree/avl_tree_test.go new file mode 100644 index 00000000..c4fc6b71 --- /dev/null +++ b/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/codes/go/chapter_tree/binary_search_tree.go b/codes/go/chapter_tree/binary_search_tree.go index 2af2e7be..61ed400d 100644 --- a/codes/go/chapter_tree/binary_search_tree.go +++ b/codes/go/chapter_tree/binary_search_tree.go @@ -10,26 +10,26 @@ import ( . "github.com/krahets/hello-algo/pkg" ) -type BinarySearchTree struct { +type binarySearchTree struct { root *TreeNode } -func NewBinarySearchTree(nums []int) *BinarySearchTree { +func newBinarySearchTree(nums []int) *binarySearchTree { // sorting array sort.Ints(nums) root := buildBinarySearchTree(nums, 0, len(nums)-1) - return &BinarySearchTree{ + return &binarySearchTree{ root: root, } } -// GetRoot Get the root node of binary search tree -func (bst *BinarySearchTree) GetRoot() *TreeNode { +/* 获取根结点 */ +func (bst *binarySearchTree) getRoot() *TreeNode { return bst.root } -// GetMin Get node with the min value -func (bst *BinarySearchTree) GetMin(node *TreeNode) *TreeNode { +/* 获取中序遍历的下一个结点 */ +func (bst *binarySearchTree) getInOrderNext(node *TreeNode) *TreeNode { if node == nil { return node } @@ -40,21 +40,8 @@ func (bst *BinarySearchTree) GetMin(node *TreeNode) *TreeNode { return node } -// GetInorderNext Get node inorder next -func (bst *BinarySearchTree) GetInorderNext(node *TreeNode) *TreeNode { - if node == nil || node.Right == nil { - return node - } - node = node.Right - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - for node.Left != nil { - node = node.Left - } - return node -} - /* 查找结点 */ -func (bst *BinarySearchTree) Search(num int) *TreeNode { +func (bst *binarySearchTree) search(num int) *TreeNode { node := bst.root // 循环查找,越过叶结点后跳出 for node != nil { @@ -74,7 +61,7 @@ func (bst *BinarySearchTree) Search(num int) *TreeNode { } /* 插入结点 */ -func (bst *BinarySearchTree) Insert(num int) *TreeNode { +func (bst *binarySearchTree) insert(num int) *TreeNode { cur := bst.root // 若树为空,直接提前返回 if cur == nil { @@ -105,7 +92,7 @@ func (bst *BinarySearchTree) Insert(num int) *TreeNode { } /* 删除结点 */ -func (bst *BinarySearchTree) Remove(num int) *TreeNode { +func (bst *binarySearchTree) remove(num int) *TreeNode { cur := bst.root // 若树为空,直接提前返回 if cur == nil { @@ -149,10 +136,10 @@ func (bst *BinarySearchTree) Remove(num int) *TreeNode { // 子结点数为 2 } else { // 获取中序遍历中待删除结点 cur 的下一个结点 - next := bst.GetInorderNext(cur) + next := bst.getInOrderNext(cur) temp := next.Val // 递归删除结点 next - bst.Remove(next.Val) + bst.remove(next.Val) // 将 next 的值复制给 cur cur.Val = temp } @@ -173,7 +160,7 @@ func buildBinarySearchTree(nums []int, left, right int) *TreeNode { return root } -// Print binary search tree -func (bst *BinarySearchTree) Print() { +// print binary search tree +func (bst *binarySearchTree) print() { PrintTree(bst.root) } diff --git a/codes/go/chapter_tree/binary_search_tree_test.go b/codes/go/chapter_tree/binary_search_tree_test.go index d98e5d66..2109d5a4 100644 --- a/codes/go/chapter_tree/binary_search_tree_test.go +++ b/codes/go/chapter_tree/binary_search_tree_test.go @@ -11,34 +11,31 @@ import ( func TestBinarySearchTree(t *testing.T) { nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - bst := NewBinarySearchTree(nums) - fmt.Println("初始化的二叉树为:") - bst.Print() + bst := newBinarySearchTree(nums) + fmt.Println("\n初始化的二叉树为:") + bst.print() // 获取根结点 - node := bst.GetRoot() - fmt.Println("二叉树的根结点为:", node.Val) - // 获取最小的结点 - node = bst.GetMin(bst.GetRoot()) - fmt.Println("二叉树的最小结点为:", node.Val) + node := bst.getRoot() + fmt.Println("\n二叉树的根结点为:", node.Val) // 查找结点 node = bst.Search(7) fmt.Println("查找到的结点对象为", node, ",结点值 =", node.Val) // 插入结点 - node = bst.Insert(16) - fmt.Println("插入结点后 16 的二叉树为:") - bst.Print() + node = bst.insert(16) + fmt.Println("\n插入结点后 16 的二叉树为:") + bst.print() // 删除结点 - bst.Remove(1) - fmt.Println("删除结点 1 后的二叉树为:") - bst.Print() - bst.Remove(2) - fmt.Println("删除结点 2 后的二叉树为:") - bst.Print() - bst.Remove(4) - fmt.Println("删除结点 4 后的二叉树为:") - 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/codes/go/chapter_tree/binary_tree_bfs_test.go b/codes/go/chapter_tree/binary_tree_bfs_test.go index 8d5c0aaf..7b1bbf1f 100644 --- a/codes/go/chapter_tree/binary_tree_bfs_test.go +++ b/codes/go/chapter_tree/binary_tree_bfs_test.go @@ -14,11 +14,11 @@ import ( func TestLevelOrder(t *testing.T) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - root := ArrayToTree([]int{1, 2, 3, 4, 5, 6, 7}) - fmt.Println("初始化二叉树: ") + root := ArrToTree([]any{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二叉树: ") PrintTree(root) // 层序遍历 nums := levelOrder(root) - fmt.Println("层序遍历的结点打印序列 =", nums) + fmt.Println("\n层序遍历的结点打印序列 =", nums) } diff --git a/codes/go/chapter_tree/binary_tree_dfs_test.go b/codes/go/chapter_tree/binary_tree_dfs_test.go index 67a3e1f3..9e0dfe22 100644 --- a/codes/go/chapter_tree/binary_tree_dfs_test.go +++ b/codes/go/chapter_tree/binary_tree_dfs_test.go @@ -14,22 +14,22 @@ import ( func TestPreInPostOrderTraversal(t *testing.T) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - root := ArrayToTree([]int{1, 2, 3, 4, 5, 6, 7}) - fmt.Println("初始化二叉树: ") + root := ArrToTree([]any{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二叉树: ") PrintTree(root) // 前序遍历 nums = nil preOrder(root) - fmt.Println("前序遍历的结点打印序列 =", nums) + fmt.Println("\n前序遍历的结点打印序列 =", nums) // 中序遍历 nums = nil inOrder(root) - fmt.Println("中序遍历的结点打印序列 =", nums) + fmt.Println("\n中序遍历的结点打印序列 =", nums) // 后序遍历 nums = nil postOrder(root) - fmt.Println("后序遍历的结点打印序列 =", nums) + fmt.Println("\n后序遍历的结点打印序列 =", nums) } diff --git a/codes/go/pkg/print_utils.go b/codes/go/pkg/print_utils.go index a42d48b2..575ab6b9 100644 --- a/codes/go/pkg/print_utils.go +++ b/codes/go/pkg/print_utils.go @@ -76,7 +76,7 @@ func printTreeHelper(root *TreeNode, prev *trunk, isLeft bool) { printTreeHelper(root.Left, trunk, false) } -// trunk Help to Print tree structure +// trunk Help to print tree structure type trunk struct { prev *trunk str string @@ -103,4 +103,4 @@ func PrintMap[K comparable, V any](m map[K]V) { for key, value := range m { fmt.Println(key, "->", value) } -} \ No newline at end of file +} diff --git a/codes/go/pkg/tree_node.go b/codes/go/pkg/tree_node.go index 2511a765..6baed50e 100644 --- a/codes/go/pkg/tree_node.go +++ b/codes/go/pkg/tree_node.go @@ -9,41 +9,48 @@ import ( ) type TreeNode struct { - Val int - Left *TreeNode - Right *TreeNode + Val int // 结点值 + Height int // 结点高度 + Left *TreeNode // 左子结点引用 + Right *TreeNode // 右子结点引用 } func NewTreeNode(v int) *TreeNode { return &TreeNode{ - Left: nil, - Right: nil, - Val: v, + Val: v, + Height: 0, + Left: nil, + Right: nil, } } -// ArrayToTree Generate a binary tree with an array -func ArrayToTree(arr []int) *TreeNode { +// ArrToTree Generate a binary tree given an array +func ArrToTree(arr []any) *TreeNode { if len(arr) <= 0 { return nil } - root := NewTreeNode(arr[0]) + // TreeNode only accept integer value for now. + root := NewTreeNode(arr[0].(int)) // Let container.list as queue queue := list.New() queue.PushBack(root) - i := 1 + i := 0 for queue.Len() > 0 { // poll node := queue.Remove(queue.Front()).(*TreeNode) + i++ if i < len(arr) { - node.Left = NewTreeNode(arr[i]) - queue.PushBack(node.Left) - i++ + if arr[i] != nil { + node.Left = NewTreeNode(arr[i].(int)) + queue.PushBack(node.Left) + } } + i++ if i < len(arr) { - node.Right = NewTreeNode(arr[i]) - queue.PushBack(node.Right) - i++ + if arr[i] != nil { + node.Right = NewTreeNode(arr[i].(int)) + queue.PushBack(node.Right) + } } } return root diff --git a/codes/go/pkg/tree_node_test.go b/codes/go/pkg/tree_node_test.go index b4c8e077..ec7831bf 100644 --- a/codes/go/pkg/tree_node_test.go +++ b/codes/go/pkg/tree_node_test.go @@ -10,8 +10,8 @@ import ( ) func TestTreeNode(t *testing.T) { - arr := []int{2, 3, 5, 6, 7} - node := ArrayToTree(arr) + arr := []any{1, 2, 3, nil, 5, 6, nil} + node := ArrToTree(arr) // print tree PrintTree(node) diff --git a/codes/java/chapter_array_and_linkedlist/array.java b/codes/java/chapter_array_and_linkedlist/array.java index 6afd3dfd..d3cb176c 100644 --- a/codes/java/chapter_array_and_linkedlist/array.java +++ b/codes/java/chapter_array_and_linkedlist/array.java @@ -1,4 +1,4 @@ -/* +/** * File: array.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_array_and_linkedlist/linked_list.java b/codes/java/chapter_array_and_linkedlist/linked_list.java index 3d445117..0db8f6ae 100644 --- a/codes/java/chapter_array_and_linkedlist/linked_list.java +++ b/codes/java/chapter_array_and_linkedlist/linked_list.java @@ -1,4 +1,4 @@ -/* +/** * File: linked_list.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -29,9 +29,9 @@ public class linked_list { /* 访问链表中索引为 index 的结点 */ static ListNode access(ListNode head, int index) { for (int i = 0; i < index; i++) { - head = head.next; if (head == null) return null; + head = head.next; } return head; } diff --git a/codes/java/chapter_array_and_linkedlist/list.java b/codes/java/chapter_array_and_linkedlist/list.java index 1f13bc2e..dc4d897f 100644 --- a/codes/java/chapter_array_and_linkedlist/list.java +++ b/codes/java/chapter_array_and_linkedlist/list.java @@ -1,4 +1,4 @@ -/* +/** * File: list.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_array_and_linkedlist/my_list.java b/codes/java/chapter_array_and_linkedlist/my_list.java index 1f9fdc3e..cce8da68 100644 --- a/codes/java/chapter_array_and_linkedlist/my_list.java +++ b/codes/java/chapter_array_and_linkedlist/my_list.java @@ -1,4 +1,4 @@ -/* +/** * File: my_list.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_computational_complexity/leetcode_two_sum.java b/codes/java/chapter_computational_complexity/leetcode_two_sum.java index 45106867..b9f8bf4d 100644 --- a/codes/java/chapter_computational_complexity/leetcode_two_sum.java +++ b/codes/java/chapter_computational_complexity/leetcode_two_sum.java @@ -1,4 +1,4 @@ -/* +/** * File: leetcode_two_sum.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_computational_complexity/space_complexity.java b/codes/java/chapter_computational_complexity/space_complexity.java index a81026d8..275a37ec 100644 --- a/codes/java/chapter_computational_complexity/space_complexity.java +++ b/codes/java/chapter_computational_complexity/space_complexity.java @@ -1,4 +1,4 @@ -/* +/** * File: space_complexity.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_computational_complexity/time_complexity.java b/codes/java/chapter_computational_complexity/time_complexity.java index d303c510..5a232e5b 100644 --- a/codes/java/chapter_computational_complexity/time_complexity.java +++ b/codes/java/chapter_computational_complexity/time_complexity.java @@ -1,4 +1,4 @@ -/* +/** * File: time_complexity.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_computational_complexity/worst_best_time_complexity.java b/codes/java/chapter_computational_complexity/worst_best_time_complexity.java index 536bf3af..c0fe0baa 100644 --- a/codes/java/chapter_computational_complexity/worst_best_time_complexity.java +++ b/codes/java/chapter_computational_complexity/worst_best_time_complexity.java @@ -1,4 +1,4 @@ -/* +/** * File: worst_best_time_complexity.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_hashing/array_hash_map.java b/codes/java/chapter_hashing/array_hash_map.java index fb0073ab..68df1f3b 100644 --- a/codes/java/chapter_hashing/array_hash_map.java +++ b/codes/java/chapter_hashing/array_hash_map.java @@ -1,4 +1,4 @@ -/* +/** * File: hash_map.java * Created Time: 2022-12-04 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_hashing/hash_map.java b/codes/java/chapter_hashing/hash_map.java index bb72a4b0..fe3f822f 100644 --- a/codes/java/chapter_hashing/hash_map.java +++ b/codes/java/chapter_hashing/hash_map.java @@ -1,4 +1,4 @@ -/* +/** * File: hash_map.java * Created Time: 2022-12-04 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_searching/binary_search.java b/codes/java/chapter_searching/binary_search.java index cb5bd941..05c2a4d3 100644 --- a/codes/java/chapter_searching/binary_search.java +++ b/codes/java/chapter_searching/binary_search.java @@ -1,4 +1,4 @@ -/* +/** * File: binary_search.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_searching/hashing_search.java b/codes/java/chapter_searching/hashing_search.java index c68025de..cfd01531 100644 --- a/codes/java/chapter_searching/hashing_search.java +++ b/codes/java/chapter_searching/hashing_search.java @@ -1,4 +1,4 @@ -/* +/** * File: hashing_search.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_searching/linear_search.java b/codes/java/chapter_searching/linear_search.java index 2e8e410e..c33b5117 100644 --- a/codes/java/chapter_searching/linear_search.java +++ b/codes/java/chapter_searching/linear_search.java @@ -1,4 +1,4 @@ -/* +/** * File: linear_search.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_sorting/bubble_sort.java b/codes/java/chapter_sorting/bubble_sort.java index f006a3ab..082d2d90 100644 --- a/codes/java/chapter_sorting/bubble_sort.java +++ b/codes/java/chapter_sorting/bubble_sort.java @@ -1,4 +1,4 @@ -/* +/** * File: bubble_sort.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_sorting/insertion_sort.java b/codes/java/chapter_sorting/insertion_sort.java index 9a6a0310..fb5b2cd6 100644 --- a/codes/java/chapter_sorting/insertion_sort.java +++ b/codes/java/chapter_sorting/insertion_sort.java @@ -1,4 +1,4 @@ -/* +/** * File: insertion_sort.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_sorting/merge_sort.java b/codes/java/chapter_sorting/merge_sort.java index 498a6fd8..68f76c9b 100644 --- a/codes/java/chapter_sorting/merge_sort.java +++ b/codes/java/chapter_sorting/merge_sort.java @@ -1,4 +1,4 @@ -/* +/** * File: merge_sort.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -28,10 +28,10 @@ public class merge_sort { // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ else if (j > rightEnd || tmp[i] <= tmp[j]) nums[k] = tmp[i++]; - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ else nums[k] = tmp[j++]; } diff --git a/codes/java/chapter_sorting/quick_sort.java b/codes/java/chapter_sorting/quick_sort.java index d65aa8e4..b121b757 100644 --- a/codes/java/chapter_sorting/quick_sort.java +++ b/codes/java/chapter_sorting/quick_sort.java @@ -1,4 +1,4 @@ -/* +/** * File: quick_sort.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_stack_and_queue/array_queue.java b/codes/java/chapter_stack_and_queue/array_queue.java index 198c9071..5a1ab645 100644 --- a/codes/java/chapter_stack_and_queue/array_queue.java +++ b/codes/java/chapter_stack_and_queue/array_queue.java @@ -1,4 +1,4 @@ -/* +/** * File: array_queue.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_stack_and_queue/array_stack.java b/codes/java/chapter_stack_and_queue/array_stack.java index 12ac5283..e32b1640 100644 --- a/codes/java/chapter_stack_and_queue/array_stack.java +++ b/codes/java/chapter_stack_and_queue/array_stack.java @@ -1,4 +1,4 @@ -/* +/** * File: array_stack.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_stack_and_queue/deque.java b/codes/java/chapter_stack_and_queue/deque.java index a29e6e7c..1a8f196b 100644 --- a/codes/java/chapter_stack_and_queue/deque.java +++ b/codes/java/chapter_stack_and_queue/deque.java @@ -1,4 +1,4 @@ -/* +/** * File: deque.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_stack_and_queue/linkedlist_queue.java b/codes/java/chapter_stack_and_queue/linkedlist_queue.java index 92cdfab0..82e2571c 100644 --- a/codes/java/chapter_stack_and_queue/linkedlist_queue.java +++ b/codes/java/chapter_stack_and_queue/linkedlist_queue.java @@ -1,4 +1,4 @@ -/* +/** * File: linkedlist_queue.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_stack_and_queue/linkedlist_stack.java b/codes/java/chapter_stack_and_queue/linkedlist_stack.java index 794c80c3..b1a78aa4 100644 --- a/codes/java/chapter_stack_and_queue/linkedlist_stack.java +++ b/codes/java/chapter_stack_and_queue/linkedlist_stack.java @@ -1,4 +1,4 @@ -/* +/** * File: linkedlist_stack.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_stack_and_queue/queue.java b/codes/java/chapter_stack_and_queue/queue.java index 232943fb..25457fbd 100644 --- a/codes/java/chapter_stack_and_queue/queue.java +++ b/codes/java/chapter_stack_and_queue/queue.java @@ -1,4 +1,4 @@ -/* +/** * File: queue.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_stack_and_queue/stack.java b/codes/java/chapter_stack_and_queue/stack.java index 9c94eb62..2a725922 100644 --- a/codes/java/chapter_stack_and_queue/stack.java +++ b/codes/java/chapter_stack_and_queue/stack.java @@ -1,4 +1,4 @@ -/* +/** * File: stack.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_tree/avl_tree.java b/codes/java/chapter_tree/avl_tree.java index 48be762e..9120262f 100644 --- a/codes/java/chapter_tree/avl_tree.java +++ b/codes/java/chapter_tree/avl_tree.java @@ -1,4 +1,4 @@ -/* +/** * File: avl_tree.java * Created Time: 2022-12-10 * Author: Krahets (krahets@163.com) @@ -138,7 +138,7 @@ class AVLTree { node = child; } else { // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode temp = minNode(node.right); + TreeNode temp = getInOrderNext(node.right); node.right = removeHelper(node.right, temp.val); node.val = temp.val; } @@ -150,8 +150,8 @@ class AVLTree { return node; } - /* 获取最小结点 */ - private TreeNode minNode(TreeNode node) { + /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + private TreeNode getInOrderNext(TreeNode node) { if (node == null) return node; // 循环访问左子结点,直到叶结点时为最小结点,跳出 while (node.left != null) { diff --git a/codes/java/chapter_tree/binary_search_tree.java b/codes/java/chapter_tree/binary_search_tree.java index 1755df56..a3b0f758 100644 --- a/codes/java/chapter_tree/binary_search_tree.java +++ b/codes/java/chapter_tree/binary_search_tree.java @@ -1,4 +1,4 @@ -/* +/** * File: binary_search_tree.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -101,7 +101,7 @@ class BinarySearchTree { // 子结点数量 = 2 else { // 获取中序遍历中 cur 的下一个结点 - TreeNode nex = min(cur.right); + TreeNode nex = getInOrderNext(cur.right); int tmp = nex.val; // 递归删除结点 nex remove(nex.val); @@ -111,8 +111,8 @@ class BinarySearchTree { return cur; } - /* 获取最小结点 */ - public TreeNode min(TreeNode root) { + /* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ + public TreeNode getInOrderNext(TreeNode root) { if (root == null) return root; // 循环访问左子结点,直到叶结点时为最小结点,跳出 while (root.left != null) { diff --git a/codes/java/chapter_tree/binary_tree.java b/codes/java/chapter_tree/binary_tree.java index 8f25a9a1..65a511e8 100644 --- a/codes/java/chapter_tree/binary_tree.java +++ b/codes/java/chapter_tree/binary_tree.java @@ -1,4 +1,4 @@ -/* +/** * File: binary_tree.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/chapter_tree/binary_tree_bfs.java b/codes/java/chapter_tree/binary_tree_bfs.java index 524274a6..450311d0 100644 --- a/codes/java/chapter_tree/binary_tree_bfs.java +++ b/codes/java/chapter_tree/binary_tree_bfs.java @@ -1,4 +1,4 @@ -/* +/** * File: binary_tree_bfs.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -30,8 +30,7 @@ public class binary_tree_bfs { public static void main(String[] args) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode root = TreeNode.arrToTree(new Integer[] { - 1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null }); + TreeNode root = TreeNode.arrToTree(new Integer[] { 1, 2, 3, 4, 5, 6, 7 }); System.out.println("\n初始化二叉树\n"); PrintUtil.printTree(root); diff --git a/codes/java/chapter_tree/binary_tree_dfs.java b/codes/java/chapter_tree/binary_tree_dfs.java index fa866d96..ad5337f2 100644 --- a/codes/java/chapter_tree/binary_tree_dfs.java +++ b/codes/java/chapter_tree/binary_tree_dfs.java @@ -1,4 +1,4 @@ -/* +/** * File: binary_tree_dfs.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -43,8 +43,7 @@ public class binary_tree_dfs { public static void main(String[] args) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode root = TreeNode.arrToTree(new Integer[] { - 1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null}); + TreeNode root = TreeNode.arrToTree(new Integer[] { 1, 2, 3, 4, 5, 6, 7 }); System.out.println("\n初始化二叉树\n"); PrintUtil.printTree(root); diff --git a/codes/java/include/ListNode.java b/codes/java/include/ListNode.java index 14308feb..60590100 100755 --- a/codes/java/include/ListNode.java +++ b/codes/java/include/ListNode.java @@ -1,4 +1,4 @@ -/* +/** * File: ListNode.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) diff --git a/codes/java/include/PrintUtil.java b/codes/java/include/PrintUtil.java index b8c97050..b2593ac1 100755 --- a/codes/java/include/PrintUtil.java +++ b/codes/java/include/PrintUtil.java @@ -1,4 +1,4 @@ -/* +/** * File: PrintUtil.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -8,6 +8,7 @@ package include; import java.util.*; + class Trunk { Trunk prev; String str; @@ -103,4 +104,11 @@ public class PrintUtil { System.out.println(kv.getKey() + " -> " + kv.getValue()); } } + + public static void printHeap(PriorityQueue queue) { + Integer[] nums = (Integer[])queue.toArray(); + TreeNode root = TreeNode.arrToTree(nums); + + printTree(root); + } } diff --git a/codes/java/include/TreeNode.java b/codes/java/include/TreeNode.java index 98b9f826..11d457a1 100644 --- a/codes/java/include/TreeNode.java +++ b/codes/java/include/TreeNode.java @@ -1,4 +1,4 @@ -/* +/** * File: TreeNode.java * Created Time: 2022-11-25 * Author: Krahets (krahets@163.com) @@ -22,7 +22,7 @@ public class TreeNode { } /** - * Generate a binary tree with an array + * Generate a binary tree given an array * @param arr * @return */ @@ -32,19 +32,19 @@ public class TreeNode { TreeNode root = new TreeNode(arr[0]); Queue queue = new LinkedList<>() {{ add(root); }}; - int i = 1; + int i = 0; while(!queue.isEmpty()) { TreeNode node = queue.poll(); + if (++i >= arr.length) break; if(arr[i] != null) { node.left = new TreeNode(arr[i]); queue.add(node.left); } - i++; + if (++i >= arr.length) break; if(arr[i] != null) { node.right = new TreeNode(arr[i]); queue.add(node.right); } - i++; } return root; } @@ -71,20 +71,4 @@ public class TreeNode { } return list; } - - /** - * Get a tree node with specific value in a binary tree - * @param root - * @param val - * @return - */ - public static TreeNode getTreeNode(TreeNode root, int val) { - if (root == null) - return null; - if (root.val == val) - return root; - TreeNode left = getTreeNode(root.left, val); - TreeNode right = getTreeNode(root.right, val); - return left != null ? left : right; - } } diff --git a/codes/javascript/chapter_array_and_linkedlist/list.js b/codes/javascript/chapter_array_and_linkedlist/list.js index 19318337..59391758 100644 --- a/codes/javascript/chapter_array_and_linkedlist/list.js +++ b/codes/javascript/chapter_array_and_linkedlist/list.js @@ -1,4 +1,4 @@ -/* +/** * File: list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) diff --git a/codes/javascript/chapter_array_and_linkedlist/my_list.js b/codes/javascript/chapter_array_and_linkedlist/my_list.js index a861b1f0..388bb1f3 100644 --- a/codes/javascript/chapter_array_and_linkedlist/my_list.js +++ b/codes/javascript/chapter_array_and_linkedlist/my_list.js @@ -1,4 +1,4 @@ -/* +/** * File: my_list.js * Created Time: 2022-12-12 * Author: Justin (xiefahit@gmail.com) diff --git a/codes/javascript/chapter_computational_complexity/time_complexity.js b/codes/javascript/chapter_computational_complexity/time_complexity.js new file mode 100644 index 00000000..2e4688dd --- /dev/null +++ b/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; + // 循环次数与数组长度成平方关系 + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方阶(冒泡排序) */ +function bubbleSort(nums) { + let count = 0; // 计数器 + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (let i = nums.length - 1; 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; + // cell 每轮一分为二,形成数列 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/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js b/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js new file mode 100644 index 00000000..949e50b0 --- /dev/null +++ b/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js @@ -0,0 +1,45 @@ +/* + * File: worst_best_time_complexity.js + * Created Time: 2023-01-05 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ +function randomNumbers(n) { + let 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++) { + let r = Math.floor(Math.random() * (i + 1)); + let 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++) { + if (nums[i] === 1) { + return i; + } + } + return -1; +} + +/* Driver Code */ +function main() { + for (let i = 0; i < 10; i++) { + let n = 100; + let nums = randomNumbers(n); + let index = findOne(nums); + console.log( + "\n数组 [ 1, 2, ..., n ] 被打乱后 = [" + nums.join(", ") + "]" + ); + console.log("数字 1 的索引为 " + index); + } +} diff --git a/codes/javascript/chapter_hashing/array_hash_map.js b/codes/javascript/chapter_hashing/array_hash_map.js index f5c77aa2..405d0f31 100644 --- a/codes/javascript/chapter_hashing/array_hash_map.js +++ b/codes/javascript/chapter_hashing/array_hash_map.js @@ -1,4 +1,4 @@ -/* +/** * File: array_hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) diff --git a/codes/javascript/chapter_hashing/hash_map.js b/codes/javascript/chapter_hashing/hash_map.js index 197fb7dd..9157b315 100644 --- a/codes/javascript/chapter_hashing/hash_map.js +++ b/codes/javascript/chapter_hashing/hash_map.js @@ -1,4 +1,4 @@ -/* +/** * File: hash_map.js * Created Time: 2022-12-26 * Author: Justin (xiefahit@gmail.com) diff --git a/codes/javascript/chapter_sorting/merge_sort.js b/codes/javascript/chapter_sorting/merge_sort.js index 31860ce1..b00c17bc 100644 --- a/codes/javascript/chapter_sorting/merge_sort.js +++ b/codes/javascript/chapter_sorting/merge_sort.js @@ -23,10 +23,10 @@ function merge(nums, left, mid, right) { // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) { nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ } else if (j > rightEnd || tmp[i] <= tmp[j]) { nums[k] = tmp[i++]; - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ } else { nums[k] = tmp[j++]; } diff --git a/codes/javascript/chapter_tree/binary_search_tree.js b/codes/javascript/chapter_tree/binary_search_tree.js index 345cff55..9c808aaa 100644 --- a/codes/javascript/chapter_tree/binary_search_tree.js +++ b/codes/javascript/chapter_tree/binary_search_tree.js @@ -98,7 +98,7 @@ function remove(num) { // 子结点数量 = 2 else { // 获取中序遍历中 cur 的下一个结点 - let nex = min(cur.right); + let nex = getInOrderNext(cur.right); let tmp = nex.val; // 递归删除结点 nex remove(nex.val); @@ -108,8 +108,8 @@ function remove(num) { return cur; } -/* 获取最小结点 */ -function min(root) { +/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ +function getInOrderNext(root) { if (root === null) return root; // 循环访问左子结点,直到叶结点时为最小结点,跳出 while (root.left !== null) { diff --git a/codes/javascript/chapter_tree/binary_tree_bfs.js b/codes/javascript/chapter_tree/binary_tree_bfs.js index b52f5282..21daaa8d 100644 --- a/codes/javascript/chapter_tree/binary_tree_bfs.js +++ b/codes/javascript/chapter_tree/binary_tree_bfs.js @@ -28,10 +28,10 @@ function hierOrder(root) { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -var root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]); +var root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log("\n初始化二叉树\n"); printTree(root); /* 层序遍历 */ let list = hierOrder(root); -console.log("\n层序遍历的结点打印序列 = " + list); \ No newline at end of file +console.log("\n层序遍历的结点打印序列 = " + list); diff --git a/codes/javascript/chapter_tree/binary_tree_dfs.js b/codes/javascript/chapter_tree/binary_tree_dfs.js index 51e55733..d23b69b6 100644 --- a/codes/javascript/chapter_tree/binary_tree_dfs.js +++ b/codes/javascript/chapter_tree/binary_tree_dfs.js @@ -40,7 +40,7 @@ function postOrder(root) { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -var root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]); +var root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log("\n初始化二叉树\n"); printTree(root); @@ -58,4 +58,3 @@ console.log("\n中序遍历的结点打印序列 = " + list); list.length = 0; postOrder(root); console.log("\n后序遍历的结点打印序列 = " + list); - diff --git a/codes/javascript/include/TreeNode.js b/codes/javascript/include/TreeNode.js index 6add12f0..ce6d667b 100644 --- a/codes/javascript/include/TreeNode.js +++ b/codes/javascript/include/TreeNode.js @@ -14,7 +14,7 @@ function TreeNode(val, left, right) { } /** -* Generate a binary tree with an array +* Generate a binary tree given an array * @param arr * @return */ @@ -24,20 +24,21 @@ function arrToTree(arr) { let root = new TreeNode(arr[0]); let queue = [root] - let i = 1; - while(queue.length) { + let i = 0; + while (queue.length) { let node = queue.shift(); - if(arr[i] !== null) { + if (++i >= arr.length) break; + if (arr[i] !== null) { node.left = new TreeNode(arr[i]); queue.push(node.left); } - i++; - if(arr[i] !== null) { + if (++i >= arr.length) break; + if (arr[i] !== null) { node.right = new TreeNode(arr[i]); queue.push(node.right); } - i++; } + return root; } diff --git a/codes/python/chapter_array_and_linkedlist/linked_list.py b/codes/python/chapter_array_and_linkedlist/linked_list.py index dce11034..4fb6b1ba 100644 --- a/codes/python/chapter_array_and_linkedlist/linked_list.py +++ b/codes/python/chapter_array_and_linkedlist/linked_list.py @@ -26,9 +26,9 @@ def remove(n0): """ 访问链表中索引为 index 的结点 """ def access(head, index): for _ in range(index): - head = head.next if not head: return None + head = head.next return head """ 在链表中查找值为 target 的首个结点 """ diff --git a/codes/python/chapter_sorting/merge_sort.py b/codes/python/chapter_sorting/merge_sort.py index f3fb538a..5fa7d83a 100644 --- a/codes/python/chapter_sorting/merge_sort.py +++ b/codes/python/chapter_sorting/merge_sort.py @@ -28,11 +28,11 @@ def merge(nums, left, mid, right): if i > left_end: nums[k] = tmp[j] j += 1 - # 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + # 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ elif j > right_end or tmp[i] <= tmp[j]: nums[k] = tmp[i] i += 1 - # 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + # 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ else: nums[k] = tmp[j] j += 1 diff --git a/codes/python/chapter_tree/avl_tree.py b/codes/python/chapter_tree/avl_tree.py index d0ff48cb..83929981 100644 --- a/codes/python/chapter_tree/avl_tree.py +++ b/codes/python/chapter_tree/avl_tree.py @@ -5,30 +5,28 @@ Author: a16su (lpluls001@gmail.com) """ import sys, os.path as osp -import typing - sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * class AVLTree: - def __init__(self, root: typing.Optional[TreeNode] = None): + def __init__(self, root: Optional[TreeNode] = None): self.root = root """ 获取结点高度 """ - def height(self, node: typing.Optional[TreeNode]) -> int: + def height(self, node: Optional[TreeNode]) -> int: # 空结点高度为 -1 ,叶结点高度为 0 if node is not None: return node.height return -1 """ 更新结点高度 """ - def __update_height(self, node: TreeNode): + def __update_height(self, node: Optional[TreeNode]): # 结点高度等于最高子树高度 + 1 node.height = max([self.height(node.left), self.height(node.right)]) + 1 """ 获取平衡因子 """ - def balance_factor(self, node: TreeNode) -> int: + def balance_factor(self, node: Optional[TreeNode]) -> int: # 空结点平衡因子为 0 if node is None: return 0 @@ -36,7 +34,7 @@ class AVLTree: return self.height(node.left) - self.height(node.right) """ 右旋操作 """ - def __right_rotate(self, node: TreeNode) -> TreeNode: + def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode: child = node.left grand_child = child.right # 以 child 为原点,将 node 向右旋转 @@ -49,7 +47,7 @@ class AVLTree: return child """ 左旋操作 """ - def __left_rotate(self, node: TreeNode) -> TreeNode: + def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode: child = node.right grand_child = child.left # 以 child 为原点,将 node 向左旋转 @@ -62,7 +60,7 @@ class AVLTree: return child """ 执行旋转操作,使该子树重新恢复平衡 """ - def __rotate(self, node: TreeNode) -> TreeNode: + def __rotate(self, node: Optional[TreeNode]) -> TreeNode: # 获取结点 node 的平衡因子 balance_factor = self.balance_factor(node) # 左偏树 @@ -92,7 +90,7 @@ class AVLTree: return self.root """ 递归插入结点(辅助函数)""" - def __insert_helper(self, node: typing.Optional[TreeNode], val: int) -> TreeNode: + def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode: if node is None: return TreeNode(val) # 1. 查找插入位置,并插入结点 @@ -114,7 +112,7 @@ class AVLTree: return root """ 递归删除结点(辅助函数) """ - def __remove_helper(self, node: typing.Optional[TreeNode], val: int) -> typing.Optional[TreeNode]: + def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]: if node is None: return None # 1. 查找结点,并删除之 @@ -132,7 +130,7 @@ class AVLTree: else: node = child else: # 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - temp = self.__min_node(node.right) + temp = self.__get_inorder_next(node.right) node.right = self.__remove_helper(node.right, temp.val) node.val = temp.val # 更新结点高度 @@ -140,8 +138,8 @@ class AVLTree: # 2. 执行旋转操作,使该子树重新恢复平衡 return self.__rotate(node) - """ 获取最小结点 """ - def __min_node(self, node: typing.Optional[TreeNode]) -> typing.Optional[TreeNode]: + """ 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """ + def __get_inorder_next(self, node: Optional[TreeNode]) -> Optional[TreeNode]: if node is None: return None # 循环访问左子结点,直到叶结点时为最小结点,跳出 diff --git a/codes/python/chapter_tree/binary_search_tree.py b/codes/python/chapter_tree/binary_search_tree.py index 811b3829..8817d4a5 100644 --- a/codes/python/chapter_tree/binary_search_tree.py +++ b/codes/python/chapter_tree/binary_search_tree.py @@ -5,20 +5,18 @@ Author: a16su (lpluls001@gmail.com) """ import sys, os.path as osp -import typing - sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * """ 二叉搜索树 """ class BinarySearchTree: - def __init__(self, nums: typing.List[int]) -> None: + def __init__(self, nums: List[int]) -> None: nums.sort() self.__root = self.build_tree(nums, 0, len(nums) - 1) """ 构建二叉搜索树 """ - def build_tree(self, nums: typing.List[int], start_index: int, end_index: int) -> typing.Optional[TreeNode]: + def build_tree(self, nums: List[int], start_index: int, end_index: int) -> Optional[TreeNode]: if start_index > end_index: return None @@ -31,11 +29,11 @@ class BinarySearchTree: return root @property - def root(self) -> typing.Optional[TreeNode]: + def root(self) -> Optional[TreeNode]: return self.__root """ 查找结点 """ - def search(self, num: int) -> typing.Optional[TreeNode]: + def search(self, num: int) -> Optional[TreeNode]: cur = self.root # 循环查找,越过叶结点后跳出 while cur is not None: @@ -51,7 +49,7 @@ class BinarySearchTree: return cur """ 插入结点 """ - def insert(self, num: int) -> typing.Optional[TreeNode]: + def insert(self, num: int) -> Optional[TreeNode]: root = self.root # 若树为空,直接提前返回 if root is None: @@ -81,7 +79,7 @@ class BinarySearchTree: return node """ 删除结点 """ - def remove(self, num: int) -> typing.Optional[TreeNode]: + def remove(self, num: int) -> Optional[TreeNode]: root = self.root # 若树为空,直接提前返回 if root is None: @@ -117,7 +115,7 @@ class BinarySearchTree: # 子结点数量 = 2 else: # 获取中序遍历中 cur 的下一个结点 - nex = self.min(cur.right) + nex = self.get_inorder_next(cur.right) tmp = nex.val # 递归删除结点 nex self.remove(nex.val) @@ -125,11 +123,10 @@ class BinarySearchTree: cur.val = tmp return cur - """ 获取最小结点 """ - def min(self, root: typing.Optional[TreeNode]) -> typing.Optional[TreeNode]: + """ 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """ + def get_inorder_next(self, root: Optional[TreeNode]) -> Optional[TreeNode]: if root is None: return root - # 循环访问左子结点,直到叶结点时为最小结点,跳出 while root.left is not None: root = root.left @@ -139,7 +136,7 @@ class BinarySearchTree: """ Driver Code """ if __name__ == "__main__": # 初始化二叉搜索树 - nums = list(range(1, 16)) + nums = list(range(1, 16)) # [1, 2, ..., 15] bst = BinarySearchTree(nums=nums) print("\n初始化的二叉树为\n") print_tree(bst.root) diff --git a/codes/python/chapter_tree/binary_tree.py b/codes/python/chapter_tree/binary_tree.py index d9902635..e00eeb4e 100644 --- a/codes/python/chapter_tree/binary_tree.py +++ b/codes/python/chapter_tree/binary_tree.py @@ -36,5 +36,5 @@ if __name__ == "__main__": print_tree(n1) # 删除结点 n1.left = n2 - print("\n删除结点 P 后\n"); + print("\n删除结点 P 后\n") print_tree(n1) diff --git a/codes/python/chapter_tree/binary_tree_bfs.py b/codes/python/chapter_tree/binary_tree_bfs.py index 0320a08d..fb3e9305 100644 --- a/codes/python/chapter_tree/binary_tree_bfs.py +++ b/codes/python/chapter_tree/binary_tree_bfs.py @@ -5,14 +5,12 @@ Author: a16su (lpluls001@gmail.com) """ import sys, os.path as osp -import typing - sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * """ 层序遍历 """ -def hier_order(root: TreeNode): +def hier_order(root: Optional[TreeNode]): # 初始化队列,加入根结点 queue = collections.deque() queue.append(root) @@ -32,7 +30,7 @@ def hier_order(root: TreeNode): if __name__ == "__main__": # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 - root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7, None, None, None, None, None, None, None, None]) + root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n初始化二叉树\n") print_tree(root) diff --git a/codes/python/chapter_tree/binary_tree_dfs.py b/codes/python/chapter_tree/binary_tree_dfs.py index 11ee8339..b0efb14c 100644 --- a/codes/python/chapter_tree/binary_tree_dfs.py +++ b/codes/python/chapter_tree/binary_tree_dfs.py @@ -5,8 +5,6 @@ Author: a16su (lpluls001@gmail.com) """ import sys, os.path as osp -import typing - sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * @@ -14,7 +12,7 @@ from include import * res = [] """ 前序遍历 """ -def pre_order(root: typing.Optional[TreeNode]): +def pre_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:根结点 -> 左子树 -> 右子树 @@ -23,7 +21,7 @@ def pre_order(root: typing.Optional[TreeNode]): pre_order(root=root.right) """ 中序遍历 """ -def in_order(root: typing.Optional[TreeNode]): +def in_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:左子树 -> 根结点 -> 右子树 @@ -32,7 +30,7 @@ def in_order(root: typing.Optional[TreeNode]): in_order(root=root.right) """ 后序遍历 """ -def post_order(root: typing.Optional[TreeNode]): +def post_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:左子树 -> 右子树 -> 根结点 @@ -45,7 +43,7 @@ def post_order(root: typing.Optional[TreeNode]): if __name__ == "__main__": # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 - root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7, None, None, None, None, None, None, None, None]) + root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n初始化二叉树\n") print_tree(root) diff --git a/codes/python/include/__init__.py b/codes/python/include/__init__.py index 31ed69ac..9d4e90be 100644 --- a/codes/python/include/__init__.py +++ b/codes/python/include/__init__.py @@ -4,7 +4,7 @@ import queue import random import functools import collections -from typing import List +from typing import Optional, List, Dict, DefaultDict, OrderedDict, Set, Deque from .linked_list import ListNode, list_to_linked_list, linked_list_to_list, get_list_node from .binary_tree import TreeNode, list_to_tree, tree_to_list, get_tree_node from .print_util import print_matrix, print_linked_list, print_tree, print_dict \ No newline at end of file diff --git a/codes/python/include/binary_tree.py b/codes/python/include/binary_tree.py index 670fd18c..9d612a7e 100644 --- a/codes/python/include/binary_tree.py +++ b/codes/python/include/binary_tree.py @@ -26,39 +26,30 @@ class TreeNode: def list_to_tree(arr): """Generate a binary tree with a list - - Args: - arr ([type]): [description] - - Returns: - [type]: [description] """ if not arr: return None - i = 1 - root = TreeNode(int(arr[0])) - queue = collections.deque() - queue.append(root) + + i = 0 + root = TreeNode(arr[0]) + queue = collections.deque([root]) while queue: node = queue.popleft() + i += 1 + if i >= len(arr): break if arr[i] != None: - node.left = TreeNode(int(arr[i])) + node.left = TreeNode(arr[i]) queue.append(node.left) i += 1 + if i >= len(arr): break if arr[i] != None: - node.right = TreeNode(int(arr[i])) + node.right = TreeNode(arr[i]) queue.append(node.right) - i += 1 + return root def tree_to_list(root): """Serialize a tree into an array - - Args: - root ([type]): [description] - - Returns: - [type]: [description] """ if not root: return [] queue = collections.deque() @@ -75,13 +66,6 @@ def tree_to_list(root): def get_tree_node(root, val): """Get a tree node with specific value in a binary tree - - Args: - root ([type]): [description] - val ([type]): [description] - - Returns: - [type]: [description] """ if not root: return diff --git a/codes/swift/.gitignore b/codes/swift/.gitignore new file mode 100644 index 00000000..6295af4c --- /dev/null +++ b/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/codes/swift/Package.swift b/codes/swift/Package.swift new file mode 100644 index 00000000..3a5a18ed --- /dev/null +++ b/codes/swift/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "HelloAlgo", + products: [ + .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"]), + .executable(name: "leetcode_two_sum", targets: ["leetcode_two_sum"]), + .executable(name: "array", targets: ["array"]), + .executable(name: "linked_list", targets: ["linked_list"]), + .executable(name: "list", targets: ["list"]), + .executable(name: "my_list", targets: ["my_list"]), + ], + targets: [ + .target(name: "utils", path: "utils"), + .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"]), + .executableTarget(name: "leetcode_two_sum", path: "chapter_computational_complexity", sources: ["leetcode_two_sum.swift"]), + .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"]), + ] +) diff --git a/codes/swift/chapter_array_and_linkedlist/array.swift b/codes/swift/chapter_array_and_linkedlist/array.swift new file mode 100644 index 00000000..344dc5ab --- /dev/null +++ b/codes/swift/chapter_array_and_linkedlist/array.swift @@ -0,0 +1,103 @@ +/** + * 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 sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) { + nums[i] = nums[i - 1] + } + // 将 num 赋给 index 处元素 + nums[index] = num +} + +/* 删除索引 index 处元素 */ +func remove(nums: inout [Int], index: Int) { + let count = nums.count + // 把索引 index 之后的所有元素向前移动一位 + for i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) { + nums[i] = nums[i + 1] + } +} + +/* 遍历数组 */ +func traverse(nums: [Int]) { + var count = 0 + // 通过索引遍历数组 + for _ in nums.indices { + count += 1 + } + // 直接遍历数组 + for _ in nums { + count += 1 + } +} + +/* 在数组中查找指定元素 */ +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/codes/swift/chapter_array_and_linkedlist/linked_list.swift b/codes/swift/chapter_array_and_linkedlist/linked_list.swift new file mode 100644 index 00000000..90218b26 --- /dev/null +++ b/codes/swift/chapter_array_and_linkedlist/linked_list.swift @@ -0,0 +1,91 @@ +/** + * 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 + n0.next = P + P.next = n1 +} + +/* 删除链表的结点 n0 之后的首个结点 */ +func remove(n0: ListNode) { + if n0.next == nil { + return + } + // n0 -> P -> n1 + let P = n0.next + let n1 = P?.next + n0.next = n1 + P?.next = nil +} + +/* 访问链表中索引为 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/codes/swift/chapter_array_and_linkedlist/list.swift b/codes/swift/chapter_array_and_linkedlist/list.swift new file mode 100644 index 00000000..27801d13 --- /dev/null +++ b/codes/swift/chapter_array_and_linkedlist/list.swift @@ -0,0 +1,64 @@ +/** + * File: list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum List { + /* Driver Code */ + static func main() { + /* 初始化列表 */ + var list = [1, 3, 2, 5, 4] + print("列表 list = \(list)") + + /* 访问元素 */ + let num = list[1] + print("访问索引 1 处的元素,得到 num = \(num)") + + /* 更新元素 */ + list[1] = 0 + print("将索引 1 处的元素更新为 0 ,得到 list = \(list)") + + /* 清空列表 */ + list.removeAll() + print("清空列表后 list = \(list)") + + /* 尾部添加元素 */ + list.append(1) + list.append(3) + list.append(2) + list.append(5) + list.append(4) + print("添加元素后 list = \(list)") + + /* 中间插入元素 */ + list.insert(6, at: 3) + print("在索引 3 处插入数字 6 ,得到 list = \(list)") + + /* 删除元素 */ + list.remove(at: 3) + print("删除索引 3 处的元素,得到 list = \(list)") + + /* 通过索引遍历列表 */ + var count = 0 + for _ in list.indices { + count += 1 + } + + /* 直接遍历列表元素 */ + count = 0 + for _ in list { + count += 1 + } + + /* 拼接两个列表 */ + let list1 = [6, 8, 7, 10, 9] + list.append(contentsOf: list1) + print("将列表 list1 拼接到 list 之后,得到 list = \(list)") + + /* 排序列表 */ + list.sort() + print("排序列表后 list = \(list)") + } +} diff --git a/codes/swift/chapter_array_and_linkedlist/my_list.swift b/codes/swift/chapter_array_and_linkedlist/my_list.swift new file mode 100644 index 00000000..423b0202 --- /dev/null +++ b/codes/swift/chapter_array_and_linkedlist/my_list.swift @@ -0,0 +1,147 @@ +/** + * File: my_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 列表类简易实现 */ +class MyList { + private var nums: [Int] // 数组(存储列表元素) + private var _capacity = 10 // 列表容量 + private var _size = 0 // 列表长度(即当前元素数量) + private let extendRatio = 2 // 每次列表扩容的倍数 + + /* 构造函数 */ + init() { + nums = Array(repeating: 0, count: _capacity) + } + + /* 获取列表长度(即当前元素数量)*/ + func size() -> Int { + _size + } + + /* 获取列表容量 */ + func capacity() -> Int { + _capacity + } + + /* 访问元素 */ + func get(index: Int) -> Int { + // 索引如果越界则抛出错误,下同 + if index >= _size { + fatalError("索引越界") + } + return nums[index] + } + + /* 更新元素 */ + func set(index: Int, num: Int) { + if index >= _size { + fatalError("索引越界") + } + nums[index] = num + } + + /* 尾部添加元素 */ + func add(num: Int) { + // 元素数量超出容量时,触发扩容机制 + if _size == _capacity { + extendCapacity() + } + nums[_size] = num + // 更新元素数量 + _size += 1 + } + + /* 中间插入元素 */ + func insert(index: Int, num: Int) { + if index >= _size { + fatalError("索引越界") + } + // 元素数量超出容量时,触发扩容机制 + if _size == _capacity { + extendCapacity() + } + // 将索引 index 以及之后的元素都向后移动一位 + for j in sequence(first: _size - 1, next: { $0 >= index + 1 ? $0 - 1 : nil }) { + nums[j + 1] = nums[j] + } + nums[index] = num + // 更新元素数量 + _size += 1 + } + + /* 删除元素 */ + @discardableResult + func remove(index: Int) -> Int { + if index >= _size { + fatalError("索引越界") + } + let num = nums[index] + // 将索引 index 之后的元素都向前移动一位 + for j in index ..< (_size - 1) { + nums[j] = nums[j + 1] + } + // 更新元素数量 + _size -= 1 + // 返回被删除元素 + return num + } + + /* 列表扩容 */ + func extendCapacity() { + // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 + nums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1)) + // 更新列表容量 + _capacity = nums.count + } + + /* 将列表转换为数组 */ + func toArray() -> [Int] { + var nums = Array(repeating: 0, count: _size) + for i in 0 ..< _size { + nums[i] = get(index: i) + } + return nums + } +} + +@main +enum _MyList { + /* Driver Code */ + static func main() { + /* 初始化列表 */ + let list = MyList() + /* 尾部添加元素 */ + list.add(num: 1) + list.add(num: 3) + list.add(num: 2) + list.add(num: 5) + list.add(num: 4) + print("列表 list = \(list.toArray()) ,容量 = \(list.capacity()) ,长度 = \(list.size())") + + /* 中间插入元素 */ + list.insert(index: 3, num: 6) + print("在索引 3 处插入数字 6 ,得到 list = \(list.toArray())") + + /* 删除元素 */ + list.remove(index: 3) + print("删除索引 3 处的元素,得到 list = \(list.toArray())") + + /* 访问元素 */ + let num = list.get(index: 1) + print("访问索引 1 处的元素,得到 num = \(num)") + + /* 更新元素 */ + list.set(index: 1, num: 0) + print("将索引 1 处的元素更新为 0 ,得到 list = \(list.toArray())") + + /* 测试扩容机制 */ + for i in 0 ..< 10 { + // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 + list.add(num: i) + } + print("扩容后的列表 list = \(list.toArray()) ,容量 = \(list.capacity()) ,长度 = \(list.size())") + } +} diff --git a/codes/swift/chapter_computational_complexity/leetcode_two_sum.swift b/codes/swift/chapter_computational_complexity/leetcode_two_sum.swift new file mode 100644 index 00000000..e82872d3 --- /dev/null +++ b/codes/swift/chapter_computational_complexity/leetcode_two_sum.swift @@ -0,0 +1,46 @@ +/** + * File: leetcode_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 { + static func main() { + // ======= Test Case ======= + let nums = [2, 7, 11, 15] + let target = 9 + // ====== Driver Code ====== + // 方法一 + var res = twoSumBruteForce(nums: nums, target: target) + print("方法一 res = \(res)") + // 方法二 + res = twoSumHashTable(nums: nums, target: target) + print("方法二 res = \(res)") + } +} diff --git a/codes/swift/chapter_computational_complexity/space_complexity.swift b/codes/swift/chapter_computational_complexity/space_complexity.swift new file mode 100644 index 00000000..fdd29f8e --- /dev/null +++ b/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 { + // do something + 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/codes/swift/chapter_computational_complexity/time_complexity.swift b/codes/swift/chapter_computational_complexity/time_complexity.swift index 38bac4fa..1b1027ef 100644 --- a/codes/swift/chapter_computational_complexity/time_complexity.swift +++ b/codes/swift/chapter_computational_complexity/time_complexity.swift @@ -1,10 +1,10 @@ -/* +/** * 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 @@ -14,7 +14,7 @@ func constant(n: Int) -> Int { return count } -// 线性阶 +/* 线性阶 */ func linear(n: Int) -> Int { var count = 0 for _ in 0 ..< n { @@ -23,7 +23,7 @@ func linear(n: Int) -> Int { return count } -// 线性阶(遍历数组) +/* 线性阶(遍历数组) */ func arrayTraversal(nums: [Int]) -> Int { var count = 0 // 循环次数与数组长度成正比 @@ -33,7 +33,7 @@ func arrayTraversal(nums: [Int]) -> Int { return count } -// 平方阶 +/* 平方阶 */ func quadratic(n: Int) -> Int { var count = 0 // 循环次数与数组长度成平方关系 @@ -45,11 +45,11 @@ func quadratic(n: Int) -> Int { return count } -// 平方阶(冒泡排序) +/* 平方阶(冒泡排序) */ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // 计数器 // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in sequence(first: nums.count - 1, next: { $0 > 0 ? $0 - 1 : nil }) { + for i in sequence(first: nums.count - 1, next: { $0 > 0 + 1 ? $0 - 1 : nil }) { // 内循环:冒泡操作 for j in 0 ..< i { if nums[j] > nums[j + 1] { @@ -64,7 +64,7 @@ func bubbleSort(nums: inout [Int]) -> Int { return count } -// 指数阶(循环实现) +/* 指数阶(循环实现) */ func exponential(n: Int) -> Int { var count = 0 var base = 1 @@ -79,7 +79,7 @@ func exponential(n: Int) -> Int { return count } -// 指数阶(递归实现) +/* 指数阶(递归实现) */ func expRecur(n: Int) -> Int { if n == 1 { return 1 @@ -87,7 +87,7 @@ func expRecur(n: Int) -> Int { return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 } -// 对数阶(循环实现) +/* 对数阶(循环实现) */ func logarithmic(n: Int) -> Int { var count = 0 var n = n @@ -98,7 +98,7 @@ func logarithmic(n: Int) -> Int { return count } -// 对数阶(递归实现) +/* 对数阶(递归实现) */ func logRecur(n: Int) -> Int { if n <= 1 { return 0 @@ -106,7 +106,7 @@ func logRecur(n: Int) -> Int { return logRecur(n: n / 2) + 1 } -// 线性对数阶 +/* 线性对数阶 */ func linearLogRecur(n: Double) -> Int { if n <= 1 { return 1 @@ -118,7 +118,7 @@ func linearLogRecur(n: Double) -> Int { return count } -// 阶乘阶(递归实现) +/* 阶乘阶(递归实现) */ func factorialRecur(n: Int) -> Int { if n == 0 { return 1 @@ -131,40 +131,42 @@ func factorialRecur(n: Int) -> Int { return count } -func main() { - // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 - let n = 8 - print("输入数据大小 n =", n) +@main +enum TimeComplexity { + /* Driver Code */ + static func main() { + // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 + let n = 8 + print("输入数据大小 n = \(n)") - var count = constant(n: n) - print("常数阶的计算操作数量 =", count) + var count = constant(n: n) + print("常数阶的计算操作数量 = \(count)") - count = linear(n: n) - print("线性阶的计算操作数量 =", count) - count = arrayTraversal(nums: Array(repeating: 0, count: 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(sequence(first: n, next: { $0 > 0 ? $0 - 1 : nil })) // [n,n-1,...,2,1] - count = bubbleSort(nums: &nums) - print("平方阶(冒泡排序)的计算操作数量 =", count) + count = quadratic(n: n) + print("平方阶的计算操作数量 = \(count)") + var nums = Array(sequence(first: n, next: { $0 > 0 + 1 ? $0 - 1 : nil })) // [n,n-1,...,2,1] + count = bubbleSort(nums: &nums) + print("平方阶(冒泡排序)的计算操作数量 = \(count)") - count = exponential(n: n) - print("指数阶(循环实现)的计算操作数量 =", count) - count = expRecur(n: n) - 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 = logarithmic(n: n) + print("对数阶(循环实现)的计算操作数量 = \(count)") + count = logRecur(n: n) + print("对数阶(递归实现)的计算操作数量 = \(count)") - count = linearLogRecur(n: Double(n)) - print("线性对数阶(递归实现)的计算操作数量 =", count) + count = linearLogRecur(n: Double(n)) + print("线性对数阶(递归实现)的计算操作数量 = \(count)") - count = factorialRecur(n: n) - print("阶乘阶(递归实现)的计算操作数量 =", count) + count = factorialRecur(n: n) + print("阶乘阶(递归实现)的计算操作数量 = \(count)") + } } - -main() diff --git a/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift b/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift index 73db6954..3dac661d 100644 --- a/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift +++ b/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift @@ -1,10 +1,10 @@ -/* +/** * File: worst_best_time_complexity.swift * Created Time: 2022-12-26 * Author: nuomi1 (nuomi1@qq.com) */ -// 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 +/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ func randomNumbers(n: Int) -> [Int] { // 生成数组 nums = { 1, 2, 3, ..., n } var nums = Array(1 ... n) @@ -13,7 +13,7 @@ func randomNumbers(n: Int) -> [Int] { return nums } -// 查找数组 nums 中数字 1 所在索引 +/* 查找数组 nums 中数字 1 所在索引 */ func findOne(nums: [Int]) -> Int { for i in nums.indices { if nums[i] == 1 { @@ -23,15 +23,16 @@ func findOne(nums: [Int]) -> Int { return -1 } -// Driver Code -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) +@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)") + } } } - -main() diff --git a/codes/swift/utils/ListNode.swift b/codes/swift/utils/ListNode.swift new file mode 100644 index 00000000..5fa3671e --- /dev/null +++ b/codes/swift/utils/ListNode.swift @@ -0,0 +1,14 @@ +/** + * File: ListNode.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +public class ListNode { + public var val: Int // 结点值 + public var next: ListNode? // 后继结点引用 + + public init(x: Int) { + val = x + } +} diff --git a/codes/swift/utils/PrintUtil.swift b/codes/swift/utils/PrintUtil.swift new file mode 100644 index 00000000..cbeab122 --- /dev/null +++ b/codes/swift/utils/PrintUtil.swift @@ -0,0 +1,71 @@ +/** + * 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, isLeft: false) + } + + private static func printTree(root: TreeNode?, prev: Trunk?, isLeft: Bool) { + if root == nil { + return + } + + var prevStr = " " + let trunk = Trunk(prev: prev, str: prevStr) + + printTree(root: root?.right, prev: trunk, isLeft: true) + + if prev == nil { + trunk.str = "———" + } else if isLeft { + 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, isLeft: false) + } + + private static func showTrunks(p: Trunk?) { + if p == nil { + return + } + + showTrunks(p: p?.prev) + print(p!.str, terminator: "") + } +} diff --git a/codes/swift/utils/TreeNode.swift b/codes/swift/utils/TreeNode.swift new file mode 100644 index 00000000..161cebd5 --- /dev/null +++ b/codes/swift/utils/TreeNode.swift @@ -0,0 +1,17 @@ +/** + * 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 + } +} diff --git a/codes/typescript/chapter_array_and_linkedlist/array.ts b/codes/typescript/chapter_array_and_linkedlist/array.ts index 237a0438..a85816b9 100644 --- a/codes/typescript/chapter_array_and_linkedlist/array.ts +++ b/codes/typescript/chapter_array_and_linkedlist/array.ts @@ -1,4 +1,4 @@ -/* +/** * File: array.ts * Created Time: 2022-12-04 * Author: Justin (xiefahit@gmail.com) diff --git a/codes/typescript/chapter_array_and_linkedlist/linked_list.ts b/codes/typescript/chapter_array_and_linkedlist/linked_list.ts index 48432258..b5eb988a 100644 --- a/codes/typescript/chapter_array_and_linkedlist/linked_list.ts +++ b/codes/typescript/chapter_array_and_linkedlist/linked_list.ts @@ -1,4 +1,4 @@ -/* +/** * File: linked_list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) diff --git a/codes/typescript/chapter_array_and_linkedlist/list.ts b/codes/typescript/chapter_array_and_linkedlist/list.ts index 8fca2bfa..e1d2d518 100644 --- a/codes/typescript/chapter_array_and_linkedlist/list.ts +++ b/codes/typescript/chapter_array_and_linkedlist/list.ts @@ -1,4 +1,4 @@ -/* +/** * File: list.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) diff --git a/codes/typescript/chapter_array_and_linkedlist/my_list.ts b/codes/typescript/chapter_array_and_linkedlist/my_list.ts index b2ce4684..e60075da 100644 --- a/codes/typescript/chapter_array_and_linkedlist/my_list.ts +++ b/codes/typescript/chapter_array_and_linkedlist/my_list.ts @@ -1,4 +1,4 @@ -/* +/** * File: my_list.ts * Created Time: 2022-12-11 * Author: Justin (xiefahit@gmail.com) diff --git a/codes/typescript/chapter_computational_complexity/leetcode_two_sum.ts b/codes/typescript/chapter_computational_complexity/leetcode_two_sum.ts index 8e3c4cd7..47c52d80 100644 --- a/codes/typescript/chapter_computational_complexity/leetcode_two_sum.ts +++ b/codes/typescript/chapter_computational_complexity/leetcode_two_sum.ts @@ -1,4 +1,4 @@ -/* +/** * File: leetcode_two_sum.ts * Created Time: 2022-12-15 * Author: gyt95 (gytkwan@gmail.com) diff --git a/codes/typescript/chapter_computational_complexity/time_complexity.ts b/codes/typescript/chapter_computational_complexity/time_complexity.ts new file mode 100644 index 00000000..e5dde67c --- /dev/null +++ b/codes/typescript/chapter_computational_complexity/time_complexity.ts @@ -0,0 +1,155 @@ +/** + * 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; + // 循环次数与数组长度成平方关系 + 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; // 计数器 + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (let i = nums.length - 1; 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; + // cell 每轮一分为二,形成数列 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); diff --git a/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts b/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts new file mode 100644 index 00000000..a7318634 --- /dev/null +++ b/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[] { + let 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++) { + let r = Math.floor(Math.random() * (i + 1)); + let 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++) { + if (nums[i] === 1) { + return i; + } + } + return -1; +} + +/* Driver Code */ +function main(): void { + for (let i = 0; i < 10; i++) { + let n = 100; + let nums = randomNumbers(n); + let index = findOne(nums); + console.log( + "\n数组 [ 1, 2, ..., n ] 被打乱后 = [" + nums.join(", ") + "]" + ); + console.log("数字 1 的索引为 " + index); + } +} diff --git a/codes/typescript/chapter_hashing/array_hash_map.ts b/codes/typescript/chapter_hashing/array_hash_map.ts index 627205f7..027cdc8e 100644 --- a/codes/typescript/chapter_hashing/array_hash_map.ts +++ b/codes/typescript/chapter_hashing/array_hash_map.ts @@ -1,4 +1,4 @@ -/* +/** * File: array_hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) diff --git a/codes/typescript/chapter_hashing/hash_map.ts b/codes/typescript/chapter_hashing/hash_map.ts index 7e54cf5c..8d73da0c 100644 --- a/codes/typescript/chapter_hashing/hash_map.ts +++ b/codes/typescript/chapter_hashing/hash_map.ts @@ -1,4 +1,4 @@ -/* +/** * File: hash_map.ts * Created Time: 2022-12-20 * Author: Daniel (better.sunjian@gmail.com) diff --git a/codes/typescript/chapter_sorting/merge_sort.ts b/codes/typescript/chapter_sorting/merge_sort.ts index 44fb4b8d..a4cb5e1c 100644 --- a/codes/typescript/chapter_sorting/merge_sort.ts +++ b/codes/typescript/chapter_sorting/merge_sort.ts @@ -23,10 +23,10 @@ function merge(nums: number[], left: number, mid: number, right: number): void { // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) { nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ } else if (j > rightEnd || tmp[i] <= tmp[j]) { nums[k] = tmp[i++]; - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ } else { nums[k] = tmp[j++]; } diff --git a/codes/typescript/chapter_tree/binary_search_tree.ts b/codes/typescript/chapter_tree/binary_search_tree.ts index 1222b8d5..9adf8cd1 100644 --- a/codes/typescript/chapter_tree/binary_search_tree.ts +++ b/codes/typescript/chapter_tree/binary_search_tree.ts @@ -120,7 +120,7 @@ function remove(num: number): TreeNode | null { // 子结点数量 = 2 else { // 获取中序遍历中 cur 的下一个结点 - let next = min(cur.right); + let next = getInOrderNext(cur.right); let tmp = next!.val; // 递归删除结点 nex remove(next!.val); @@ -130,8 +130,8 @@ function remove(num: number): TreeNode | null { return cur; } -/* 获取最小结点 */ -function min(root: TreeNode | null): TreeNode | null { +/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ +function getInOrderNext(root: TreeNode | null): TreeNode | null { if (root === null) { return null; } diff --git a/codes/typescript/chapter_tree/binary_tree_bfs.ts b/codes/typescript/chapter_tree/binary_tree_bfs.ts index 1e57ef62..7ec4a2a5 100644 --- a/codes/typescript/chapter_tree/binary_tree_bfs.ts +++ b/codes/typescript/chapter_tree/binary_tree_bfs.ts @@ -30,7 +30,7 @@ function hierOrder(root: TreeNode | null): number[] { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -var root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]); +var root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树\n'); printTree(root); diff --git a/codes/typescript/chapter_tree/binary_tree_dfs.ts b/codes/typescript/chapter_tree/binary_tree_dfs.ts index d9e982df..a61855af 100644 --- a/codes/typescript/chapter_tree/binary_tree_dfs.ts +++ b/codes/typescript/chapter_tree/binary_tree_dfs.ts @@ -47,7 +47,7 @@ function postOrder(root: TreeNode | null): void { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -const root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]); +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树\n'); printTree(root); diff --git a/codes/typescript/module/ListNode.ts b/codes/typescript/module/ListNode.ts index d6d60616..17fdbb95 100644 --- a/codes/typescript/module/ListNode.ts +++ b/codes/typescript/module/ListNode.ts @@ -1,4 +1,4 @@ -/* +/** * File: ListNode.ts * Created Time: 2022-12-10 * Author: Justin (xiefahit@gmail.com) diff --git a/codes/typescript/module/PrintUtil.ts b/codes/typescript/module/PrintUtil.ts index 9174be15..59f1af0e 100644 --- a/codes/typescript/module/PrintUtil.ts +++ b/codes/typescript/module/PrintUtil.ts @@ -1,4 +1,4 @@ -/* +/** * File: PrintUtil.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) diff --git a/codes/typescript/module/TreeNode.ts b/codes/typescript/module/TreeNode.ts index 0bc783aa..0e342500 100644 --- a/codes/typescript/module/TreeNode.ts +++ b/codes/typescript/module/TreeNode.ts @@ -1,4 +1,4 @@ -/* +/** * File: TreeNode.ts * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) @@ -20,7 +20,7 @@ class TreeNode { } /** - * Generate a binary tree with an array + * Generate a binary tree given an array * @param arr * @return */ @@ -31,19 +31,19 @@ function arrToTree(arr: (number | null)[]): TreeNode | null { const root = new TreeNode(arr[0] as number); const queue = [root]; - let i = 1; + let i = 0; while (queue.length) { let node = queue.shift() as TreeNode; + if (++i >= arr.length) break; if (arr[i] !== null) { node.left = new TreeNode(arr[i] as number); queue.push(node.left); } - i++; + if (++i >= arr.length) break; if (arr[i] !== null) { node.right = new TreeNode(arr[i] as number); queue.push(node.right); } - i++; } return root; } diff --git a/codes/zig/.gitignore b/codes/zig/.gitignore new file mode 100644 index 00000000..4a0641ed --- /dev/null +++ b/codes/zig/.gitignore @@ -0,0 +1,2 @@ +zig-cache/ +zig-out/ \ No newline at end of file diff --git a/codes/zig/build.zig b/codes/zig/build.zig new file mode 100644 index 00000000..b04b3fd1 --- /dev/null +++ b/codes/zig/build.zig @@ -0,0 +1,46 @@ +const std = @import("std"); + +// zig version 0.10.0 +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const mode = b.standardReleaseOptions(); + + // "chapter_computational_complexity/time_complexity.zig" + // Run Command: zig build run_time_complexity + const exe_time_complexity = b.addExecutable("time_complexity", "chapter_computational_complexity/time_complexity.zig"); + exe_time_complexity.addPackagePath("include", "include/include.zig"); + exe_time_complexity.setTarget(target); + exe_time_complexity.setBuildMode(mode); + exe_time_complexity.install(); + const run_cmd_time_complexity = exe_time_complexity.run(); + run_cmd_time_complexity.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd_time_complexity.addArgs(args); + const run_step_time_complexity = b.step("run_time_complexity", "Run time_complexity"); + run_step_time_complexity.dependOn(&run_cmd_time_complexity.step); + + // "chapter_computational_complexity/worst_best_time_complexity.zig" + // Run Command: zig build run_worst_best_time_complexity + const exe_worst_best_time_complexity = b.addExecutable("worst_best_time_complexity", "chapter_computational_complexity/worst_best_time_complexity.zig"); + exe_worst_best_time_complexity.addPackagePath("include", "include/include.zig"); + exe_worst_best_time_complexity.setTarget(target); + exe_worst_best_time_complexity.setBuildMode(mode); + exe_worst_best_time_complexity.install(); + const run_cmd_worst_best_time_complexity = exe_worst_best_time_complexity.run(); + run_cmd_worst_best_time_complexity.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd_worst_best_time_complexity.addArgs(args); + const run_step_worst_best_time_complexity = b.step("run_worst_best_time_complexity", "Run worst_best_time_complexity"); + run_step_worst_best_time_complexity.dependOn(&run_cmd_worst_best_time_complexity.step); + + // "chapter_computational_complexity/leetcode_two_sum.zig" + // Run Command: zig build run_leetcode_two_sum + const exe_leetcode_two_sum = b.addExecutable("leetcode_two_sum", "chapter_computational_complexity/leetcode_two_sum.zig"); + exe_leetcode_two_sum.addPackagePath("include", "include/include.zig"); + exe_leetcode_two_sum.setTarget(target); + exe_leetcode_two_sum.setBuildMode(mode); + exe_leetcode_two_sum.install(); + const run_cmd_leetcode_two_sum = exe_leetcode_two_sum.run(); + run_cmd_leetcode_two_sum.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd_leetcode_two_sum.addArgs(args); + const run_step_leetcode_two_sum = b.step("run_leetcode_two_sum", "Run leetcode_two_sum"); + run_step_leetcode_two_sum.dependOn(&run_cmd_leetcode_two_sum.step); +} diff --git a/codes/zig/chapter_computational_complexity/leetcode_two_sum.zig b/codes/zig/chapter_computational_complexity/leetcode_two_sum.zig new file mode 100644 index 00000000..66b96f95 --- /dev/null +++ b/codes/zig/chapter_computational_complexity/leetcode_two_sum.zig @@ -0,0 +1,61 @@ +// File: leetcode_two_sum.zig +// Created Time: 2023-01-07 +// Author: sjinzh (sjinzh@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +const SolutionBruteForce = struct { + pub fn twoSum(self: *SolutionBruteForce, nums: []i32, target: i32) [2]i32 { + _ = self; + 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(i32, i), @intCast(i32, j)}; + } + } + } + return undefined; + } +}; + +const SolutionHashMap = struct { + pub fn twoSum(self: *SolutionHashMap, nums: []i32, target: i32) ![2]i32 { + _ = self; + 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(i32, i)}; + } + try dic.put(nums[i], @intCast(i32, i)); + } + return undefined; + } +}; + +// Driver Code +pub fn main() !void { + // ======= Test Case ======= + var nums = [_]i32{ 2, 7, 11, 15 }; + var target: i32 = 9; + // 方法一 + var slt1 = SolutionBruteForce{}; + var res = slt1.twoSum(&nums, target); + std.debug.print("方法一 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + // 方法二 + var slt2 = SolutionHashMap{}; + res = try slt2.twoSum(&nums, target); + std.debug.print("方法二 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); +} + diff --git a/codes/zig/chapter_computational_complexity/time_complexity.zig b/codes/zig/chapter_computational_complexity/time_complexity.zig new file mode 100644 index 00000000..dab17034 --- /dev/null +++ b/codes/zig/chapter_computational_complexity/time_complexity.zig @@ -0,0 +1,179 @@ +// File: time_complexity.zig +// Created Time: 2022-12-28 +// Author: sjinzh (sjinzh@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; + // 内循环:冒泡操作 + while (j < i) : (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; + // cell 每轮一分为二,形成数列 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: f32) 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: f32) i32 +{ + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +// 线性对数阶 +fn linearLogRecur(n: f32) i32 +{ + if (n <= 1) return 1; + var count: i32 = linearLogRecur(n / 2) + + linearLogRecur(n / 2); + var i: f32 = 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) |*num, i| { + num.* = n - @intCast(i32, 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(@as(f32, n)); + std.debug.print("对数阶(循环实现)的计算操作数量 = {}\n", .{count}); + count = logRecur(@as(f32, n)); + std.debug.print("对数阶(递归实现)的计算操作数量 = {}\n", .{count}); + + count = linearLogRecur(@as(f32, n)); + std.debug.print("线性对数阶(递归实现)的计算操作数量 = {}\n", .{count}); + + count = factorialRecur(n); + std.debug.print("阶乘阶(递归实现)的计算操作数量 = {}\n", .{count}); +} + diff --git a/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig b/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig new file mode 100644 index 00000000..bd2a1464 --- /dev/null +++ b/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig @@ -0,0 +1,41 @@ +// File: worst_best_time_complexity.zig +// Created Time: 2022-12-28 +// Author: sjinzh (sjinzh@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) |*num, i| { + num.* = @intCast(i32, i) + 1; + } + // 随机打乱数组元素 + const rand = std.crypto.random; + rand.shuffle(i32, &nums); + return nums; +} + +// 查找数组 nums 中数字 1 所在索引 +pub fn findOne(nums: []i32) i32 { + for (nums) |num, i| { + if (num == 1) return @intCast(i32, 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}); + } +} + diff --git a/codes/zig/include/PrintUtil.zig b/codes/zig/include/PrintUtil.zig new file mode 100644 index 00000000..5adac2a6 --- /dev/null +++ b/codes/zig/include/PrintUtil.zig @@ -0,0 +1,13 @@ +// File: TreeNode.zig +// Created Time: 2023-01-07 +// Author: sjinzh (sjinzh@gmail.com) + +const std = @import("std"); + +// Print an Array +pub fn printArray(comptime T: type, nums: []T) void { + std.debug.print("[", .{}); + for (nums) |num, j| { + std.debug.print("{}{s}", .{num, if (j == nums.len-1) "]\n" else ", " }); + } +} diff --git a/codes/zig/include/include.zig b/codes/zig/include/include.zig new file mode 100644 index 00000000..b4f34782 --- /dev/null +++ b/codes/zig/include/include.zig @@ -0,0 +1,5 @@ +// File: include.zig +// Created Time: 2023-01-04 +// Author: sjinzh (sjinzh@gmail.com) + +pub const PrintUtil = @import("PrintUtil.zig"); \ No newline at end of file diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 3228e678..2251362d 100644 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -14,7 +14,7 @@ comments: true 观察上图,我们发现 **数组首元素的索引为 $0$** 。你可能会想,这并不符合日常习惯,首个元素的索引为什么不是 $1$ 呢,这不是更加自然吗?我认同你的想法,但请先记住这个设定,后面讲内存地址计算时,我会尝试解答这个问题。 -**数组有多种初始化写法。** 根据实际需要,选代码最短的那一种就好。 +**数组有多种初始化写法**。根据实际需要,选代码最短的那一种就好。 === "Java" @@ -43,7 +43,12 @@ comments: true === "Go" ```go title="array.go" - + /* 初始化数组 */ + var arr [5]int + // 在 Go 中,指定长度时([5]int)为数组,不指定长度时([]int)为切片 + // 由于 Go 的数组被设计为在编译期确定长度,因此只能使用常量来指定长度 + // 为了方便实现扩容 extend() 方法,以下将切片(Slice)看作数组(Array) + nums := []int{1, 3, 2, 5, 4} ``` === "JavaScript" @@ -76,9 +81,17 @@ comments: true int[] nums = { 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] + ``` + ## 数组优点 -**在数组中访问元素非常高效。** 这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。 +**在数组中访问元素非常高效**。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。 ![array_memory_location_calculation](array.assets/array_memory_location_calculation.png) @@ -133,7 +146,14 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Go" ```go title="array.go" - + /* 随机返回一个数组元素 */ + func randomAccess(nums []int) (randomNum int) { + // 在区间 [0, nums.length) 中随机抽取一个数字 + randomIndex := rand.Intn(len(nums)) + // 获取并返回随机元素 + randomNum = nums[randomIndex] + return + } ``` === "JavaScript" @@ -181,9 +201,22 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` +=== "Swift" + + ```swift title="array.swift" + /* 随机返回一个数组元素 */ + func randomAccess(nums: [Int]) -> Int { + // 在区间 [0, nums.count) 中随机抽取一个数字 + let randomIndex = nums.indices.randomElement()! + // 获取并返回随机元素 + let randomNum = nums[randomIndex] + return randomNum + } + ``` + ## 数组缺点 -**数组在初始化后长度不可变。** 由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。 +**数组在初始化后长度不可变**。由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。 === "Java" @@ -236,7 +269,17 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Go" ```go title="array.go" - + /* 扩展数组长度 */ + func extend(nums []int, enlarge int) []int { + // 初始化一个扩展长度后的数组 + res := make([]int, len(nums)+enlarge) + // 将原数组中的所有元素复制到新数组 + for i, num := range nums { + res[i] = num + } + // 返回扩展后的新数组 + return res + } ``` === "JavaScript" @@ -295,11 +338,27 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` -**数组中插入或删除元素效率低下。** 假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点: +=== "Swift" -- **时间复杂度高:** 数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。 -- **丢失元素:** 由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。 -- **内存浪费:** 我们一般会初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是我们不关心的,但这样做同时也会造成内存空间的浪费。 + ```swift title="array.swift" + /* 扩展数组长度 */ + 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 + } + ``` + +**数组中插入或删除元素效率低下**。假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点: + +- **时间复杂度高**:数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。 +- **丢失元素**:由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。 +- **内存浪费**:我们一般会初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是我们不关心的,但这样做同时也会造成内存空间的浪费。 ![array_insert_remove_element](array.assets/array_insert_remove_element.png) @@ -370,7 +429,23 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Go" ```go title="array.go" - + /* 在数组的索引 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] + } + } ``` === "JavaScript" @@ -448,9 +523,32 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` +=== "Swift" + + ```swift title="array.swift" + /* 在数组的索引 index 处插入元素 num */ + func insert(nums: inout [Int], num: Int, index: Int) { + // 把索引 index 以及之后的所有元素向后移动一位 + for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) { + nums[i] = nums[i - 1] + } + // 将 num 赋给 index 处元素 + nums[index] = num + } + + /* 删除索引 index 处元素 */ + func remove(nums: inout [Int], index: Int) { + let count = nums.count + // 把索引 index 之后的所有元素向前移动一位 + for i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) { + nums[i] = nums[i + 1] + } + } + ``` + ## 数组常用操作 -**数组遍历。** 以下介绍两种常用的遍历方法。 +**数组遍历**。以下介绍两种常用的遍历方法。 === "Java" @@ -499,7 +597,18 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Go" ```go title="array.go" - + /* 遍历数组 */ + func traverse(nums []int) { + count := 0 + // 通过索引遍历数组 + for i := 0; i < len(nums); i++ { + count++ + } + // 直接遍历数组 + for range nums { + count++ + } + } ``` === "JavaScript" @@ -562,7 +671,24 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` -**数组查找。** 通过遍历数组,查找数组内的指定元素,并输出对应索引。 +=== "Swift" + + ```swift title="array.swift" + /* 遍历数组 */ + func traverse(nums: [Int]) { + var count = 0 + // 通过索引遍历数组 + for _ in nums.indices { + count += 1 + } + // 直接遍历数组 + for _ in nums { + count += 1 + } + } + ``` + +**数组查找**。通过遍历数组,查找数组内的指定元素,并输出对应索引。 === "Java" @@ -604,7 +730,17 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Go" ```go title="array.go" - + /* 在数组中查找指定元素 */ + 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 + } ``` === "JavaScript" @@ -654,10 +790,24 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` +=== "Swift" + + ```swift title="array.swift" + /* 在数组中查找指定元素 */ + func find(nums: [Int], target: Int) -> Int { + for i in nums.indices { + if nums[i] == target { + return i + } + } + return -1 + } + ``` + ## 数组典型应用 -**随机访问。** 如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。 +**随机访问**。如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。 -**二分查找。** 例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的“翻开中间,排除一半”的方式,来实现一个查电子字典的算法。 +**二分查找**。例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的“翻开中间,排除一半”的方式,来实现一个查电子字典的算法。 -**深度学习。** 神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。 +**深度学习**。神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。 diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index 33cfdd34..8dd06978 100644 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -51,7 +51,19 @@ comments: true === "Go" ```go title="" - + /* 链表结点结构体 */ + type ListNode struct { + Val int // 结点值 + Next *ListNode // 指向下一结点的指针(引用) + } + + // NewListNode 构造函数,创建一个新的链表 + func NewListNode(val int) *ListNode { + return &ListNode{ + Val: val, + Next: nil, + } + } ``` === "JavaScript" @@ -100,9 +112,23 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + /* 链表结点类 */ + class ListNode { + var val: Int // 结点值 + var next: ListNode? // 指向下一结点的指针(引用) + + init(x: Int) { // 构造函数 + val = x + } + } + ``` + **尾结点指向什么?** 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 `null` / `nullptr` / `None` 。在不引起歧义下,本书都使用 `null` 来表示空。 -**链表初始化方法。** 建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。 +**链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。 !!! tip @@ -110,7 +136,7 @@ comments: true === "Java" - ```java title="" + ```java title="linked_list.java" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 ListNode n0 = new ListNode(1); @@ -127,7 +153,7 @@ comments: true === "C++" - ```cpp title="" + ```cpp title="linked_list.cpp" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 ListNode* n0 = new ListNode(1); @@ -144,7 +170,7 @@ comments: true === "Python" - ```python title="" + ```python title="linked_list.py" """ 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """ # 初始化各个结点 n0 = ListNode(1) @@ -161,13 +187,25 @@ comments: true === "Go" - ```go title="" - + ```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 ``` === "JavaScript" - ```js title="" + ```js title="linked_list.js" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 const n0 = new ListNode(1); @@ -184,7 +222,7 @@ comments: true === "TypeScript" - ```typescript title="" + ```typescript title="linked_list.ts" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 const n0 = new ListNode(1); @@ -201,13 +239,13 @@ comments: true === "C" - ```c title="" + ```c title="linked_list.c" ``` === "C#" - ```csharp title="" + ```csharp title="linked_list.cs" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 ListNode n0 = new ListNode(1); @@ -222,9 +260,26 @@ comments: true 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 + ``` + ## 链表优点 -**在链表中,插入与删除结点的操作效率高。** 例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。 +**在链表中,插入与删除结点的操作效率高**。例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。 ![linkedlist_insert_remove_node](linked_list.assets/linkedlist_insert_remove_node.png) @@ -232,7 +287,7 @@ comments: true === "Java" - ```java title="" + ```java title="linked_list.java" /* 在链表的结点 n0 之后插入结点 P */ void insert(ListNode n0, ListNode P) { ListNode n1 = n0.next; @@ -253,7 +308,7 @@ comments: true === "C++" - ```cpp title="" + ```cpp title="linked_list.cpp" /* 在链表的结点 n0 之后插入结点 P */ void insert(ListNode* n0, ListNode* P) { ListNode* n1 = n0->next; @@ -269,12 +324,14 @@ comments: true ListNode* P = n0->next; ListNode* n1 = P->next; n0->next = n1; + // 释放内存 + delete P; } ``` === "Python" - ```python title="" + ```python title="linked_list.py" """ 在链表的结点 n0 之后插入结点 P """ def insert(n0, P): n1 = n0.next @@ -293,13 +350,29 @@ comments: true === "Go" - ```go title="" + ```go title="linked_list.go" + /* 在链表的结点 n0 之后插入结点 P */ + func insert(n0 *ListNode, P *ListNode) { + n1 := n0.Next + n0.Next = P + P.Next = n1 + } + /* 删除链表的结点 n0 之后的首个结点 */ + func removeNode(n0 *ListNode) { + if n0.Next == nil { + return + } + // n0 -> P -> n1 + P := n0.Next + n1 := P.Next + n0.Next = n1 + } ``` === "JavaScript" - ```js title="" + ```js title="linked_list.js" /* 在链表的结点 n0 之后插入结点 P */ function insert(n0, P) { let n1 = n0.next; @@ -320,13 +393,14 @@ comments: true === "TypeScript" - ```typescript title="" + ```typescript title="linked_list.ts" /* 在链表的结点 n0 之后插入结点 P */ function insert(n0: ListNode, P: ListNode): void { const n1 = n0.next; n0.next = P; P.next = n1; } + /* 删除链表的结点 n0 之后的首个结点 */ function remove(n0: ListNode): void { if (!n0.next) { @@ -341,13 +415,13 @@ comments: true === "C" - ```c title="" + ```c title="linked_list.c" ``` === "C#" - ```csharp title="" + ```csharp title="linked_list.cs" // 在链表的结点 n0 之后插入结点 P void Insert(ListNode n0, ListNode P) { @@ -368,19 +442,42 @@ comments: true } ``` +=== "Swift" + + ```swift title="linked_list.swift" + /* 在链表的结点 n0 之后插入结点 P */ + func insert(n0: ListNode, P: ListNode) { + let n1 = n0.next + n0.next = P + P.next = n1 + } + + /* 删除链表的结点 n0 之后的首个结点 */ + func remove(n0: ListNode) { + if n0.next == nil { + return + } + // n0 -> P -> n1 + let P = n0.next + let n1 = P?.next + n0.next = n1 + P?.next = nil + } + ``` + ## 链表缺点 -**链表访问结点效率低。** 上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。 +**链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。 === "Java" - ```java title="" + ```java title="linked_list.java" /* 访问链表中索引为 index 的结点 */ ListNode access(ListNode head, int index) { for (int i = 0; i < index; i++) { - head = head.next; if (head == null) return null; + head = head.next; } return head; } @@ -388,13 +485,13 @@ comments: true === "C++" - ```cpp title="" + ```cpp title="linked_list.cpp" /* 访问链表中索引为 index 的结点 */ ListNode* access(ListNode* head, int index) { for (int i = 0; i < index; i++) { - head = head->next; if (head == nullptr) return nullptr; + head = head->next; } return head; } @@ -402,25 +499,34 @@ comments: true === "Python" - ```python title="" + ```python title="linked_list.py" """ 访问链表中索引为 index 的结点 """ def access(head, index): for _ in range(index): - head = head.next if not head: return None + head = head.next return head ``` === "Go" - ```go title="" - + ```go title="linked_list.go" + /* 访问链表中索引为 index 的结点 */ + func access(head *ListNode, index int) *ListNode { + for i := 0; i < index; i++ { + if head == nil { + return nil + } + head = head.Next + } + return head + } ``` === "JavaScript" - ```js title="" + ```js title="linked_list.js" /* 访问链表中索引为 index 的结点 */ function access(head, index) { for (let i = 0; i < index; i++) { @@ -434,7 +540,7 @@ comments: true === "TypeScript" - ```typescript title="" + ```typescript title="linked_list.ts" /* 访问链表中索引为 index 的结点 */ function access(head: ListNode | null, index: number): ListNode | null { for (let i = 0; i < index; i++) { @@ -449,35 +555,51 @@ comments: true === "C" - ```c title="" + ```c title="linked_list.c" ``` === "C#" - ```csharp title="" + ```csharp title="linked_list.cs" // 访问链表中索引为 index 的结点 ListNode Access(ListNode head, int index) { for (int i = 0; i < index; i++) { - head = head.next; if (head == null) return null; + head = head.next; } return head; } ``` -**链表的内存占用多。** 链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。 +=== "Swift" + + ```swift title="linked_list.swift" + /* 访问链表中索引为 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` 的结点,输出结点在链表中的索引。 +**遍历链表查找**。遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。 === "Java" - ```java title="" + ```java title="linked_list.java" /* 在链表中查找值为 target 的首个结点 */ int find(ListNode head, int target) { int index = 0; @@ -493,7 +615,7 @@ comments: true === "C++" - ```cpp title="" + ```cpp title="linked_list.cpp" /* 在链表中查找值为 target 的首个结点 */ int find(ListNode* head, int target) { int index = 0; @@ -509,7 +631,7 @@ comments: true === "Python" - ```python title="" + ```python title="linked_list.py" """ 在链表中查找值为 target 的首个结点 """ def find(head, target): index = 0 @@ -523,13 +645,24 @@ comments: true === "Go" - ```go title="" - + ```go title="linked_list.go" + /* 在链表中查找值为 target 的首个结点 */ + func find(head *ListNode, target int) int { + index := 0 + for head != nil { + if head.Val == target { + return index + } + head = head.Next + index++ + } + return -1 + } ``` === "JavaScript" - ```js title="" + ```js title="linked_list.js" /* 在链表中查找值为 target 的首个结点 */ function find(head, target) { let index = 0; @@ -546,7 +679,7 @@ comments: true === "TypeScript" - ```typescript title="" + ```typescript title="linked_list.ts" /* 在链表中查找值为 target 的首个结点 */ function find(head: ListNode | null, target: number): number { let index = 0; @@ -563,13 +696,13 @@ comments: true === "C" - ```c title="" + ```c title="linked_list.c" ``` === "C#" - ```csharp title="" + ```csharp title="linked_list.cs" // 在链表中查找值为 target 的首个结点 int Find(ListNode head, int target) { @@ -585,13 +718,31 @@ comments: true } ``` +=== "Swift" + + ```swift title="linked_list.swift" + /* 在链表中查找值为 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 + } + ``` + ## 常见链表类型 -**单向链表。** 即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。 +**单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。 -**环形链表。** 如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。 +**环形链表**。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。 -**双向链表。** 单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。 +**双向链表**。单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。 === "Java" @@ -631,7 +782,21 @@ comments: true === "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, + } + } ``` === "JavaScript" @@ -684,6 +849,21 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + /* 双向链表结点类 */ + class ListNode { + var val: Int // 结点值 + var next: ListNode? // 指向后继结点的指针(引用) + var prev: ListNode? // 指向前驱结点的指针(引用) + + init(x: Int) { // 构造函数 + val = x + } + } + ``` + ![linkedlist_common_types](linked_list.assets/linkedlist_common_types.png)

Fig. 常见链表类型

diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index 749b1948..1a47a0d1 100644 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -4,13 +4,13 @@ comments: true # 列表 -**由于长度不可变,数组的实用性大大降低。** 在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。 +**由于长度不可变,数组的实用性大大降低**。在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。 为了解决此问题,诞生了一种被称为「列表 List」的数据结构。列表可以被理解为长度可变的数组,因此也常被称为「动态数组 Dynamic Array」。列表基于数组实现,继承了数组的优点,同时还可以在程序运行中实时扩容。在列表中,我们可以自由地添加元素,而不用担心超过容量限制。 ## 列表常用操作 -**初始化列表。** 我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。 +**初始化列表**。我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。 === "Java" @@ -91,7 +91,17 @@ comments: true List list = numbers.ToList(); ``` -**访问与更新元素。** 列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。 +=== "Swift" + + ```swift title="list.swift" + /* 初始化列表 */ + // 无初始值 + let list1: [Int] = [] + // 有初始值 + var list = [1, 3, 2, 5, 4] + ``` + +**访问与更新元素**。列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。 === "Java" @@ -169,7 +179,17 @@ comments: true list[1] = 0; // 将索引 1 处的元素更新为 0 ``` -**在列表中添加、插入、删除元素。** 相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。 +=== "Swift" + + ```swift title="list.swift" + /* 访问元素 */ + let num = list[1] // 访问索引 1 处的元素 + + /* 更新元素 */ + list[1] = 0 // 将索引 1 处的元素更新为 0 + ``` + +**在列表中添加、插入、删除元素**。相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。 === "Java" @@ -317,7 +337,27 @@ comments: true list.RemoveAt(3); ``` -**遍历列表。** 与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。 +=== "Swift" + + ```swift title="list.swift" + /* 清空列表 */ + list.removeAll() + + /* 尾部添加元素 */ + list.append(1) + list.append(3) + list.append(2) + list.append(5) + list.append(4) + + /* 中间插入元素 */ + list.insert(6, at: 3) // 在索引 3 处插入数字 6 + + /* 删除元素 */ + list.remove(at: 3) // 删除索引 3 处的元素 + ``` + +**遍历列表**。与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。 === "Java" @@ -437,7 +477,23 @@ comments: true } ``` -**拼接两个列表。** 再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。 +=== "Swift" + + ```swift title="list.swift" + /* 通过索引遍历列表 */ + var count = 0 + for _ in list.indices { + count += 1 + } + + /* 直接遍历列表元素 */ + count = 0 + for _ in list { + count += 1 + } + ``` + +**拼接两个列表**。再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。 === "Java" @@ -502,7 +558,15 @@ comments: true list.AddRange(list1); // 将列表 list1 拼接到 list 之后 ``` -**排序列表。** 排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。 +=== "Swift" + + ```swift title="list.swift" + /* 拼接两个列表 */ + let list1 = [6, 8, 7, 10, 9] + list.append(contentsOf: list1) // 将列表 list1 拼接到 list 之后 + ``` + +**排序列表**。排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。 === "Java" @@ -559,13 +623,20 @@ comments: true list.Sort(); // 排序后,列表元素从小到大排列 ``` +=== "Swift" + + ```swift title="list.swift" + /* 排序列表 */ + list.sort() // 排序后,列表元素从小到大排列 + ``` + ## 列表简易实现 * 为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点: -- **初始容量:** 选取一个合理的数组的初始容量 `initialCapacity` 。在本示例中,我们选择 10 作为初始容量。 -- **数量记录:** 需要声明一个变量 `size` ,用来记录列表当前有多少个元素,并随着元素插入与删除实时更新。根据此变量,可以定位列表的尾部,以及判断是否需要扩容。 -- **扩容机制:** 插入元素有可能导致超出列表容量,此时需要扩容列表,方法是建立一个更大的数组来替换当前数组。需要给定一个扩容倍数 `extendRatio` ,在本示例中,我们规定每次将数组扩容至之前的 2 倍。 +- **初始容量**:选取一个合理的数组的初始容量 `initialCapacity` 。在本示例中,我们选择 10 作为初始容量。 +- **数量记录**:需要声明一个变量 `size` ,用来记录列表当前有多少个元素,并随着元素插入与删除实时更新。根据此变量,可以定位列表的尾部,以及判断是否需要扩容。 +- **扩容机制**:插入元素有可能导致超出列表容量,此时需要扩容列表,方法是建立一个更大的数组来替换当前数组。需要给定一个扩容倍数 `extendRatio` ,在本示例中,我们规定每次将数组扩容至之前的 2 倍。 本示例是为了帮助读者对如何实现列表产生直观的认识。实际编程语言中,列表的实现远比以下代码复杂且标准,感兴趣的读者可以查阅源码学习。 @@ -828,7 +899,7 @@ comments: true ```go title="my_list.go" /* 列表类简易实现 */ - type MyList struct { + type myList struct { numsCapacity int nums []int numsSize int @@ -836,8 +907,8 @@ comments: true } /* 构造函数 */ - func newMyList() *MyList { - return &MyList{ + func newMyList() *myList { + return &myList{ numsCapacity: 10, // 列表容量 nums: make([]int, 10), // 数组(存储列表元素) numsSize: 0, // 列表长度(即当前元素数量) @@ -846,17 +917,17 @@ comments: true } /* 获取列表长度(即当前元素数量) */ - func (l *MyList) size() int { + func (l *myList) size() int { return l.numsSize } /* 获取列表容量 */ - func (l *MyList) capacity() int { + func (l *myList) capacity() int { return l.numsCapacity } /* 访问元素 */ - func (l *MyList) get(index int) int { + func (l *myList) get(index int) int { // 索引如果越界则抛出异常,下同 if index >= l.numsSize { panic("索引越界") @@ -865,7 +936,7 @@ comments: true } /* 更新元素 */ - func (l *MyList) set(num, index int) { + func (l *myList) set(num, index int) { if index >= l.numsSize { panic("索引越界") } @@ -873,7 +944,7 @@ comments: true } /* 尾部添加元素 */ - func (l *MyList) add(num int) { + func (l *myList) add(num int) { // 元素数量超出容量时,触发扩容机制 if l.numsSize == l.numsCapacity { l.extendCapacity() @@ -884,7 +955,7 @@ comments: true } /* 中间插入元素 */ - func (l *MyList) insert(num, index int) { + func (l *myList) insert(num, index int) { if index >= l.numsSize { panic("索引越界") } @@ -902,20 +973,23 @@ comments: true } /* 删除元素 */ - func (l *MyList) Remove(index int) { + func (l *myList) remove(index int) int { if index >= l.numsSize { panic("索引越界") } + num := l.nums[index] // 索引 i 之后的元素都向前移动一位 for j := index; j < l.numsSize-1; j++ { l.nums[j] = l.nums[j+1] } // 更新元素数量 l.numsSize-- + // 返回被删除元素 + return num } /* 列表扩容 */ - func (l *MyList) extendCapacity() { + func (l *myList) extendCapacity() { // 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组 l.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...) // 更新列表容量 @@ -1220,3 +1294,110 @@ comments: true } } ``` + +=== "Swift" + + ```swift title="my_list.swift" + /* 列表类简易实现 */ + class MyList { + private var nums: [Int] // 数组(存储列表元素) + private var _capacity = 10 // 列表容量 + private var _size = 0 // 列表长度(即当前元素数量) + private let extendRatio = 2 // 每次列表扩容的倍数 + + /* 构造函数 */ + init() { + nums = Array(repeating: 0, count: _capacity) + } + + /* 获取列表长度(即当前元素数量)*/ + func size() -> Int { + _size + } + + /* 获取列表容量 */ + func capacity() -> Int { + _capacity + } + + /* 访问元素 */ + func get(index: Int) -> Int { + // 索引如果越界则抛出错误,下同 + if index >= _size { + fatalError("索引越界") + } + return nums[index] + } + + /* 更新元素 */ + func set(index: Int, num: Int) { + if index >= _size { + fatalError("索引越界") + } + nums[index] = num + } + + /* 尾部添加元素 */ + func add(num: Int) { + // 元素数量超出容量时,触发扩容机制 + if _size == _capacity { + extendCapacity() + } + nums[_size] = num + // 更新元素数量 + _size += 1 + } + + /* 中间插入元素 */ + func insert(index: Int, num: Int) { + if index >= _size { + fatalError("索引越界") + } + // 元素数量超出容量时,触发扩容机制 + if _size == _capacity { + extendCapacity() + } + // 将索引 index 以及之后的元素都向后移动一位 + for j in sequence(first: _size - 1, next: { $0 >= index + 1 ? $0 - 1 : nil }) { + nums[j + 1] = nums[j] + } + nums[index] = num + // 更新元素数量 + _size += 1 + } + + /* 删除元素 */ + @discardableResult + func remove(index: Int) -> Int { + if index >= _size { + fatalError("索引越界") + } + let num = nums[index] + // 将索引 index 之后的元素都向前移动一位 + for j in index ..< (_size - 1) { + nums[j] = nums[j + 1] + } + // 更新元素数量 + _size -= 1 + // 返回被删除元素 + return num + } + + /* 列表扩容 */ + func extendCapacity() { + // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 + nums = nums + Array(repeating: 0, count: _capacity * (extendRatio - 1)) + // 更新列表容量 + _capacity = nums.count + } + + /* 将列表转换为数组 */ + func toArray() -> [Int] { + var nums = Array(repeating: 0, count: _size) + for i in 0 ..< _size { + nums[i] = get(index: i) + } + return nums + } + } + ``` diff --git a/docs/chapter_array_and_linkedlist/summary.md b/docs/chapter_array_and_linkedlist/summary.md index 35e4e17c..60fe604f 100644 --- a/docs/chapter_array_and_linkedlist/summary.md +++ b/docs/chapter_array_and_linkedlist/summary.md @@ -4,7 +4,7 @@ comments: true # 小结 -- 数组和链表是两种基本数据结构,代表了数据在计算机内存中的两种存储方式,即连续空间存储和离散空间存储。两者的优缺点呈现出此消彼长的关系。 +- 数组和链表是两种基本数据结构,代表了数据在计算机内存中的两种存储方式,即连续空间存储和离散空间存储。两者的优点与缺点呈现出此消彼长的关系。 - 数组支持随机访问、内存空间占用小;但插入与删除元素效率低,且初始化后长度不可变。 - 链表可通过更改指针实现高效的结点插入与删除,并且可以灵活地修改长度;但结点访问效率低、占用内存多。常见的链表类型有单向链表、循环链表、双向链表。 - 列表又称动态数组,是基于数组实现的一种数据结构,其保存了数组的优势,且可以灵活改变长度。列表的出现大大提升了数组的实用性,但副作用是会造成部分内存空间浪费。 diff --git a/docs/chapter_computational_complexity/performance_evaluation.md b/docs/chapter_computational_complexity/performance_evaluation.md index c664a208..9d6fd662 100644 --- a/docs/chapter_computational_complexity/performance_evaluation.md +++ b/docs/chapter_computational_complexity/performance_evaluation.md @@ -8,15 +8,15 @@ comments: true 在开始学习算法之前,我们首先要想清楚算法的设计目标是什么,或者说,如何来评判算法的好与坏。整体上看,我们设计算法时追求两个层面的目标。 -1. **找到问题解法。** 算法需要能够在规定的输入范围下,可靠地求得问题的正确解。 -2. **寻求最优解法。** 同一个问题可能存在多种解法,而我们希望算法效率尽可能的高。 +1. **找到问题解法**。算法需要能够在规定的输入范围下,可靠地求得问题的正确解。 +2. **寻求最优解法**。同一个问题可能存在多种解法,而我们希望算法效率尽可能的高。 换言之,在可以解决问题的前提下,算法效率则是主要评价维度,包括: -- **时间效率** ,即算法的运行速度的快慢。 -- **空间效率** ,即算法占用的内存空间大小。 +- **时间效率**,即算法的运行速度的快慢。 +- **空间效率**,即算法占用的内存空间大小。 -数据结构与算法追求“运行得快、内存占用少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。 +数据结构与算法追求“运行速度快、占用内存少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。 ## 效率评估方法 @@ -24,17 +24,17 @@ comments: true 假设我们现在有算法 A 和 算法 B ,都能够解决同一问题,现在需要对比两个算法之间的效率。我们能够想到的最直接的方式,就是找一台计算机,把两个算法都完整跑一遍,并监控记录运行时间和内存占用情况。这种评估方式能够反映真实情况,但是也存在很大的硬伤。 -**难以排除测试环境的干扰因素。** 硬件配置会影响到算法的性能表现。例如,在某台计算机中,算法 A 比算法 B 运行时间更短;但换到另一台配置不同的计算机中,可能会得到相反的测试结果。这意味着我们需要在各种机器上展开测试,而这是不现实的。 +**难以排除测试环境的干扰因素**。硬件配置会影响到算法的性能表现。例如,在某台计算机中,算法 A 比算法 B 运行时间更短;但换到另一台配置不同的计算机中,可能会得到相反的测试结果。这意味着我们需要在各种机器上展开测试,而这是不现实的。 -**展开完整测试非常耗费资源。** 随着输入数据量的大小变化,算法会呈现出不同的效率表现。比如,有可能输入数据量较小时,算法 A 运行时间短于算法 B ,而在输入数据量较大时,测试结果截然相反。因此,若想要达到具有说服力的对比结果,那么需要输入各种体量数据,这样的测试需要占用大量计算资源。 +**展开完整测试非常耗费资源**。随着输入数据量的大小变化,算法会呈现出不同的效率表现。比如,有可能输入数据量较小时,算法 A 运行时间短于算法 B ,而在输入数据量较大时,测试结果截然相反。因此,若想要达到具有说服力的对比结果,那么需要输入各种体量数据,这样的测试需要占用大量计算资源。 ### 理论估算 既然实际测试具有很大的局限性,那么我们是否可以仅通过一些计算,就获知算法的效率水平呢?答案是肯定的,我们将此估算方法称为「复杂度分析 Complexity Analysis」或「渐近复杂度分析 Asymptotic Complexity Analysis」。 -**复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势** 。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。 +**复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势**。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。 -**复杂度分析克服了实际测试方法的弊端。** 一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。 +**复杂度分析克服了实际测试方法的弊端**。一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。 ## 复杂度分析的重要性 diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index df5d98f3..e594bec8 100644 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -4,7 +4,7 @@ comments: true # 空间复杂度 -「空间复杂度 Space Complexity」统计 **算法使用内存空间随着数据量变大时的增长趋势** 。这个概念与时间复杂度很类似。 +「空间复杂度 Space Complexity」统计 **算法使用内存空间随着数据量变大时的增长趋势**。这个概念与时间复杂度很类似。 ## 算法相关空间 @@ -103,14 +103,14 @@ comments: true ```go title="" /* 结构体 */ - type Node struct { + type node struct { val int - next *Node + next *node } - - /* 创建 Node 结构体 */ - func newNode(val int) *Node { - return &Node{val: val} + + /* 创建 node 结构体 */ + func newNode(val int) *node { + return &node{val: val} } /* 函数 */ @@ -174,14 +174,42 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + /* 类 */ + class Node { + var val: Int + var next: Node? + + init(x: Int) { + val = x + } + } + + /* 函数 */ + func function() -> Int { + // do something... + 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 // 输出数据 + } + ``` + ## 推算方法 空间复杂度的推算方法和时间复杂度总体类似,只是从统计“计算操作数量”变为统计“使用空间大小”。与时间复杂度不同的是,**我们一般只关注「最差空间复杂度」**。这是因为内存空间是一个硬性要求,我们必须保证在所有输入数据下都有足够的内存空间预留。 **最差空间复杂度中的“最差”有两层含义**,分别为输入数据的最差分布、算法运行中的最差时间点。 -- **以最差输入数据为准。** 当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但是当 $n > 10$ 时,初始化的数组 `nums` 使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; -- **以算法运行过程中的峰值内存为准。** 程序在执行最后一行之前,使用 $O(1)$ 空间;当初始化数组 `nums` 时,程序使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; +- **以最差输入数据为准**。当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但是当 $n > 10$ 时,初始化的数组 `nums` 使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; +- **以算法运行过程中的峰值内存为准**。程序在执行最后一行之前,使用 $O(1)$ 空间;当初始化数组 `nums` 时,程序使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; === "Java" @@ -219,11 +247,11 @@ comments: true ```go title="" func algorithm(n int) { - a := 0 // O(1) - b := make([]int, 10000) // O(1) + a := 0 // O(1) + b := make([]int, 10000) // O(1) var nums []int if n > 10 { - nums = make([]int, 10000) // O(n) + nums := make([]int, n) // O(n) } fmt.Println(a, b, nums) } @@ -261,7 +289,19 @@ comments: true } ``` -**在递归函数中,需要注意统计栈帧空间。** 例如函数 `loop()`,在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行中会同时存在 $n$ 个未返回的 `recur()` ,从而使用 $O(n)$ 的栈帧空间。 +=== "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) + } + } + ``` + +**在递归函数中,需要注意统计栈帧空间**。例如函数 `loop()`,在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行中会同时存在 $n$ 个未返回的 `recur()` ,从而使用 $O(n)$ 的栈帧空间。 === "Java" @@ -387,6 +427,31 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + @discardableResult + func function() -> Int { + // do something + 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) + } + ``` + ## 常见类型 设输入数据大小为 $n$ ,常见的空间复杂度类型有(从低到高排列) @@ -536,6 +601,27 @@ $$ } ``` +=== "Swift" + + ```swift title="space_complexity.swift" + /* 常数阶 */ + 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() + } + } + ``` + ### 线性阶 $O(n)$ 线性阶常见于元素数量与 $n$ 成正比的数组、链表、栈、队列等。 @@ -601,7 +687,7 @@ $$ // 长度为 n 的数组占用 O(n) 空间 _ = make([]int, n) // 长度为 n 的列表占用 O(n) 空间 - var nodes []*Node + var nodes []*node for i := 0; i < n; i++ { nodes = append(nodes, newNode(i)) } @@ -654,6 +740,20 @@ $$ } ``` +=== "Swift" + + ```swift title="space_complexity.swift" + /* 线性阶 */ + 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)") }) + } + ``` + 以下递归函数会同时存在 $n$ 个未返回的 `algorithm()` 函数,使用 $O(n)$ 大小的栈帧空间。 === "Java" @@ -731,6 +831,19 @@ $$ } ``` +=== "Swift" + + ```swift title="space_complexity.swift" + /* 线性阶(递归实现) */ + func linearRecur(n: Int) { + print("递归 n = \(n)") + if n == 1 { + return + } + linearRecur(n: n - 1) + } + ``` + ![space_complexity_recursive_linear](space_complexity.assets/space_complexity_recursive_linear.png)

Fig. 递归函数产生的线性阶空间复杂度

@@ -838,7 +951,17 @@ $$ ``` -在以下递归函数中,同时存在 $n$ 个未返回的 `algorihtm()` ,并且每个函数中都初始化了一个数组,长度分别为 $n, n-1, n-2, ..., 2, 1$ ,平均长度为 $\frac{n}{2}$ ,因此总体使用 $O(n^2)$ 空间。 +=== "Swift" + + ```swift title="space_complexity.swift" + /* 平方阶 */ + func quadratic(n: Int) { + // 二维列表占用 O(n^2) 空间 + let numList = Array(repeating: Array(repeating: 0, count: n), count: n) + } + ``` + +在以下递归函数中,同时存在 $n$ 个未返回的 `algorithm()` ,并且每个函数中都初始化了一个数组,长度分别为 $n, n-1, n-2, ..., 2, 1$ ,平均长度为 $\frac{n}{2}$ ,因此总体使用 $O(n^2)$ 空间。 === "Java" @@ -921,6 +1044,20 @@ $$ ``` +=== "Swift" + + ```swift title="space_complexity.swift" + /* 平方阶(递归实现) */ + func quadraticRecur(n: Int) -> Int { + if n <= 0 { + return 0 + } + // 数组 nums 长度为 n, n-1, ..., 2, 1 + let nums = Array(repeating: 0, count: n) + return quadraticRecur(n: n - 1) + } + ``` + ![space_complexity_recursive_quadratic](space_complexity.assets/space_complexity_recursive_quadratic.png)

Fig. 递归函数产生的平方阶空间复杂度

@@ -971,7 +1108,7 @@ $$ ```go title="space_complexity.go" /* 指数阶(建立满二叉树) */ - func buildTree(n int) *TreeNode { + func buildTree(n int) *treeNode { if n == 0 { return nil } @@ -1014,6 +1151,21 @@ $$ } ``` +=== "Swift" + + ```swift title="space_complexity.swift" + /* 指数阶(建立满二叉树) */ + 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 + } + ``` + ![space_complexity_exponential](space_complexity.assets/space_complexity_exponential.png)

Fig. 满二叉树下的指数阶空间复杂度

diff --git a/docs/chapter_computational_complexity/space_time_tradeoff.md b/docs/chapter_computational_complexity/space_time_tradeoff.md index 651aff14..49cf3377 100644 --- a/docs/chapter_computational_complexity/space_time_tradeoff.md +++ b/docs/chapter_computational_complexity/space_time_tradeoff.md @@ -6,7 +6,7 @@ comments: true 理想情况下,我们希望算法的时间复杂度和空间复杂度都能够达到最优,而实际上,同时优化时间复杂度和空间复杂度是非常困难的。 -**降低时间复杂度,往往是以提升空间复杂度为代价的,反之亦然。** 我们把牺牲内存空间来提升算法运行速度的思路称为「以空间换时间」;反之,称之为「以时间换空间」。选择哪种思路取决于我们更看重哪个方面。 +**降低时间复杂度,往往是以提升空间复杂度为代价的,反之亦然**。我们把牺牲内存空间来提升算法运行速度的思路称为「以空间换时间」;反之,称之为「以时间换空间」。选择哪种思路取决于我们更看重哪个方面。 大多数情况下,时间都是比空间更宝贵的,只要空间复杂度不要太离谱、能接受就行,**因此以空间换时间最为常用**。 @@ -149,6 +149,22 @@ comments: true } ``` +=== "Swift" + + ```swift title="leetcode_two_sum.swift" + 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] + } + ``` + ### 方法二:辅助哈希表 时间复杂度 $O(N)$ ,空间复杂度 $O(N)$ ,属于「空间换时间」。 @@ -294,3 +310,20 @@ comments: true } } ``` + +=== "Swift" + + ```swift title="leetcode_two_sum.swift" + 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] + } + ``` diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index aafb0d89..09aee7b7 100644 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -79,19 +79,46 @@ $$ === "JavaScript" ```js 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 + } + } ``` === "TypeScript" ```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 + } + } ``` === "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 + } + } ``` === "C#" @@ -126,7 +153,7 @@ $$ } ``` -但实际上, **统计算法的运行时间既不合理也不现实。** 首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。 +但实际上, **统计算法的运行时间既不合理也不现实**。首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。 ## 统计时间增长趋势 @@ -220,19 +247,65 @@ $$ === "JavaScript" ```js title="" + // 算法 A 时间复杂度:常数阶 + function algorithm_A(n) { + console.log(0); + } + // 算法 B 时间复杂度:线性阶 + function algorithm_B(n) { + for (let i = 0; i < n; i++) { + console.log(0); + } + } + // 算法 C 时间复杂度:常数阶 + function algorithm_C(n) { + for (let i = 0; i < 1000000; i++) { + console.log(0); + } + } ``` === "TypeScript" ```typescript title="" - + // 算法 A 时间复杂度:常数阶 + function algorithm_A(n: number): void { + console.log(0); + } + // 算法 B 时间复杂度:线性阶 + function algorithm_B(n: number): void { + for (let i = 0; i < n; i++) { + console.log(0); + } + } + // 算法 C 时间复杂度:常数阶 + function algorithm_C(n: number): void { + for (let i = 0; i < 1000000; i++) { + console.log(0); + } + } ``` === "C" ```c title="" - + // 算法 A 时间复杂度:常数阶 + void algorithm_A(int n) { + printf("%d", 0); + } + // 算法 B 时间复杂度:线性阶 + void algorithm_B(int n) { + for (int i = 0; i < n; i++) { + printf("%d", 0); + } + } + // 算法 C 时间复杂度:常数阶 + void algorithm_C(int n) { + for (int i = 0; i < 1000000; i++) { + printf("%d", 0); + } + } ``` === "C#" @@ -290,15 +363,15 @@ $$ 相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足? -**时间复杂度可以有效评估算法效率。** 算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。 +**时间复杂度可以有效评估算法效率**。算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。 -**时间复杂度分析将统计「计算操作的运行时间」简化为统计「计算操作的数量」。** 这是因为,无论是运行平台、还是计算操作类型,都与算法运行时间的增长趋势无关。因此,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”。 +**时间复杂度的推算方法更加简便**。在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。 -**时间复杂度也存在一定的局限性。** 比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。即使存在这些问题,计算复杂度仍然是评判算法效率的最有效、最常用方法。 +**时间复杂度也存在一定的局限性**。比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。 ## 函数渐近上界 -设算法「计算操作数量」为 $T(n)$ ,其是一个关于输入数据大小 $n$ 的函数。例如,以下算法的操作数量为 +设算法「计算操作数量」为 $T(n)$ ,其是一个关于输入数据大小 $n$ 的函数。例如,以下算法的操作数量为 $$ T(n) = 3 + 2n @@ -348,32 +421,58 @@ $$ ```go title="" func algorithm(n int) { - a := 1 // +1 - a = a + 1 // +1 - a = a * 2 // +1 + a := 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 // 循环 n 次 - for i := 0; i < n; i++ { // +1 - fmt.Println(a) // +1 - } + for i := 0; i < n; i++ { // +1 + fmt.Println(a) // +1 + } } ``` === "JavaScript" ```js 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 + } + } ``` === "TypeScript" ```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 + } + } ``` === "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 + } + } ``` === "C#" @@ -439,9 +538,9 @@ $T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得 对着代码,从上到下一行一行地计数即可。然而,**由于上述 $c \cdot f(n)$ 中的常数项 $c$ 可以取任意大小,因此操作数量 $T(n)$ 中的各种系数、常数项都可以被忽略**。根据此原则,可以总结出以下计数偷懒技巧: -1. **跳过数量与 $n$ 无关的操作。** 因为他们都是 $T(n)$ 中的常数项,对时间复杂度不产生影响。 -2. **省略所有系数。** 例如,循环 $2n$ 次、$5n + 1$ 次、……,都可以化简记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度也不产生影响。 -3. **循环嵌套时使用乘法。** 总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。 +1. **跳过数量与 $n$ 无关的操作**。因为他们都是 $T(n)$ 中的常数项,对时间复杂度不产生影响。 +2. **省略所有系数**。例如,循环 $2n$ 次、$5n + 1$ 次、……,都可以化简记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度也不产生影响。 +3. **循环嵌套时使用乘法**。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。 根据以下示例,使用上述技巧前、后的统计结果分别为 @@ -530,19 +629,58 @@ $$ === "JavaScript" ```js 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); + } + } + } ``` === "TypeScript" ```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); + } + } + } ``` === "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); + } + } + } ``` === "C#" @@ -685,19 +823,40 @@ $$ === "JavaScript" ```js title="time_complexity.js" - + /* 常数阶 */ + function constant(n) { + let count = 0; + const size = 100000; + for (let i = 0; i < size; i++) count++; + return count; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" - + /* 常数阶 */ + function constant(n: number): number { + let count = 0; + const size = 100000; + for (let i = 0; i < size; i++) count++; + return count; + } ``` === "C" ```c title="time_complexity.c" - + /* 常数阶 */ + int constant(int n) { + int count = 0; + int size = 100000; + int i = 0; + for (int i = 0; i < size; i++) { + count ++; + } + return count; + } ``` === "C#" @@ -717,7 +876,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 常数阶 + /* 常数阶 */ func constant(n: Int) -> Int { var count = 0 let size = 100000 @@ -783,19 +942,36 @@ $$ === "JavaScript" ```js title="time_complexity.js" - + /* 线性阶 */ + function linear(n) { + let count = 0; + for (let i = 0; i < n; i++) count++; + return count; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" - + /* 线性阶 */ + function linear(n: number): number { + let count = 0; + for (let i = 0; i < n; i++) count++; + return count; + } ``` === "C" ```c title="time_complexity.c" - + /* 线性阶 */ + int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) { + count ++; + } + return count; + } ``` === "C#" @@ -814,7 +990,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 线性阶 + /* 线性阶 */ func linear(n: Int) -> Int { var count = 0 for _ in 0 ..< n { @@ -828,7 +1004,7 @@ $$ !!! tip - **数据大小 $n$ 是根据输入数据的类型来确定的。** 比如,在上述示例中,我们直接将 $n$ 看作输入数据大小;以下遍历数组示例中,数据大小 $n$ 为数组的长度。 + **数据大小 $n$ 是根据输入数据的类型来确定的**。比如,在上述示例中,我们直接将 $n$ 看作输入数据大小;以下遍历数组示例中,数据大小 $n$ 为数组的长度。 === "Java" @@ -887,19 +1063,43 @@ $$ === "JavaScript" ```js title="time_complexity.js" - + /* 线性阶(遍历数组) */ + function arrayTraversal(nums) { + let count = 0; + // 循环次数与数组长度成正比 + for (let i = 0; i < nums.length; i++) { + count++; + } + return count; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" - + /* 线性阶(遍历数组) */ + function arrayTraversal(nums: number[]): number { + let count = 0; + // 循环次数与数组长度成正比 + for (let i = 0; i < nums.length; i++) { + count++; + } + return count; + } ``` === "C" ```c title="time_complexity.c" - + /* 线性阶(遍历数组) */ + int arrayTraversal(int *nums, int n) { + int count = 0; + // 循环次数与数组长度成正比 + for (int i = 0; i < n; i++) { + count ++; + } + return count; + } ``` === "C#" @@ -921,7 +1121,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 线性阶(遍历数组) + /* 线性阶(遍历数组) */ func arrayTraversal(nums: [Int]) -> Int { var count = 0 // 循环次数与数组长度成正比 @@ -1000,19 +1200,49 @@ $$ === "JavaScript" ```js title="time_complexity.js" - + /* 平方阶 */ + function quadratic(n) { + let count = 0; + // 循环次数与数组长度成平方关系 + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" - + /* 平方阶 */ + function quadratic(n: number): number { + let count = 0; + // 循环次数与数组长度成平方关系 + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; + } ``` === "C" ```c title="time_complexity.c" - + /* 平方阶 */ + int quadratic(int n) { + int count = 0; + // 循环次数与数组长度成平方关系 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count ++; + } + } + return count; + } ``` === "C#" @@ -1037,7 +1267,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 平方阶 + /* 平方阶 */ func quadratic(n: Int) -> Int { var count = 0 // 循环次数与数组长度成平方关系 @@ -1151,19 +1381,69 @@ $$ === "JavaScript" ```js title="time_complexity.js" - + /* 平方阶(冒泡排序) */ + function bubbleSort(nums) { + let count = 0; // 计数器 + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (let i = nums.length - 1; 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; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" - + /* 平方阶(冒泡排序) */ + function bubbleSort(nums: number[]): number { + let count = 0; // 计数器 + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (let i = nums.length - 1; 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; + } ``` === "C" ```c title="time_complexity.c" - + /* 平方阶(冒泡排序) */ + int bubbleSort(int *nums, int n) { + int count = 0; // 计数器 + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (int i = n - 1; i > 0; i--) { + // 内循环:冒泡操作 + for (int j = 0; j < i; j++) { + // 交换 nums[j] 与 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交换包含 3 个单元操作 + } + + } + return count; + } ``` === "C#" @@ -1197,11 +1477,11 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 平方阶(冒泡排序) + /* 平方阶(冒泡排序) */ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // 计数器 // 外循环:待排序元素数量为 n-1, n-2, ..., 1 - for i in sequence(first: nums.count - 1, next: { $0 > 0 ? $0 - 1 : nil }) { + for i in sequence(first: nums.count - 1, next: { $0 > 0 + 1 ? $0 - 1 : nil }) { // 内循环:冒泡操作 for j in 0 ..< i { if nums[j] > nums[j + 1] { @@ -1297,19 +1577,59 @@ $$ === "JavaScript" ```js title="time_complexity.js" + /* 指数阶(循环实现) */ + function exponential(n) { + let count = 0, + base = 1; + // cell 每轮一分为二,形成数列 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; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" - + /* 指数阶(循环实现) */ + function exponential(n: number): number { + let count = 0, + base = 1; + // cell 每轮一分为二,形成数列 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; + } ``` === "C" ```c title="time_complexity.c" - + /* 指数阶(循环实现) */ + int exponential(int n) { + int count = 0; + int bas = 1; + // cell 每轮一分为二,形成数列 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; + } ``` === "C#" @@ -1336,7 +1656,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 指数阶(循环实现) + /* 指数阶(循环实现) */ func exponential(n: Int) -> Int { var count = 0 var base = 1 @@ -1402,19 +1722,32 @@ $$ === "JavaScript" ```js title="time_complexity.js" - + /* 指数阶(递归实现) */ + function expRecur(n) { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" + /* 指数阶(递归实现) */ + function expRecur(n: number): number { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; + } ``` === "C" ```c title="time_complexity.c" - + /* 指数阶(递归实现) */ + int expRecur(int n) { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; + } ``` === "C#" @@ -1431,7 +1764,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 指数阶(递归实现) + /* 指数阶(递归实现) */ func expRecur(n: Int) -> Int { if n == 1 { return 1 @@ -1442,7 +1775,7 @@ $$ ### 对数阶 $O(\log n)$ -对数阶与指数阶正好相反,后者反映“每轮增加到两倍的情况”,而前者反映“每轮缩减到一半的情况”。对数阶仅次于常数阶,时间增长的很慢,是理想的时间复杂度。 +对数阶与指数阶正好相反,后者反映“每轮增加到两倍的情况”,而前者反映“每轮缩减到一半的情况”。对数阶仅次于常数阶,时间增长得很慢,是理想的时间复杂度。 对数阶常出现于「二分查找」和「分治算法」中,体现“一分为多”、“化繁为简”的算法思想。 @@ -1505,19 +1838,43 @@ $$ === "JavaScript" ```js title="time_complexity.js" - + /* 对数阶(循环实现) */ + function logarithmic(n) { + let count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" - + /* 对数阶(循环实现) */ + function logarithmic(n: number): number { + let count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; + } ``` === "C" ```c title="time_complexity.c" - + /* 对数阶(循环实现) */ + int logarithmic(float n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; + } ``` === "C#" @@ -1539,7 +1896,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 对数阶(循环实现) + /* 对数阶(循环实现) */ func logarithmic(n: Int) -> Int { var count = 0 var n = n @@ -1601,19 +1958,31 @@ $$ === "JavaScript" ```js title="time_complexity.js" - + /* 对数阶(递归实现) */ + function logRecur(n) { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" - + /* 对数阶(递归实现) */ + function logRecur(n: number): number { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; + } ``` === "C" ```c title="time_complexity.c" - + /* 对数阶(递归实现) */ + int logRecur(float n) { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; + } ``` === "C#" @@ -1630,7 +1999,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 对数阶(递归实现) + /* 对数阶(递归实现) */ func logRecur(n: Int) -> Int { if n <= 1 { return 0 @@ -1651,7 +2020,7 @@ $$ /* 线性对数阶 */ int linearLogRecur(float n) { if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (int i = 0; i < n; i++) { count++; @@ -1666,7 +2035,7 @@ $$ /* 线性对数阶 */ int linearLogRecur(float n) { if (n <= 1) return 1; - int count = linearLogRecur(n / 2) + + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); for (int i = 0; i < n; i++) { count++; @@ -1708,19 +2077,44 @@ $$ === "JavaScript" ```js title="time_complexity.js" - + /* 线性对数阶 */ + 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; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" - + /* 线性对数阶 */ + 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; + } ``` === "C" ```c title="time_complexity.c" - + /* 线性对数阶 */ + int linearLogRecur(float n) { + if (n <= 1) return 1; + int count = linearLogRecur(n / 2) + + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count ++; + } + return count; + } ``` === "C#" @@ -1743,7 +2137,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 线性对数阶 + /* 线性对数阶 */ func linearLogRecur(n: Double) -> Int { if n <= 1 { return 1 @@ -1833,19 +2227,45 @@ $$ === "JavaScript" ```js title="time_complexity.js" - + /* 阶乘阶(递归实现) */ + 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; + } ``` === "TypeScript" ```typescript title="time_complexity.ts" - + /* 阶乘阶(递归实现) */ + 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; + } ``` === "C" ```c title="time_complexity.c" - + /* 阶乘阶(递归实现) */ + 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; + } ``` === "C#" @@ -1868,7 +2288,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 阶乘阶(递归实现) + /* 阶乘阶(递归实现) */ func factorialRecur(n: Int) -> Int { if n == 0 { return 1 @@ -1888,7 +2308,7 @@ $$ ## 最差、最佳、平均时间复杂度 -**某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关。** 举一个例子,输入一个长度为 $n$ 数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 $1$ 的索引。我们可以得出以下结论: +**某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关**。举一个例子,输入一个长度为 $n$ 数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 $1$ 的索引。我们可以得出以下结论: - 当 `nums = [?, ?, ..., 1]`,即当末尾元素是 $1$ 时,则需完整遍历数组,此时达到 **最差时间复杂度 $O(n)$** ; - 当 `nums = [1, ?, ?, ...]` ,即当首个数字为 $1$ 时,无论数组多长都不需要继续遍历,此时达到 **最佳时间复杂度 $\Omega(1)$** ; @@ -1915,7 +2335,7 @@ $$ } return res; } - + /* 查找数组 nums 中数字 1 所在索引 */ int findOne(int[] nums) { for (int i = 0; i < nums.length; i++) { @@ -1924,7 +2344,7 @@ $$ } return -1; } - + /* Driver Code */ public void main(String[] args) { for (int i = 0; i < 10; i++) { @@ -1984,7 +2404,7 @@ $$ ```python title="worst_best_time_complexity.py" """ 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 """ def random_numbers(n): - # 生成数组 nums =: 1, 2, 3, ..., n + # 生成数组 nums =: 1, 2, 3, ..., n nums = [i for i in range(1, n + 1)] # 随机打乱数组元素 random.shuffle(nums) @@ -2049,19 +2469,140 @@ $$ === "JavaScript" ```js title="worst_best_time_complexity.js" + /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ + function randomNumbers(n) { + let 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++) { + let r = Math.floor(Math.random() * (i + 1)); + let 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++) { + if (nums[i] === 1) { + return i; + } + } + return -1; + } + + /* Driver Code */ + function main() { + for (let i = 0; i < 10; i++) { + let n = 100; + let nums = randomNumbers(n); + let index = findOne(nums); + console.log( + "\n数组 [ 1, 2, ..., n ] 被打乱后 = [" + nums.join(", ") + "]" + ); + console.log("数字 1 的索引为 " + index); + } + } ``` === "TypeScript" ```typescript title="worst_best_time_complexity.ts" + /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ + function randomNumbers(n: number): number[] { + let 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++) { + let r = Math.floor(Math.random() * (i + 1)); + let 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++) { + if (nums[i] === 1) { + return i; + } + } + return -1; + } + + /* Driver Code */ + function main(): void { + for (let i = 0; i < 10; i++) { + let n = 100; + let nums = randomNumbers(n); + let index = findOne(nums); + console.log( + "\n数组 [ 1, 2, ..., n ] 被打乱后 = [" + nums.join(", ") + "]" + ); + console.log("数字 1 的索引为 " + index); + } + } ``` === "C" ```c title="worst_best_time_complexity.c" + /* 生成一个数组,元素为 { 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++) { + 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; + } + } + getchar(); + return 0; + } ``` === "C#" @@ -2116,8 +2657,8 @@ $$ === "Swift" - ```swift title="" - // 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 + ```swift title="worst_best_time_complexity.swift" + /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ func randomNumbers(n: Int) -> [Int] { // 生成数组 nums = { 1, 2, 3, ..., n } var nums = Array(1 ... n) @@ -2126,7 +2667,7 @@ $$ return nums } - // 查找数组 nums 中数字 1 所在索引 + /* 查找数组 nums 中数字 1 所在索引 */ func findOne(nums: [Int]) -> Int { for i in nums.indices { if nums[i] == 1 { @@ -2136,14 +2677,14 @@ $$ return -1 } - // Driver Code + /* Driver Code */ 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) + print("数组 [ 1, 2, ..., n ] 被打乱后 = \(nums)") + print("数字 1 的索引为 \(index)") } } ``` diff --git a/docs/chapter_data_structure/classification_of_data_strcuture.assets/classification_logic_structure.png b/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png similarity index 100% rename from docs/chapter_data_structure/classification_of_data_strcuture.assets/classification_logic_structure.png rename to docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png diff --git a/docs/chapter_data_structure/classification_of_data_strcuture.assets/classification_phisical_structure.png b/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png similarity index 100% rename from docs/chapter_data_structure/classification_of_data_strcuture.assets/classification_phisical_structure.png rename to docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png diff --git a/docs/chapter_data_structure/classification_of_data_strcuture.md b/docs/chapter_data_structure/classification_of_data_structure.md similarity index 52% rename from docs/chapter_data_structure/classification_of_data_strcuture.md rename to docs/chapter_data_structure/classification_of_data_structure.md index b41266bb..202af50a 100644 --- a/docs/chapter_data_structure/classification_of_data_strcuture.md +++ b/docs/chapter_data_structure/classification_of_data_structure.md @@ -8,14 +8,14 @@ comments: true ## 逻辑结构:线性与非线性 -**「逻辑结构」反映了数据之间的逻辑关系。** 数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。 +**「逻辑结构」反映了数据之间的逻辑关系**。数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。 我们一般将逻辑结构分为「线性」和「非线性」两种。“线性”这个概念很直观,即表明数据在逻辑关系上是排成一条线的;而如果数据之间的逻辑关系是非线形的(例如是网状或树状的),那么就是非线性数据结构。 -- **线性数据结构:** 数组、链表、栈、队列、哈希表; -- **非线性数据结构:** 树、图、堆、哈希表; +- **线性数据结构**:数组、链表、栈、队列、哈希表; +- **非线性数据结构**:树、图、堆、哈希表; -![classification_logic_structure](classification_of_data_strcuture.assets/classification_logic_structure.png) +![classification_logic_structure](classification_of_data_structure.assets/classification_logic_structure.png)

Fig. 线性与非线性数据结构

@@ -25,16 +25,16 @@ comments: true 若感到阅读困难,建议先看完下个章节「数组与链表」,再回过头来理解物理结构的含义。 -**「物理结构」反映了数据在计算机内存中的存储方式。** 从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储** 。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。 +**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储**。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。 -![classification_phisical_structure](classification_of_data_strcuture.assets/classification_phisical_structure.png) +![classification_phisical_structure](classification_of_data_structure.assets/classification_phisical_structure.png)

Fig. 连续空间存储与离散空间存储

-**所有数据结构都是基于数组、或链表、或两者组合实现的。** 例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。 +**所有数据结构都是基于数组、或链表、或两者组合实现的**。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。 -- **基于数组可实现:** 栈、队列、堆、哈希表、矩阵、张量(维度 $\geq 3$ 的数组)等; -- **基于链表可实现:** 栈、队列、堆、哈希表、树、图等; +- **基于数组可实现**:栈、队列、堆、哈希表、矩阵、张量(维度 $\geq 3$ 的数组)等; +- **基于链表可实现**:栈、队列、堆、哈希表、树、图等; 基于数组实现的数据结构也被称为「静态数据结构」,这意味着该数据结构在在被初始化后,长度不可变。相反地,基于链表实现的数据结构被称为「动态数据结构」,该数据结构在被初始化后,我们也可以在程序运行中修改其长度。 diff --git a/docs/chapter_data_structure/data_and_memory.md b/docs/chapter_data_structure/data_and_memory.md index 3e1e2167..93147fb6 100644 --- a/docs/chapter_data_structure/data_and_memory.md +++ b/docs/chapter_data_structure/data_and_memory.md @@ -42,7 +42,7 @@ comments: true **「基本数据类型」与「数据结构」之间的联系与区别** -我们知道,数据结构是在计算机中 **组织与存储数据的方式** ,它的主语是“结构”,而不是“数据”。比如,我们想要表示“一排数字”,自然应该使用「数组」这个数据结构。数组的存储方式使之可以表示数字的相邻关系、先后关系等一系列我们需要的信息,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char ,**则与所谓的数据的结构无关了**。 +我们知道,数据结构是在计算机中 **组织与存储数据的方式**,它的主语是“结构”,而不是“数据”。比如,我们想要表示“一排数字”,自然应该使用「数组」这个数据结构。数组的存储方式使之可以表示数字的相邻关系、先后关系等一系列我们需要的信息,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char ,**则与所谓的数据的结构无关了**。 === "Java" @@ -114,16 +114,26 @@ comments: true bool[] booleans = new bool[5]; ``` +=== "Swift" + + ```swift title="" + /* 使用多种「基本数据类型」来初始化「数组」 */ + let numbers = Array(repeating: Int(), count: 5) + let decimals = Array(repeating: Double(), count: 5) + let characters = Array(repeating: Character("a"), count: 5) + let booleans = Array(repeating: Bool(), count: 5) + ``` + ## 计算机内存 在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度更快,但容量较小(通常为 GB 级别)。 -**算法运行中,相关数据都被存储在内存中。** 下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。 +**算法运行中,相关数据都被存储在内存中**。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。 -**系统通过「内存地址 Memory Location」来访问目标内存位置的数据。** 计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。 +**系统通过「内存地址 Memory Location」来访问目标内存位置的数据**。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。 ![computer_memory_location](data_and_memory.assets/computer_memory_location.png)

Fig. 内存条、内存空间、内存地址

-**内存资源是设计数据结构与算法的重要考虑因素。** 内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。 +**内存资源是设计数据结构与算法的重要考虑因素**。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。 diff --git a/docs/chapter_hashing/hash_collision.md b/docs/chapter_hashing/hash_collision.md index 05dc2088..45a52997 100644 --- a/docs/chapter_hashing/hash_collision.md +++ b/docs/chapter_hashing/hash_collision.md @@ -24,9 +24,9 @@ comments: true 在原始哈希表中,一个桶地址只能存储一个元素(即键值对)。**考虑将桶地址内的单个元素转变成一个链表,将所有冲突元素都存储在一个链表中**,此时哈希表操作方法为: -- **查询元素:** 先将 key 输入到哈希函数得到桶地址(即访问链表头部),再遍历链表来确定对应的 value 。 -- **添加元素:** 先通过哈希函数访问链表头部,再将元素直接添加到链表头部即可。 -- **删除元素:** 同样先访问链表头部,再遍历链表查找对应元素,删除之即可。 +- **查询元素**:先将 key 输入到哈希函数得到桶地址(即访问链表头部),再遍历链表来确定对应的 value 。 +- **添加元素**:先通过哈希函数访问链表头部,再将元素直接添加到链表头部即可。 +- **删除元素**:同样先访问链表头部,再遍历链表查找对应元素,删除之即可。 (图) @@ -46,9 +46,9 @@ comments: true 「线性探测」使用固定步长的线性查找来解决哈希冲突。 -**插入元素:** 如果出现哈希冲突,则从冲突位置向后线性遍历(步长一般取 1 ),直到找到一个空位,则将元素插入到该空位中。 +**插入元素**:如果出现哈希冲突,则从冲突位置向后线性遍历(步长一般取 1 ),直到找到一个空位,则将元素插入到该空位中。 -**查找元素:** 若出现哈希冲突,则使用相同步长执行线性查找,会遇到两种情况: +**查找元素**:若出现哈希冲突,则使用相同步长执行线性查找,会遇到两种情况: 1. 找到对应元素,返回 value 即可; 2. 若遇到空位,则说明查找键值对不在哈希表中; @@ -64,9 +64,9 @@ comments: true 顾名思义,「多次哈希」的思路是基于多个哈希函数 $f_1(x)$ , $f_2(x)$ , $f_3(x)$ , $\cdots$ 进行探测。 -**插入元素:** 若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推……直到找到空位后插入元素。 +**插入元素**:若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推……直到找到空位后插入元素。 -**查找元素:** 以相同的哈希函数顺序查找,存在两种情况: +**查找元素**:以相同的哈希函数顺序查找,存在两种情况: 1. 找到目标元素,则返回之; 2. 到空位或已尝试所有哈希函数,说明哈希表中无此元素; diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index d8244d80..cbb4ba04 100644 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -16,10 +16,10 @@ comments: true 除了哈希表之外,还可以使用以下数据结构来实现上述查询功能: -1. **无序数组:** 每个元素为 `[学号, 姓名]` ; -2. **有序数组:** 将 `1.` 中的数组按照学号从小到大排序; -3. **链表:** 每个结点的值为 `[学号, 姓名]` ; -4. **二叉搜索树:** 每个结点的值为 `[学号, 姓名]` ,根据学号大小来构建树; +1. **无序数组**:每个元素为 `[学号, 姓名]` ; +2. **有序数组**:将 `1.` 中的数组按照学号从小到大排序; +3. **链表**:每个结点的值为 `[学号, 姓名]` ; +4. **二叉搜索树**:每个结点的值为 `[学号, 姓名]` ,根据学号大小来构建树; 使用上述方法,各项操作的时间复杂度如下表所示(在此不做赘述,详解可见 [二叉搜索树章节](https://www.hello-algo.com/chapter_tree/binary_search_tree/#_6))。无论是查找元素、还是增删元素,哈希表的时间复杂度都是 $O(1)$ ,全面胜出! @@ -108,25 +108,25 @@ comments: true === "Go" - ```go title="hash_map_test.go" - /* 初始化哈希表 */ - mapp := make(map[int]string) + ```go title="hash_map.go" + /* 初始化哈希表 */ + mapp := make(map[int]string) - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - mapp[12836] = "小哈" - mapp[15937] = "小啰" - mapp[16750] = "小算" - mapp[13276] = "小法" - mapp[10583] = "小鸭" + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + mapp[12836] = "小哈" + mapp[15937] = "小啰" + mapp[16750] = "小算" + mapp[13276] = "小法" + mapp[10583] = "小鸭" - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - name := mapp[15937] + /* 查询操作 */ + // 向哈希表输入键 key ,得到值 value + name := mapp[15937] - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - delete(mapp, 10583) + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + delete(mapp, 10583) ``` === "JavaScript" @@ -207,6 +207,12 @@ comments: true map.Remove(10583); ``` +=== "Swift" + + ```swift title="hash_map.swift" + + ``` + 遍历哈希表有三种方式,即 **遍历键值对、遍历键、遍历值**。 === "Java" @@ -339,6 +345,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="hash_map.swift" + + ``` + ## 哈希函数 哈希表中存储元素的数据结构被称为「桶 Bucket」,底层实现可能是数组、链表、二叉树(红黑树),或是它们的组合。 @@ -512,30 +524,30 @@ $$ ```go title="array_hash_map.go" /* 键值对 int->String */ - type Entry struct { + type entry struct { key int val string } /* 基于数组简易实现的哈希表 */ - type ArrayHashMap struct { - bucket []*Entry + type arrayHashMap struct { + bucket []*entry } - func newArrayHashMap() *ArrayHashMap { + func newArrayHashMap() *arrayHashMap { // 初始化一个长度为 100 的桶(数组) - bucket := make([]*Entry, 100) - return &ArrayHashMap{bucket: bucket} + bucket := make([]*entry, 100) + return &arrayHashMap{bucket: bucket} } /* 哈希函数 */ - func (a *ArrayHashMap) hashFunc(key int) int { + func (a *arrayHashMap) hashFunc(key int) int { index := key % 100 return index } /* 查询操作 */ - func (a *ArrayHashMap) get(key int) string { + func (a *arrayHashMap) get(key int) string { index := a.hashFunc(key) pair := a.bucket[index] if pair == nil { @@ -545,16 +557,16 @@ $$ } /* 添加操作 */ - func (a *ArrayHashMap) put(key int, val string) { - pair := &Entry{key: key, val: val} + func (a *arrayHashMap) put(key int, val string) { + pair := &entry{key: key, val: val} index := a.hashFunc(key) a.bucket[index] = pair } /* 删除操作 */ - func (a *ArrayHashMap) remove(key int) { + func (a *arrayHashMap) remove(key int) { index := a.hashFunc(key) - // 置为空字符,代表删除 + // 置为 nil ,代表删除 a.bucket[index] = nil } ``` @@ -756,6 +768,12 @@ $$ } ``` +=== "Swift" + + ```swift title="array_hash_map.swift" + + ``` + ## 哈希冲突 细心的同学可能会发现,**哈希函数 $f(x) = x \% 100$ 会在某些情况下失效**。具体地,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 12836 和 20336 ,则有 diff --git a/docs/chapter_introduction/algorithms_are_everywhere.md b/docs/chapter_introduction/algorithms_are_everywhere.md index b80a525b..5d80f9c9 100644 --- a/docs/chapter_introduction/algorithms_are_everywhere.md +++ b/docs/chapter_introduction/algorithms_are_everywhere.md @@ -6,13 +6,13 @@ comments: true 听到“算法”这个词,我们一般会联想到数学。但实际上,大多数算法并不包含复杂的数学,而更像是在考察基本逻辑,而这些逻辑在我们日常生活中处处可见。 -在正式介绍算法之前,我想告诉你一件有趣的事:**其实,你在过去已经学会了很多算法,并且已经习惯将它们应用到日常生活中。** 接下来,我将介绍两个具体例子来佐证。 +在正式介绍算法之前,我想告诉你一件有趣的事:**其实,你在过去已经学会了很多算法,并且已经习惯将它们应用到日常生活中**。接下来,我将介绍两个具体例子来佐证。 -**例一:拼积木。** 一套积木,除了有许多部件之外,还会附送详细的拼装说明书。我们按照说明书上一步步操作,即可拼出复杂的积木模型。 +**例一:拼积木**。一套积木,除了有许多部件之外,还会附送详细的拼装说明书。我们按照说明书上一步步操作,即可拼出复杂的积木模型。 如果从数据结构与算法的角度看,大大小小的「积木」就是数据结构,而「拼装说明书」上的一系列步骤就是算法。 -**例二:查字典。** 在字典中,每个汉字都有一个对应的拼音,而字典是按照拼音的英文字母表顺序排列的。假设需要在字典中查询任意一个拼音首字母为 $r$ 的字,一般我们会这样做: +**例二:查字典**。在字典中,每个汉字都有一个对应的拼音,而字典是按照拼音的英文字母表顺序排列的。假设需要在字典中查询任意一个拼音首字母为 $r$ 的字,一般我们会这样做: 1. 打开字典大致一半页数的位置,查看此页的首字母是什么(假设为 $m$ ); 2. 由于在英文字母表中 $r$ 在 $m$ 的后面,因此应排除字典前半部分,查找范围仅剩后半部分; diff --git a/docs/chapter_preface/about_the_book.md b/docs/chapter_preface/about_the_book.md index db70a8ac..01f513f0 100644 --- a/docs/chapter_preface/about_the_book.md +++ b/docs/chapter_preface/about_the_book.md @@ -44,13 +44,13 @@ comments: true 首先介绍数据结构与算法的评价维度、算法效率的评估方法,引出了计算复杂度概念。 -接下来,从 **函数渐近上界** 入手,分别介绍了 **时间复杂度** 和 **空间复杂度** ,包括推算方法、常见类型、示例等。同时,剖析了 **最差、最佳、平均** 时间复杂度的联系与区别。 +接下来,从 **函数渐近上界** 入手,分别介绍了 **时间复杂度** 和 **空间复杂度**,包括推算方法、常见类型、示例等。同时,剖析了 **最差、最佳、平均** 时间复杂度的联系与区别。 ### 数据结构 首先介绍了常用的 **基本数据类型** 、以及它们是如何在内存中存储的。 -接下来,介绍了两种 **数据结构分类方法** ,包括逻辑结构与物理结构。 +接下来,介绍了两种 **数据结构分类方法**,包括逻辑结构与物理结构。 后续展开介绍了 **数组、链表、栈、队列、散列表、树、堆、图** 等数据结构,关心以下内容: @@ -82,28 +82,146 @@ comments: true ## 风格约定 -- 文章中的重要名词会用「」符号标注,例如「数组 Array」。名词混淆会导致不必要的歧义,因此最好可以记住这类名词(包括中文和英文),以便后续阅读文献时使用。 -- 重点内容、总起句、总结句会被 **加粗** ,此类文字值得更多关注。 -- 专有名词和有特指含义的词句会使用 “ ” 标注,以避免歧义。 - 标题后标注 * 符号的是选读章节,如果你的时间有限,可以先跳过这些章节。 +- 文章中的重要名词会用「」符号标注,例如「数组 Array」。名词混淆会导致不必要的歧义,因此最好可以记住这类名词(包括中文和英文),以便后续阅读文献时使用。 +- 重点内容、总起句、总结句会被 **加粗**,此类文字值得特别关注。 +- 专有名词和有特指含义的词句会使用 “ ” 标注,以避免歧义。 +- 在工程应用中,每种语言都有注释规范;而本书放弃了一部分的注释规范性,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。 + +=== "Java" + + ```java title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "C++" + + ```cpp title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "Python" + + ```python title="" + """ 标题注释,用于标注函数、类、测试样例等 """ + + # 内容注释,用于详解代码 + + """ + 多行 + 注释 + """ + ``` + +=== "Go" + + ```go title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "JavaScript" + + ```js title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "TypeScript" + + ```typescript title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "C" + + ```c title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "C#" + + ```csharp title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + +=== "Swift" + + ```swift title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` ## 本书特点 * ??? abstract "默认折叠,可以跳过" - **以实践为主。** 我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。 + **以实践为主**。我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。 本书的理论部分占少量篇幅,主要分为两类:一是基础且必要的概念知识,以培养读者对于算法的感性认识;二是重要的分类、对比或总结,这是为了帮助你站在更高视角俯瞰各个知识点,形成连点成面的效果。 实践部分主要由示例和代码组成。代码配有简要注释,复杂示例会尽可能地使用视觉化的形式呈现。我强烈建议读者对照着代码自己敲一遍,如果时间有限,也至少逐行读、复制并运行一遍,配合着讲解将代码吃透。 - **视觉化学习。** 信息时代以来,视觉化的脚步从未停止。媒体形式经历了文字短信、图文 Email 、动图、短(长)视频、交互式 Web 、3D 游戏等演变过程,信息的视觉化程度越来越高、愈加符合人类感官、信息传播效率大大提升。科技界也在向视觉化迈进,iPhone 就是一个典型例子,其相对于传统手机是高度视觉化的,包含精心设计的字体、主题配色、交互动画等。 + **视觉化学习**。信息时代以来,视觉化的脚步从未停止。媒体形式经历了文字短信、图文 Email 、动图、短(长)视频、交互式 Web 、3D 游戏等演变过程,信息的视觉化程度越来越高、愈加符合人类感官、信息传播效率大大提升。科技界也在向视觉化迈进,iPhone 就是一个典型例子,其相对于传统手机是高度视觉化的,包含精心设计的字体、主题配色、交互动画等。 近两年,短视频成为最受欢迎的信息媒介,可以在短时间内将高密度的信息“灌”给我们,有着极其舒适的观看体验。阅读则不然,读者与书本之间天然存在一种“疏离感”,我们看书会累、会走神、会停下来想其他事、会划下喜欢的句子、会思考某一片段的含义,这种疏离感给了读者与书本之间对话的可能,拓宽了想象空间。 本书作为一本入门教材,希望可以保有书本的“慢节奏”,但也会避免与读者产生过多“疏离感”,而是努力将知识完整清晰地推送到你聪明的小脑袋瓜中。我将采用视觉化的方式(例如配图、动画),尽我可能清晰易懂地讲解复杂概念和抽象示例。 - **内容精简化。** 大多数的经典教科书,会把每个主题都讲的很透彻。虽然透彻性正是其获得读者青睐的原因,但对于想要快速入门的初学者来说,这些教材的实用性不足。本书会避免引入非必要的概念、名词、定义等,也避免展开不必要的理论分析,毕竟这不是一本真正意义上的教材,主要任务是尽快地带领读者入门。 + **内容精简化**。大多数的经典教科书,会把每个主题都讲的很透彻。虽然透彻性正是其获得读者青睐的原因,但对于想要快速入门的初学者来说,这些教材的实用性不足。本书会避免引入非必要的概念、名词、定义等,也避免展开不必要的理论分析,毕竟这不是一本真正意义上的教材,主要任务是尽快地带领读者入门。 引入一些生活案例或趣味内容,非常适合作为知识点的引子或者解释的补充,但当融入过多额外元素时,内容会稍显冗长,也许反而使读者容易迷失、抓不住重点,这也是本书需要避免的。 diff --git a/docs/chapter_preface/contribution.assets/edit_markdown.png b/docs/chapter_preface/contribution.assets/edit_markdown.png index 02fc6318..25037fce 100644 Binary files a/docs/chapter_preface/contribution.assets/edit_markdown.png and b/docs/chapter_preface/contribution.assets/edit_markdown.png differ diff --git a/docs/chapter_preface/suggestions.md b/docs/chapter_preface/suggestions.md index c0377ee3..b2e18905 100644 --- a/docs/chapter_preface/suggestions.md +++ b/docs/chapter_preface/suggestions.md @@ -32,7 +32,7 @@ git clone https://github.com/krahets/hello-algo.git ### 运行源代码 -本书提供配套 Java, C++, Python 代码仓(后续可能拓展支持语言)。书中的代码栏上若标有 `*.java` , `*.cpp` , `*.py` ,则可在仓库 codes 文件夹中找到对应的 **代码源文件** 。 +本书提供配套 Java, C++, Python 代码仓(后续可能拓展支持语言)。书中的代码栏上若标有 `*.java` , `*.cpp` , `*.py` ,则可在仓库 codes 文件夹中找到对应的 **代码源文件**。 ![code_md_to_repo](suggestions.assets/code_md_to_repo.png) @@ -54,10 +54,10 @@ git clone https://github.com/krahets/hello-algo.git ## 算法学习“三步走” -**第一阶段,算法入门,也正是本书的定位。** 熟悉各种数据结构的特点、用法,学习各种算法的工作原理、用途、效率等。 +**第一阶段,算法入门,也正是本书的定位**。熟悉各种数据结构的特点、用法,学习各种算法的工作原理、用途、效率等。 -**第二阶段,刷算法题。** 可以先从热门题单开刷,推荐 [剑指 Offer](https://leetcode.cn/problem-list/xb9nqhhg/)、[LeetCode 热题 HOT 100](https://leetcode.cn/problem-list/2cktkvj/) ,先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,当做了三遍以上,往往就能牢记于心了。 +**第二阶段,刷算法题**。可以先从热门题单开刷,推荐 [剑指 Offer](https://leetcode.cn/problem-list/xb9nqhhg/)、[LeetCode 热题 HOT 100](https://leetcode.cn/problem-list/2cktkvj/) ,先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,当做了三遍以上,往往就能牢记于心了。 -**第三阶段,搭建知识体系。** 在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,刷题方案在社区中可以找到一些讲解,在此不做赘述。 +**第三阶段,搭建知识体系**。在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,刷题方案在社区中可以找到一些讲解,在此不做赘述。 ![learning_route](suggestions.assets/learning_route.png) diff --git a/docs/chapter_searching/binary_search.md b/docs/chapter_searching/binary_search.md index 35d6a321..2bd2d439 100644 --- a/docs/chapter_searching/binary_search.md +++ b/docs/chapter_searching/binary_search.md @@ -9,7 +9,7 @@ comments: true 使用二分查找有两个前置条件: - **要求输入数据是有序的**,这样才能通过判断大小关系来排除一半的搜索区间; -- **二分查找仅适用于数组** ,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。 +- **二分查找仅适用于数组**,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。 ## 算法实现 @@ -210,6 +210,12 @@ $$ } ``` +=== "Swift" + + ```swift title="binary_search.swift" + + ``` + ### “左闭右开”实现 当然,我们也可以使用“左闭右开”的表示方法,写出相同功能的二分查找代码。 @@ -374,6 +380,12 @@ $$ } ``` +=== "Swift" + + ```swift title="binary_search.swift" + + ``` + ### 两种表示对比 对比下来,两种表示的代码写法有以下不同点: @@ -460,21 +472,27 @@ $$ int m = i + (j - i) / 2; ``` +=== "Swift" + + ```swift title="" + + ``` + ## 复杂度分析 -**时间复杂度 $O(\log n)$ :** 其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。 +**时间复杂度 $O(\log n)$** :其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。 -**空间复杂度 $O(1)$ :** 指针 `i` , `j` 使用常数大小空间。 +**空间复杂度 $O(1)$** :指针 `i` , `j` 使用常数大小空间。 -## 优缺点 +## 优点与缺点 二分查找效率很高,体现在: -- **二分查找时间复杂度低。** 对数阶在数据量很大时具有巨大优势,例如,当数据大小 $n = 2^{20}$ 时,线性查找需要 $2^{20} = 1048576$ 轮循环,而二分查找仅需要 $\log_2 2^{20} = 20$ 轮循环。 -- **二分查找不需要额外空间。** 相对于借助额外数据结构来实现查找的算法来说,其更加节约空间使用。 +- **二分查找时间复杂度低**。对数阶在数据量很大时具有巨大优势,例如,当数据大小 $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$ 较小时,线性查找反而比二分查找更快。 +- **二分查找仅适用于有序数据**。如果输入数据是无序的,为了使用二分查找而专门执行数据排序,那么是得不偿失的,因为排序算法的时间复杂度一般为 $O(n \log n)$ ,比线性查找和二分查找都更差。再例如,对于频繁插入元素的场景,为了保持数组的有序性,需要将元素插入到特定位置,时间复杂度为 $O(n)$ ,也是非常昂贵的。 +- **二分查找仅适用于数组**。由于在二分查找中,访问索引是 ”非连续“ 的,因此链表或者基于链表实现的数据结构都无法使用。 +- **在小数据量下,线性查找的性能更好**。在线性查找中,每轮只需要 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,在数据量 $n$ 较小时,线性查找反而比二分查找更快。 diff --git a/docs/chapter_searching/hashing_search.md b/docs/chapter_searching/hashing_search.md index a52f72e2..07645730 100644 --- a/docs/chapter_searching/hashing_search.md +++ b/docs/chapter_searching/hashing_search.md @@ -95,6 +95,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="hashing_search.swift" + + ``` + 再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。 ![hash_search_listnode](hashing_search.assets/hash_search_listnode.png) @@ -179,13 +185,19 @@ comments: true } ``` +=== "Swift" + + ```swift title="hashing_search.swift" + + ``` + ## 复杂度分析 -**时间复杂度:** $O(1)$ ,哈希表的查找操作使用 $O(1)$ 时间。 +**时间复杂度 $O(1)$** :哈希表的查找操作使用 $O(1)$ 时间。 -**空间复杂度:** $O(n)$ ,其中 $n$ 为数组或链表长度。 +**空间复杂度 $O(n)$** :其中 $n$ 为数组或链表长度。 -## 优缺点 +## 优点与缺点 在哈希表中,**查找、插入、删除操作的平均时间复杂度都为 $O(1)$** ,这意味着无论是高频增删还是高频查找场景,哈希查找的性能表现都非常好。当然,一切的前提是保证哈希表未退化。 diff --git a/docs/chapter_searching/linear_search.md b/docs/chapter_searching/linear_search.md index 3284db06..d9371ab2 100644 --- a/docs/chapter_searching/linear_search.md +++ b/docs/chapter_searching/linear_search.md @@ -122,6 +122,12 @@ comments: true ``` +=== "Swift" + + ```swift title="linear_search.swift" + + ``` + 再比如,我们想要在给定一个目标结点值 `target` ,返回此结点对象,也可以在链表中进行线性查找。 === "Java" @@ -238,14 +244,20 @@ comments: true } ``` +=== "Swift" + + ```swift title="linear_search.swift" + + ``` + ## 复杂度分析 -**时间复杂度 $O(n)$ :** 其中 $n$ 为数组或链表长度。 +**时间复杂度 $O(n)$** :其中 $n$ 为数组或链表长度。 -**空间复杂度 $O(1)$ :** 无需使用额外空间。 +**空间复杂度 $O(1)$** :无需使用额外空间。 -## 优缺点 +## 优点与缺点 -**线性查找的通用性极佳。** 由于线性查找是依次访问元素的,即没有跳跃访问元素,因此数组或链表皆适用。 +**线性查找的通用性极佳**。由于线性查找是依次访问元素的,即没有跳跃访问元素,因此数组或链表皆适用。 -**线性查找的时间复杂度太高。** 在数据量 $n$ 很大时,查找效率很低。 +**线性查找的时间复杂度太高**。在数据量 $n$ 很大时,查找效率很低。 diff --git a/docs/chapter_sorting/bubble_sort.md b/docs/chapter_sorting/bubble_sort.md index 336f3c2d..e3e394b2 100644 --- a/docs/chapter_sorting/bubble_sort.md +++ b/docs/chapter_sorting/bubble_sort.md @@ -170,8 +170,7 @@ comments: true ```c title="bubble_sort.c" /* 冒泡排序 */ - void bubble_sort(int nums[], int size) - { + void bubble_sort(int nums[], int size) { // 外循环:待排序元素数量为 n-1, n-2, ..., 1 for (int i = 0; i < size - 1; i++) { @@ -213,17 +212,23 @@ comments: true } ``` +=== "Swift" + + ```swift title="bubble_sort.swift" + + ``` + ## 算法特性 -**时间复杂度 $O(n^2)$ :** 各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。 +**时间复杂度 $O(n^2)$** :各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。 -**空间复杂度 $O(1)$ :** 指针 $i$ , $j$ 使用常数大小的额外空间。 +**空间复杂度 $O(1)$** :指针 $i$ , $j$ 使用常数大小的额外空间。 -**原地排序:** 指针变量仅使用常数大小额外空间。 +**原地排序**:指针变量仅使用常数大小额外空间。 -**稳定排序:** 不交换相等元素。 +**稳定排序**:不交换相等元素。 -**自适排序:** 引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。 +**自适排序**:引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。 ## 效率优化 @@ -368,8 +373,7 @@ comments: true ```c title="bubble_sort.c" /* 冒泡排序 */ - void bubble_sort(int nums[], int size) - { + void bubble_sort(int nums[], int size) { // 外循环:待排序元素数量为 n-1, n-2, ..., 1 for (int i = 0; i < size - 1; i++) { @@ -416,3 +420,9 @@ comments: true } } ``` + +=== "Swift" + + ```swift title="bubble_sort.swift" + + ``` diff --git a/docs/chapter_sorting/insertion_sort.md b/docs/chapter_sorting/insertion_sort.md index 1614b5c3..05ee7293 100644 --- a/docs/chapter_sorting/insertion_sort.md +++ b/docs/chapter_sorting/insertion_sort.md @@ -135,7 +135,23 @@ comments: true === "C" ```c title="insertion_sort.c" - + /* 插入排序 */ + void insertionSort(int nums[], int size) { + // 外循环:base = nums[1], nums[2], ..., nums[n-1] + for (int i = 1; i < size; i++) + { + int base = nums[i], j = i - 1; + // 内循环:将 base 插入到左边的正确位置 + while (j >= 0 && nums[j] > base) + { + // 1. 将 nums[j] 向右移动一位 + nums[j + 1] = nums[j]; + j--; + } + // 2. 将 base 赋值到正确位置 + nums[j + 1] = base; + } + } ``` === "C#" @@ -159,17 +175,23 @@ comments: true } ``` +=== "Swift" + + ```swift title="insertion_sort.swift" + + ``` + ## 算法特性 -**时间复杂度 $O(n^2)$ :** 最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。 +**时间复杂度 $O(n^2)$** :最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。 -**空间复杂度 $O(1)$ :** 指针 $i$ , $j$ 使用常数大小的额外空间。 +**空间复杂度 $O(1)$** :指针 $i$ , $j$ 使用常数大小的额外空间。 -**原地排序:** 指针变量仅使用常数大小额外空间。 +**原地排序**:指针变量仅使用常数大小额外空间。 -**稳定排序:** 不交换相等元素。 +**稳定排序**:不交换相等元素。 -**自适应排序:** 最佳情况下,时间复杂度为 $O(n)$ 。 +**自适应排序**:最佳情况下,时间复杂度为 $O(n)$ 。 ## 插入排序 vs 冒泡排序 @@ -177,7 +199,7 @@ comments: true 虽然「插入排序」和「冒泡排序」的时间复杂度皆为 $O(n^2)$ ,但实际运行速度却有很大差别,这是为什么呢? -回顾复杂度分析,两个方法的循环次数都是 $\frac{(n - 1) n}{2}$ 。但不同的是,「冒泡操作」是在做 **元素交换** ,需要借助一个临时变量实现,共 3 个单元操作;而「插入操作」是在做 **赋值** ,只需 1 个单元操作;因此,可以粗略估计出冒泡排序的计算开销约为插入排序的 3 倍。 +回顾复杂度分析,两个方法的循环次数都是 $\frac{(n - 1) n}{2}$ 。但不同的是,「冒泡操作」是在做 **元素交换**,需要借助一个临时变量实现,共 3 个单元操作;而「插入操作」是在做 **赋值**,只需 1 个单元操作;因此,可以粗略估计出冒泡排序的计算开销约为插入排序的 3 倍。 插入排序运行速度快,并且具有原地、稳定、自适应的优点,因此很受欢迎。实际上,包括 Java 在内的许多编程语言的排序库函数的实现都用到了插入排序。库函数的大致思路: diff --git a/docs/chapter_sorting/intro_to_sort.md b/docs/chapter_sorting/intro_to_sort.md index df84ce13..c4d50c53 100644 --- a/docs/chapter_sorting/intro_to_sort.md +++ b/docs/chapter_sorting/intro_to_sort.md @@ -7,7 +7,7 @@ comments: true 「排序算法 Sorting Algorithm」使得列表中的所有元素按照从小到大的顺序排列。 - 待排序的列表的 **元素类型** 可以是整数、浮点数、字符、或字符串; -- 排序算法可以根据需要设定 **判断规则** ,例如数字大小、字符 ASCII 码顺序、自定义规则; +- 排序算法可以根据需要设定 **判断规则**,例如数字大小、字符 ASCII 码顺序、自定义规则; ![sorting_examples](intro_to_sort.assets/sorting_examples.png) @@ -22,7 +22,7 @@ comments: true - 「稳定排序」在完成排序后,**不改变** 相等元素在数组中的相对顺序。 - 「非稳定排序」在完成排序后,相等元素在数组中的相对位置 **可能被改变**。 -假设我们有一个存储学生信息当表格,第 1, 2 列分别是姓名和年龄。那么在以下示例中,「非稳定排序」会导致输入数据的有序性丢失。因此「稳定排序」是很好的特性,**在多级排序中是必须的**。 +假设我们有一个存储学生信息的表格,第 1, 2 列分别是姓名和年龄。那么在以下示例中,「非稳定排序」会导致输入数据的有序性丢失。因此「稳定排序」是很好的特性,**在多级排序中是必须的**。 ```shell # 输入数据是按照姓名排序好的 @@ -55,7 +55,7 @@ comments: true - 「自适应排序」的时间复杂度受输入数据影响,即最佳 / 最差 / 平均时间复杂度不相等。 - 「非自适应排序」的时间复杂度恒定,与输入数据无关。 -我们希望 **最差 = 平均** ,即不希望排序算法的运行效率在某些输入数据下发生劣化。 +我们希望 **最差 = 平均**,即不希望排序算法的运行效率在某些输入数据下发生劣化。 ### 比较类 diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md index 9ee98427..5895fcb8 100644 --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -6,8 +6,8 @@ comments: true 「归并排序 Merge Sort」是算法中“分治思想”的典型体现,其有「划分」和「合并」两个阶段: -1. **划分阶段:** 通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题; -2. **合并阶段:** 划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序; +1. **划分阶段**:通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题; +2. **合并阶段**:划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序; ![merge_sort_preview](merge_sort.assets/merge_sort_preview.png) @@ -15,14 +15,14 @@ comments: true ## 算法流程 -**「递归划分」** 从顶至底递归地 **将数组从中点切为两个子数组** ,直至长度为 1 ; +**「递归划分」** 从顶至底递归地 **将数组从中点切为两个子数组**,直至长度为 1 ; 1. 计算数组中点 `mid` ,递归划分左子数组(区间 `[left, mid]` )和右子数组(区间 `[mid + 1, right]` ); 2. 递归执行 `1.` 步骤,直至子数组区间长度为 1 时,终止递归划分; **「回溯合并」** 从底至顶地将左子数组和右子数组合并为一个 **有序数组** ; -需要注意,由于从长度为 1 的子数组开始合并,所以 **每个子数组都是有序的** 。因此,合并任务本质是要 **将两个有序子数组合并为一个有序数组** 。 +需要注意,由于从长度为 1 的子数组开始合并,所以 **每个子数组都是有序的**。因此,合并任务本质是要 **将两个有序子数组合并为一个有序数组**。 === "Step1" ![merge_sort_step1](merge_sort.assets/merge_sort_step1.png) @@ -56,8 +56,8 @@ comments: true 观察发现,归并排序的递归顺序就是二叉树的「后序遍历」。 -- **后序遍历:** 先递归左子树、再递归右子树、最后处理根结点。 -- **归并排序:** 先递归左子树、再递归右子树、最后处理合并。 +- **后序遍历**:先递归左子树、再递归右子树、最后处理根结点。 +- **归并排序**:先递归左子树、再递归右子树、最后处理合并。 === "Java" @@ -81,10 +81,10 @@ comments: true // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ else if (j > rightEnd || tmp[i] <= tmp[j]) nums[k] = tmp[i++]; - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ else nums[k] = tmp[j++]; } @@ -125,10 +125,10 @@ comments: true // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ else if (j > rightEnd || tmp[i] <= tmp[j]) nums[k] = tmp[i++]; - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ else nums[k] = tmp[j++]; } @@ -170,11 +170,11 @@ comments: true if i > left_end: nums[k] = tmp[j] j += 1 - # 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + # 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ elif j > right_end or tmp[i] <= tmp[j]: nums[k] = tmp[i] i += 1 - # 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + # 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ else: nums[k] = tmp[j] j += 1 @@ -201,36 +201,35 @@ comments: true 右子数组区间 [mid + 1, right] */ func merge(nums []int, left, mid, right int) { - // 初始化辅助数组 借助 copy模块 + // 初始化辅助数组 借助 copy 模块 tmp := make([]int, right-left+1) for i := left; i <= right; i++ { tmp[i-left] = nums[i] } // 左子数组的起始索引和结束索引 - left_start, left_end := left-left, mid-left + leftStart, leftEnd := left-left, mid-left // 右子数组的起始索引和结束索引 - right_start, right_end := mid+1-left, right-left + rightStart, rightEnd := mid+1-left, right-left // i, j 分别指向左子数组、右子数组的首元素 - i, j := left_start, right_start + i, j := leftStart, rightStart // 通过覆盖原数组 nums 来合并左子数组和右子数组 for k := left; k <= right; k++ { // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > left_end { + if i > leftEnd { nums[k] = tmp[j] j++ - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ - } else if j > right_end || tmp[i] <= tmp[j] { + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ + } else if j > rightEnd || tmp[i] <= tmp[j] { nums[k] = tmp[i] i++ - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ } else { nums[k] = tmp[j] j++ } } } - - /* 归并排序 */ + func mergeSort(nums []int, left, right int) { // 终止条件 if left >= right { @@ -267,10 +266,10 @@ comments: true // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) { nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ } else if (j > rightEnd || tmp[i] <= tmp[j]) { nums[k] = tmp[i++]; - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ } else { nums[k] = tmp[j++]; } @@ -312,10 +311,10 @@ comments: true // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) { nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ } else if (j > rightEnd || tmp[i] <= tmp[j]) { nums[k] = tmp[i++]; - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ } else { nums[k] = tmp[j++]; } @@ -365,10 +364,10 @@ comments: true // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) nums[k] = tmp[j++]; - // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ else if (j > rightEnd || tmp[i] <= tmp[j]) nums[k] = tmp[i++]; - // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ else nums[k] = tmp[j++]; } @@ -388,6 +387,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="merge_sort.swift" + + ``` + 下面重点解释一下合并方法 `merge()` 的流程: 1. 初始化一个辅助数组 `tmp` 暂存待合并区间 `[left, right]` 内的元素,后续通过覆盖原数组 `nums` 的元素来实现合并; @@ -401,11 +406,11 @@ comments: true ## 算法特性 -- **时间复杂度 $O(n \log n)$ :** 划分形成高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,总体使用 $O(n \log n)$ 时间。 -- **空间复杂度 $O(n)$ :** 需借助辅助数组实现合并,使用 $O(n)$ 大小的额外空间;递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。 -- **非原地排序:** 辅助数组需要使用 $O(n)$ 额外空间。 -- **稳定排序:** 在合并时可保证相等元素的相对位置不变。 -- **非自适应排序:** 对于任意输入数据,归并排序的时间复杂度皆相同。 +- **时间复杂度 $O(n \log n)$** :划分形成高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,总体使用 $O(n \log n)$ 时间。 +- **空间复杂度 $O(n)$** :需借助辅助数组实现合并,使用 $O(n)$ 大小的额外空间;递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。 +- **非原地排序**:辅助数组需要使用 $O(n)$ 额外空间。 +- **稳定排序**:在合并时可保证相等元素的相对位置不变。 +- **非自适应排序**:对于任意输入数据,归并排序的时间复杂度皆相同。 ## 链表排序 * diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index b7b607f2..a712a1bf 100644 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -6,13 +6,13 @@ comments: true 「快速排序 Quick Sort」是一种基于“分治思想”的排序算法,速度很快、应用很广。 -快速排序的核心操作为「哨兵划分」,其目标为:选取数组某个元素为 **基准数** ,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。「哨兵划分」的实现流程为: +快速排序的核心操作为「哨兵划分」,其目标为:选取数组某个元素为 **基准数**,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。「哨兵划分」的实现流程为: 1. 以数组最左端元素作为基准数,初始化两个指针 `i` , `j` 指向数组两端; 2. 设置一个循环,每轮中使用 `i` / `j` 分别寻找首个比基准数大 / 小的元素,并交换此两元素; 3. 不断循环步骤 `2.` ,直至 `i` , `j` 相遇时跳出,最终把基准数交换至两个子数组的分界线; -「哨兵划分」执行完毕后,原数组被划分成两个部分,即 **左子数组** 和 **右子数组** ,且满足 **左子数组任意元素 < 基准数 < 右子数组任意元素**。因此,接下来我们只需要排序两个子数组即可。 +「哨兵划分」执行完毕后,原数组被划分成两个部分,即 **左子数组** 和 **右子数组**,且满足 **左子数组任意元素 < 基准数 < 右子数组任意元素**。因此,接下来我们只需要排序两个子数组即可。 === "Step 1" ![pivot_division_step1](quick_sort.assets/pivot_division_step1.png) @@ -111,21 +111,21 @@ comments: true ```go title="quick_sort.go" /* 哨兵划分 */ func partition(nums []int, left, right int) int { - //以 nums[left] 作为基准数 + // 以 nums[left] 作为基准数 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { - j-- //从右向左找首个小于基准数的元素 + j-- // 从右向左找首个小于基准数的元素 } for i < j && nums[i] <= nums[left] { - i++ //从左向右找首个大于基准数的元素 + i++ // 从左向右找首个大于基准数的元素 } //元素交换 nums[i], nums[j] = nums[j], nums[i] } - //将基准数交换至两子数组的分界线 + // 将基准数交换至两子数组的分界线 nums[i], nums[left] = nums[left], nums[i] - return i //返回基准数的索引 + return i // 返回基准数的索引 } ``` @@ -223,15 +223,21 @@ comments: true ``` +=== "Swift" + + ```swift title="quick_sort.swift" + + ``` + !!! note "快速排序的分治思想" 哨兵划分的实质是将 **一个长数组的排序问题** 简化为 **两个短数组的排序问题**。 ## 算法流程 -1. 首先,对数组执行一次「哨兵划分」,得到待排序的 **左子数组** 和 **右子数组** 。 +1. 首先,对数组执行一次「哨兵划分」,得到待排序的 **左子数组** 和 **右子数组**; 2. 接下来,对 **左子数组** 和 **右子数组** 分别 **递归执行**「哨兵划分」…… -3. 直至子数组长度为 1 时 **终止递归** ,即可完成对整个数组的排序。 +3. 直至子数组长度为 1 时 **终止递归**,即可完成对整个数组的排序; 观察发现,快速排序和「二分查找」的原理类似,都是以对数阶的时间复杂度来缩小处理区间。 @@ -359,33 +365,39 @@ comments: true ``` +=== "Swift" + + ```swift title="quick_sort.swift" + + ``` + ## 算法特性 -**平均时间复杂度 $O(n \log n)$ :** 平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。 +**平均时间复杂度 $O(n \log n)$** :平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。 -**最差时间复杂度 $O(n^2)$ :** 最差情况下,哨兵划分操作将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ 层,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。 +**最差时间复杂度 $O(n^2)$** :最差情况下,哨兵划分操作将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ 层,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。 -**空间复杂度 $O(n)$ :** 输入数组完全倒序下,达到最差递归深度 $n$ 。 +**空间复杂度 $O(n)$** :输入数组完全倒序下,达到最差递归深度 $n$ 。 -**原地排序:** 只在递归中使用 $O(\log n)$ 大小的栈帧空间。 +**原地排序**:只在递归中使用 $O(\log n)$ 大小的栈帧空间。 -**非稳定排序:** 哨兵划分操作可能改变相等元素的相对位置。 +**非稳定排序**:哨兵划分操作可能改变相等元素的相对位置。 -**自适应排序:** 最差情况下,时间复杂度劣化至 $O(n^2)$ 。 +**自适应排序**:最差情况下,时间复杂度劣化至 $O(n^2)$ 。 ## 快排为什么快? -从命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 **效率更高** ,这是因为: +从命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 **效率更高**,这是因为: -- **出现最差情况的概率很低:** 虽然快速排序的最差时间复杂度为 $O(n^2)$ ,不如归并排序,但绝大部分情况下,快速排序可以达到 $O(n \log n)$ 的复杂度。 -- **缓存使用效率高:** 哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。 -- **复杂度的常数系数低:** 在提及的三种算法中,快速排序的 **比较**、**赋值**、**交换** 三种操作的总体数量最少(类似于「插入排序」快于「冒泡排序」的原因)。 +- **出现最差情况的概率很低**:虽然快速排序的最差时间复杂度为 $O(n^2)$ ,不如归并排序,但绝大部分情况下,快速排序可以达到 $O(n \log n)$ 的复杂度。 +- **缓存使用效率高**:哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。 +- **复杂度的常数系数低**:在提及的三种算法中,快速排序的 **比较**、**赋值**、**交换** 三种操作的总体数量最少(类似于「插入排序」快于「冒泡排序」的原因)。 ## 基准数优化 -**普通快速排序在某些输入下的时间效率变差。** 举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$ 、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。 +**普通快速排序在某些输入下的时间效率变差**。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。 -为了尽量避免这种情况发生,我们可以优化一下基准数的选取策略。首先,在哨兵划分中,我们可以 **随机选取一个元素作为基准数** 。但如果运气很差,每次都选择到比较差的基准数,那么效率依然不好。 +为了尽量避免这种情况发生,我们可以优化一下基准数的选取策略。首先,在哨兵划分中,我们可以 **随机选取一个元素作为基准数**。但如果运气很差,每次都选择到比较差的基准数,那么效率依然不好。 进一步地,我们可以在数组中选取 3 个候选元素(一般为数组的首、尾、中点元素),**并将三个候选元素的中位数作为基准数**,这样基准数“既不大也不小”的概率就大大提升了。当然,如果数组很长的话,我们也可以选取更多候选元素,来进一步提升算法的稳健性。采取该方法后,时间复杂度劣化至 $O(n^2)$ 的概率极低。 @@ -574,9 +586,15 @@ comments: true } ``` +=== "Swift" + + ```swift title="quick_sort.swift" + + ``` + ## 尾递归优化 -**普通快速排序在某些输入下的空间效率变差。** 仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。 +**普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。 为了避免栈帧空间的累积,我们可以在每轮哨兵排序完成后,判断两个子数组的长度大小,仅递归排序较短的子数组。由于较短的子数组长度不会超过 $\frac{n}{2}$ ,因此这样做能保证递归深度不超过 $\log n$ ,即最差空间复杂度被优化至 $O(\log n)$ 。 @@ -652,10 +670,10 @@ comments: true // 对两个子数组中较短的那个执行快排 if pivot-left < right-pivot { quickSort(nums, left, pivot-1) // 递归排序左子数组 - left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right] + left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right] } else { quickSort(nums, pivot+1, right) // 递归排序右子数组 - right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1] + right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1] } } } @@ -734,3 +752,9 @@ comments: true } } ``` + +=== "Swift" + + ```swift title="quick_sort.swift" + + ``` diff --git a/docs/chapter_stack_and_queue/deque.md b/docs/chapter_stack_and_queue/deque.md index ca55f822..52b0540d 100644 --- a/docs/chapter_stack_and_queue/deque.md +++ b/docs/chapter_stack_and_queue/deque.md @@ -12,7 +12,7 @@ comments: true ## 双向队列常用操作 -双向队列的常用操作见下表,方法名需根据编程语言设定来具体确定。 +双向队列的常用操作见下表(方法命名以 Java 为例)。

Table. 双向队列的常用操作

@@ -38,25 +38,25 @@ comments: true ```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 pollFirst = deque.pollFirst(); // 队首元素出队 int pollLast = deque.pollLast(); // 队尾元素出队 - + /* 获取双向队列的长度 */ int size = deque.size(); - + /* 判断双向队列是否为空 */ boolean isEmpty = deque.isEmpty(); ``` @@ -66,25 +66,25 @@ comments: true ```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(); ``` @@ -94,25 +94,25 @@ comments: true ```python title="deque.py" """ 初始化双向队列 """ duque = deque() - + """ 元素入队 """ duque.append(2) # 添加至队尾 duque.append(5) duque.append(4) duque.appendleft(3) # 添加至队首 duque.appendleft(1) - + """ 访问元素 """ front = duque[0] # 队首元素 rear = duque[-1] # 队尾元素 - + """ 元素出队 """ pop_front = duque.popleft() # 队首元素出队 pop_rear = duque.pop() # 队尾元素出队 - + """ 获取双向队列的长度 """ size = len(duque) - + """ 判断双向队列是否为空 """ is_empty = len(duque) == 0 ``` @@ -123,25 +123,25 @@ comments: true /* 初始化双向队列 */ // 在 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 ``` @@ -149,23 +149,52 @@ comments: true === "JavaScript" ```js title="deque.js" - + ``` === "TypeScript" ```typescript title="deque.ts" - + ``` === "C" ```c title="deque.c" - + ``` === "C#" ```csharp title="deque.cs" + /* 初始化双向队列 */ + // 在 C# 中,将链表 LinkedList 看作双向队列来使用 + LinkedList deque = new LinkedList(); + + /* 元素入队 */ + 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; + ``` + +=== "Swift" + + ```swift title="deque.swift" ``` diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index 3c5c0528..0c589fa6 100644 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -14,7 +14,7 @@ comments: true ## 队列常用操作 -队列的常用操作见下表,方法命名需根据编程语言的设定来具体确定。 +队列的常用操作见下表(方法命名以 Java 为例)。

Table. 队列的常用操作

@@ -37,23 +37,23 @@ comments: true ```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 poll = queue.poll(); - + /* 获取队列的长度 */ int size = queue.size(); - + /* 判断队列是否为空 */ boolean isEmpty = queue.isEmpty(); ``` @@ -91,23 +91,23 @@ comments: true # 在 Python 中,我们一般将双向队列类 deque 看作队列使用 # 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不建议 que = collections.deque() - + """ 元素入队 """ que.append(1) que.append(3) que.append(2) que.append(5) que.append(4) - + """ 访问队首元素 """ front = que[0]; - + """ 元素出队 """ pop = que.popleft() - + """ 获取队列的长度 """ size = len(que) - + """ 判断队列是否为空 """ is_empty = len(que) == 0 ``` @@ -118,24 +118,24 @@ comments: true /* 初始化队列 */ // 在 Go 中,将 list 作为队列来使用 queue := list.New() - + /* 元素入队 */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) - + /* 访问队首元素 */ peek := queue.Front() - + /* 元素出队 */ poll := queue.Front() queue.Remove(poll) - + /* 获取队列的长度 */ size := queue.Len() - + /* 判断队列是否为空 */ isEmpty := queue.Len() == 0 ``` @@ -146,24 +146,24 @@ comments: true /* 初始化队列 */ // 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 poll = queue.shift(); - + /* 获取队列的长度 */ const size = queue.length; - + /* 判断队列是否为空 */ const empty = queue.length === 0; ``` @@ -174,24 +174,24 @@ comments: true /* 初始化队列 */ // 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 poll = queue.shift(); - + /* 获取队列的长度 */ const size = queue.length; - + /* 判断队列是否为空 */ const empty = queue.length === 0; ``` @@ -199,7 +199,7 @@ comments: true === "C" ```c title="queue.c" - + ``` === "C#" @@ -207,27 +207,33 @@ comments: true ```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 poll = queue.Dequeue(); - + /* 获取队列的长度 */ int size = queue.Count(); - + /* 判断队列是否为空 */ bool isEmpty = queue.Count() == 0; ``` +=== "Swift" + + ```swift title="queue.swift" + + ``` + ## 队列实现 队列需要一种可以在一端添加,并在另一端删除的数据结构,也可以使用链表或数组来实现。 @@ -243,7 +249,7 @@ comments: true class LinkedListQueue { private ListNode front, rear; // 头结点 front ,尾结点 rear private int queSize = 0; - + public LinkedListQueue() { front = null; rear = null; @@ -296,7 +302,7 @@ comments: true private: ListNode *front, *rear; // 头结点 front ,尾结点 rear int queSize; - + public: LinkedListQueue() { front = nullptr; @@ -328,12 +334,14 @@ comments: true queSize++; } /* 出队 */ - int poll() { + void poll() { int num = peek(); // 删除头结点 + ListNode *tmp = front; front = front->next; + // 释放内存 + delete tmp; queSize--; - return num; } /* 访问队首元素 */ int peek() { @@ -353,15 +361,15 @@ comments: true self.__front = None # 头结点 front self.__rear = None # 尾结点 rear self.__size = 0 - + """ 获取队列的长度 """ def size(self): return self.__size - + """ 判断队列是否为空 """ def is_empty(self): return not self.__front - + """ 入队 """ def push(self, num): # 尾结点后添加 num @@ -375,7 +383,7 @@ comments: true self.__rear.next = node self.__rear = node self.__size += 1 - + """ 出队 """ def poll(self): num = self.peek() @@ -383,7 +391,7 @@ comments: true self.__front = self.__front.next self.__size -= 1 return num - + """ 访问队首元素 """ def peek(self): if self.size() == 0: @@ -396,43 +404,49 @@ comments: true ```go title="linkedlist_queue.go" /* 基于链表实现的队列 */ - type LinkedListQueue struct { + type linkedListQueue struct { // 使用内置包 list 来实现队列 data *list.List } - // NewLinkedListQueue 初始化链表 - func NewLinkedListQueue() *LinkedListQueue { - return &LinkedListQueue{ + + // newLinkedListQueue 初始化链表 + func newLinkedListQueue() *linkedListQueue { + return &linkedListQueue{ data: list.New(), } } - // Offer 入队 - func (s *LinkedListQueue) Offer(value any) { + + // offer 入队 + func (s *linkedListQueue) offer(value any) { s.data.PushBack(value) } - // Poll 出队 - func (s *LinkedListQueue) Poll() any { - if s.IsEmpty() { + + // poll 出队 + func (s *linkedListQueue) poll() any { + if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } - // Peek 访问队首元素 - func (s *LinkedListQueue) Peek() any { - if s.IsEmpty() { + + // peek 访问队首元素 + func (s *linkedListQueue) peek() any { + if s.isEmpty() { return nil } e := s.data.Front() return e.Value } - // Size 获取队列的长度 - func (s *LinkedListQueue) Size() int { + + // size 获取队列的长度 + func (s *linkedListQueue) size() int { return s.data.Len() } - // IsEmpty 判断队列是否为空 - func (s *LinkedListQueue) IsEmpty() bool { + + // isEmpty 判断队列是否为空 + func (s *linkedListQueue) isEmpty() bool { return s.data.Len() == 0 } ``` @@ -546,7 +560,7 @@ comments: true === "C" ```c title="linkedlist_queue.c" - + ``` === "C#" @@ -610,6 +624,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="linkedlist_queue.swift" + + ``` + ### 基于数组的实现 数组的删除首元素的时间复杂度为 $O(n)$ ,因此不适合直接用来实现队列。然而,我们可以借助两个指针 `front` , `rear` 来分别记录队首和队尾的索引位置,在入队 / 出队时分别将 `front` / `rear` 向后移动一位即可,这样每次仅需操作一个元素,时间复杂度降至 $O(1)$ 。 @@ -628,7 +648,7 @@ comments: true private int[] nums; // 用于存储队列元素的数组 private int front = 0; // 头指针,指向队首 private int rear = 0; // 尾指针,指向队尾 + 1 - + public ArrayQueue(int capacity) { // 初始化数组 nums = new int[capacity]; @@ -684,7 +704,7 @@ comments: true int cap; // 队列容量 int front = 0; // 头指针,指向队首 int rear = 0; // 尾指针,指向队尾 + 1 - + public: ArrayQueue(int capacity) { // 初始化数组 @@ -716,11 +736,10 @@ comments: true rear = (rear + 1) % capacity(); } /* 出队 */ - int poll() { + void poll() { int num = peek(); // 队头指针向后移动一位,若越过尾部则返回到数组头部 front = (front + 1) % capacity(); - return num; } /* 访问队首元素 */ int peek() { @@ -740,20 +759,20 @@ comments: true self.__nums = [0] * size # 用于存储队列元素的数组 self.__front = 0 # 头指针,指向队首 self.__rear = 0 # 尾指针,指向队尾 + 1 - + """ 获取队列的容量 """ def capacity(self): return len(self.__nums) - + """ 获取队列的长度 """ def size(self): # 由于将数组看作为环形,可能 rear < front ,因此需要取余数 return (self.capacity() + self.__rear - self.__front) % self.capacity() - + """ 判断队列是否为空 """ def is_empty(self): return (self.__rear - self.__front) == 0 - + """ 入队 """ def push(self, val): if self.size() == self.capacity(): @@ -763,21 +782,21 @@ comments: true self.__nums[self.__rear] = val # 尾指针向后移动一位,越过尾部后返回到数组头部 self.__rear = (self.__rear + 1) % self.capacity() - + """ 出队 """ def poll(self): num = self.peek() # 队头指针向后移动一位,若越过尾部则返回到数组头部 self.__front = (self.__front + 1) % self.capacity() return num - + """ 访问队首元素 """ def peek(self): if self.is_empty(): print("队列为空") return False return self.__nums[self.__front] - + """ 返回列表用于打印 """ def to_list(self): res = [0] * self.size() @@ -792,34 +811,38 @@ comments: true ```go title="array_queue.go" /* 基于环形数组实现的队列 */ - type ArrayQueue struct { + type arrayQueue struct { data []int // 用于存储队列元素的数组 capacity int // 队列容量(即最多容量的元素个数) front int // 头指针,指向队首 rear int // 尾指针,指向队尾 + 1 } - // NewArrayQueue 基于环形数组实现的队列 - func NewArrayQueue(capacity int) *ArrayQueue { - return &ArrayQueue{ + + // newArrayQueue 基于环形数组实现的队列 + func newArrayQueue(capacity int) *arrayQueue { + return &arrayQueue{ data: make([]int, capacity), capacity: capacity, front: 0, rear: 0, } } - // Size 获取队列的长度 - func (q *ArrayQueue) Size() int { + + // size 获取队列的长度 + func (q *arrayQueue) size() int { size := (q.capacity + q.rear - q.front) % q.capacity return size } - // IsEmpty 判断队列是否为空 - func (q *ArrayQueue) IsEmpty() bool { + + // isEmpty 判断队列是否为空 + func (q *arrayQueue) isEmpty() bool { return q.rear-q.front == 0 } - // Offer 入队 - func (q *ArrayQueue) Offer(v int) { + + // offer 入队 + func (q *arrayQueue) offer(v int) { // 当 rear == capacity 表示队列已满 - if q.Size() == q.capacity { + if q.size() == q.capacity { return } // 尾结点后添加 @@ -827,9 +850,10 @@ comments: true // 尾指针向后移动一位,越过尾部后返回到数组头部 q.rear = (q.rear + 1) % q.capacity } - // Poll 出队 - func (q *ArrayQueue) Poll() any { - if q.IsEmpty() { + + // poll 出队 + func (q *arrayQueue) poll() any { + if q.isEmpty() { return nil } v := q.data[q.front] @@ -837,9 +861,10 @@ comments: true q.front = (q.front + 1) % q.capacity return v } - // Peek 访问队首元素 - func (q *ArrayQueue) Peek() any { - if q.IsEmpty() { + + // peek 访问队首元素 + func (q *arrayQueue) peek() any { + if q.isEmpty() { return nil } v := q.data[q.front] @@ -949,7 +974,7 @@ comments: true === "C" ```c title="array_queue.c" - + ``` === "C#" @@ -1014,7 +1039,13 @@ comments: true } ``` +=== "Swift" + + ```swift title="array_queue.swift" + + ``` + ## 队列典型应用 -- **淘宝订单。** 购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。 -- **各种待办事项。** 例如打印机的任务队列、餐厅的出餐队列等等。 +- **淘宝订单**。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。 +- **各种待办事项**。例如打印机的任务队列、餐厅的出餐队列等等。 diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index b0d577bc..d5a3f916 100644 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -6,7 +6,9 @@ comments: true 「栈 Stack」是一种遵循「先入后出 first in, last out」数据操作规则的线性数据结构。我们可以将栈类比为放在桌面上的一摞盘子,如果需要拿出底部的盘子,则需要先将上面的盘子依次取出。 -我们将顶部盘子称为「栈顶」,底部盘子称为「栈底」,将把元素添加到栈顶的操作称为「入栈」,将删除栈顶元素的操作称为「出栈」。 +“盘子”是一种形象比喻,我们将盘子替换为任意一种元素(例如整数、字符、对象等),就得到了栈数据结构。 + +我们将这一摞元素的顶部称为「栈顶」,将底部称为「栈底」,将把元素添加到栈顶的操作称为「入栈」,将删除栈顶元素的操作称为「出栈」。 ![stack_operations](stack.assets/stack_operations.png) @@ -14,7 +16,7 @@ comments: true ## 栈常用操作 -栈的常用操作见下表,方法名需根据编程语言设定来具体确定。 +栈的常用操作见下表(方法命名以 Java 为例)。

Table. 栈的常用操作

@@ -38,23 +40,23 @@ comments: true /* 初始化栈 */ // 在 Java 中,推荐将 LinkedList 当作栈来使用 LinkedList stack = new LinkedList<>(); - + /* 元素入栈 */ stack.addLast(1); stack.addLast(3); stack.addLast(2); stack.addLast(5); stack.addLast(4); - + /* 访问栈顶元素 */ int peek = stack.peekLast(); - + /* 元素出栈 */ int pop = stack.removeLast(); - + /* 获取栈的长度 */ int size = stack.size(); - + /* 判断是否为空 */ boolean isEmpty = stack.isEmpty(); ``` @@ -64,23 +66,23 @@ comments: true ```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(); ``` @@ -91,23 +93,23 @@ comments: true """ 初始化栈 """ # Python 没有内置的栈类,可以把 List 当作栈来使用 stack = [] - + """ 元素入栈 """ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) - + """ 访问栈顶元素 """ peek = stack[-1] - + """ 元素出栈 """ pop = stack.pop() - + """ 获取栈的长度 """ size = len(stack) - + """ 判断是否为空 """ is_empty = len(stack) == 0 ``` @@ -118,24 +120,24 @@ comments: true /* 初始化栈 */ // 在 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 ``` @@ -146,23 +148,23 @@ comments: true /* 初始化栈 */ // 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; ``` @@ -173,23 +175,23 @@ comments: true /* 初始化栈 */ // 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; ``` @@ -197,7 +199,7 @@ comments: true === "C" ```c title="stack.c" - + ``` === "C#" @@ -205,27 +207,33 @@ comments: true ```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; ``` +=== "Swift" + + ```swift title="stack.swift" + + ``` + ## 栈的实现 为了更加清晰地了解栈的运行机制,接下来我们来自己动手实现一个栈类。 @@ -234,7 +242,9 @@ comments: true ### 基于链表的实现 -使用「链表」实现栈时,将链表的尾结点看作栈顶即可。 +使用「链表」实现栈时,将链表的头结点看作栈顶,尾结点看作栈底。 + +对于入栈操作,将元素插入到链表头部即可,这种结点添加方式被称为“头插法”。而对于出栈操作,则将头结点从链表中删除即可。 受益于链表的离散存储方式,栈的扩容更加灵活,删除元素的内存也会被系统自动回收;缺点是无法像数组一样高效地随机访问,并且由于链表结点需存储指针,导致单个元素占用空间更大。 @@ -287,7 +297,7 @@ comments: true private: ListNode* stackTop; // 将头结点作为栈顶 int stkSize; // 栈的长度 - + public: LinkedListStack() { stackTop = nullptr; @@ -309,11 +319,13 @@ comments: true stkSize++; } /* 出栈 */ - int pop() { + void pop() { int num = top(); + ListNode *tmp = stackTop; stackTop = stackTop->next; + // 释放内存 + delete tmp; stkSize--; - return num; } /* 访问栈顶元素 */ int top() { @@ -332,29 +344,29 @@ comments: true def __init__(self): self.__peek = None self.__size = 0 - + """ 获取栈的长度 """ def size(self): return self.__size - + """ 判断栈是否为空 """ def is_empty(self): return not self.__peek - + """ 入栈 """ def push(self, val): node = ListNode(val) node.next = self.__peek self.__peek = node self.__size += 1 - + """ 出栈 """ def pop(self): num = self.peek() self.__peek = self.__peek.next self.__size -= 1 return num - + """ 访问栈顶元素 """ def peek(self): # 判空处理 @@ -366,43 +378,49 @@ comments: true ```go title="linkedlist_stack.go" /* 基于链表实现的栈 */ - type LinkedListStack struct { + type linkedListStack struct { // 使用内置包 list 来实现栈 data *list.List } - // NewLinkedListStack 初始化链表 - func NewLinkedListStack() *LinkedListStack { - return &LinkedListStack{ + + // newLinkedListStack 初始化链表 + func newLinkedListStack() *linkedListStack { + return &linkedListStack{ data: list.New(), } } - // Push 入栈 - func (s *LinkedListStack) Push(value int) { + + // push 入栈 + func (s *linkedListStack) push(value int) { s.data.PushBack(value) } - // Pop 出栈 - func (s *LinkedListStack) Pop() any { - if s.IsEmpty() { + + // pop 出栈 + func (s *linkedListStack) pop() any { + if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } - // Peek 访问栈顶元素 - func (s *LinkedListStack) Peek() any { - if s.IsEmpty() { + + // peek 访问栈顶元素 + func (s *linkedListStack) peek() any { + if s.isEmpty() { return nil } e := s.data.Back() return e.Value } - // Size 获取栈的长度 - func (s *LinkedListStack) Size() int { + + // size 获取栈的长度 + func (s *linkedListStack) size() int { return s.data.Len() } - // IsEmpty 判断栈是否为空 - func (s *LinkedListStack) IsEmpty() bool { + + // isEmpty 判断栈是否为空 + func (s *linkedListStack) isEmpty() bool { return s.data.Len() == 0 } ``` @@ -414,21 +432,21 @@ comments: true 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); @@ -436,7 +454,7 @@ comments: true this.#stackPeek = node; this.#stkSize++; } - + /* 出栈 */ pop() { const num = this.peek(); @@ -447,7 +465,7 @@ comments: true this.#stkSize--; return num; } - + /* 访问栈顶元素 */ peek() { if (!this.#stackPeek) { @@ -455,7 +473,7 @@ comments: true } return this.#stackPeek.val; } - + /* 将链表转化为 Array 并返回 */ toArray() { let node = this.#stackPeek; @@ -476,21 +494,21 @@ comments: true 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); @@ -498,7 +516,7 @@ comments: true this.stackPeek = node; this.stkSize++; } - + /* 出栈 */ pop(): number { const num = this.peek(); @@ -509,7 +527,7 @@ comments: true this.stkSize--; return num; } - + /* 访问栈顶元素 */ peek(): number { if (!this.stackPeek) { @@ -517,7 +535,7 @@ comments: true } return this.stackPeek.val; } - + /* 将链表转化为 Array 并返回 */ toArray(): number[] { let node = this.stackPeek; @@ -534,7 +552,7 @@ comments: true === "C" ```c title="linkedlist_stack.c" - + ``` === "C#" @@ -585,9 +603,15 @@ comments: true } ``` +=== "Swift" + + ```swift title="linkedlist_stack.swift" + + ``` + ### 基于数组的实现 -使用「数组」实现栈时,将数组的尾部当作栈顶。准确地说,我们需要使用「列表」,因为入栈的元素可能是源源不断的,因此使用动态数组可以方便扩容。 +使用「数组」实现栈时,将数组的尾部当作栈顶,这样可以保证入栈与出栈操作的时间复杂度都为 $O(1)$ 。准确地说,由于入栈的元素可能是源源不断的,我们需要使用可以动态扩容的「列表」。 基于数组实现的栈,优点是支持随机访问,缺点是会造成一定的空间浪费,因为列表的容量始终 $\geq$ 元素数量。 @@ -650,10 +674,9 @@ comments: true stack.push_back(num); } /* 出栈 */ - int pop() { + void pop() { int oldTop = top(); stack.pop_back(); - return oldTop; } /* 访问栈顶元素 */ int top() { @@ -671,24 +694,24 @@ comments: true class ArrayStack: def __init__(self): self.__stack = [] - + """ 获取栈的长度 """ def size(self): return len(self.__stack) - + """ 判断栈是否为空 """ def is_empty(self): return self.__stack == [] - + """ 入栈 """ def push(self, item): self.__stack.append(item) - + """ 出栈 """ def pop(self): assert not self.is_empty(), "栈为空" return self.__stack.pop() - + """ 访问栈顶元素 """ def peek(self): assert not self.is_empty(), "栈为空" @@ -699,41 +722,47 @@ comments: true ```go title="array_stack.go" /* 基于数组实现的栈 */ - type ArrayStack struct { + type arrayStack struct { data []int // 数据 } - func NewArrayStack() *ArrayStack { - return &ArrayStack{ + + func newArrayStack() *arrayStack { + return &arrayStack{ // 设置栈的长度为 0,容量为 16 data: make([]int, 0, 16), } } - // Size 栈的长度 - func (s *ArrayStack) Size() int { + + // size 栈的长度 + func (s *arrayStack) size() int { return len(s.data) } - // IsEmpty 栈是否为空 - func (s *ArrayStack) IsEmpty() bool { - return s.Size() == 0 + + // isEmpty 栈是否为空 + func (s *arrayStack) isEmpty() bool { + return s.size() == 0 } - // Push 入栈 - func (s *ArrayStack) Push(v int) { + + // push 入栈 + func (s *arrayStack) push(v int) { // 切片会自动扩容 s.data = append(s.data, v) } - // Pop 出栈 - func (s *ArrayStack) Pop() any { + + // pop 出栈 + func (s *arrayStack) pop() any { // 弹出栈前,先判断是否为空 - if s.IsEmpty() { + if s.isEmpty() { return nil } - val := s.Peek() + val := s.peek() s.data = s.data[:len(s.data)-1] return val } - // Peek 获取栈顶元素 - func (s *ArrayStack) Peek() any { - if s.IsEmpty() { + + // peek 获取栈顶元素 + func (s *arrayStack) peek() any { + if s.isEmpty() { return nil } val := s.data[len(s.data)-1] @@ -816,7 +845,7 @@ comments: true === "C" ```c title="array_stack.c" - + ``` === "C#" @@ -865,11 +894,17 @@ comments: true } ``` +=== "Swift" + + ```swift title="array_stack.swift" + + ``` + !!! tip - 实际编程中,我们一般直接将 `ArrayList` 或 `LinkedList` 当作「栈」来使用。我们仅需通过脑补来屏蔽无关操作,而不用专门去包装它。 + 某些语言并未专门提供栈类,但我们可以直接把该语言的「数组」或「链表」看作栈来使用,并通过“脑补”来屏蔽无关操作,而无需像上述代码去特意包装一层。 ## 栈典型应用 -- **浏览器中的后退与前进、软件中的撤销与反撤销。** 每当我们打开新的网页,浏览器就讲上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。 -- **程序内存管理。** 每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。 +- **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就将上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。 +- **程序内存管理**。每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。 diff --git a/docs/chapter_tree/avl_tree.assets/left_rotate.png b/docs/chapter_tree/avl_tree.assets/left_rotate.png new file mode 100644 index 00000000..4c07196d Binary files /dev/null and b/docs/chapter_tree/avl_tree.assets/left_rotate.png differ diff --git a/docs/chapter_tree/avl_tree.assets/left_rotate_with_grandchild.png b/docs/chapter_tree/avl_tree.assets/left_rotate_with_grandchild.png index 8384696b..949c5c20 100644 Binary files a/docs/chapter_tree/avl_tree.assets/left_rotate_with_grandchild.png and b/docs/chapter_tree/avl_tree.assets/left_rotate_with_grandchild.png differ diff --git a/docs/chapter_tree/avl_tree.assets/right_rotate_step2.png b/docs/chapter_tree/avl_tree.assets/right_rotate_step2.png index 108e2754..8cabc712 100644 Binary files a/docs/chapter_tree/avl_tree.assets/right_rotate_step2.png and b/docs/chapter_tree/avl_tree.assets/right_rotate_step2.png differ diff --git a/docs/chapter_tree/avl_tree.assets/right_rotate_step3.png b/docs/chapter_tree/avl_tree.assets/right_rotate_step3.png index 7d9c996f..8a138635 100644 Binary files a/docs/chapter_tree/avl_tree.assets/right_rotate_step3.png and b/docs/chapter_tree/avl_tree.assets/right_rotate_step3.png differ diff --git a/docs/chapter_tree/avl_tree.assets/right_rotate_step4.png b/docs/chapter_tree/avl_tree.assets/right_rotate_step4.png index eccc0f9d..5ede1ed2 100644 Binary files a/docs/chapter_tree/avl_tree.assets/right_rotate_step4.png and b/docs/chapter_tree/avl_tree.assets/right_rotate_step4.png differ diff --git a/docs/chapter_tree/avl_tree.assets/right_rotate_with_grandchild.png b/docs/chapter_tree/avl_tree.assets/right_rotate_with_grandchild.png index a1c80c6e..ea07a8a2 100644 Binary files a/docs/chapter_tree/avl_tree.assets/right_rotate_with_grandchild.png and b/docs/chapter_tree/avl_tree.assets/right_rotate_with_grandchild.png differ diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index e9a656be..6f627264 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -60,7 +60,13 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Go" ```go title="avl_tree.go" - + /* AVL 树结点类 */ + type TreeNode struct { + Val int // 结点值 + Height int // 结点高度 + Left *TreeNode // 左子结点引用 + Right *TreeNode // 右子结点引用 + } ``` === "JavaScript" @@ -94,7 +100,13 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit } ``` -「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1** 。我们封装两个工具函数,分别用于获取与更新结点的高度。 +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + +「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1**。我们封装两个工具函数,分别用于获取与更新结点的高度。 === "Java" @@ -122,14 +134,14 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```python title="avl_tree.py" """ 获取结点高度 """ - def height(self, node: typing.Optional[TreeNode]) -> int: + def height(self, node: Optional[TreeNode]) -> int: # 空结点高度为 -1 ,叶结点高度为 0 if node is not None: return node.height return -1 - + """ 更新结点高度 """ - def __update_height(self, node: TreeNode): + def __update_height(self, node: Optional[TreeNode]): # 结点高度等于最高子树高度 + 1 node.height = max([self.height(node.left), self.height(node.right)]) + 1 ``` @@ -137,7 +149,26 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Go" ```go title="avl_tree.go" - + /* 获取结点高度 */ + func height(node *TreeNode) int { + // 空结点高度为 -1 ,叶结点高度为 0 + if node != nil { + return node.Height + } + return -1 + } + + /* 更新结点高度 */ + func updateHeight(node *TreeNode) { + lh := height(node.Left) + rh := height(node.Right) + // 结点高度等于最高子树高度 + 1 + if lh > rh { + node.Height = lh + 1 + } else { + node.Height = rh + 1 + } + } ``` === "JavaScript" @@ -167,7 +198,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit // 空结点高度为 -1 ,叶结点高度为 0 return node == null ? -1 : node.height; } - + /* 更新结点高度 */ private void updateHeight(TreeNode node) { @@ -176,6 +207,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ### 结点平衡因子 结点的「平衡因子 Balance Factor」是 **结点的左子树高度减去右子树高度**,并定义空结点的平衡因子为 0 。同样地,我们将获取结点平衡因子封装成函数,以便后续使用。 @@ -202,7 +239,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```python title="avl_tree.py" """ 获取平衡因子 """ - def balance_factor(self, node: TreeNode) -> int: + def balance_factor(self, node: Optional[TreeNode]) -> int: # 空结点平衡因子为 0 if node is None: return 0 @@ -213,7 +250,15 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Go" ```go title="avl_tree.go" - + /* 获取平衡因子 */ + func balanceFactor(node *TreeNode) int { + // 空结点平衡因子为 0 + if node == nil { + return 0 + } + // 结点平衡因子 = 左子树高度 - 右子树高度 + return height(node.Left) - height(node.Right) + } ``` === "JavaScript" @@ -247,19 +292,25 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + !!! note 设平衡因子为 $f$ ,则一棵 AVL 树的任意结点的平衡因子皆满足 $-1 \le f \le 1$ 。 ## AVL 树旋转 -AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡。** 换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。 +AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡**。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。 我们将平衡因子的绝对值 $> 1$ 的结点称为「失衡结点」。根据结点的失衡情况,旋转操作分为 **右旋、左旋、先右旋后左旋、先左旋后右旋**,接下来我们来一起来看看它们是如何操作的。 ### Case 1 - 右旋 -如下图所示(结点下方为「平衡因子」),从底至顶看,二叉树中首个失衡结点是 **结点 3** 。我们聚焦在以该失衡结点为根结点的子树上,将该结点记为 `node` ,将其左子节点记为 `child` ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。 +如下图所示(结点下方为「平衡因子」),从底至顶看,二叉树中首个失衡结点是 **结点 3**。我们聚焦在以该失衡结点为根结点的子树上,将该结点记为 `node` ,将其左子节点记为 `child` ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。 === "Step 1" ![right_rotate_step1](avl_tree.assets/right_rotate_step1.png) @@ -304,7 +355,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 ```python title="avl_tree.py" """ 右旋操作 """ - def __right_rotate(self, node: TreeNode) -> TreeNode: + def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode: child = node.left grand_child = child.right # 以 child 为原点,将 node 向右旋转 @@ -320,7 +371,19 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "Go" ```go title="avl_tree.go" - + /* 右旋操作 */ + func rightRotate(node *TreeNode) *TreeNode { + child := node.Left + grandChild := child.Right + // 以 child 为原点,将 node 向右旋转 + child.Right = node + node.Left = grandChild + // 更新结点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child + } ``` === "JavaScript" @@ -361,13 +424,23 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ### Case 2 - 左旋 -类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。观察发现,**「左旋」和「右旋」操作是镜像对称的,两者对应解决的两种失衡情况也是对称的**。 +类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。 + +![left_rotate](avl_tree.assets/left_rotate.png) + +同理,若结点 `child` 本身有左子结点(记为 `grandChild`),则需要在「左旋」中添加一步:将 `grandChild` 作为 `node` 的右子结点。 ![left_rotate_with_grandchild](avl_tree.assets/left_rotate_with_grandchild.png) -根据对称性,我们可以很方便地从「右旋」推导出「左旋」。具体地,把所有的 `left` 替换为 `right` 、所有的 `right` 替换为 `left` 即可。 +观察发现,**「左旋」和「右旋」操作是镜像对称的,两者对应解决的两种失衡情况也是对称的**。根据对称性,我们可以很方便地从「右旋」推导出「左旋」。具体地,只需将「右旋」代码中的把所有的 `left` 替换为 `right` 、所有的 `right` 替换为 `left` ,即可得到「左旋」代码。 === "Java" @@ -397,7 +470,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 ```python title="avl_tree.py" """ 左旋操作 """ - def __left_rotate(self, node: TreeNode) -> TreeNode: + def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode: child = node.right grand_child = child.left # 以 child 为原点,将 node 向左旋转 @@ -413,7 +486,19 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "Go" ```go title="avl_tree.go" - + /* 左旋操作 */ + func leftRotate(node *TreeNode) *TreeNode { + child := node.Right + grandChild := child.Left + // 以 child 为原点,将 node 向左旋转 + child.Left = node + node.Right = grandChild + // 更新结点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child + } ``` === "JavaScript" @@ -453,6 +538,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ### Case 3 - 先左后右 对于下图的失衡结点 3 ,**单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。 @@ -467,11 +558,11 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 ### 旋转的选择 -下图描述的四种失衡情况与上述 Cases 一一对应,分别采用右旋、左旋、先右后左、先左后右的旋转组合。 +下图描述的四种失衡情况与上述 Cases 逐个对应,分别需采用 **右旋、左旋、先右后左、先左后右** 的旋转操作。 ![rotation_cases](avl_tree.assets/rotation_cases.png) -具体地,需要使用 **失衡结点的平衡因子、较高一侧子结点的平衡因子** 来确定失衡结点属于上图中的哪种情况。 +具体地,在代码中使用 **失衡结点的平衡因子、较高一侧子结点的平衡因子** 来确定失衡结点属于上图中的哪种情况。
@@ -484,7 +575,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
-根据以上规则,我们将旋转操作封装成一个函数。至此,**我们可以使用此函数来旋转各种失衡情况,使失衡结点重新恢复平衡**。 +为方便使用,我们将旋转操作封装成一个函数。至此,**我们可以使用此函数来旋转各种失衡情况,使失衡结点重新恢复平衡**。 === "Java" @@ -530,7 +621,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 ```python title="avl_tree.py" """ 执行旋转操作,使该子树重新恢复平衡 """ - def __rotate(self, node: TreeNode) -> TreeNode: + def __rotate(self, node: Optional[TreeNode]) -> TreeNode: # 获取结点 node 的平衡因子 balance_factor = self.balance_factor(node) # 左偏树 @@ -558,7 +649,36 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "Go" ```go title="avl_tree.go" - + /* 执行旋转操作,使该子树重新恢复平衡 */ + func rotate(node *TreeNode) *TreeNode { + // 获取结点 node 的平衡因子 + // Go 推荐短变量,这里 bf 指代 balanceFactor + 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 + } ``` === "JavaScript" @@ -622,6 +742,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ## AVL 树常用操作 ### 插入结点 @@ -668,9 +794,9 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 def insert(self, val) -> TreeNode: self.root = self.__insert_helper(self.root, val) return self.root - + """ 递归插入结点(辅助函数)""" - def __insert_helper(self, node: typing.Optional[TreeNode], val: int) -> TreeNode: + def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode: if node is None: return TreeNode(val) # 1. 查找插入位置,并插入结点 @@ -690,7 +816,32 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "Go" ```go title="avl_tree.go" - + /* 插入结点 */ + func (t *avlTree) insert(val int) *TreeNode { + t.root = insertHelper(t.root, val) + return t.root + } + /* 递归插入结点(辅助函数) */ + func insertHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + 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 + } ``` === "JavaScript" @@ -720,7 +871,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 root = insertHelper(root, val); return root; } - + /* 递归插入结点(辅助函数) */ private TreeNode? insertHelper(TreeNode? node, int val) { @@ -740,6 +891,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ### 删除结点 「AVL 树」删除结点操作与「二叉搜索树」删除结点操作总体相同。类似地,**在删除结点后,也需要从底至顶地执行旋转操作,使所有失衡结点恢复平衡**。 @@ -772,7 +929,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 node = child; } else { // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode temp = minNode(node.right); + TreeNode temp = getInOrderNext(node.right); node.right = removeHelper(node.right, temp.val); node.val = temp.val; } @@ -783,16 +940,6 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 返回子树的根节点 return node; } - - /* 获取最小结点 */ - TreeNode minNode(TreeNode node) { - if (node == null) return node; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (node.left != null) { - node = node.left; - } - return node; - } ``` === "C++" @@ -808,9 +955,9 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 def remove(self, val: int): root = self.__remove_helper(self.root, val) return root - + """ 递归删除结点(辅助函数) """ - def __remove_helper(self, node: typing.Optional[TreeNode], val: int) -> typing.Optional[TreeNode]: + def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]: if node is None: return None # 1. 查找结点,并删除之 @@ -828,28 +975,61 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 else: node = child else: # 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - temp = self.min_node(node.right) + temp = self.__get_inorder_next(node.right) node.right = self.__remove_helper(node.right, temp.val) node.val = temp.val # 更新结点高度 self.__update_height(node) # 2. 执行旋转操作,使该子树重新恢复平衡 return self.__rotate(node) - - """ 获取最小结点 """ - def min_node(self, node: typing.Optional[TreeNode]) -> typing.Optional[TreeNode]: - if node is None: - return None - # 循环访问左子结点,直到叶结点时为最小结点,跳出 - while node.left is not None: - node = node.left - return node ``` === "Go" ```go title="avl_tree.go" + /* 删除结点 */ + func (t *avlTree) remove(val int) *TreeNode { + root := removeHelper(t.root, val) + return root + } + /* 递归删除结点(辅助函数) */ + func removeHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return nil + } + /* 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 == nil || node.Right == nil { + child := node.Left + if node.Right != nil { + child = node.Right + } + // 子结点数量 = 0 ,直接删除 node 并返回 + if child == nil { + return nil + } else { + // 子结点数量 = 1 ,直接删除 node + node = child + } + } else { + // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 + temp := getInOrderNext(node.Right) + node.Right = removeHelper(node.Right, temp.Val) + node.Val = temp.Val + } + } + // 更新结点高度 + updateHeight(node) + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node) + // 返回子树的根节点 + return node + } ``` === "JavaScript" @@ -879,7 +1059,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 root = removeHelper(root, val); return root; } - + /* 递归删除结点(辅助函数) */ private TreeNode? removeHelper(TreeNode? node, int val) { @@ -904,7 +1084,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 else { // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 - TreeNode? temp = minNode(node.right); + TreeNode? temp = getInOrderNext(node.right); node.right = removeHelper(node.right, temp.val); node.val = temp.val; } @@ -915,18 +1095,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 // 返回子树的根节点 return node; } + ``` + +=== "Swift" + + ```swift title="avl_tree.swift" - /* 获取最小结点 */ - private TreeNode? minNode(TreeNode? node) - { - if (node == null) return node; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (node.left != null) - { - node = node.left; - } - return node; - } ``` ### 查找结点 diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index 629c051f..a0322154 100644 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -83,7 +83,7 @@ comments: true ```python title="binary_search_tree.py" """ 查找结点 """ - def search(self, num: int) -> typing.Optional[TreeNode]: + def search(self, num: int) -> Optional[TreeNode]: cur = self.root # 循环查找,越过叶结点后跳出 while cur is not None: @@ -103,7 +103,7 @@ comments: true ```go title="binary_search_tree.go" /* 查找结点 */ - func (bst *BinarySearchTree) Search(num int) *TreeNode { + func (bst *binarySearchTree) search(num int) *TreeNode { node := bst.root // 循环查找,越过叶结点后跳出 for node != nil { @@ -192,12 +192,18 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_search_tree.swift" + + ``` + ### 插入结点 给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根结点 < 右子树”的性质,插入操作分为两步: -1. **查找插入位置:** 与查找操作类似,我们从根结点出发,根据当前结点值和 `num` 的大小关系循环向下搜索,直到越过叶结点(遍历到 $\text{null}$ )时跳出循环; -2. **在该位置插入结点:** 初始化结点 `num` ,将该结点放到 $\text{null}$ 的位置 ; +1. **查找插入位置**:与查找操作类似,我们从根结点出发,根据当前结点值和 `num` 的大小关系循环向下搜索,直到越过叶结点(遍历到 $\text{null}$ )时跳出循环; +2. **在该位置插入结点**:初始化结点 `num` ,将该结点放到 $\text{null}$ 的位置 ; 二叉搜索树不允许存在重复结点,否则将会违背其定义。因此若待插入结点在树中已经存在,则不执行插入,直接返回即可。 @@ -259,7 +265,7 @@ comments: true ```python title="binary_search_tree.py" """ 插入结点 """ - def insert(self, num: int) -> typing.Optional[TreeNode]: + def insert(self, num: int) -> Optional[TreeNode]: root = self.root # 若树为空,直接提前返回 if root is None: @@ -293,7 +299,7 @@ comments: true ```go title="binary_search_tree.go" /* 插入结点 */ - func (bst *BinarySearchTree) Insert(num int) *TreeNode { + func (bst *binarySearchTree) insert(num int) *TreeNode { cur := bst.root // 若树为空,直接提前返回 if cur == nil { @@ -422,6 +428,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_search_tree.swift" + + ``` + 为了插入结点,需要借助 **辅助结点 `prev`** 保存上一轮循环的结点,这样在遍历到 $\text{null}$ 时,我们也可以获取到其父结点,从而完成结点插入操作。 与查找结点相同,插入结点使用 $O(\log n)$ 时间。 @@ -430,34 +442,30 @@ comments: true 与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况: -**待删除结点的子结点数量 $= 0$ 。** 表明待删除结点是叶结点,直接删除即可。 +**当待删除结点的子结点数量 $= 0$ 时**,表明待删除结点是叶结点,直接删除即可。 ![bst_remove_case1](binary_search_tree.assets/bst_remove_case1.png) -**待删除结点的子结点数量 $= 1$ 。** 将待删除结点替换为其子结点。 +**当待删除结点的子结点数量 $= 1$ 时**,将待删除结点替换为其子结点即可。 ![bst_remove_case2](binary_search_tree.assets/bst_remove_case2.png) -**待删除结点的子结点数量 $= 2$ 。** 删除操作分为三步: +**当待删除结点的子结点数量 $= 2$ 时**,删除操作分为三步: 1. 找到待删除结点在 **中序遍历序列** 中的下一个结点,记为 `nex` ; 2. 在树中递归删除结点 `nex` ; 3. 使用 `nex` 替换待删除结点; === "Step 1" - ![bst_remove_case3_1](binary_search_tree.assets/bst_remove_case3_1.png) === "Step 2" - ![bst_remove_case3_2](binary_search_tree.assets/bst_remove_case3_2.png) === "Step 3" - ![bst_remove_case3_3](binary_search_tree.assets/bst_remove_case3_3.png) === "Step 4" - ![bst_remove_case3_4](binary_search_tree.assets/bst_remove_case3_4.png) 删除结点操作也使用 $O(\log n)$ 时间,其中查找待删除结点 $O(\log n)$ ,获取中序遍历后继结点 $O(\log n)$ 。 @@ -489,11 +497,13 @@ comments: true // 删除结点 cur if (pre.left == cur) pre.left = child; else pre.right = child; + // 释放内存 + delete cur; } // 子结点数量 = 2 else { // 获取中序遍历中 cur 的下一个结点 - TreeNode nex = min(cur.right); + TreeNode nex = getInOrderNext(cur.right); int tmp = nex.val; // 递归删除结点 nex remove(nex.val); @@ -502,15 +512,6 @@ comments: true } return cur; } - /* 获取最小结点 */ - TreeNode min(TreeNode root) { - if (root == null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root.left != null) { - root = root.left; - } - return root; - } ``` === "C++" @@ -544,7 +545,7 @@ comments: true // 子结点数量 = 2 else { // 获取中序遍历中 cur 的下一个结点 - TreeNode* nex = min(cur->right); + TreeNode* nex = getInOrderNext(cur->right); int tmp = nex->val; // 递归删除结点 nex remove(nex->val); @@ -553,22 +554,13 @@ comments: true } return cur; } - /* 获取最小结点 */ - TreeNode* min(TreeNode* root) { - if (root == nullptr) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root->left != nullptr) { - root = root->left; - } - return root; - } ``` === "Python" ```python title="binary_search_tree.py" """ 删除结点 """ - def remove(self, num: int) -> typing.Optional[TreeNode]: + def remove(self, num: int) -> Optional[TreeNode]: root = self.root # 若树为空,直接提前返回 if root is None: @@ -604,30 +596,20 @@ comments: true # 子结点数量 = 2 else: # 获取中序遍历中 cur 的下一个结点 - nex = self.min(cur.right) + nex = self.get_inorder_next(cur.right) tmp = nex.val # 递归删除结点 nex self.remove(nex.val) # 将 nex 的值复制给 cur cur.val = tmp return cur - - """ 获取最小结点 """ - def min(self, root: typing.Optional[TreeNode]) -> typing.Optional[TreeNode]: - if root is None: - return root - - # 循环访问左子结点,直到叶结点时为最小结点,跳出 - while root.left is not None: - root = root.left - return root ``` === "Go" ```go title="binary_search_tree.go" /* 删除结点 */ - func (bst *BinarySearchTree) Remove(num int) *TreeNode { + func (bst *binarySearchTree) remove(num int) *TreeNode { cur := bst.root // 若树为空,直接提前返回 if cur == nil { @@ -671,10 +653,10 @@ comments: true // 子结点数为 2 } else { // 获取中序遍历中待删除结点 cur 的下一个结点 - next := bst.GetInorderNext(cur) + next := bst.getInOrderNext(cur) temp := next.Val // 递归删除结点 next - bst.Remove(next.Val) + bst.remove(next.Val) // 将 next 的值复制给 cur cur.Val = temp } @@ -713,7 +695,7 @@ comments: true // 子结点数量 = 2 else { // 获取中序遍历中 cur 的下一个结点 - let nex = min(cur.right); + let nex = getInOrderNext(cur.right); let tmp = nex.val; // 递归删除结点 nex remove(nex.val); @@ -766,7 +748,7 @@ comments: true // 子结点数量 = 2 else { // 获取中序遍历中 cur 的下一个结点 - let next = min(cur.right); + let next = getInOrderNext(cur.right); let tmp = next!.val; // 递归删除结点 nex remove(next!.val); @@ -824,7 +806,7 @@ comments: true else { // 获取中序遍历中 cur 的下一个结点 - TreeNode? nex = min(cur.right); + TreeNode? nex = getInOrderNext(cur.right); if (nex != null) { int tmp = nex.val; @@ -836,35 +818,29 @@ comments: true } return cur; } - - /* 获取最小结点 */ - TreeNode? min(TreeNode? root) - { - if (root == null) return root; - // 循环访问左子结点,直到叶结点时为最小结点,跳出 - while (root.left != null) - { - root = root.left; - } - return root; - } + ``` + +=== "Swift" + + ```swift title="binary_search_tree.swift" + ``` ## 二叉搜索树的优势 假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为: -- **查找元素:** 由于数组是无序的,因此需要遍历数组来确定,使用 $O(n)$ 时间; -- **插入元素:** 只需将元素添加至数组尾部即可,使用 $O(1)$ 时间; -- **删除元素:** 先查找元素,使用 $O(n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; -- **获取最小 / 最大元素:** 需要遍历数组来确定,使用 $O(n)$ 时间; +- **查找元素**:由于数组是无序的,因此需要遍历数组来确定,使用 $O(n)$ 时间; +- **插入元素**:只需将元素添加至数组尾部即可,使用 $O(1)$ 时间; +- **删除元素**:先查找元素,使用 $O(n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; +- **获取最小 / 最大元素**:需要遍历数组来确定,使用 $O(n)$ 时间; 为了得到先验信息,我们也可以预先将数组元素进行排序,得到一个「排序数组」,此时操作效率为: -- **查找元素:** 由于数组已排序,可以使用二分查找,平均使用 $O(\log n)$ 时间; -- **插入元素:** 先查找插入位置,使用 $O(\log n)$ 时间,再插入到指定位置,使用 $O(n)$ 时间; -- **删除元素:** 先查找元素,使用 $O(\log n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; -- **获取最小 / 最大元素:** 数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间; +- **查找元素**:由于数组已排序,可以使用二分查找,平均使用 $O(\log n)$ 时间; +- **插入元素**:先查找插入位置,使用 $O(\log n)$ 时间,再插入到指定位置,使用 $O(n)$ 时间; +- **删除元素**:先查找元素,使用 $O(\log n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间; +- **获取最小 / 最大元素**:数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间; 观察发现,无序数组和有序数组中的各项操作的时间复杂度是“偏科”的,即有的快有的慢;**而二叉搜索树的各项操作的时间复杂度都是对数阶,在数据量 $n$ 很大时有巨大优势**。 diff --git a/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png b/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png index b7922fcb..d95c48f0 100644 Binary files a/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png and b/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png differ diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 0c858fd3..077c03e0 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -106,6 +106,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + + ``` + 结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。 除了叶结点外,每个结点都有子结点和子树。例如,若将上图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 以下的树」和「结点 5 以下的树」。 @@ -120,7 +126,7 @@ comments: true - 「根结点 Root Node」:二叉树最顶层的结点,其没有父结点; - 「叶结点 Leaf Node」:没有子结点的结点,其两个指针都指向 $\text{null}$ ; -- 结点所处「层 Level」:从顶置底依次增加,根结点所处层为 1 ; +- 结点所处「层 Level」:从顶至底依次增加,根结点所处层为 1 ; - 结点「度 Degree」:结点的子结点数量。二叉树中,度的范围是 0, 1, 2 ; - 「边 Edge」:连接两个结点的边,即结点指针; - 二叉树「高度」:二叉树中根结点到最远叶结点走过边的数量; @@ -137,7 +143,7 @@ comments: true ## 二叉树基本操作 -**初始化二叉树。** 与链表类似,先初始化结点,再构建引用指向(即指针)。 +**初始化二叉树**。与链表类似,先初始化结点,再构建引用指向(即指针)。 === "Java" @@ -263,7 +269,13 @@ comments: true n2.right = n5; ``` -**插入与删除结点。** 与链表类似,插入与删除结点都可以通过修改指针实现。 +=== "Swift" + + ```swift title="binary_tree.swift" + + ``` + +**插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。 ![binary_tree_add_remove](binary_tree.assets/binary_tree_add_remove.png) @@ -358,6 +370,12 @@ comments: true n1.left = n2; ``` +=== "Swift" + + ```swift title="binary_tree.swift" + + ``` + !!! note 插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。 @@ -495,9 +513,15 @@ comments: true int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` +=== "Swift" + + ```swift title="" + + ``` + ![array_representation_with_empty](binary_tree.assets/array_representation_with_empty.png) -回顾「完全二叉树」的满足条件,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。“便于使用数组表示”也是完全二叉树受欢迎的原因之一。 +回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。 ![array_representation_complete_binary_tree](binary_tree.assets/array_representation_complete_binary_tree.png) diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md index f4634841..650a1529 100644 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -66,7 +66,7 @@ comments: true ```python title="binary_tree_bfs.py" """ 层序遍历 """ - def hier_order(root: TreeNode): + def hier_order(root: Optional[TreeNode]): # 初始化队列,加入根结点 queue = collections.deque() queue.append(root) @@ -185,6 +185,12 @@ comments: true ``` +=== "Swift" + + ```swift title="binary_tree_bfs.swift" + + ``` + ## 前序、中序、后序遍历 相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。 @@ -271,7 +277,7 @@ comments: true ```python title="binary_tree_dfs.py" """ 前序遍历 """ - def pre_order(root: typing.Optional[TreeNode]): + def pre_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:根结点 -> 左子树 -> 右子树 @@ -280,7 +286,7 @@ comments: true pre_order(root=root.right) """ 中序遍历 """ - def in_order(root: typing.Optional[TreeNode]): + def in_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:左子树 -> 根结点 -> 右子树 @@ -289,7 +295,7 @@ comments: true in_order(root=root.right) """ 后序遍历 """ - def post_order(root: typing.Optional[TreeNode]): + def post_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:左子树 -> 右子树 -> 根结点 @@ -443,6 +449,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_tree_dfs.swift" + + ``` + !!! note 使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。 diff --git a/docs/index.md b/docs/index.md index 71c34360..3b640520 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,7 +10,7 @@ hide: ![conceptual_rendering](index.assets/conceptual_rendering.png){ align=left width=350 }




《 Hello,算法 》

-

动画图解、能运行、可讨论的
数据结构与算法快速入门教程

+

动画图解、能运行、可提问的
数据结构与算法快速入门教程

[![github-stars](https://img.shields.io/github/stars/krahets/hello-algo?style=social)](https://github.com/krahets/hello-algo)

[@Krahets](https://leetcode.cn/u/jyd/)
diff --git a/mkdocs.yml b/mkdocs.yml index 4334382f..9ddae9f3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -32,7 +32,7 @@ theme: - navigation.sections # - navigation.tabs # - navigation.tabs.sticky - # - navigation.top + - navigation.top - navigation.footer - navigation.tracking - search.highlight @@ -139,7 +139,7 @@ nav: - 小结: chapter_computational_complexity/summary.md - 数据结构简介: - 数据与内存: chapter_data_structure/data_and_memory.md - - 数据结构分类: chapter_data_structure/classification_of_data_strcuture.md + - 数据结构分类: chapter_data_structure/classification_of_data_structure.md - 小结: chapter_data_structure/summary.md - 数组与链表: - 数组(Array): chapter_array_and_linkedlist/array.md