diff --git a/.gitignore b/.gitignore index 4e17eaf..dc1ffac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,62 +1,10 @@ -# ---> CMake -CMakeLists.txt.user -CMakeCache.txt -CMakeFiles -CMakeScripts -Testing -Makefile -cmake_install.cmake -install_manifest.txt -compile_commands.json -CTestTestfile.cmake -_deps +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ -# ---> VisualStudioCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets +*.dSYM/ -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -# ---> C++ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -build \ No newline at end of file +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1e80bc4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/chapter_array_and_linkedlist/CMakeLists.txt b/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 0000000..2e933e0 --- /dev/null +++ b/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array array.cpp) +add_executable(linked_list linked_list.cpp) +add_executable(list list.cpp) +add_executable(my_list my_list.cpp) diff --git a/chapter_array_and_linkedlist/array.cpp b/chapter_array_and_linkedlist/array.cpp new file mode 100644 index 0000000..dd87b92 --- /dev/null +++ b/chapter_array_and_linkedlist/array.cpp @@ -0,0 +1,113 @@ +/** + * File: array.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 随机访问元素 */ +int randomAccess(int *nums, int size) { + // 在区间 [0, size) 中随机抽取一个数字 + int randomIndex = rand() % size; + // 获取并返回随机元素 + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* 扩展数组长度 */ +int *extend(int *nums, int size, int enlarge) { + // 初始化一个扩展长度后的数组 + int *res = new int[size + enlarge]; + // 将原数组中的所有元素复制到新数组 + for (int i = 0; i < size; i++) { + res[i] = nums[i]; + } + // 释放内存 + delete[] nums; + // 返回扩展后的新数组 + return res; +} + +/* 在数组的索引 index 处插入元素 num */ +void insert(int *nums, int size, int num, int index) { + // 把索引 index 以及之后的所有元素向后移动一位 + for (int i = size - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // 将 num 赋给 index 处的元素 + nums[index] = num; +} + +/* 删除索引 index 处的元素 */ +void remove(int *nums, int size, int index) { + // 把索引 index 之后的所有元素向前移动一位 + for (int i = index; i < size - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 遍历数组 */ +void traverse(int *nums, int size) { + int count = 0; + // 通过索引遍历数组 + for (int i = 0; i < size; i++) { + count += nums[i]; + } +} + +/* 在数组中查找指定元素 */ +int find(int *nums, int size, int target) { + for (int i = 0; i < size; i++) { + if (nums[i] == target) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 初始化数组 */ + int size = 5; + int *arr = new int[size]; + cout << "数组 arr = "; + printArray(arr, size); + + int *nums = new int[size]{1, 3, 2, 5, 4}; + cout << "数组 nums = "; + printArray(nums, size); + + /* 随机访问 */ + int randomNum = randomAccess(nums, size); + cout << "在 nums 中获取随机元素 " << randomNum << endl; + + /* 长度扩展 */ + int enlarge = 3; + nums = extend(nums, size, enlarge); + size += enlarge; + cout << "将数组长度扩展至 8 ,得到 nums = "; + printArray(nums, size); + + /* 插入元素 */ + insert(nums, size, 6, 3); + cout << "在索引 3 处插入数字 6 ,得到 nums = "; + printArray(nums, size); + + /* 删除元素 */ + remove(nums, size, 2); + cout << "删除索引 2 处的元素,得到 nums = "; + printArray(nums, size); + + /* 遍历数组 */ + traverse(nums, size); + + /* 查找元素 */ + int index = find(nums, size, 3); + cout << "在 nums 中查找元素 3 ,得到索引 = " << index << endl; + + // 释放内存 + delete[] arr; + delete[] nums; + + return 0; +} diff --git a/chapter_array_and_linkedlist/linked_list.cpp b/chapter_array_and_linkedlist/linked_list.cpp new file mode 100644 index 0000000..092226e --- /dev/null +++ b/chapter_array_and_linkedlist/linked_list.cpp @@ -0,0 +1,89 @@ +/** + * File: linked_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 在链表的节点 n0 之后插入节点 P */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; +} + +/* 删除链表的节点 n0 之后的首个节点 */ +void remove(ListNode *n0) { + if (n0->next == nullptr) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // 释放内存 + delete P; +} + +/* 访问链表中索引为 index 的节点 */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == nullptr) + return nullptr; + head = head->next; + } + return head; +} + +/* 在链表中查找值为 target 的首个节点 */ +int find(ListNode *head, int target) { + int index = 0; + while (head != nullptr) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 初始化链表 */ + // 初始化各个节点 + ListNode *n0 = new ListNode(1); + ListNode *n1 = new ListNode(3); + ListNode *n2 = new ListNode(2); + ListNode *n3 = new ListNode(5); + ListNode *n4 = new ListNode(4); + // 构建节点之间的引用 + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + cout << "初始化的链表为" << endl; + printLinkedList(n0); + + /* 插入节点 */ + insert(n0, new ListNode(0)); + cout << "插入节点后的链表为" << endl; + printLinkedList(n0); + + /* 删除节点 */ + remove(n0); + cout << "删除节点后的链表为" << endl; + printLinkedList(n0); + + /* 访问节点 */ + ListNode *node = access(n0, 3); + cout << "链表中索引 3 处的节点的值 = " << node->val << endl; + + /* 查找节点 */ + int index = find(n0, 2); + cout << "链表中值为 2 的节点的索引 = " << index << endl; + + // 释放内存 + freeMemoryLinkedList(n0); + + return 0; +} diff --git a/chapter_array_and_linkedlist/list.cpp b/chapter_array_and_linkedlist/list.cpp new file mode 100644 index 0000000..e0502cc --- /dev/null +++ b/chapter_array_and_linkedlist/list.cpp @@ -0,0 +1,72 @@ +/** + * File: list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化列表 */ + vector nums = {1, 3, 2, 5, 4}; + cout << "列表 nums = "; + printVector(nums); + + /* 访问元素 */ + int num = nums[1]; + cout << "访问索引 1 处的元素,得到 num = " << num << endl; + + /* 更新元素 */ + nums[1] = 0; + cout << "将索引 1 处的元素更新为 0 ,得到 nums = "; + printVector(nums); + + /* 清空列表 */ + nums.clear(); + cout << "清空列表后 nums = "; + printVector(nums); + + /* 在尾部添加元素 */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + cout << "添加元素后 nums = "; + printVector(nums); + + /* 在中间插入元素 */ + nums.insert(nums.begin() + 3, 6); + cout << "在索引 3 处插入数字 6 ,得到 nums = "; + printVector(nums); + + /* 删除元素 */ + nums.erase(nums.begin() + 3); + cout << "删除索引 3 处的元素,得到 nums = "; + printVector(nums); + + /* 通过索引遍历列表 */ + int count = 0; + for (int i = 0; i < nums.size(); i++) { + count += nums[i]; + } + /* 直接遍历列表元素 */ + count = 0; + for (int x : nums) { + count += x; + } + + /* 拼接两个列表 */ + vector nums1 = {6, 8, 7, 10, 9}; + nums.insert(nums.end(), nums1.begin(), nums1.end()); + cout << "将列表 nums1 拼接到 nums 之后,得到 nums = "; + printVector(nums); + + /* 排序列表 */ + sort(nums.begin(), nums.end()); + cout << "排序列表后 nums = "; + printVector(nums); + + return 0; +} diff --git a/chapter_array_and_linkedlist/my_list.cpp b/chapter_array_and_linkedlist/my_list.cpp new file mode 100644 index 0000000..4fff94b --- /dev/null +++ b/chapter_array_and_linkedlist/my_list.cpp @@ -0,0 +1,171 @@ +/** + * File: my_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 列表类 */ +class MyList { + private: + int *arr; // 数组(存储列表元素) + int arrCapacity = 10; // 列表容量 + int arrSize = 0; // 列表长度(当前元素数量) + int extendRatio = 2; // 每次列表扩容的倍数 + + public: + /* 构造方法 */ + MyList() { + arr = new int[arrCapacity]; + } + + /* 析构方法 */ + ~MyList() { + delete[] arr; + } + + /* 获取列表长度(当前元素数量)*/ + int size() { + return arrSize; + } + + /* 获取列表容量 */ + int capacity() { + return arrCapacity; + } + + /* 访问元素 */ + int get(int index) { + // 索引如果越界,则抛出异常,下同 + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + return arr[index]; + } + + /* 更新元素 */ + void set(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + arr[index] = num; + } + + /* 在尾部添加元素 */ + void add(int num) { + // 元素数量超出容量时,触发扩容机制 + if (size() == capacity()) + extendCapacity(); + arr[size()] = num; + // 更新元素数量 + arrSize++; + } + + /* 在中间插入元素 */ + void insert(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + // 元素数量超出容量时,触发扩容机制 + if (size() == capacity()) + extendCapacity(); + // 将索引 index 以及之后的元素都向后移动一位 + for (int j = size() - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // 更新元素数量 + arrSize++; + } + + /* 删除元素 */ + int remove(int index) { + if (index < 0 || index >= size()) + throw out_of_range("索引越界"); + int num = arr[index]; + // 将索引 index 之后的元素都向前移动一位 + for (int j = index; j < size() - 1; j++) { + arr[j] = arr[j + 1]; + } + // 更新元素数量 + arrSize--; + // 返回被删除的元素 + return num; + } + + /* 列表扩容 */ + void extendCapacity() { + // 新建一个长度为原数组 extendRatio 倍的新数组 + int newCapacity = capacity() * extendRatio; + int *tmp = arr; + arr = new int[newCapacity]; + // 将原数组中的所有元素复制到新数组 + for (int i = 0; i < size(); i++) { + arr[i] = tmp[i]; + } + // 释放内存 + delete[] tmp; + arrCapacity = newCapacity; + } + + /* 将列表转换为 Vector 用于打印 */ + vector toVector() { + // 仅转换有效长度范围内的列表元素 + vector vec(size()); + for (int i = 0; i < size(); i++) { + vec[i] = arr[i]; + } + return vec; + } +}; + +/* Driver Code */ +int main() { + /* 初始化列表 */ + MyList *nums = new MyList(); + /* 在尾部添加元素 */ + nums->add(1); + nums->add(3); + nums->add(2); + nums->add(5); + nums->add(4); + cout << "列表 nums = "; + vector vec = nums->toVector(); + printVector(vec); + cout << "容量 = " << nums->capacity() << " ,长度 = " << nums->size() << endl; + + /* 在中间插入元素 */ + nums->insert(3, 6); + cout << "在索引 3 处插入数字 6 ,得到 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 删除元素 */ + nums->remove(3); + cout << "删除索引 3 处的元素,得到 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 访问元素 */ + int num = nums->get(1); + cout << "访问索引 1 处的元素,得到 num = " << num << endl; + + /* 更新元素 */ + nums->set(1, 0); + cout << "将索引 1 处的元素更新为 0 ,得到 nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 测试扩容机制 */ + for (int i = 0; i < 10; i++) { + // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 + nums->add(i); + } + cout << "扩容后的列表 nums = "; + vec = nums->toVector(); + printVector(vec); + cout << "容量 = " << nums->capacity() << " ,长度 = " << nums->size() << endl; + + // 释放内存 + delete nums; + + return 0; +} diff --git a/chapter_backtracking/CMakeLists.txt b/chapter_backtracking/CMakeLists.txt new file mode 100644 index 0000000..6c271e3 --- /dev/null +++ b/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) +add_executable(permutations_i permutations_i.cpp) +add_executable(permutations_ii permutations_ii.cpp) +add_executable(n_queens n_queens.cpp) +add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) +add_executable(subset_sum_i subset_sum_i.cpp) +add_executable(subset_sum_ii subset_sum_ii.cpp) diff --git a/chapter_backtracking/n_queens.cpp b/chapter_backtracking/n_queens.cpp new file mode 100644 index 0000000..9f759d7 --- /dev/null +++ b/chapter_backtracking/n_queens.cpp @@ -0,0 +1,65 @@ +/** + * File: n_queens.cpp + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:n 皇后 */ +void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, + vector &diags1, vector &diags2) { + // 当放置完所有行时,记录解 + if (row == n) { + res.push_back(state); + return; + } + // 遍历所有列 + for (int col = 0; col < n; col++) { + // 计算该格子对应的主对角线和次对角线 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 尝试:将皇后放置在该格子 + state[row][col] = "Q"; + cols[col] = diags1[diag1] = diags2[diag2] = true; + // 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 回退:将该格子恢复为空位 + state[row][col] = "#"; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* 求解 n 皇后 */ +vector>> nQueens(int n) { + // 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + vector> state(n, vector(n, "#")); + vector cols(n, false); // 记录列是否有皇后 + vector diags1(2 * n - 1, false); // 记录主对角线上是否有皇后 + vector diags2(2 * n - 1, false); // 记录次对角线上是否有皇后 + vector>> res; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + vector>> res = nQueens(n); + + cout << "输入棋盘长宽为 " << n << endl; + cout << "皇后放置方案共有 " << res.size() << " 种" << endl; + for (const vector> &state : res) { + cout << "--------------------" << endl; + for (const vector &row : state) { + printVector(row); + } + } + + return 0; +} diff --git a/chapter_backtracking/permutations_i.cpp b/chapter_backtracking/permutations_i.cpp new file mode 100644 index 0000000..a72fa6c --- /dev/null +++ b/chapter_backtracking/permutations_i.cpp @@ -0,0 +1,54 @@ +/** + * File: permutations_i.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:全排列 I */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // 当状态长度等于元素数量时,记录解 + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 + if (!selected[i]) { + // 尝试:做出选择,更新状态 + selected[i] = true; + state.push_back(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.pop_back(); + } + } +} + +/* 全排列 I */ +vector> permutationsI(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 2, 3}; + + vector> res = permutationsI(nums); + + cout << "输入数组 nums = "; + printVector(nums); + cout << "所有排列 res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/chapter_backtracking/permutations_ii.cpp b/chapter_backtracking/permutations_ii.cpp new file mode 100644 index 0000000..e3e3e7e --- /dev/null +++ b/chapter_backtracking/permutations_ii.cpp @@ -0,0 +1,56 @@ +/** + * File: permutations_ii.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:全排列 II */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // 当状态长度等于元素数量时,记录解 + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // 遍历所有选择 + unordered_set duplicated; + for (int i = 0; i < choices.size(); i++) { + int choice = choices[i]; + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if (!selected[i] && duplicated.find(choice) == duplicated.end()) { + // 尝试:做出选择,更新状态 + duplicated.emplace(choice); // 记录选择过的元素值 + selected[i] = true; + state.push_back(choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + selected[i] = false; + state.pop_back(); + } + } +} + +/* 全排列 II */ +vector> permutationsII(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 1, 2}; + + vector> res = permutationsII(nums); + + cout << "输入数组 nums = "; + printVector(nums); + cout << "所有排列 res = "; + printVectorMatrix(res); + + return 0; +} diff --git a/chapter_backtracking/preorder_traversal_i_compact.cpp b/chapter_backtracking/preorder_traversal_i_compact.cpp new file mode 100644 index 0000000..d1d80bd --- /dev/null +++ b/chapter_backtracking/preorder_traversal_i_compact.cpp @@ -0,0 +1,39 @@ +/** + * File: preorder_traversal_i_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector res; + +/* 前序遍历:例题一 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + if (root->val == 7) { + // 记录解 + res.push_back(root); + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二叉树" << endl; + printTree(root); + + // 前序遍历 + preOrder(root); + + cout << "\n输出所有值为 7 的节点" << endl; + vector vals; + for (TreeNode *node : res) { + vals.push_back(node->val); + } + printVector(vals); +} diff --git a/chapter_backtracking/preorder_traversal_ii_compact.cpp b/chapter_backtracking/preorder_traversal_ii_compact.cpp new file mode 100644 index 0000000..3d75ae8 --- /dev/null +++ b/chapter_backtracking/preorder_traversal_ii_compact.cpp @@ -0,0 +1,46 @@ +/** + * File: preorder_traversal_ii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* 前序遍历:例题二 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + // 尝试 + path.push_back(root); + if (root->val == 7) { + // 记录解 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 回退 + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二叉树" << endl; + printTree(root); + + // 前序遍历 + preOrder(root); + + cout << "\n输出所有根节点到节点 7 的路径" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/chapter_backtracking/preorder_traversal_iii_compact.cpp b/chapter_backtracking/preorder_traversal_iii_compact.cpp new file mode 100644 index 0000000..28a73bd --- /dev/null +++ b/chapter_backtracking/preorder_traversal_iii_compact.cpp @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* 前序遍历:例题三 */ +void preOrder(TreeNode *root) { + // 剪枝 + if (root == nullptr || root->val == 3) { + return; + } + // 尝试 + path.push_back(root); + if (root->val == 7) { + // 记录解 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 回退 + path.pop_back(); +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二叉树" << endl; + printTree(root); + + // 前序遍历 + preOrder(root); + + cout << "\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/chapter_backtracking/preorder_traversal_iii_template.cpp b/chapter_backtracking/preorder_traversal_iii_template.cpp new file mode 100644 index 0000000..c48fb3d --- /dev/null +++ b/chapter_backtracking/preorder_traversal_iii_template.cpp @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 判断当前状态是否为解 */ +bool isSolution(vector &state) { + return !state.empty() && state.back()->val == 7; +} + +/* 记录解 */ +void recordSolution(vector &state, vector> &res) { + res.push_back(state); +} + +/* 判断在当前状态下,该选择是否合法 */ +bool isValid(vector &state, TreeNode *choice) { + return choice != nullptr && choice->val != 3; +} + +/* 更新状态 */ +void makeChoice(vector &state, TreeNode *choice) { + state.push_back(choice); +} + +/* 恢复状态 */ +void undoChoice(vector &state, TreeNode *choice) { + state.pop_back(); +} + +/* 回溯算法:例题三 */ +void backtrack(vector &state, vector &choices, vector> &res) { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + } + // 遍历所有选择 + for (TreeNode *choice : choices) { + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + // 进行下一轮选择 + vector nextChoices{choice->left, choice->right}; + backtrack(state, nextChoices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +int main() { + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); + cout << "\n初始化二叉树" << endl; + printTree(root); + + // 回溯算法 + vector state; + vector choices = {root}; + vector> res; + backtrack(state, choices, res); + + cout << "\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + printVector(vals); + } +} diff --git a/chapter_backtracking/subset_sum_i.cpp b/chapter_backtracking/subset_sum_i.cpp new file mode 100644 index 0000000..fe4999c --- /dev/null +++ b/chapter_backtracking/subset_sum_i.cpp @@ -0,0 +1,57 @@ +/** + * File: subset_sum_i.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:子集和 I */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.push_back(state); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (int i = start; i < choices.size(); i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 尝试:做出选择,更新 target, start + state.push_back(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop_back(); + } +} + +/* 求解子集和 I */ +vector> subsetSumI(vector &nums, int target) { + vector state; // 状态(子集) + sort(nums.begin(), nums.end()); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + vector> res; // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumI(nums, target); + + cout << "输入数组 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等于 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/chapter_backtracking/subset_sum_i_naive.cpp b/chapter_backtracking/subset_sum_i_naive.cpp new file mode 100644 index 0000000..64647bd --- /dev/null +++ b/chapter_backtracking/subset_sum_i_naive.cpp @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:子集和 I */ +void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { + // 子集和等于 target 时,记录解 + if (total == target) { + res.push_back(state); + return; + } + // 遍历所有选择 + for (size_t i = 0; i < choices.size(); i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + choices[i] > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + state.push_back(choices[i]); + // 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop_back(); + } +} + +/* 求解子集和 I(包含重复子集) */ +vector> subsetSumINaive(vector &nums, int target) { + vector state; // 状态(子集) + int total = 0; // 子集和 + vector> res; // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumINaive(nums, target); + + cout << "输入数组 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等于 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/chapter_backtracking/subset_sum_ii.cpp b/chapter_backtracking/subset_sum_ii.cpp new file mode 100644 index 0000000..b233581 --- /dev/null +++ b/chapter_backtracking/subset_sum_ii.cpp @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯算法:子集和 II */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + res.push_back(state); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (int i = start; i < choices.size(); i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - choices[i] < 0) { + break; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 尝试:做出选择,更新 target, start + state.push_back(choices[i]); + // 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + state.pop_back(); + } +} + +/* 求解子集和 II */ +vector> subsetSumII(vector &nums, int target) { + vector state; // 状态(子集) + sort(nums.begin(), nums.end()); // 对 nums 进行排序 + int start = 0; // 遍历起始点 + vector> res; // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +int main() { + vector nums = {4, 4, 5}; + int target = 9; + + vector> res = subsetSumII(nums, target); + + cout << "输入数组 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "所有和等于 " << target << " 的子集 res = " << endl; + printVectorMatrix(res); + + return 0; +} diff --git a/chapter_computational_complexity/CMakeLists.txt b/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 0000000..ea2845b --- /dev/null +++ b/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(iteration iteration.cpp) +add_executable(recursion recursion.cpp) +add_executable(space_complexity space_complexity.cpp) +add_executable(time_complexity time_complexity.cpp) +add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) \ No newline at end of file diff --git a/chapter_computational_complexity/iteration.cpp b/chapter_computational_complexity/iteration.cpp new file mode 100644 index 0000000..ef262b7 --- /dev/null +++ b/chapter_computational_complexity/iteration.cpp @@ -0,0 +1,76 @@ +/** + * File: iteration.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* for 循环 */ +int forLoop(int n) { + int res = 0; + // 循环求和 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + res += i; + } + return res; +} + +/* while 循环 */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 2, ..., n-1, n + while (i <= n) { + res += i; + i++; // 更新条件变量 + } + return res; +} + +/* while 循环(两次更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 初始化条件变量 + // 循环求和 1, 4, 10, ... + while (i <= n) { + res += i; + // 更新条件变量 + i++; + i *= 2; + } + return res; +} + +/* 双层 for 循环 */ +string nestedForLoop(int n) { + ostringstream res; + // 循环 i = 1, 2, ..., n-1, n + for (int i = 1; i <= n; ++i) { + // 循环 j = 1, 2, ..., n-1, n + for (int j = 1; j <= n; ++j) { + res << "(" << i << ", " << j << "), "; + } + } + return res.str(); +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + cout << "\nfor 循环的求和结果 res = " << res << endl; + + res = whileLoop(n); + cout << "\nwhile 循环的求和结果 res = " << res << endl; + + res = whileLoopII(n); + cout << "\nwhile 循环(两次更新)求和结果 res = " << res << endl; + + string resStr = nestedForLoop(n); + cout << "\n双层 for 循环的遍历结果 " << resStr << endl; + + return 0; +} diff --git a/chapter_computational_complexity/recursion.cpp b/chapter_computational_complexity/recursion.cpp new file mode 100644 index 0000000..3781556 --- /dev/null +++ b/chapter_computational_complexity/recursion.cpp @@ -0,0 +1,78 @@ +/** + * File: recursion.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 递归 */ +int recur(int n) { + // 终止条件 + if (n == 1) + return 1; + // 递:递归调用 + int res = recur(n - 1); + // 归:返回结果 + return n + res; +} + +/* 使用迭代模拟递归 */ +int forLoopRecur(int n) { + // 使用一个显式的栈来模拟系统调用栈 + stack stack; + int res = 0; + // 递:递归调用 + for (int i = n; i > 0; i--) { + // 通过“入栈操作”模拟“递” + stack.push(i); + } + // 归:返回结果 + while (!stack.empty()) { + // 通过“出栈操作”模拟“归” + res += stack.top(); + stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 尾递归 */ +int tailRecur(int n, int res) { + // 终止条件 + if (n == 0) + return res; + // 尾递归调用 + return tailRecur(n - 1, res + n); +} + +/* 斐波那契数列:递归 */ +int fib(int n) { + // 终止条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // 递归调用 f(n) = f(n-1) + f(n-2) + int res = fib(n - 1) + fib(n - 2); + // 返回结果 f(n) + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + cout << "\n递归函数的求和结果 res = " << res << endl; + + res = forLoopRecur(n); + cout << "\n使用迭代模拟递归求和结果 res = " << res << endl; + + res = tailRecur(n, 0); + cout << "\n尾递归函数的求和结果 res = " << res << endl; + + res = fib(n); + cout << "\n斐波那契数列的第 " << n << " 项为 " << res << endl; + + return 0; +} diff --git a/chapter_computational_complexity/space_complexity.cpp b/chapter_computational_complexity/space_complexity.cpp new file mode 100644 index 0000000..a41e7e1 --- /dev/null +++ b/chapter_computational_complexity/space_complexity.cpp @@ -0,0 +1,107 @@ +/** + * File: space_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 函数 */ +int func() { + // 执行某些操作 + return 0; +} + +/* 常数阶 */ +void constant(int n) { + // 常量、变量、对象占用 O(1) 空间 + const int a = 0; + int b = 0; + vector nums(10000); + ListNode node(0); + // 循环中的变量占用 O(1) 空间 + for (int i = 0; i < n; i++) { + int c = 0; + } + // 循环中的函数占用 O(1) 空间 + for (int i = 0; i < n; i++) { + func(); + } +} + +/* 线性阶 */ +void linear(int n) { + // 长度为 n 的数组占用 O(n) 空间 + vector nums(n); + // 长度为 n 的列表占用 O(n) 空间 + vector nodes; + for (int i = 0; i < n; i++) { + nodes.push_back(ListNode(i)); + } + // 长度为 n 的哈希表占用 O(n) 空间 + unordered_map map; + for (int i = 0; i < n; i++) { + map[i] = to_string(i); + } +} + +/* 线性阶(递归实现) */ +void linearRecur(int n) { + cout << "递归 n = " << n << endl; + if (n == 1) + return; + linearRecur(n - 1); +} + +/* 平方阶 */ +void quadratic(int n) { + // 二维列表占用 O(n^2) 空间 + vector> numMatrix; + for (int i = 0; i < n; i++) { + vector tmp; + for (int j = 0; j < n; j++) { + tmp.push_back(0); + } + numMatrix.push_back(tmp); + } +} + +/* 平方阶(递归实现) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + vector nums(n); + cout << "递归 n = " << n << " 中的 nums 长度 = " << nums.size() << endl; + return quadraticRecur(n - 1); +} + +/* 指数阶(建立满二叉树) */ +TreeNode *buildTree(int n) { + if (n == 0) + return nullptr; + TreeNode *root = new TreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +int main() { + int n = 5; + // 常数阶 + constant(n); + // 线性阶 + linear(n); + linearRecur(n); + // 平方阶 + quadratic(n); + quadraticRecur(n); + // 指数阶 + TreeNode *root = buildTree(n); + printTree(root); + + // 释放内存 + freeMemoryTree(root); + + return 0; +} diff --git a/chapter_computational_complexity/time_complexity.cpp b/chapter_computational_complexity/time_complexity.cpp new file mode 100644 index 0000000..845e62c --- /dev/null +++ b/chapter_computational_complexity/time_complexity.cpp @@ -0,0 +1,168 @@ +/** + * File: time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 常数阶 */ +int constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; +} + +/* 线性阶 */ +int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; +} + +/* 线性阶(遍历数组) */ +int arrayTraversal(vector &nums) { + int count = 0; + // 循环次数与数组长度成正比 + for (int num : nums) { + count++; + } + return count; +} + +/* 平方阶 */ +int quadratic(int n) { + int count = 0; + // 循环次数与数组长度成平方关系 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 平方阶(冒泡排序) */ +int bubbleSort(vector &nums) { + int count = 0; // 计数器 + // 外循环:未排序区间为 [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交换包含 3 个单元操作 + } + } + } + return count; +} + +/* 指数阶(循环实现) */ +int exponential(int n) { + int count = 0, base = 1; + // 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) { + for (int j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指数阶(递归实现) */ +int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 对数阶(循环实现) */ +int logarithmic(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; + // 从 1 个分裂出 n 个 + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +int main() { + // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 + int n = 8; + cout << "输入数据大小 n = " << n << endl; + + int count = constant(n); + cout << "常数阶的操作数量 = " << count << endl; + + count = linear(n); + cout << "线性阶的操作数量 = " << count << endl; + vector arr(n); + count = arrayTraversal(arr); + cout << "线性阶(遍历数组)的操作数量 = " << count << endl; + + count = quadratic(n); + cout << "平方阶的操作数量 = " << count << endl; + vector nums(n); + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + cout << "平方阶(冒泡排序)的操作数量 = " << count << endl; + + count = exponential(n); + cout << "指数阶(循环实现)的操作数量 = " << count << endl; + count = expRecur(n); + cout << "指数阶(递归实现)的操作数量 = " << count << endl; + + count = logarithmic((float)n); + cout << "对数阶(循环实现)的操作数量 = " << count << endl; + count = logRecur((float)n); + cout << "对数阶(递归实现)的操作数量 = " << count << endl; + + count = linearLogRecur((float)n); + cout << "线性对数阶(递归实现)的操作数量 = " << count << endl; + + count = factorialRecur(n); + cout << "阶乘阶(递归实现)的操作数量 = " << count << endl; + + return 0; +} diff --git a/chapter_computational_complexity/worst_best_time_complexity.cpp b/chapter_computational_complexity/worst_best_time_complexity.cpp new file mode 100644 index 0000000..f2ce76b --- /dev/null +++ b/chapter_computational_complexity/worst_best_time_complexity.cpp @@ -0,0 +1,45 @@ +/** + * File: worst_best_time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ +vector randomNumbers(int n) { + vector nums(n); + // 生成数组 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 使用系统时间生成随机种子 + unsigned seed = chrono::system_clock::now().time_since_epoch().count(); + // 随机打乱数组元素 + shuffle(nums.begin(), nums.end(), default_random_engine(seed)); + return nums; +} + +/* 查找数组 nums 中数字 1 所在索引 */ +int findOne(vector &nums) { + for (int i = 0; i < nums.size(); i++) { + // 当元素 1 在数组头部时,达到最佳时间复杂度 O(1) + // 当元素 1 在数组尾部时,达到最差时间复杂度 O(n) + if (nums[i] == 1) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + for (int i = 0; i < 1000; i++) { + int n = 100; + vector nums = randomNumbers(n); + int index = findOne(nums); + cout << "\n数组 [ 1, 2, ..., n ] 被打乱后 = "; + printVector(nums); + cout << "数字 1 的索引为 " << index << endl; + } + return 0; +} diff --git a/chapter_divide_and_conquer/CMakeLists.txt b/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 0000000..38dfff7 --- /dev/null +++ b/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.cpp) +add_executable(build_tree build_tree.cpp) +add_executable(hanota hanota.cpp) \ No newline at end of file diff --git a/chapter_divide_and_conquer/binary_search_recur.cpp b/chapter_divide_and_conquer/binary_search_recur.cpp new file mode 100644 index 0000000..5bb3b24 --- /dev/null +++ b/chapter_divide_and_conquer/binary_search_recur.cpp @@ -0,0 +1,46 @@ +/** + * File: binary_search_recur.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分查找:问题 f(i, j) */ +int dfs(vector &nums, int target, int i, int j) { + // 若区间为空,代表无目标元素,则返回 -1 + if (i > j) { + return -1; + } + // 计算中点索引 m + int m = (i + j) / 2; + if (nums[m] < target) { + // 递归子问题 f(m+1, j) + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 递归子问题 f(i, m-1) + return dfs(nums, target, i, m - 1); + } else { + // 找到目标元素,返回其索引 + return m; + } +} + +/* 二分查找 */ +int binarySearch(vector &nums, int target) { + int n = nums.size(); + // 求解问题 f(0, n-1) + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + // 二分查找(双闭区间) + int index = binarySearch(nums, target); + cout << "目标元素 6 的索引 = " << index << endl; + + return 0; +} \ No newline at end of file diff --git a/chapter_divide_and_conquer/build_tree.cpp b/chapter_divide_and_conquer/build_tree.cpp new file mode 100644 index 0000000..ca5389e --- /dev/null +++ b/chapter_divide_and_conquer/build_tree.cpp @@ -0,0 +1,51 @@ +/** + * File: build_tree.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 构建二叉树:分治 */ +TreeNode *dfs(vector &preorder, unordered_map &inorderMap, int i, int l, int r) { + // 子树区间为空时终止 + if (r - l < 0) + return NULL; + // 初始化根节点 + TreeNode *root = new TreeNode(preorder[i]); + // 查询 m ,从而划分左右子树 + int m = inorderMap[preorder[i]]; + // 子问题:构建左子树 + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 子问题:构建右子树 + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 返回根节点 + return root; +} + +/* 构建二叉树 */ +TreeNode *buildTree(vector &preorder, vector &inorder) { + // 初始化哈希表,存储 inorder 元素到索引的映射 + unordered_map inorderMap; + for (int i = 0; i < inorder.size(); i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1); + return root; +} + +/* Driver Code */ +int main() { + vector preorder = {3, 9, 2, 1, 7}; + vector inorder = {9, 3, 1, 2, 7}; + cout << "前序遍历 = "; + printVector(preorder); + cout << "中序遍历 = "; + printVector(inorder); + + TreeNode *root = buildTree(preorder, inorder); + cout << "构建的二叉树为:\n"; + printTree(root); + + return 0; +} diff --git a/chapter_divide_and_conquer/hanota.cpp b/chapter_divide_and_conquer/hanota.cpp new file mode 100644 index 0000000..7a2bc5a --- /dev/null +++ b/chapter_divide_and_conquer/hanota.cpp @@ -0,0 +1,66 @@ +/** + * File: hanota.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 移动一个圆盘 */ +void move(vector &src, vector &tar) { + // 从 src 顶部拿出一个圆盘 + int pan = src.back(); + src.pop_back(); + // 将圆盘放入 tar 顶部 + tar.push_back(pan); +} + +/* 求解汉诺塔问题 f(i) */ +void dfs(int i, vector &src, vector &buf, vector &tar) { + // 若 src 只剩下一个圆盘,则直接将其移到 tar + if (i == 1) { + move(src, tar); + return; + } + // 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf + dfs(i - 1, src, tar, buf); + // 子问题 f(1) :将 src 剩余一个圆盘移到 tar + move(src, tar); + // 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar + dfs(i - 1, buf, src, tar); +} + +/* 求解汉诺塔问题 */ +void solveHanota(vector &A, vector &B, vector &C) { + int n = A.size(); + // 将 A 顶部 n 个圆盘借助 B 移到 C + dfs(n, A, B, C); +} + +/* Driver Code */ +int main() { + // 列表尾部是柱子顶部 + vector A = {5, 4, 3, 2, 1}; + vector B = {}; + vector C = {}; + + cout << "初始状态下:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + solveHanota(A, B, C); + + cout << "圆盘移动完成后:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + return 0; +} diff --git a/chapter_dynamic_programming/CMakeLists.txt b/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 0000000..ed18545 --- /dev/null +++ b/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) +add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) +add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) +add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) +add_executable(min_path_sum min_path_sum.cpp) +add_executable(unbounded_knapsack unbounded_knapsack.cpp) +add_executable(coin_change coin_change.cpp) +add_executable(coin_change_ii coin_change_ii.cpp) +add_executable(edit_distance edit_distance.cpp) \ No newline at end of file diff --git a/chapter_dynamic_programming/climbing_stairs_backtrack.cpp b/chapter_dynamic_programming/climbing_stairs_backtrack.cpp new file mode 100644 index 0000000..4080f28 --- /dev/null +++ b/chapter_dynamic_programming/climbing_stairs_backtrack.cpp @@ -0,0 +1,43 @@ + +/** + * File: climbing_stairs_backtrack.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 回溯 */ +void backtrack(vector &choices, int state, int n, vector &res) { + // 当爬到第 n 阶时,方案数量加 1 + if (state == n) + res[0]++; + // 遍历所有选择 + for (auto &choice : choices) { + // 剪枝:不允许越过第 n 阶 + if (state + choice > n) + continue; + // 尝试:做出选择,更新状态 + backtrack(choices, state + choice, n, res); + // 回退 + } +} + +/* 爬楼梯:回溯 */ +int climbingStairsBacktrack(int n) { + vector choices = {1, 2}; // 可选择向上爬 1 阶或 2 阶 + int state = 0; // 从第 0 阶开始爬 + vector res = {0}; // 使用 res[0] 记录方案数量 + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + return 0; +} diff --git a/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp b/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp new file mode 100644 index 0000000..eb5fc75 --- /dev/null +++ b/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp @@ -0,0 +1,37 @@ +/** + * File: climbing_stairs_constraint_dp.cpp + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 带约束爬楼梯:动态规划 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 初始化 dp 表,用于存储子问题的解 + vector> dp(n + 1, vector(3, 0)); + // 初始状态:预设最小子问题的解 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + return 0; +} diff --git a/chapter_dynamic_programming/climbing_stairs_dfs.cpp b/chapter_dynamic_programming/climbing_stairs_dfs.cpp new file mode 100644 index 0000000..ffcff18 --- /dev/null +++ b/chapter_dynamic_programming/climbing_stairs_dfs.cpp @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 搜索 */ +int dfs(int i) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 爬楼梯:搜索 */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + return 0; +} diff --git a/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp b/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp new file mode 100644 index 0000000..7ae62cb --- /dev/null +++ b/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_dfs_mem.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 记忆化搜索 */ +int dfs(int i, vector &mem) { + // 已知 dp[1] 和 dp[2] ,返回之 + if (i == 1 || i == 2) + return i; + // 若存在记录 dp[i] ,则直接返回之 + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // 记录 dp[i] + mem[i] = count; + return count; +} + +/* 爬楼梯:记忆化搜索 */ +int climbingStairsDFSMem(int n) { + // mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 + vector mem(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + return 0; +} diff --git a/chapter_dynamic_programming/climbing_stairs_dp.cpp b/chapter_dynamic_programming/climbing_stairs_dp.cpp new file mode 100644 index 0000000..ce822e4 --- /dev/null +++ b/chapter_dynamic_programming/climbing_stairs_dp.cpp @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 爬楼梯:动态规划 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 初始化 dp 表,用于存储子问题的解 + vector dp(n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = 1; + dp[2] = 2; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 爬楼梯:空间优化后的动态规划 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + res = climbingStairsDPComp(n); + cout << "爬 " << n << " 阶楼梯共有 " << res << " 种方案" << endl; + + return 0; +} diff --git a/chapter_dynamic_programming/coin_change.cpp b/chapter_dynamic_programming/coin_change.cpp new file mode 100644 index 0000000..c59b83a --- /dev/null +++ b/chapter_dynamic_programming/coin_change.cpp @@ -0,0 +1,70 @@ +/** + * File: coin_change.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零钱兑换:动态规划 */ +int coinChangeDP(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // 初始化 dp 表 + vector> dp(n + 1, vector(amt + 1, 0)); + // 状态转移:首行首列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状态转移:其余行和列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; +} + +/* 零钱兑换:空间优化后的动态规划 */ +int coinChangeDPComp(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // 初始化 dp 表 + vector dp(amt + 1, MAX); + dp[0] = 0; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案的较小值 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 4; + + // 动态规划 + int res = coinChangeDP(coins, amt); + cout << "凑到目标金额所需的最少硬币数量为 " << res << endl; + + // 空间优化后的动态规划 + res = coinChangeDPComp(coins, amt); + cout << "凑到目标金额所需的最少硬币数量为 " << res << endl; + + return 0; +} diff --git a/chapter_dynamic_programming/coin_change_ii.cpp b/chapter_dynamic_programming/coin_change_ii.cpp new file mode 100644 index 0000000..7364074 --- /dev/null +++ b/chapter_dynamic_programming/coin_change_ii.cpp @@ -0,0 +1,68 @@ +/** + * File: coin_change_ii.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零钱兑换 II:动态规划 */ +int coinChangeIIDP(vector &coins, int amt) { + int n = coins.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(amt + 1, 0)); + // 初始化首列 + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[i][a] = dp[i - 1][a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* 零钱兑换 II:空间优化后的动态规划 */ +int coinChangeIIDPComp(vector &coins, int amt) { + int n = coins.size(); + // 初始化 dp 表 + vector dp(amt + 1, 0); + dp[0] = 1; + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 若超过目标金额,则不选硬币 i + dp[a] = dp[a]; + } else { + // 不选和选硬币 i 这两种方案之和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver code */ +int main() { + vector coins = {1, 2, 5}; + int amt = 5; + + // 动态规划 + int res = coinChangeIIDP(coins, amt); + cout << "凑出目标金额的硬币组合数量为 " << res << endl; + + // 空间优化后的动态规划 + res = coinChangeIIDPComp(coins, amt); + cout << "凑出目标金额的硬币组合数量为 " << res << endl; + + return 0; +} diff --git a/chapter_dynamic_programming/edit_distance.cpp b/chapter_dynamic_programming/edit_distance.cpp new file mode 100644 index 0000000..21a04b0 --- /dev/null +++ b/chapter_dynamic_programming/edit_distance.cpp @@ -0,0 +1,136 @@ +/** + * File: edit_distance.cpp + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 编辑距离:暴力搜索 */ +int editDistanceDFS(string s, string t, int i, int j) { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 为空,则返回 t 长度 + if (i == 0) + return j; + // 若 t 为空,则返回 s 长度 + if (j == 0) + return i; + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) + return editDistanceDFS(s, t, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 返回最少编辑步数 + return min(min(insert, del), replace) + 1; +} + +/* 编辑距离:记忆化搜索 */ +int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { + // 若 s 和 t 都为空,则返回 0 + if (i == 0 && j == 0) + return 0; + // 若 s 为空,则返回 t 长度 + if (i == 0) + return j; + // 若 t 为空,则返回 s 长度 + if (j == 0) + return i; + // 若已有记录,则直接返回之 + if (mem[i][j] != -1) + return mem[i][j]; + // 若两字符相等,则直接跳过此两字符 + if (s[i - 1] == t[j - 1]) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 记录并返回最少编辑步数 + mem[i][j] = min(min(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 编辑距离:动态规划 */ +int editDistanceDP(string s, string t) { + int n = s.length(), m = t.length(); + vector> dp(n + 1, vector(m + 1, 0)); + // 状态转移:首行首列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状态转移:其余行和列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 编辑距离:空间优化后的动态规划 */ +int editDistanceDPComp(string s, string t) { + int n = s.length(), m = t.length(); + vector dp(m + 1, 0); + // 状态转移:首行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 状态转移:其余行 + for (int i = 1; i <= n; i++) { + // 状态转移:首列 + int leftup = dp[0]; // 暂存 dp[i-1, j-1] + dp[0] = i; + // 状态转移:其余列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 若两字符相等,则直接跳过此两字符 + dp[j] = leftup; + } else { + // 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 更新为下一轮的 dp[i-1, j-1] + } + } + return dp[m]; +} + +/* Driver Code */ +int main() { + string s = "bag"; + string t = "pack"; + int n = s.length(), m = t.length(); + + // 暴力搜索 + int res = editDistanceDFS(s, t, n, m); + cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; + + // 记忆化搜索 + vector> mem(n + 1, vector(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; + + // 动态规划 + res = editDistanceDP(s, t); + cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; + + // 空间优化后的动态规划 + res = editDistanceDPComp(s, t); + cout << "将 " << s << " 更改为 " << t << " 最少需要编辑 " << res << " 步\n"; + + return 0; +} diff --git a/chapter_dynamic_programming/knapsack.cpp b/chapter_dynamic_programming/knapsack.cpp new file mode 100644 index 0000000..a0848c8 --- /dev/null +++ b/chapter_dynamic_programming/knapsack.cpp @@ -0,0 +1,109 @@ +#include +#include +#include + +using namespace std; + +/* 0-1 背包:暴力搜索 */ +int knapsackDFS(vector &wgt, vector &val, int i, int c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 返回两种方案中价值更大的那一个 + return max(no, yes); +} + +/* 0-1 背包:记忆化搜索 */ +int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { + // 若已选完所有物品或背包无剩余容量,则返回价值 0 + if (i == 0 || c == 0) { + return 0; + } + // 若已有记录,则直接返回 + if (mem[i][c] != -1) { + return mem[i][c]; + } + // 若超过背包容量,则只能选择不放入背包 + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 计算不放入和放入物品 i 的最大价值 + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 记录并返回两种方案中价值更大的那一个 + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* 0-1 背包:动态规划 */ +int knapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(cap + 1, 0)); + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 0-1 背包:空间优化后的动态规划 */ +int knapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector dp(cap + 1, 0); + // 状态转移 + for (int i = 1; i <= n; i++) { + // 倒序遍历 + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + int n = wgt.size(); + + // 暴力搜索 + int res = knapsackDFS(wgt, val, n, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + // 记忆化搜索 + vector> mem(n + 1, vector(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + // 动态规划 + res = knapsackDP(wgt, val, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + // 空间优化后的动态规划 + res = knapsackDPComp(wgt, val, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + return 0; +} diff --git a/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp b/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp new file mode 100644 index 0000000..7736940 --- /dev/null +++ b/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 爬楼梯最小代价:动态规划 */ +int minCostClimbingStairsDP(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + // 初始化 dp 表,用于存储子问题的解 + vector dp(n + 1); + // 初始状态:预设最小子问题的解 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状态转移:从较小子问题逐步求解较大子问题 + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 爬楼梯最小代价:空间优化后的动态规划 */ +int minCostClimbingStairsDPComp(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + cout << "输入楼梯的代价列表为 "; + printVector(cost); + + int res = minCostClimbingStairsDP(cost); + cout << "爬完楼梯的最低代价为 " << res << endl; + + res = minCostClimbingStairsDPComp(cost); + cout << "爬完楼梯的最低代价为 " << res << endl; + + return 0; +} diff --git a/chapter_dynamic_programming/min_path_sum.cpp b/chapter_dynamic_programming/min_path_sum.cpp new file mode 100644 index 0000000..a59f6bc --- /dev/null +++ b/chapter_dynamic_programming/min_path_sum.cpp @@ -0,0 +1,116 @@ +/** + * File: min_path_sum.cpp + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最小路径和:暴力搜索 */ +int minPathSumDFS(vector> &grid, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 返回从左上角到 (i, j) 的最小路径代价 + return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; +} + +/* 最小路径和:记忆化搜索 */ +int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { + // 若为左上角单元格,则终止搜索 + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 若行列索引越界,则返回 +∞ 代价 + if (i < 0 || j < 0) { + return INT_MAX; + } + // 若已有记录,则直接返回 + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左边和上边单元格的最小路径代价 + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 记录并返回左上角到 (i, j) 的最小路径代价 + mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* 最小路径和:动态规划 */ +int minPathSumDP(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // 初始化 dp 表 + vector> dp(n, vector(m)); + dp[0][0] = grid[0][0]; + // 状态转移:首行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状态转移:首列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状态转移:其余行和列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小路径和:空间优化后的动态规划 */ +int minPathSumDPComp(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // 初始化 dp 表 + vector dp(m); + // 状态转移:首行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状态转移:其余行 + for (int i = 1; i < n; i++) { + // 状态转移:首列 + dp[0] = dp[0] + grid[i][0]; + // 状态转移:其余列 + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +int main() { + vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = grid.size(), m = grid[0].size(); + + // 暴力搜索 + int res = minPathSumDFS(grid, n - 1, m - 1); + cout << "从左上角到右下角的最小路径和为 " << res << endl; + + // 记忆化搜索 + vector> mem(n, vector(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + cout << "从左上角到右下角的最小路径和为 " << res << endl; + + // 动态规划 + res = minPathSumDP(grid); + cout << "从左上角到右下角的最小路径和为 " << res << endl; + + // 空间优化后的动态规划 + res = minPathSumDPComp(grid); + cout << "从左上角到右下角的最小路径和为 " << res << endl; + + return 0; +} diff --git a/chapter_dynamic_programming/unbounded_knapsack.cpp b/chapter_dynamic_programming/unbounded_knapsack.cpp new file mode 100644 index 0000000..9e1b6dd --- /dev/null +++ b/chapter_dynamic_programming/unbounded_knapsack.cpp @@ -0,0 +1,64 @@ +/** + * File: unbounded_knapsack.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 完全背包:动态规划 */ +int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector> dp(n + 1, vector(cap + 1, 0)); + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[i][c] = dp[i - 1][c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 完全背包:空间优化后的动态规划 */ +int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 初始化 dp 表 + vector dp(cap + 1, 0); + // 状态转移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // 若超过背包容量,则不选物品 i + dp[c] = dp[c]; + } else { + // 不选和选物品 i 这两种方案的较大值 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver code */ +int main() { + vector wgt = {1, 2, 3}; + vector val = {5, 11, 15}; + int cap = 4; + + // 动态规划 + int res = unboundedKnapsackDP(wgt, val, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + // 空间优化后的动态规划 + res = unboundedKnapsackDPComp(wgt, val, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + return 0; +} diff --git a/chapter_graph/CMakeLists.txt b/chapter_graph/CMakeLists.txt new file mode 100644 index 0000000..4a56ce3 --- /dev/null +++ b/chapter_graph/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(graph_bfs graph_bfs.cpp) +add_executable(graph_dfs graph_dfs.cpp) +# add_executable(graph_adjacency_list graph_adjacency_list.cpp) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) +add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) diff --git a/chapter_graph/graph_adjacency_list.cpp b/chapter_graph/graph_adjacency_list.cpp new file mode 100644 index 0000000..e23026f --- /dev/null +++ b/chapter_graph/graph_adjacency_list.cpp @@ -0,0 +1,90 @@ +/** + * File: graph_adjacency_list.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基于邻接表实现的无向图类 */ +class GraphAdjList { + public: + // 邻接表,key:顶点,value:该顶点的所有邻接顶点 + unordered_map> adjList; + + /* 在 vector 中删除指定节点 */ + void remove(vector &vec, Vertex *vet) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == vet) { + vec.erase(vec.begin() + i); + break; + } + } + } + + /* 构造方法 */ + GraphAdjList(const vector> &edges) { + // 添加所有顶点和边 + for (const vector &edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int size() { + return adjList.size(); + } + + /* 添加边 */ + void addEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("不存在顶点"); + // 添加边 vet1 - vet2 + adjList[vet1].push_back(vet2); + adjList[vet2].push_back(vet1); + } + + /* 删除边 */ + void removeEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("不存在顶点"); + // 删除边 vet1 - vet2 + remove(adjList[vet1], vet2); + remove(adjList[vet2], vet1); + } + + /* 添加顶点 */ + void addVertex(Vertex *vet) { + if (adjList.count(vet)) + return; + // 在邻接表中添加一个新链表 + adjList[vet] = vector(); + } + + /* 删除顶点 */ + void removeVertex(Vertex *vet) { + if (!adjList.count(vet)) + throw invalid_argument("不存在顶点"); + // 在邻接表中删除顶点 vet 对应的链表 + adjList.erase(vet); + // 遍历其他顶点的链表,删除所有包含 vet 的边 + for (auto &adj : adjList) { + remove(adj.second, vet); + } + } + + /* 打印邻接表 */ + void print() { + cout << "邻接表 =" << endl; + for (auto &adj : adjList) { + const auto &key = adj.first; + const auto &vec = adj.second; + cout << key->val << ": "; + printVector(vetsToVals(vec)); + } + } +}; + +// 测试样例请见 graph_adjacency_list_test.cpp diff --git a/chapter_graph/graph_adjacency_list_test.cpp b/chapter_graph/graph_adjacency_list_test.cpp new file mode 100644 index 0000000..39ea794 --- /dev/null +++ b/chapter_graph/graph_adjacency_list_test.cpp @@ -0,0 +1,49 @@ +/** + * File: graph_adjacency_list_test.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "./graph_adjacency_list.cpp" + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + vector v = valsToVets(vector{1, 3, 2, 5, 4}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + GraphAdjList graph(edges); + cout << "\n初始化后,图为" << endl; + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 即 v[0], v[2] + graph.addEdge(v[0], v[2]); + cout << "\n添加边 1-2 后,图为" << endl; + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 即 v[0], v[1] + graph.removeEdge(v[0], v[1]); + cout << "\n删除边 1-3 后,图为" << endl; + graph.print(); + + /* 添加顶点 */ + Vertex *v5 = new Vertex(6); + graph.addVertex(v5); + cout << "\n添加顶点 6 后,图为" << endl; + graph.print(); + + /* 删除顶点 */ + // 顶点 3 即 v[1] + graph.removeVertex(v[1]); + cout << "\n删除顶点 3 后,图为" << endl; + graph.print(); + + // 释放内存 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/chapter_graph/graph_adjacency_matrix.cpp b/chapter_graph/graph_adjacency_matrix.cpp new file mode 100644 index 0000000..91d51a6 --- /dev/null +++ b/chapter_graph/graph_adjacency_matrix.cpp @@ -0,0 +1,127 @@ +/** + * File: graph_adjacency_matrix.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* 基于邻接矩阵实现的无向图类 */ +class GraphAdjMat { + vector vertices; // 顶点列表,元素代表“顶点值”,索引代表“顶点索引” + vector> adjMat; // 邻接矩阵,行列索引对应“顶点索引” + + public: + /* 构造方法 */ + GraphAdjMat(const vector &vertices, const vector> &edges) { + // 添加顶点 + for (int val : vertices) { + addVertex(val); + } + // 添加边 + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + for (const vector &edge : edges) { + addEdge(edge[0], edge[1]); + } + } + + /* 获取顶点数量 */ + int size() const { + return vertices.size(); + } + + /* 添加顶点 */ + void addVertex(int val) { + int n = size(); + // 向顶点列表中添加新顶点的值 + vertices.push_back(val); + // 在邻接矩阵中添加一行 + adjMat.emplace_back(vector(n, 0)); + // 在邻接矩阵中添加一列 + for (vector &row : adjMat) { + row.push_back(0); + } + } + + /* 删除顶点 */ + void removeVertex(int index) { + if (index >= size()) { + throw out_of_range("顶点不存在"); + } + // 在顶点列表中移除索引 index 的顶点 + vertices.erase(vertices.begin() + index); + // 在邻接矩阵中删除索引 index 的行 + adjMat.erase(adjMat.begin() + index); + // 在邻接矩阵中删除索引 index 的列 + for (vector &row : adjMat) { + row.erase(row.begin() + index); + } + } + + /* 添加边 */ + // 参数 i, j 对应 vertices 元素索引 + void addEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("顶点不存在"); + } + // 在无向图中,邻接矩阵关于主对角线对称,即满足 (i, j) == (j, i) + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 删除边 */ + // 参数 i, j 对应 vertices 元素索引 + void removeEdge(int i, int j) { + // 索引越界与相等处理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw out_of_range("顶点不存在"); + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 打印邻接矩阵 */ + void print() { + cout << "顶点列表 = "; + printVector(vertices); + cout << "邻接矩阵 =" << endl; + printVectorMatrix(adjMat); + } +}; + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + // 请注意,edges 元素代表顶点索引,即对应 vertices 元素索引 + vector vertices = {1, 3, 2, 5, 4}; + vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + GraphAdjMat graph(vertices, edges); + cout << "\n初始化后,图为" << endl; + graph.print(); + + /* 添加边 */ + // 顶点 1, 2 的索引分别为 0, 2 + graph.addEdge(0, 2); + cout << "\n添加边 1-2 后,图为" << endl; + graph.print(); + + /* 删除边 */ + // 顶点 1, 3 的索引分别为 0, 1 + graph.removeEdge(0, 1); + cout << "\n删除边 1-3 后,图为" << endl; + graph.print(); + + /* 添加顶点 */ + graph.addVertex(6); + cout << "\n添加顶点 6 后,图为" << endl; + graph.print(); + + /* 删除顶点 */ + // 顶点 3 的索引为 1 + graph.removeVertex(1); + cout << "\n删除顶点 3 后,图为" << endl; + graph.print(); + + return 0; +} diff --git a/chapter_graph/graph_bfs.cpp b/chapter_graph/graph_bfs.cpp new file mode 100644 index 0000000..a4efd8a --- /dev/null +++ b/chapter_graph/graph_bfs.cpp @@ -0,0 +1,59 @@ +/** + * File: graph_bfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* 广度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +vector graphBFS(GraphAdjList &graph, Vertex *startVet) { + // 顶点遍历序列 + vector res; + // 哈希表,用于记录已被访问过的顶点 + unordered_set visited = {startVet}; + // 队列用于实现 BFS + queue que; + que.push(startVet); + // 以顶点 vet 为起点,循环直至访问完所有顶点 + while (!que.empty()) { + Vertex *vet = que.front(); + que.pop(); // 队首顶点出队 + res.push_back(vet); // 记录访问顶点 + // 遍历该顶点的所有邻接顶点 + for (auto adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳过已被访问的顶点 + que.push(adjVet); // 只入队未访问的顶点 + visited.emplace(adjVet); // 标记该顶点已被访问 + } + } + // 返回顶点遍历序列 + return res; +} + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + vector v = valsToVets({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, + {v[2], v[5]}, {v[3], v[4]}, {v[3], v[6]}, {v[4], v[5]}, + {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; + GraphAdjList graph(edges); + cout << "\n初始化后,图为\\n"; + graph.print(); + + /* 广度优先遍历 */ + vector res = graphBFS(graph, v[0]); + cout << "\n广度优先遍历(BFS)顶点序列为" << endl; + printVector(vetsToVals(res)); + + // 释放内存 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/chapter_graph/graph_dfs.cpp b/chapter_graph/graph_dfs.cpp new file mode 100644 index 0000000..e1c5c6f --- /dev/null +++ b/chapter_graph/graph_dfs.cpp @@ -0,0 +1,55 @@ +/** + * File: graph_dfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* 深度优先遍历辅助函数 */ +void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) { + res.push_back(vet); // 记录访问顶点 + visited.emplace(vet); // 标记该顶点已被访问 + // 遍历该顶点的所有邻接顶点 + for (Vertex *adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // 跳过已被访问的顶点 + // 递归访问邻接顶点 + dfs(graph, visited, res, adjVet); + } +} + +/* 深度优先遍历 */ +// 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 +vector graphDFS(GraphAdjList &graph, Vertex *startVet) { + // 顶点遍历序列 + vector res; + // 哈希表,用于记录已被访问过的顶点 + unordered_set visited; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +int main() { + /* 初始化无向图 */ + vector v = valsToVets(vector{0, 1, 2, 3, 4, 5, 6}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; + GraphAdjList graph(edges); + cout << "\n初始化后,图为" << endl; + graph.print(); + + /* 深度优先遍历 */ + vector res = graphDFS(graph, v[0]); + cout << "\n深度优先遍历(DFS)顶点序列为" << endl; + printVector(vetsToVals(res)); + + // 释放内存 + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/chapter_greedy/CMakeLists.txt b/chapter_greedy/CMakeLists.txt new file mode 100644 index 0000000..9178866 --- /dev/null +++ b/chapter_greedy/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(coin_change_greedy coin_change_greedy.cpp) +add_executable(fractional_knapsack fractional_knapsack.cpp) +add_executable(max_capacity max_capacity.cpp) \ No newline at end of file diff --git a/chapter_greedy/coin_change_greedy.cpp b/chapter_greedy/coin_change_greedy.cpp new file mode 100644 index 0000000..5f1db7c --- /dev/null +++ b/chapter_greedy/coin_change_greedy.cpp @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 零钱兑换:贪心 */ +int coinChangeGreedy(vector &coins, int amt) { + // 假设 coins 列表有序 + int i = coins.size() - 1; + int count = 0; + // 循环进行贪心选择,直到无剩余金额 + while (amt > 0) { + // 找到小于且最接近剩余金额的硬币 + while (i > 0 && coins[i] > amt) { + i--; + } + // 选择 coins[i] + amt -= coins[i]; + count++; + } + // 若未找到可行方案,则返回 -1 + return amt == 0 ? count : -1; +} + +/* Driver Code */ +int main() { + // 贪心:能够保证找到全局最优解 + vector coins = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "凑到 " << amt << " 所需的最少硬币数量为 " << res << endl; + + // 贪心:无法保证找到全局最优解 + coins = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "凑到 " << amt << " 所需的最少硬币数量为 " << res << endl; + cout << "实际上需要的最少数量为 3 ,即 20 + 20 + 20" << endl; + + // 贪心:无法保证找到全局最优解 + coins = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins, amt); + cout << "\ncoins = "; + printVector(coins); + cout << "amt = " << amt << endl; + cout << "凑到 " << amt << " 所需的最少硬币数量为 " << res << endl; + cout << "实际上需要的最少数量为 2 ,即 49 + 49" << endl; + + return 0; +} diff --git a/chapter_greedy/fractional_knapsack.cpp b/chapter_greedy/fractional_knapsack.cpp new file mode 100644 index 0000000..a9cdc4f --- /dev/null +++ b/chapter_greedy/fractional_knapsack.cpp @@ -0,0 +1,56 @@ +/** + * File: fractional_knapsack.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 物品 */ +class Item { + public: + int w; // 物品重量 + int v; // 物品价值 + + Item(int w, int v) : w(w), v(v) { + } +}; + +/* 分数背包:贪心 */ +double fractionalKnapsack(vector &wgt, vector &val, int cap) { + // 创建物品列表,包含两个属性:重量、价值 + vector items; + for (int i = 0; i < wgt.size(); i++) { + items.push_back(Item(wgt[i], val[i])); + } + // 按照单位价值 item.v / item.w 从高到低进行排序 + sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; }); + // 循环贪心选择 + double res = 0; + for (auto &item : items) { + if (item.w <= cap) { + // 若剩余容量充足,则将当前物品整个装进背包 + res += item.v; + cap -= item.w; + } else { + // 若剩余容量不足,则将当前物品的一部分装进背包 + res += (double)item.v / item.w * cap; + // 已无剩余容量,因此跳出循环 + break; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector wgt = {10, 20, 30, 40, 50}; + vector val = {50, 120, 150, 210, 240}; + int cap = 50; + + // 贪心算法 + double res = fractionalKnapsack(wgt, val, cap); + cout << "不超过背包容量的最大物品价值为 " << res << endl; + + return 0; +} diff --git a/chapter_greedy/max_capacity.cpp b/chapter_greedy/max_capacity.cpp new file mode 100644 index 0000000..eff2f6c --- /dev/null +++ b/chapter_greedy/max_capacity.cpp @@ -0,0 +1,39 @@ +/** + * File: max_capacity.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最大容量:贪心 */ +int maxCapacity(vector &ht) { + // 初始化 i, j,使其分列数组两端 + int i = 0, j = ht.size() - 1; + // 初始最大容量为 0 + int res = 0; + // 循环贪心选择,直至两板相遇 + while (i < j) { + // 更新最大容量 + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // 向内移动短板 + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main() { + vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; + + // 贪心算法 + int res = maxCapacity(ht); + cout << "最大容量为 " << res << endl; + + return 0; +} diff --git a/chapter_greedy/max_product_cutting.cpp b/chapter_greedy/max_product_cutting.cpp new file mode 100644 index 0000000..880d828 --- /dev/null +++ b/chapter_greedy/max_product_cutting.cpp @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最大切分乘积:贪心 */ +int maxProductCutting(int n) { + // 当 n <= 3 时,必须切分出一个 1 + if (n <= 3) { + return 1 * (n - 1); + } + // 贪心地切分出 3 ,a 为 3 的个数,b 为余数 + int a = n / 3; + int b = n % 3; + if (b == 1) { + // 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 + return (int)pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 当余数为 2 时,不做处理 + return (int)pow(3, a) * 2; + } + // 当余数为 0 时,不做处理 + return (int)pow(3, a); +} + +/* Driver Code */ +int main() { + int n = 58; + + // 贪心算法 + int res = maxProductCutting(n); + cout << "最大切分乘积为" << res << endl; + + return 0; +} diff --git a/chapter_hashing/CMakeLists.txt b/chapter_hashing/CMakeLists.txt new file mode 100644 index 0000000..6b583ef --- /dev/null +++ b/chapter_hashing/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(hash_map hash_map.cpp) +add_executable(array_hash_map_test array_hash_map_test.cpp) +add_executable(hash_map_chaining hash_map_chaining.cpp) +add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) +add_executable(simple_hash simple_hash.cpp) +add_executable(built_in_hash built_in_hash.cpp) \ No newline at end of file diff --git a/chapter_hashing/array_hash_map.cpp b/chapter_hashing/array_hash_map.cpp new file mode 100644 index 0000000..96a1a14 --- /dev/null +++ b/chapter_hashing/array_hash_map.cpp @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* 键值对 */ +struct Pair { + public: + int key; + string val; + Pair(int key, string val) { + this->key = key; + this->val = val; + } +}; + +/* 基于数组实现的哈希表 */ +class ArrayHashMap { + private: + vector buckets; + + public: + ArrayHashMap() { + // 初始化数组,包含 100 个桶 + buckets = vector(100); + } + + ~ArrayHashMap() { + // 释放内存 + for (const auto &bucket : buckets) { + delete bucket; + } + buckets.clear(); + } + + /* 哈希函数 */ + int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* 查询操作 */ + string get(int key) { + int index = hashFunc(key); + Pair *pair = buckets[index]; + if (pair == nullptr) + return ""; + return pair->val; + } + + /* 添加操作 */ + void put(int key, string val) { + Pair *pair = new Pair(key, val); + int index = hashFunc(key); + buckets[index] = pair; + } + + /* 删除操作 */ + void remove(int key) { + int index = hashFunc(key); + // 释放内存并置为 nullptr + delete buckets[index]; + buckets[index] = nullptr; + } + + /* 获取所有键值对 */ + vector pairSet() { + vector pairSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + pairSet.push_back(pair); + } + } + return pairSet; + } + + /* 获取所有键 */ + vector keySet() { + vector keySet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + keySet.push_back(pair->key); + } + } + return keySet; + } + + /* 获取所有值 */ + vector valueSet() { + vector valueSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + valueSet.push_back(pair->val); + } + } + return valueSet; + } + + /* 打印哈希表 */ + void print() { + for (Pair *kv : pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + } +}; + +// 测试样例请见 array_hash_map_test.cpp diff --git a/chapter_hashing/array_hash_map_test.cpp b/chapter_hashing/array_hash_map_test.cpp new file mode 100644 index 0000000..4b3ccc8 --- /dev/null +++ b/chapter_hashing/array_hash_map_test.cpp @@ -0,0 +1,52 @@ +/** + * File: array_hash_map_test.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "./array_hash_map.cpp" + +/* Driver Code */ +int main() { + /* 初始化哈希表 */ + ArrayHashMap map = ArrayHashMap(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); + cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; + map.print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + string name = map.get(15937); + cout << "\n输入学号 15937 ,查询到姓名 " << name << endl; + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583); + cout << "\n删除 10583 后,哈希表为\nKey -> Value" << endl; + map.print(); + + /* 遍历哈希表 */ + cout << "\n遍历键值对 Key->Value" << endl; + for (auto kv : map.pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + + cout << "\n单独遍历键 Key" << endl; + for (auto key : map.keySet()) { + cout << key << endl; + } + + cout << "\n单独遍历值 Value" << endl; + for (auto val : map.valueSet()) { + cout << val << endl; + } + + return 0; +} diff --git a/chapter_hashing/built_in_hash.cpp b/chapter_hashing/built_in_hash.cpp new file mode 100644 index 0000000..da3f90c --- /dev/null +++ b/chapter_hashing/built_in_hash.cpp @@ -0,0 +1,29 @@ +/** + * File: built_in_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + int num = 3; + size_t hashNum = hash()(num); + cout << "整数 " << num << " 的哈希值为 " << hashNum << "\n"; + + bool bol = true; + size_t hashBol = hash()(bol); + cout << "布尔量 " << bol << " 的哈希值为 " << hashBol << "\n"; + + double dec = 3.14159; + size_t hashDec = hash()(dec); + cout << "小数 " << dec << " 的哈希值为 " << hashDec << "\n"; + + string str = "Hello 算法"; + size_t hashStr = hash()(str); + cout << "字符串 " << str << " 的哈希值为 " << hashStr << "\n"; + + // 在 C++ 中,内置 std:hash() 仅提供基本数据类型的哈希值计算 + // 数组、对象的哈希值计算需要自行实现 +} diff --git a/chapter_hashing/hash_map.cpp b/chapter_hashing/hash_map.cpp new file mode 100644 index 0000000..8875032 --- /dev/null +++ b/chapter_hashing/hash_map.cpp @@ -0,0 +1,46 @@ +/** + * File: hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化哈希表 */ + unordered_map map; + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map[12836] = "小哈"; + map[15937] = "小啰"; + map[16750] = "小算"; + map[13276] = "小法"; + map[10583] = "小鸭"; + cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; + printHashMap(map); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + string name = map[15937]; + cout << "\n输入学号 15937 ,查询到姓名 " << name << endl; + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.erase(10583); + cout << "\n删除 10583 后,哈希表为\nKey -> Value" << endl; + printHashMap(map); + + /* 遍历哈希表 */ + cout << "\n遍历键值对 Key->Value" << endl; + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << endl; + } + cout << "\n使用迭代器遍历 Key->Value" << endl; + for (auto iter = map.begin(); iter != map.end(); iter++) { + cout << iter->first << "->" << iter->second << endl; + } + + return 0; +} diff --git a/chapter_hashing/hash_map_chaining.cpp b/chapter_hashing/hash_map_chaining.cpp new file mode 100644 index 0000000..4fc373e --- /dev/null +++ b/chapter_hashing/hash_map_chaining.cpp @@ -0,0 +1,150 @@ +/** + * File: hash_map_chaining.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* 链式地址哈希表 */ +class HashMapChaining { + private: + int size; // 键值对数量 + int capacity; // 哈希表容量 + double loadThres; // 触发扩容的负载因子阈值 + int extendRatio; // 扩容倍数 + vector> buckets; // 桶数组 + + public: + /* 构造方法 */ + HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) { + buckets.resize(capacity); + } + + /* 析构方法 */ + ~HashMapChaining() { + for (auto &bucket : buckets) { + for (Pair *pair : bucket) { + // 释放内存 + delete pair; + } + } + } + + /* 哈希函数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double loadFactor() { + return (double)size / (double)capacity; + } + + /* 查询操作 */ + string get(int key) { + int index = hashFunc(key); + // 遍历桶,若找到 key ,则返回对应 val + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + return pair->val; + } + } + // 若未找到 key ,则返回空字符串 + return ""; + } + + /* 添加操作 */ + void put(int key, string val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // 遍历桶,若遇到指定 key ,则更新对应 val 并返回 + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + pair->val = val; + return; + } + } + // 若无该 key ,则将键值对添加至尾部 + buckets[index].push_back(new Pair(key, val)); + size++; + } + + /* 删除操作 */ + void remove(int key) { + int index = hashFunc(key); + auto &bucket = buckets[index]; + // 遍历桶,从中删除键值对 + for (int i = 0; i < bucket.size(); i++) { + if (bucket[i]->key == key) { + Pair *tmp = bucket[i]; + bucket.erase(bucket.begin() + i); // 从中删除键值对 + delete tmp; // 释放内存 + size--; + return; + } + } + } + + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + vector> bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets.clear(); + buckets.resize(capacity); + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (auto &bucket : bucketsTmp) { + for (Pair *pair : bucket) { + put(pair->key, pair->val); + // 释放内存 + delete pair; + } + } + } + + /* 打印哈希表 */ + void print() { + for (auto &bucket : buckets) { + cout << "["; + for (Pair *pair : bucket) { + cout << pair->key << " -> " << pair->val << ", "; + } + cout << "]\n"; + } + } +}; + +/* Driver Code */ +int main() { + /* 初始化哈希表 */ + HashMapChaining map = HashMapChaining(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); + cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; + map.print(); + + /* 查询操作 */ + // 向哈希表中输入键 key ,得到值 value + string name = map.get(13276); + cout << "\n输入学号 13276 ,查询到姓名 " << name << endl; + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(12836); + cout << "\n删除 12836 后,哈希表为\nKey -> Value" << endl; + map.print(); + + return 0; +} diff --git a/chapter_hashing/hash_map_open_addressing.cpp b/chapter_hashing/hash_map_open_addressing.cpp new file mode 100644 index 0000000..7f9bf5a --- /dev/null +++ b/chapter_hashing/hash_map_open_addressing.cpp @@ -0,0 +1,171 @@ +/** + * File: hash_map_open_addressing.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* 开放寻址哈希表 */ +class HashMapOpenAddressing { + private: + int size; // 键值对数量 + int capacity = 4; // 哈希表容量 + const double loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值 + const int extendRatio = 2; // 扩容倍数 + vector buckets; // 桶数组 + Pair *TOMBSTONE = new Pair(-1, "-1"); // 删除标记 + + public: + /* 构造方法 */ + HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { + } + + /* 析构方法 */ + ~HashMapOpenAddressing() { + for (Pair *pair : buckets) { + if (pair != nullptr && pair != TOMBSTONE) { + delete pair; + } + } + delete TOMBSTONE; + } + + /* 哈希函数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 负载因子 */ + double loadFactor() { + return (double)size / capacity; + } + + /* 搜索 key 对应的桶索引 */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 线性探测,当遇到空桶时跳出 + while (buckets[index] != nullptr) { + // 若遇到 key ,返回对应的桶索引 + if (buckets[index]->key == key) { + // 若之前遇到了删除标记,则将键值对移动至该索引处 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 返回移动后的桶索引 + } + return index; // 返回桶索引 + } + // 记录遇到的首个删除标记 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // 计算桶索引,越过尾部则返回头部 + index = (index + 1) % capacity; + } + // 若 key 不存在,则返回添加点的索引 + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 查询操作 */ + string get(int key) { + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则返回对应 val + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + return buckets[index]->val; + } + // 若键值对不存在,则返回空字符串 + return ""; + } + + /* 添加操作 */ + void put(int key, string val) { + // 当负载因子超过阈值时,执行扩容 + if (loadFactor() > loadThres) { + extend(); + } + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则覆盖 val 并返回 + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + buckets[index]->val = val; + return; + } + // 若键值对不存在,则添加该键值对 + buckets[index] = new Pair(key, val); + size++; + } + + /* 删除操作 */ + void remove(int key) { + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则用删除标记覆盖它 + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + delete buckets[index]; + buckets[index] = TOMBSTONE; + size--; + } + } + + /* 扩容哈希表 */ + void extend() { + // 暂存原哈希表 + vector bucketsTmp = buckets; + // 初始化扩容后的新哈希表 + capacity *= extendRatio; + buckets = vector(capacity, nullptr); + size = 0; + // 将键值对从原哈希表搬运至新哈希表 + for (Pair *pair : bucketsTmp) { + if (pair != nullptr && pair != TOMBSTONE) { + put(pair->key, pair->val); + delete pair; + } + } + } + + /* 打印哈希表 */ + void print() { + for (Pair *pair : buckets) { + if (pair == nullptr) { + cout << "nullptr" << endl; + } else if (pair == TOMBSTONE) { + cout << "TOMBSTONE" << endl; + } else { + cout << pair->key << " -> " << pair->val << endl; + } + } + } +}; + +/* Driver Code */ +int main() { + // 初始化哈希表 + HashMapOpenAddressing hashmap; + + // 添加操作 + // 在哈希表中添加键值对 (key, val) + hashmap.put(12836, "小哈"); + hashmap.put(15937, "小啰"); + hashmap.put(16750, "小算"); + hashmap.put(13276, "小法"); + hashmap.put(10583, "小鸭"); + cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; + hashmap.print(); + + // 查询操作 + // 向哈希表中输入键 key ,得到值 val + string name = hashmap.get(13276); + cout << "\n输入学号 13276 ,查询到姓名 " << name << endl; + + // 删除操作 + // 在哈希表中删除键值对 (key, val) + hashmap.remove(16750); + cout << "\n删除 16750 后,哈希表为\nKey -> Value" << endl; + hashmap.print(); + + return 0; +} diff --git a/chapter_hashing/simple_hash.cpp b/chapter_hashing/simple_hash.cpp new file mode 100644 index 0000000..02b3eb7 --- /dev/null +++ b/chapter_hashing/simple_hash.cpp @@ -0,0 +1,66 @@ +/** + * File: simple_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 加法哈希 */ +int addHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* 乘法哈希 */ +int mulHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (31 * hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* 异或哈希 */ +int xorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash ^= (int)c; + } + return hash & MODULUS; +} + +/* 旋转哈希 */ +int rotHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS; + } + return (int)hash; +} + +/* Driver Code */ +int main() { + string key = "Hello dsad3241241dsa算123法"; + + int hash = addHash(key); + cout << "加法哈希值为 " << hash << endl; + + hash = mulHash(key); + cout << "乘法哈希值为 " << hash << endl; + + hash = xorHash(key); + cout << "异或哈希值为 " << hash << endl; + + hash = rotHash(key); + cout << "旋转哈希值为 " << hash << endl; + + return 0; +} diff --git a/chapter_heap/CMakeLists.txt b/chapter_heap/CMakeLists.txt new file mode 100644 index 0000000..1ac33a4 --- /dev/null +++ b/chapter_heap/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(heap heap.cpp) +add_executable(my_heap my_heap.cpp) +add_executable(top_k top_k.cpp) diff --git a/chapter_heap/heap.cpp b/chapter_heap/heap.cpp new file mode 100644 index 0000000..84f70bb --- /dev/null +++ b/chapter_heap/heap.cpp @@ -0,0 +1,66 @@ +/** + * File: heap.cpp + * Created Time: 2023-01-19 + * Author: LoneRanger(836253168@qq.com) + */ + +#include "../utils/common.hpp" + +void testPush(priority_queue &heap, int val) { + heap.push(val); // 元素入堆 + cout << "\n元素 " << val << " 入堆后" << endl; + printHeap(heap); +} + +void testPop(priority_queue &heap) { + int val = heap.top(); + heap.pop(); + cout << "\n堆顶元素 " << val << " 出堆后" << endl; + printHeap(heap); +} + +/* Driver Code */ +int main() { + /* 初始化堆 */ + // 初始化小顶堆 + // priority_queue, greater> minHeap; + // 初始化大顶堆 + priority_queue, less> maxHeap; + + cout << "\n以下测试样例为大顶堆" << endl; + + /* 元素入堆 */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* 获取堆顶元素 */ + int peek = maxHeap.top(); + cout << "\n堆顶元素为 " << peek << endl; + + /* 堆顶元素出堆 */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* 获取堆大小 */ + int size = maxHeap.size(); + cout << "\n堆元素数量为 " << size << endl; + + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.empty(); + cout << "\n堆是否为空 " << isEmpty << endl; + + /* 输入列表并建堆 */ + // 时间复杂度为 O(n) ,而非 O(nlogn) + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + cout << "输入列表并建立小顶堆后" << endl; + printHeap(minHeap); + + return 0; +} diff --git a/chapter_heap/my_heap.cpp b/chapter_heap/my_heap.cpp new file mode 100644 index 0000000..5e6299e --- /dev/null +++ b/chapter_heap/my_heap.cpp @@ -0,0 +1,155 @@ +/** + * File: my_heap.cpp + * Created Time: 2023-02-04 + * Author: LoneRanger (836253168@qq.com), what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* 大顶堆 */ +class MaxHeap { + private: + // 使用动态数组,这样无须考虑扩容问题 + vector maxHeap; + + /* 获取左子节点的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 获取右子节点的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 获取父节点的索引 */ + int parent(int i) { + return (i - 1) / 2; // 向下整除 + } + + /* 从节点 i 开始,从底至顶堆化 */ + void siftUp(int i) { + while (true) { + // 获取节点 i 的父节点 + int p = parent(i); + // 当“越过根节点”或“节点无须修复”时,结束堆化 + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 交换两节点 + swap(maxHeap[i], maxHeap[p]); + // 循环向上堆化 + i = p; + } + } + + /* 从节点 i 开始,从顶至底堆化 */ + void siftDown(int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = left(i), r = right(i), ma = i; + if (l < size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) + break; + swap(maxHeap[i], maxHeap[ma]); + // 循环向下堆化 + i = ma; + } + } + + public: + /* 构造方法,根据输入列表建堆 */ + MaxHeap(vector nums) { + // 将列表元素原封不动添加进堆 + maxHeap = nums; + // 堆化除叶节点以外的其他所有节点 + for (int i = parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 获取堆大小 */ + int size() { + return maxHeap.size(); + } + + /* 判断堆是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 访问堆顶元素 */ + int peek() { + return maxHeap[0]; + } + + /* 元素入堆 */ + void push(int val) { + // 添加节点 + maxHeap.push_back(val); + // 从底至顶堆化 + siftUp(size() - 1); + } + + /* 元素出堆 */ + void pop() { + // 判空处理 + if (isEmpty()) { + throw out_of_range("堆为空"); + } + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(maxHeap[0], maxHeap[size() - 1]); + // 删除节点 + maxHeap.pop_back(); + // 从顶至底堆化 + siftDown(0); + } + + /* 打印堆(二叉树)*/ + void print() { + cout << "堆的数组表示:"; + printVector(maxHeap); + cout << "堆的树状表示:" << endl; + TreeNode *root = vectorToTree(maxHeap); + printTree(root); + freeMemoryTree(root); + } +}; + +/* Driver Code */ +int main() { + /* 初始化大顶堆 */ + vector vec{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap maxHeap(vec); + cout << "\n输入列表并建堆后" << endl; + maxHeap.print(); + + /* 获取堆顶元素 */ + int peek = maxHeap.peek(); + cout << "\n堆顶元素为 " << peek << endl; + + /* 元素入堆 */ + int val = 7; + maxHeap.push(val); + cout << "\n元素 " << val << " 入堆后" << endl; + maxHeap.print(); + + /* 堆顶元素出堆 */ + peek = maxHeap.peek(); + maxHeap.pop(); + cout << "\n堆顶元素 " << peek << " 出堆后" << endl; + maxHeap.print(); + + /* 获取堆大小 */ + int size = maxHeap.size(); + cout << "\n堆元素数量为 " << size << endl; + + /* 判断堆是否为空 */ + bool isEmpty = maxHeap.isEmpty(); + cout << "\n堆是否为空 " << isEmpty << endl; + + return 0; +} diff --git a/chapter_heap/top_k.cpp b/chapter_heap/top_k.cpp new file mode 100644 index 0000000..0b4b11a --- /dev/null +++ b/chapter_heap/top_k.cpp @@ -0,0 +1,38 @@ +/** + * File: top_k.cpp + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基于堆查找数组中最大的 k 个元素 */ +priority_queue, greater> topKHeap(vector &nums, int k) { + // 初始化小顶堆 + priority_queue, greater> heap; + // 将数组的前 k 个元素入堆 + for (int i = 0; i < k; i++) { + heap.push(nums[i]); + } + // 从第 k+1 个元素开始,保持堆的长度为 k + for (int i = k; i < nums.size(); i++) { + // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 + if (nums[i] > heap.top()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +// Driver Code +int main() { + vector nums = {1, 7, 6, 3, 2}; + int k = 3; + + priority_queue, greater> res = topKHeap(nums, k); + cout << "最大的 " << k << " 个元素为: "; + printHeap(res); + + return 0; +} diff --git a/chapter_searching/CMakeLists.txt b/chapter_searching/CMakeLists.txt new file mode 100644 index 0000000..60a223d --- /dev/null +++ b/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.cpp) +add_executable(binary_search_insertion binary_search_insertion.cpp) +add_executable(binary_search_edge binary_search_edge.cpp) +add_executable(two_sum two_sum.cpp) diff --git a/chapter_searching/binary_search.cpp b/chapter_searching/binary_search.cpp new file mode 100644 index 0000000..a36670e --- /dev/null +++ b/chapter_searching/binary_search.cpp @@ -0,0 +1,59 @@ +/** + * File: binary_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分查找(双闭区间) */ +int binarySearch(vector &nums, int target) { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + int i = 0, j = nums.size() - 1; + // 循环,当搜索区间为空时跳出(当 i > j 时为空) + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 + i = m + 1; + else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 + j = m - 1; + else // 找到目标元素,返回其索引 + return m; + } + // 未找到目标元素,返回 -1 + return -1; +} + +/* 二分查找(左闭右开区间) */ +int binarySearchLCRO(vector &nums, int target) { + // 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + int i = 0, j = nums.size(); + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while (i < j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 + i = m + 1; + else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 + j = m; + else // 找到目标元素,返回其索引 + return m; + } + // 未找到目标元素,返回 -1 + return -1; +} + +/* Driver Code */ +int main() { + int target = 6; + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + /* 二分查找(双闭区间) */ + int index = binarySearch(nums, target); + cout << "目标元素 6 的索引 = " << index << endl; + + /* 二分查找(左闭右开区间) */ + index = binarySearchLCRO(nums, target); + cout << "目标元素 6 的索引 = " << index << endl; + + return 0; +} diff --git a/chapter_searching/binary_search_edge.cpp b/chapter_searching/binary_search_edge.cpp new file mode 100644 index 0000000..6a536c2 --- /dev/null +++ b/chapter_searching/binary_search_edge.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_edge.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分查找插入点(存在重复元素) */ +int binarySearchInsertion(const vector &nums, int target) { + int i = 0, j = nums.size() - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else { + j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 + } + } + // 返回插入点 i + return i; +} + +/* 二分查找最左一个 target */ +int binarySearchLeftEdge(vector &nums, int target) { + // 等价于查找 target 的插入点 + int i = binarySearchInsertion(nums, target); + // 未找到 target ,返回 -1 + if (i == nums.size() || nums[i] != target) { + return -1; + } + // 找到 target ,返回索引 i + return i; +} + +/* 二分查找最右一个 target */ +int binarySearchRightEdge(vector &nums, int target) { + // 转化为查找最左一个 target + 1 + int i = binarySearchInsertion(nums, target + 1); + // j 指向最右一个 target ,i 指向首个大于 target 的元素 + int j = i - 1; + // 未找到 target ,返回 -1 + if (j == -1 || nums[j] != target) { + return -1; + } + // 找到 target ,返回索引 j + return j; +} + +/* Driver Code */ +int main() { + // 包含重复元素的数组 + vector nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\n数组 nums = "; + printVector(nums); + + // 二分查找左边界和右边界 + for (int target : {6, 7}) { + int index = binarySearchLeftEdge(nums, target); + cout << "最左一个元素 " << target << " 的索引为 " << index << endl; + index = binarySearchRightEdge(nums, target); + cout << "最右一个元素 " << target << " 的索引为 " << index << endl; + } + + return 0; +} diff --git a/chapter_searching/binary_search_insertion.cpp b/chapter_searching/binary_search_insertion.cpp new file mode 100644 index 0000000..f2fd15a --- /dev/null +++ b/chapter_searching/binary_search_insertion.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_insertion.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分查找插入点(无重复元素) */ +int binarySearchInsertionSimple(vector &nums, int target) { + int i = 0, j = nums.size() - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + return m; // 找到 target ,返回插入点 m + } + } + // 未找到 target ,返回插入点 i + return i; +} + +/* 二分查找插入点(存在重复元素) */ +int binarySearchInsertion(vector &nums, int target) { + int i = 0, j = nums.size() - 1; // 初始化双闭区间 [0, n-1] + while (i <= j) { + int m = i + (j - i) / 2; // 计算中点索引 m + if (nums[m] < target) { + i = m + 1; // target 在区间 [m+1, j] 中 + } else if (nums[m] > target) { + j = m - 1; // target 在区间 [i, m-1] 中 + } else { + j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中 + } + } + // 返回插入点 i + return i; +} + +/* Driver Code */ +int main() { + // 无重复元素的数组 + vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + cout << "\n数组 nums = "; + printVector(nums); + // 二分查找插入点 + for (int target : {6, 9}) { + int index = binarySearchInsertionSimple(nums, target); + cout << "元素 " << target << " 的插入点的索引为 " << index << endl; + } + + // 包含重复元素的数组 + nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + cout << "\n数组 nums = "; + printVector(nums); + // 二分查找插入点 + for (int target : {2, 6, 20}) { + int index = binarySearchInsertion(nums, target); + cout << "元素 " << target << " 的插入点的索引为 " << index << endl; + } + + return 0; +} diff --git a/chapter_searching/hashing_search.cpp b/chapter_searching/hashing_search.cpp new file mode 100644 index 0000000..4bd6bd5 --- /dev/null +++ b/chapter_searching/hashing_search.cpp @@ -0,0 +1,53 @@ +/** + * File: hashing_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 哈希查找(数组) */ +int hashingSearchArray(unordered_map map, int target) { + // 哈希表的 key: 目标元素,value: 索引 + // 若哈希表中无此 key ,返回 -1 + if (map.find(target) == map.end()) + return -1; + return map[target]; +} + +/* 哈希查找(链表) */ +ListNode *hashingSearchLinkedList(unordered_map map, int target) { + // 哈希表的 key: 目标节点值,value: 节点对象 + // 若哈希表中无此 key ,返回 nullptr + if (map.find(target) == map.end()) + return nullptr; + return map[target]; +} + +/* Driver Code */ +int main() { + int target = 3; + + /* 哈希查找(数组) */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + // 初始化哈希表 + unordered_map map; + for (int i = 0; i < nums.size(); i++) { + map[nums[i]] = i; // key: 元素,value: 索引 + } + int index = hashingSearchArray(map, target); + cout << "目标元素 3 的索引 = " << index << endl; + + /* 哈希查找(链表) */ + ListNode *head = vecToLinkedList(nums); + // 初始化哈希表 + unordered_map map1; + while (head != nullptr) { + map1[head->val] = head; // key: 节点值,value: 节点 + head = head->next; + } + ListNode *node = hashingSearchLinkedList(map1, target); + cout << "目标节点值 3 的对应节点对象为 " << node << endl; + + return 0; +} diff --git a/chapter_searching/linear_search.cpp b/chapter_searching/linear_search.cpp new file mode 100644 index 0000000..fb4aece --- /dev/null +++ b/chapter_searching/linear_search.cpp @@ -0,0 +1,49 @@ +/** + * File: linear_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 线性查找(数组) */ +int linearSearchArray(vector &nums, int target) { + // 遍历数组 + for (int i = 0; i < nums.size(); i++) { + // 找到目标元素,返回其索引 + if (nums[i] == target) + return i; + } + // 未找到目标元素,返回 -1 + return -1; +} + +/* 线性查找(链表) */ +ListNode *linearSearchLinkedList(ListNode *head, int target) { + // 遍历链表 + while (head != nullptr) { + // 找到目标节点,返回之 + if (head->val == target) + return head; + head = head->next; + } + // 未找到目标节点,返回 nullptr + return nullptr; +} + +/* Driver Code */ +int main() { + int target = 3; + + /* 在数组中执行线性查找 */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + int index = linearSearchArray(nums, target); + cout << "目标元素 3 的索引 = " << index << endl; + + /* 在链表中执行线性查找 */ + ListNode *head = vecToLinkedList(nums); + ListNode *node = linearSearchLinkedList(head, target); + cout << "目标节点值 3 的对应节点对象为 " << node << endl; + + return 0; +} diff --git a/chapter_searching/two_sum.cpp b/chapter_searching/two_sum.cpp new file mode 100644 index 0000000..eba46c1 --- /dev/null +++ b/chapter_searching/two_sum.cpp @@ -0,0 +1,54 @@ +/** + * File: two_sum.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 方法一:暴力枚举 */ +vector twoSumBruteForce(vector &nums, int target) { + int size = nums.size(); + // 两层循环,时间复杂度为 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return {i, j}; + } + } + return {}; +} + +/* 方法二:辅助哈希表 */ +vector twoSumHashTable(vector &nums, int target) { + int size = nums.size(); + // 辅助哈希表,空间复杂度为 O(n) + unordered_map dic; + // 单层循环,时间复杂度为 O(n) + for (int i = 0; i < size; i++) { + if (dic.find(target - nums[i]) != dic.end()) { + return {dic[target - nums[i]], i}; + } + dic.emplace(nums[i], i); + } + return {}; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + vector nums = {2, 7, 11, 15}; + int target = 13; + + // ====== Driver Code ====== + // 方法一 + vector res = twoSumBruteForce(nums, target); + cout << "方法一 res = "; + printVector(res); + // 方法二 + res = twoSumHashTable(nums, target); + cout << "方法二 res = "; + printVector(res); + + return 0; +} diff --git a/chapter_sorting/CMakeLists.txt b/chapter_sorting/CMakeLists.txt new file mode 100644 index 0000000..e6347cf --- /dev/null +++ b/chapter_sorting/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(selection_sort selection_sort.cpp) +add_executable(bubble_sort bubble_sort.cpp) +add_executable(insertion_sort insertion_sort.cpp) +add_executable(merge_sort merge_sort.cpp) +add_executable(quick_sort quick_sort.cpp) +add_executable(heap_sort heap_sort.cpp) \ No newline at end of file diff --git a/chapter_sorting/bubble_sort.cpp b/chapter_sorting/bubble_sort.cpp new file mode 100644 index 0000000..a64d239 --- /dev/null +++ b/chapter_sorting/bubble_sort.cpp @@ -0,0 +1,56 @@ +/** + * File: bubble_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 冒泡排序 */ +void bubbleSort(vector &nums) { + // 外循环:未排序区间为 [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + // 这里使用了 std::swap() 函数 + swap(nums[j], nums[j + 1]); + } + } + } +} + +/* 冒泡排序(标志优化)*/ +void bubbleSortWithFlag(vector &nums) { + // 外循环:未排序区间为 [0, i] + for (int i = nums.size() - 1; i > 0; i--) { + bool flag = false; // 初始化标志位 + // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // 交换 nums[j] 与 nums[j + 1] + // 这里使用了 std::swap() 函数 + swap(nums[j], nums[j + 1]); + flag = true; // 记录交换元素 + } + } + if (!flag) + break; // 此轮“冒泡”未交换任何元素,直接跳出 + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + bubbleSort(nums); + cout << "冒泡排序完成后 nums = "; + printVector(nums); + + vector nums1 = {4, 1, 3, 1, 5, 2}; + bubbleSortWithFlag(nums1); + cout << "冒泡排序完成后 nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/chapter_sorting/bucket_sort.cpp b/chapter_sorting/bucket_sort.cpp new file mode 100644 index 0000000..42be5c3 --- /dev/null +++ b/chapter_sorting/bucket_sort.cpp @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.cpp + * Created Time: 2023-03-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 桶排序 */ +void bucketSort(vector &nums) { + // 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 + int k = nums.size() / 2; + vector> buckets(k); + // 1. 将数组元素分配到各个桶中 + for (float num : nums) { + // 输入数据范围为 [0, 1),使用 num * k 映射到索引范围 [0, k-1] + int i = num * k; + // 将 num 添加进桶 bucket_idx + buckets[i].push_back(num); + } + // 2. 对各个桶执行排序 + for (vector &bucket : buckets) { + // 使用内置排序函数,也可以替换成其他排序算法 + sort(bucket.begin(), bucket.end()); + } + // 3. 遍历桶合并结果 + int i = 0; + for (vector &bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +int main() { + // 设输入数据为浮点数,范围为 [0, 1) + vector nums = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums); + cout << "桶排序完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/chapter_sorting/counting_sort.cpp b/chapter_sorting/counting_sort.cpp new file mode 100644 index 0000000..824ef0a --- /dev/null +++ b/chapter_sorting/counting_sort.cpp @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cpp + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 计数排序 */ +// 简单实现,无法用于排序对象 +void countingSortNaive(vector &nums) { + // 1. 统计数组最大元素 m + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 3. 遍历 counter ,将各元素填入原数组 nums + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* 计数排序 */ +// 完整实现,可排序对象,并且是稳定排序 +void countingSort(vector &nums) { + // 1. 统计数组最大元素 m + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. 统计各数字的出现次数 + // counter[num] 代表 num 的出现次数 + vector counter(m + 1, 0); + for (int num : nums) { + counter[num]++; + } + // 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” + // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. 倒序遍历 nums ,将各元素填入结果数组 res + // 初始化数组 res 用于记录结果 + int n = nums.size(); + vector res(n); + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // 将 num 放置到对应索引处 + counter[num]--; // 令前缀和自减 1 ,得到下次放置 num 的索引 + } + // 使用结果数组 res 覆盖原数组 nums + nums = res; +} + +/* Driver Code */ +int main() { + vector nums = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSortNaive(nums); + cout << "计数排序(无法排序对象)完成后 nums = "; + printVector(nums); + + vector nums1 = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSort(nums1); + cout << "计数排序完成后 nums1 = "; + printVector(nums1); + + return 0; +} diff --git a/chapter_sorting/heap_sort.cpp b/chapter_sorting/heap_sort.cpp new file mode 100644 index 0000000..1e568b8 --- /dev/null +++ b/chapter_sorting/heap_sort.cpp @@ -0,0 +1,54 @@ +/** + * File: heap_sort.cpp + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */ +void siftDown(vector &nums, int n, int i) { + while (true) { + // 判断节点 i, l, r 中值最大的节点,记为 ma + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 + if (ma == i) { + break; + } + // 交换两节点 + swap(nums[i], nums[ma]); + // 循环向下堆化 + i = ma; + } +} + +/* 堆排序 */ +void heapSort(vector &nums) { + // 建堆操作:堆化除叶节点以外的其他所有节点 + for (int i = nums.size() / 2 - 1; i >= 0; --i) { + siftDown(nums, nums.size(), i); + } + // 从堆中提取最大元素,循环 n-1 轮 + for (int i = nums.size() - 1; i > 0; --i) { + // 交换根节点与最右叶节点(交换首元素与尾元素) + swap(nums[0], nums[i]); + // 以根节点为起点,从顶至底进行堆化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + heapSort(nums); + cout << "堆排序完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/chapter_sorting/insertion_sort.cpp b/chapter_sorting/insertion_sort.cpp new file mode 100644 index 0000000..d9ea719 --- /dev/null +++ b/chapter_sorting/insertion_sort.cpp @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 插入排序 */ +void insertionSort(vector &nums) { + // 外循环:已排序元素数量为 1, 2, ..., n + for (int i = 1; i < nums.size(); i++) { + int base = nums[i], j = i - 1; + // 内循环:将 base 插入到已排序部分的正确位置 + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位 + j--; + } + nums[j + 1] = base; // 将 base 赋值到正确位置 + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + insertionSort(nums); + cout << "插入排序完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/chapter_sorting/merge_sort.cpp b/chapter_sorting/merge_sort.cpp new file mode 100644 index 0000000..98a2795 --- /dev/null +++ b/chapter_sorting/merge_sort.cpp @@ -0,0 +1,58 @@ +/** + * File: merge_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 合并左子数组和右子数组 */ +void merge(vector &nums, int left, int mid, int right) { + // 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right] + // 创建一个临时数组 tmp ,用于存放合并后的结果 + vector tmp(right - left + 1); + // 初始化左子数组和右子数组的起始索引 + int i = left, j = mid + 1, k = 0; + // 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中 + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // 将左子数组和右子数组的剩余元素复制到临时数组中 + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 + for (k = 0; k < tmp.size(); k++) { + nums[left + k] = tmp[k]; + } +} + +/* 归并排序 */ +void mergeSort(vector &nums, int left, int right) { + // 终止条件 + if (left >= right) + return; // 当子数组长度为 1 时终止递归 + // 划分阶段 + int mid = (left + right) / 2; // 计算中点 + mergeSort(nums, left, mid); // 递归左子数组 + mergeSort(nums, mid + 1, right); // 递归右子数组 + // 合并阶段 + merge(nums, left, mid, right); +} + +/* Driver Code */ +int main() { + /* 归并排序 */ + vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; + mergeSort(nums, 0, nums.size() - 1); + cout << "归并排序完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/chapter_sorting/quick_sort.cpp b/chapter_sorting/quick_sort.cpp new file mode 100644 index 0000000..32d3fb4 --- /dev/null +++ b/chapter_sorting/quick_sort.cpp @@ -0,0 +1,168 @@ +/** + * File: quick_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 快速排序类 */ +class QuickSort { + private: + /* 元素交换 */ + static void swap(vector &nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵划分 */ + static int partition(vector &nums, int left, int right) { + // 以 nums[left] 为基准数 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + + public: + /* 快速排序 */ + static void quickSort(vector &nums, int left, int right) { + // 子数组长度为 1 时终止递归 + if (left >= right) + return; + // 哨兵划分 + int pivot = partition(nums, left, right); + // 递归左子数组、右子数组 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +/* 快速排序类(中位基准数优化) */ +class QuickSortMedian { + private: + /* 元素交换 */ + static void swap(vector &nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 选取三个候选元素的中位数 */ + static int medianThree(vector &nums, int left, int mid, int right) { + // 此处使用异或运算来简化代码 + // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 + if ((nums[left] < nums[mid]) ^ (nums[left] < nums[right])) + return left; + else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) + return mid; + else + return right; + } + + /* 哨兵划分(三数取中值) */ + static int partition(vector &nums, int left, int right) { + // 选取三个候选元素的中位数 + int med = medianThree(nums, left, (left + right) / 2, right); + // 将中位数交换至数组最左端 + swap(nums, left, med); + // 以 nums[left] 为基准数 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + + public: + /* 快速排序 */ + static void quickSort(vector &nums, int left, int right) { + // 子数组长度为 1 时终止递归 + if (left >= right) + return; + // 哨兵划分 + int pivot = partition(nums, left, right); + // 递归左子数组、右子数组 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +/* 快速排序类(尾递归优化) */ +class QuickSortTailCall { + private: + /* 元素交换 */ + static void swap(vector &nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵划分 */ + static int partition(vector &nums, int left, int right) { + // 以 nums[left] 为基准数 + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + + public: + /* 快速排序(尾递归优化) */ + static void quickSort(vector &nums, int left, int right) { + // 子数组长度为 1 时终止 + while (left < right) { + // 哨兵划分操作 + int pivot = partition(nums, left, right); + // 对两个子数组中较短的那个执行快速排序 + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 递归排序左子数组 + left = pivot + 1; // 剩余未排序区间为 [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 递归排序右子数组 + right = pivot - 1; // 剩余未排序区间为 [left, pivot - 1] + } + } + } +}; + +/* Driver Code */ +int main() { + /* 快速排序 */ + vector nums{2, 4, 1, 0, 3, 5}; + QuickSort::quickSort(nums, 0, nums.size() - 1); + cout << "快速排序完成后 nums = "; + printVector(nums); + + /* 快速排序(中位基准数优化) */ + vector nums1 = {2, 4, 1, 0, 3, 5}; + QuickSortMedian::quickSort(nums1, 0, nums1.size() - 1); + cout << "快速排序(中位基准数优化)完成后 nums = "; + printVector(nums); + + /* 快速排序(尾递归优化) */ + vector nums2 = {2, 4, 1, 0, 3, 5}; + QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1); + cout << "快速排序(尾递归优化)完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/chapter_sorting/radix_sort.cpp b/chapter_sorting/radix_sort.cpp new file mode 100644 index 0000000..ef4ce40 --- /dev/null +++ b/chapter_sorting/radix_sort.cpp @@ -0,0 +1,65 @@ +/** + * File: radix_sort.cpp + * Created Time: 2023-03-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 获取元素 num 的第 k 位,其中 exp = 10^(k-1) */ +int digit(int num, int exp) { + // 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 + return (num / exp) % 10; +} + +/* 计数排序(根据 nums 第 k 位排序) */ +void countingSortDigit(vector &nums, int exp) { + // 十进制的位范围为 0~9 ,因此需要长度为 10 的桶数组 + vector counter(10, 0); + int n = nums.size(); + // 统计 0~9 各数字的出现次数 + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // 获取 nums[i] 第 k 位,记为 d + counter[d]++; // 统计数字 d 的出现次数 + } + // 求前缀和,将“出现个数”转换为“数组索引” + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 倒序遍历,根据桶内统计结果,将各元素填入 res + vector res(n, 0); + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // 获取 d 在数组中的索引 j + res[j] = nums[i]; // 将当前元素填入索引 j + counter[d]--; // 将 d 的数量减 1 + } + // 使用结果覆盖原数组 nums + for (int i = 0; i < n; i++) + nums[i] = res[i]; +} + +/* 基数排序 */ +void radixSort(vector &nums) { + // 获取数组的最大元素,用于判断最大位数 + int m = *max_element(nums.begin(), nums.end()); + // 按照从低位到高位的顺序遍历 + for (int exp = 1; exp <= m; exp *= 10) + // 对数组元素的第 k 位执行计数排序 + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // 即 exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +int main() { + // 基数排序 + vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + radixSort(nums); + cout << "基数排序完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/chapter_sorting/selection_sort.cpp b/chapter_sorting/selection_sort.cpp new file mode 100644 index 0000000..027d2ac --- /dev/null +++ b/chapter_sorting/selection_sort.cpp @@ -0,0 +1,34 @@ +/** + * File: selection_sort.cpp + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 选择排序 */ +void selectionSort(vector &nums) { + int n = nums.size(); + // 外循环:未排序区间为 [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 内循环:找到未排序区间内的最小元素 + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 记录最小元素的索引 + } + // 将该最小元素与未排序区间的首个元素交换 + swap(nums[i], nums[k]); + } +} + +/* Driver Code */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + selectionSort(nums); + + cout << "选择排序完成后 nums = "; + printVector(nums); + + return 0; +} diff --git a/chapter_stack_and_queue/CMakeLists.txt b/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 0000000..b55878a --- /dev/null +++ b/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(array_deque array_deque.cpp) +add_executable(array_queue array_queue.cpp) +add_executable(array_stack array_stack.cpp) +add_executable(deque deque.cpp) +add_executable(linkedlist_deque linkedlist_deque.cpp) +add_executable(linkedlist_queue linkedlist_queue.cpp) +add_executable(linkedlist_stack linkedlist_stack.cpp) +add_executable(queue queue.cpp) +add_executable(stack stack.cpp) diff --git a/chapter_stack_and_queue/array_deque.cpp b/chapter_stack_and_queue/array_deque.cpp new file mode 100644 index 0000000..00bf776 --- /dev/null +++ b/chapter_stack_and_queue/array_deque.cpp @@ -0,0 +1,156 @@ +/** + * File: array_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基于环形数组实现的双向队列 */ +class ArrayDeque { + private: + vector nums; // 用于存储双向队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 双向队列长度 + + public: + /* 构造方法 */ + ArrayDeque(int capacity) { + nums.resize(capacity); + front = queSize = 0; + } + + /* 获取双向队列的容量 */ + int capacity() { + return nums.size(); + } + + /* 获取双向队列的长度 */ + int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + bool isEmpty() { + return queSize == 0; + } + + /* 计算环形数组索引 */ + int index(int i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 队首入队 */ + void pushFirst(int num) { + if (queSize == capacity()) { + cout << "双向队列已满" << endl; + return; + } + // 队首指针向左移动一位 + // 通过取余操作实现 front 越过数组头部后回到尾部 + front = index(front - 1); + // 将 num 添加至队首 + nums[front] = num; + queSize++; + } + + /* 队尾入队 */ + void pushLast(int num) { + if (queSize == capacity()) { + cout << "双向队列已满" << endl; + return; + } + // 计算队尾指针,指向队尾索引 + 1 + int rear = index(front + queSize); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 队首出队 */ + int popFirst() { + int num = peekFirst(); + // 队首指针向后移动一位 + front = index(front + 1); + queSize--; + return num; + } + + /* 队尾出队 */ + int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* 访问队首元素 */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("双向队列为空"); + return nums[front]; + } + + /* 访问队尾元素 */ + int peekLast() { + if (isEmpty()) + throw out_of_range("双向队列为空"); + // 计算尾元素索引 + int last = index(front + queSize - 1); + return nums[last]; + } + + /* 返回数组用于打印 */ + vector toVector() { + // 仅转换有效长度范围内的列表元素 + vector res(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化双向队列 */ + ArrayDeque *deque = new ArrayDeque(10); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "双向队列 deque = "; + printVector(deque->toVector()); + + /* 访问元素 */ + int peekFirst = deque->peekFirst(); + cout << "队首元素 peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "队尾元素 peekLast = " << peekLast << endl; + + /* 元素入队 */ + deque->pushLast(4); + cout << "元素 4 队尾入队后 deque = "; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "元素 1 队首入队后 deque = "; + printVector(deque->toVector()); + + /* 元素出队 */ + int popLast = deque->popLast(); + cout << "队尾出队元素 = " << popLast << ",队尾出队后 deque = "; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "队首出队元素 = " << popFirst << ",队首出队后 deque = "; + printVector(deque->toVector()); + + /* 获取双向队列的长度 */ + int size = deque->size(); + cout << "双向队列长度 size = " << size << endl; + + /* 判断双向队列是否为空 */ + bool isEmpty = deque->isEmpty(); + cout << "双向队列是否为空 = " << boolalpha << isEmpty << endl; + return 0; +} diff --git a/chapter_stack_and_queue/array_queue.cpp b/chapter_stack_and_queue/array_queue.cpp new file mode 100644 index 0000000..e6def13 --- /dev/null +++ b/chapter_stack_and_queue/array_queue.cpp @@ -0,0 +1,129 @@ +/** + * File: array_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基于环形数组实现的队列 */ +class ArrayQueue { + private: + int *nums; // 用于存储队列元素的数组 + int front; // 队首指针,指向队首元素 + int queSize; // 队列长度 + int queCapacity; // 队列容量 + + public: + ArrayQueue(int capacity) { + // 初始化数组 + nums = new int[capacity]; + queCapacity = capacity; + front = queSize = 0; + } + + ~ArrayQueue() { + delete[] nums; + } + + /* 获取队列的容量 */ + int capacity() { + return queCapacity; + } + + /* 获取队列的长度 */ + int size() { + return queSize; + } + + /* 判断队列是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入队 */ + void push(int num) { + if (queSize == queCapacity) { + cout << "队列已满" << endl; + return; + } + // 计算队尾指针,指向队尾索引 + 1 + // 通过取余操作实现 rear 越过数组尾部后回到头部 + int rear = (front + queSize) % queCapacity; + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 出队 */ + int pop() { + int num = peek(); + // 队首指针向后移动一位,若越过尾部,则返回到数组头部 + front = (front + 1) % queCapacity; + queSize--; + return num; + } + + /* 访问队首元素 */ + int peek() { + if (isEmpty()) + throw out_of_range("队列为空"); + return nums[front]; + } + + /* 将数组转化为 Vector 并返回 */ + vector toVector() { + // 仅转换有效长度范围内的列表元素 + vector arr(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + arr[i] = nums[j % queCapacity]; + } + return arr; + } +}; + +/* Driver Code */ +int main() { + /* 初始化队列 */ + int capacity = 10; + ArrayQueue *queue = new ArrayQueue(capacity); + + /* 元素入队 */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "队列 queue = "; + printVector(queue->toVector()); + + /* 访问队首元素 */ + int peek = queue->peek(); + cout << "队首元素 peek = " << peek << endl; + + /* 元素出队 */ + peek = queue->pop(); + cout << "出队元素 pop = " << peek << ",出队后 queue = "; + printVector(queue->toVector()); + + /* 获取队列的长度 */ + int size = queue->size(); + cout << "队列长度 size = " << size << endl; + + /* 判断队列是否为空 */ + bool empty = queue->isEmpty(); + cout << "队列是否为空 = " << empty << endl; + + /* 测试环形数组 */ + for (int i = 0; i < 10; i++) { + queue->push(i); + queue->pop(); + cout << "第 " << i << " 轮入队 + 出队后 queue = "; + printVector(queue->toVector()); + } + + // 释放内存 + delete queue; + + return 0; +} diff --git a/chapter_stack_and_queue/array_stack.cpp b/chapter_stack_and_queue/array_stack.cpp new file mode 100644 index 0000000..c59e076 --- /dev/null +++ b/chapter_stack_and_queue/array_stack.cpp @@ -0,0 +1,85 @@ +/** + * File: array_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* 基于数组实现的栈 */ +class ArrayStack { + private: + vector stack; + + public: + /* 获取栈的长度 */ + int size() { + return stack.size(); + } + + /* 判断栈是否为空 */ + bool isEmpty() { + return stack.size() == 0; + } + + /* 入栈 */ + void push(int num) { + stack.push_back(num); + } + + /* 出栈 */ + int pop() { + int num = top(); + stack.pop_back(); + return num; + } + + /* 访问栈顶元素 */ + int top() { + if (isEmpty()) + throw out_of_range("栈为空"); + return stack.back(); + } + + /* 返回 Vector */ + vector toVector() { + return stack; + } +}; + +/* Driver Code */ +int main() { + /* 初始化栈 */ + ArrayStack *stack = new ArrayStack(); + + /* 元素入栈 */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "栈 stack = "; + printVector(stack->toVector()); + + /* 访问栈顶元素 */ + int top = stack->top(); + cout << "栈顶元素 top = " << top << endl; + + /* 元素出栈 */ + top = stack->pop(); + cout << "出栈元素 pop = " << top << ",出栈后 stack = "; + printVector(stack->toVector()); + + /* 获取栈的长度 */ + int size = stack->size(); + cout << "栈的长度 size = " << size << endl; + + /* 判断是否为空 */ + bool empty = stack->isEmpty(); + cout << "栈是否为空 = " << empty << endl; + + // 释放内存 + delete stack; + + return 0; +} diff --git a/chapter_stack_and_queue/deque.cpp b/chapter_stack_and_queue/deque.cpp new file mode 100644 index 0000000..16da9ae --- /dev/null +++ b/chapter_stack_and_queue/deque.cpp @@ -0,0 +1,46 @@ +/** + * File: deque.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化双向队列 */ + deque deque; + + /* 元素入队 */ + deque.push_back(2); + deque.push_back(5); + deque.push_back(4); + deque.push_front(3); + deque.push_front(1); + cout << "双向队列 deque = "; + printDeque(deque); + + /* 访问元素 */ + int front = deque.front(); + cout << "队首元素 front = " << front << endl; + int back = deque.back(); + cout << "队尾元素 back = " << back << endl; + + /* 元素出队 */ + deque.pop_front(); + cout << "队首出队元素 popFront = " << front << ",队首出队后 deque = "; + printDeque(deque); + deque.pop_back(); + cout << "队尾出队元素 popLast = " << back << ",队尾出队后 deque = "; + printDeque(deque); + + /* 获取双向队列的长度 */ + int size = deque.size(); + cout << "双向队列长度 size = " << size << endl; + + /* 判断双向队列是否为空 */ + bool empty = deque.empty(); + cout << "双向队列是否为空 = " << empty << endl; + + return 0; +} diff --git a/chapter_stack_and_queue/linkedlist_deque.cpp b/chapter_stack_and_queue/linkedlist_deque.cpp new file mode 100644 index 0000000..7eea86c --- /dev/null +++ b/chapter_stack_and_queue/linkedlist_deque.cpp @@ -0,0 +1,194 @@ +/** + * File: linkedlist_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 双向链表节点 */ +struct DoublyListNode { + int val; // 节点值 + DoublyListNode *next; // 后继节点指针 + DoublyListNode *prev; // 前驱节点指针 + DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) { + } +}; + +/* 基于双向链表实现的双向队列 */ +class LinkedListDeque { + private: + DoublyListNode *front, *rear; // 头节点 front ,尾节点 rear + int queSize = 0; // 双向队列的长度 + + public: + /* 构造方法 */ + LinkedListDeque() : front(nullptr), rear(nullptr) { + } + + /* 析构方法 */ + ~LinkedListDeque() { + // 遍历链表删除节点,释放内存 + DoublyListNode *pre, *cur = front; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } + } + + /* 获取双向队列的长度 */ + int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入队操作 */ + void push(int num, bool isFront) { + DoublyListNode *node = new DoublyListNode(num); + // 若链表为空,则令 front 和 rear 都指向 node + if (isEmpty()) + front = rear = node; + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + front->prev = node; + node->next = front; + front = node; // 更新头节点 + // 队尾入队操作 + } else { + // 将 node 添加至链表尾部 + rear->next = node; + node->prev = rear; + rear = node; // 更新尾节点 + } + queSize++; // 更新队列长度 + } + + /* 队首入队 */ + void pushFirst(int num) { + push(num, true); + } + + /* 队尾入队 */ + void pushLast(int num) { + push(num, false); + } + + /* 出队操作 */ + int pop(bool isFront) { + if (isEmpty()) + throw out_of_range("队列为空"); + int val; + // 队首出队操作 + if (isFront) { + val = front->val; // 暂存头节点值 + // 删除头节点 + DoublyListNode *fNext = front->next; + if (fNext != nullptr) { + fNext->prev = nullptr; + front->next = nullptr; + } + delete front; + front = fNext; // 更新头节点 + // 队尾出队操作 + } else { + val = rear->val; // 暂存尾节点值 + // 删除尾节点 + DoublyListNode *rPrev = rear->prev; + if (rPrev != nullptr) { + rPrev->next = nullptr; + rear->prev = nullptr; + } + delete rear; + rear = rPrev; // 更新尾节点 + } + queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + int popFirst() { + return pop(true); + } + + /* 队尾出队 */ + int popLast() { + return pop(false); + } + + /* 访问队首元素 */ + int peekFirst() { + if (isEmpty()) + throw out_of_range("双向队列为空"); + return front->val; + } + + /* 访问队尾元素 */ + int peekLast() { + if (isEmpty()) + throw out_of_range("双向队列为空"); + return rear->val; + } + + /* 返回数组用于打印 */ + vector toVector() { + DoublyListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化双向队列 */ + LinkedListDeque *deque = new LinkedListDeque(); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "双向队列 deque = "; + printVector(deque->toVector()); + + /* 访问元素 */ + int peekFirst = deque->peekFirst(); + cout << "队首元素 peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "队尾元素 peekLast = " << peekLast << endl; + + /* 元素入队 */ + deque->pushLast(4); + cout << "元素 4 队尾入队后 deque ="; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "元素 1 队首入队后 deque = "; + printVector(deque->toVector()); + + /* 元素出队 */ + int popLast = deque->popLast(); + cout << "队尾出队元素 = " << popLast << ",队尾出队后 deque = "; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "队首出队元素 = " << popFirst << ",队首出队后 deque = "; + printVector(deque->toVector()); + + /* 获取双向队列的长度 */ + int size = deque->size(); + cout << "双向队列长度 size = " << size << endl; + + /* 判断双向队列是否为空 */ + bool isEmpty = deque->isEmpty(); + cout << "双向队列是否为空 = " << boolalpha << isEmpty << endl; + + // 释放内存 + delete deque; + + return 0; +} diff --git a/chapter_stack_and_queue/linkedlist_queue.cpp b/chapter_stack_and_queue/linkedlist_queue.cpp new file mode 100644 index 0000000..687f136 --- /dev/null +++ b/chapter_stack_and_queue/linkedlist_queue.cpp @@ -0,0 +1,120 @@ +/** + * File: linkedlist_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 基于链表实现的队列 */ +class LinkedListQueue { + private: + ListNode *front, *rear; // 头节点 front ,尾节点 rear + int queSize; + + public: + LinkedListQueue() { + front = nullptr; + rear = nullptr; + queSize = 0; + } + + ~LinkedListQueue() { + // 遍历链表删除节点,释放内存 + freeMemoryLinkedList(front); + } + + /* 获取队列的长度 */ + int size() { + return queSize; + } + + /* 判断队列是否为空 */ + bool isEmpty() { + return queSize == 0; + } + + /* 入队 */ + void push(int num) { + // 在尾节点后添加 num + ListNode *node = new ListNode(num); + // 如果队列为空,则令头、尾节点都指向该节点 + if (front == nullptr) { + front = node; + rear = node; + } + // 如果队列不为空,则将该节点添加到尾节点后 + else { + rear->next = node; + rear = node; + } + queSize++; + } + + /* 出队 */ + int pop() { + int num = peek(); + // 删除头节点 + ListNode *tmp = front; + front = front->next; + // 释放内存 + delete tmp; + queSize--; + return num; + } + + /* 访问队首元素 */ + int peek() { + if (size() == 0) + throw out_of_range("队列为空"); + return front->val; + } + + /* 将链表转化为 Vector 并返回 */ + vector toVector() { + ListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化队列 */ + LinkedListQueue *queue = new LinkedListQueue(); + + /* 元素入队 */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "队列 queue = "; + printVector(queue->toVector()); + + /* 访问队首元素 */ + int peek = queue->peek(); + cout << "队首元素 peek = " << peek << endl; + + /* 元素出队 */ + peek = queue->pop(); + cout << "出队元素 pop = " << peek << ",出队后 queue = "; + printVector(queue->toVector()); + + /* 获取队列的长度 */ + int size = queue->size(); + cout << "队列长度 size = " << size << endl; + + /* 判断队列是否为空 */ + bool empty = queue->isEmpty(); + cout << "队列是否为空 = " << empty << endl; + + // 释放内存 + delete queue; + + return 0; +} diff --git a/chapter_stack_and_queue/linkedlist_stack.cpp b/chapter_stack_and_queue/linkedlist_stack.cpp new file mode 100644 index 0000000..dbf1f67 --- /dev/null +++ b/chapter_stack_and_queue/linkedlist_stack.cpp @@ -0,0 +1,109 @@ +/** + * File: linkedlist_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* 基于链表实现的栈 */ +class LinkedListStack { + private: + ListNode *stackTop; // 将头节点作为栈顶 + int stkSize; // 栈的长度 + + public: + LinkedListStack() { + stackTop = nullptr; + stkSize = 0; + } + + ~LinkedListStack() { + // 遍历链表删除节点,释放内存 + freeMemoryLinkedList(stackTop); + } + + /* 获取栈的长度 */ + int size() { + return stkSize; + } + + /* 判断栈是否为空 */ + bool isEmpty() { + return size() == 0; + } + + /* 入栈 */ + void push(int num) { + ListNode *node = new ListNode(num); + node->next = stackTop; + stackTop = node; + stkSize++; + } + + /* 出栈 */ + int pop() { + int num = top(); + ListNode *tmp = stackTop; + stackTop = stackTop->next; + // 释放内存 + delete tmp; + stkSize--; + return num; + } + + /* 访问栈顶元素 */ + int top() { + if (isEmpty()) + throw out_of_range("栈为空"); + return stackTop->val; + } + + /* 将 List 转化为 Array 并返回 */ + vector toVector() { + ListNode *node = stackTop; + vector res(size()); + for (int i = res.size() - 1; i >= 0; i--) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* Driver Code */ +int main() { + /* 初始化栈 */ + LinkedListStack *stack = new LinkedListStack(); + + /* 元素入栈 */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "栈 stack = "; + printVector(stack->toVector()); + + /* 访问栈顶元素 */ + int top = stack->top(); + cout << "栈顶元素 top = " << top << endl; + + /* 元素出栈 */ + top = stack->pop(); + cout << "出栈元素 pop = " << top << ",出栈后 stack = "; + printVector(stack->toVector()); + + /* 获取栈的长度 */ + int size = stack->size(); + cout << "栈的长度 size = " << size << endl; + + /* 判断是否为空 */ + bool empty = stack->isEmpty(); + cout << "栈是否为空 = " << empty << endl; + + // 释放内存 + delete stack; + + return 0; +} diff --git a/chapter_stack_and_queue/queue.cpp b/chapter_stack_and_queue/queue.cpp new file mode 100644 index 0000000..b94ba40 --- /dev/null +++ b/chapter_stack_and_queue/queue.cpp @@ -0,0 +1,41 @@ +/** + * File: queue.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化队列 */ + queue queue; + + /* 元素入队 */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + cout << "队列 queue = "; + printQueue(queue); + + /* 访问队首元素 */ + int front = queue.front(); + cout << "队首元素 front = " << front << endl; + + /* 元素出队 */ + queue.pop(); + cout << "出队元素 front = " << front << ",出队后 queue = "; + printQueue(queue); + + /* 获取队列的长度 */ + int size = queue.size(); + cout << "队列长度 size = " << size << endl; + + /* 判断队列是否为空 */ + bool empty = queue.empty(); + cout << "队列是否为空 = " << empty << endl; + + return 0; +} diff --git a/chapter_stack_and_queue/stack.cpp b/chapter_stack_and_queue/stack.cpp new file mode 100644 index 0000000..18743bf --- /dev/null +++ b/chapter_stack_and_queue/stack.cpp @@ -0,0 +1,41 @@ +/** + * File: stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化栈 */ + stack stack; + + /* 元素入栈 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + cout << "栈 stack = "; + printStack(stack); + + /* 访问栈顶元素 */ + int top = stack.top(); + cout << "栈顶元素 top = " << top << endl; + + /* 元素出栈 */ + stack.pop(); // 无返回值 + cout << "出栈元素 pop = " << top << ",出栈后 stack = "; + printStack(stack); + + /* 获取栈的长度 */ + int size = stack.size(); + cout << "栈的长度 size = " << size << endl; + + /* 判断是否为空 */ + bool empty = stack.empty(); + cout << "栈是否为空 = " << empty << endl; + + return 0; +} diff --git a/chapter_tree/CMakeLists.txt b/chapter_tree/CMakeLists.txt new file mode 100644 index 0000000..fa7009b --- /dev/null +++ b/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.cpp) +add_executable(binary_search_tree binary_search_tree.cpp) +add_executable(binary_tree binary_tree.cpp) +add_executable(binary_tree_bfs binary_tree_bfs.cpp) +add_executable(binary_tree_dfs binary_tree_dfs.cpp) +add_executable(array_binary_tree array_binary_tree.cpp) \ No newline at end of file diff --git a/chapter_tree/array_binary_tree.cpp b/chapter_tree/array_binary_tree.cpp new file mode 100644 index 0000000..031c664 --- /dev/null +++ b/chapter_tree/array_binary_tree.cpp @@ -0,0 +1,137 @@ +/** + * File: array_binary_tree.cpp + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 数组表示下的二叉树类 */ +class ArrayBinaryTree { + public: + /* 构造方法 */ + ArrayBinaryTree(vector arr) { + tree = arr; + } + + /* 列表容量 */ + int size() { + return tree.size(); + } + + /* 获取索引为 i 节点的值 */ + int val(int i) { + // 若索引越界,则返回 INT_MAX ,代表空位 + if (i < 0 || i >= size()) + return INT_MAX; + return tree[i]; + } + + /* 获取索引为 i 节点的左子节点的索引 */ + int left(int i) { + return 2 * i + 1; + } + + /* 获取索引为 i 节点的右子节点的索引 */ + int right(int i) { + return 2 * i + 2; + } + + /* 获取索引为 i 节点的父节点的索引 */ + int parent(int i) { + return (i - 1) / 2; + } + + /* 层序遍历 */ + vector levelOrder() { + vector res; + // 直接遍历数组 + for (int i = 0; i < size(); i++) { + if (val(i) != INT_MAX) + res.push_back(val(i)); + } + return res; + } + + /* 前序遍历 */ + vector preOrder() { + vector res; + dfs(0, "pre", res); + return res; + } + + /* 中序遍历 */ + vector inOrder() { + vector res; + dfs(0, "in", res); + return res; + } + + /* 后序遍历 */ + vector postOrder() { + vector res; + dfs(0, "post", res); + return res; + } + + private: + vector tree; + + /* 深度优先遍历 */ + void dfs(int i, string order, vector &res) { + // 若为空位,则返回 + if (val(i) == INT_MAX) + return; + // 前序遍历 + if (order == "pre") + res.push_back(val(i)); + dfs(left(i), order, res); + // 中序遍历 + if (order == "in") + res.push_back(val(i)); + dfs(right(i), order, res); + // 后序遍历 + if (order == "post") + res.push_back(val(i)); + } +}; + +/* Driver Code */ +int main() { + // 初始化二叉树 + // 使用 INT_MAX 代表空位 nullptr + vector arr = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + TreeNode *root = vectorToTree(arr); + cout << "\n初始化二叉树\n"; + cout << "二叉树的数组表示:\n"; + printVector(arr); + cout << "二叉树的链表表示:\n"; + printTree(root); + + // 数组表示下的二叉树类 + ArrayBinaryTree abt(arr); + + // 访问节点 + int i = 1; + int l = abt.left(i), r = abt.right(i), p = abt.parent(i); + cout << "\n当前节点的索引为 " << i << ",值为 " << abt.val(i) << "\n"; + cout << "其左子节点的索引为 " << l << ",值为 " << (l != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; + cout << "其右子节点的索引为 " << r << ",值为 " << (r != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; + cout << "其父节点的索引为 " << p << ",值为 " << (p != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; + + // 遍历树 + vector res = abt.levelOrder(); + cout << "\n层序遍历为: "; + printVector(res); + res = abt.preOrder(); + cout << "前序遍历为: "; + printVector(res); + res = abt.inOrder(); + cout << "中序遍历为: "; + printVector(res); + res = abt.postOrder(); + cout << "后序遍历为: "; + printVector(res); + + return 0; +} diff --git a/chapter_tree/avl_tree.cpp b/chapter_tree/avl_tree.cpp new file mode 100644 index 0000000..be5b40d --- /dev/null +++ b/chapter_tree/avl_tree.cpp @@ -0,0 +1,233 @@ +/** + * File: avl_tree.cpp + * Created Time: 2023-02-03 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* AVL 树 */ +class AVLTree { + public: + TreeNode *root; // 根节点 + private: + /* 更新节点高度 */ + void updateHeight(TreeNode *node) { + // 节点高度等于最高子树高度 + 1 + node->height = max(height(node->left), height(node->right)) + 1; + } + + /* 右旋操作 */ + TreeNode *rightRotate(TreeNode *node) { + TreeNode *child = node->left; + TreeNode *grandChild = child->right; + // 以 child 为原点,将 node 向右旋转 + child->right = node; + node->left = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 左旋操作 */ + TreeNode *leftRotate(TreeNode *node) { + TreeNode *child = node->right; + TreeNode *grandChild = child->left; + // 以 child 为原点,将 node 向左旋转 + child->left = node; + node->right = grandChild; + // 更新节点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode *rotate(TreeNode *node) { + // 获取节点 node 的平衡因子 + int _balanceFactor = balanceFactor(node); + // 左偏树 + if (_balanceFactor > 1) { + if (balanceFactor(node->left) >= 0) { + // 右旋 + return rightRotate(node); + } else { + // 先左旋后右旋 + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // 右偏树 + if (_balanceFactor < -1) { + if (balanceFactor(node->right) <= 0) { + // 左旋 + return leftRotate(node); + } else { + // 先右旋后左旋 + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // 平衡树,无须旋转,直接返回 + return node; + } + + /* 递归插入节点(辅助方法) */ + TreeNode *insertHelper(TreeNode *node, int val) { + if (node == nullptr) + return new TreeNode(val); + /* 1. 查找插入位置并插入节点 */ + if (val < node->val) + node->left = insertHelper(node->left, val); + else if (val > node->val) + node->right = insertHelper(node->right, val); + else + return node; // 重复节点不插入,直接返回 + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + + /* 递归删除节点(辅助方法) */ + TreeNode *removeHelper(TreeNode *node, int val) { + if (node == nullptr) + return nullptr; + /* 1. 查找节点并删除 */ + if (val < node->val) + node->left = removeHelper(node->left, val); + else if (val > node->val) + node->right = removeHelper(node->right, val); + else { + if (node->left == nullptr || node->right == nullptr) { + TreeNode *child = node->left != nullptr ? node->left : node->right; + // 子节点数量 = 0 ,直接删除 node 并返回 + if (child == nullptr) { + delete node; + return nullptr; + } + // 子节点数量 = 1 ,直接删除 node + else { + delete node; + node = child; + } + } else { + // 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点 + TreeNode *temp = node->right; + while (temp->left != nullptr) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + updateHeight(node); // 更新节点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + + public: + /* 获取节点高度 */ + int height(TreeNode *node) { + // 空节点高度为 -1 ,叶节点高度为 0 + return node == nullptr ? -1 : node->height; + } + + /* 获取平衡因子 */ + int balanceFactor(TreeNode *node) { + // 空节点平衡因子为 0 + if (node == nullptr) + return 0; + // 节点平衡因子 = 左子树高度 - 右子树高度 + return height(node->left) - height(node->right); + } + + /* 插入节点 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* 删除节点 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* 查找节点 */ + TreeNode *search(int val) { + TreeNode *cur = root; + // 循环查找,越过叶节点后跳出 + while (cur != nullptr) { + // 目标节点在 cur 的右子树中 + if (cur->val < val) + cur = cur->right; + // 目标节点在 cur 的左子树中 + else if (cur->val > val) + cur = cur->left; + // 找到目标节点,跳出循环 + else + break; + } + // 返回目标节点 + return cur; + } + + /*构造方法*/ + AVLTree() : root(nullptr) { + } + + /*析构方法*/ + ~AVLTree() { + freeMemoryTree(root); + } +}; + +void testInsert(AVLTree &tree, int val) { + tree.insert(val); + cout << "\n插入节点 " << val << " 后,AVL 树为" << endl; + printTree(tree.root); +} + +void testRemove(AVLTree &tree, int val) { + tree.remove(val); + cout << "\n删除节点 " << val << " 后,AVL 树为" << endl; + printTree(tree.root); +} + +/* Driver Code */ +int main() { + /* 初始化空 AVL 树 */ + AVLTree avlTree; + + /* 插入节点 */ + // 请关注插入节点后,AVL 树是如何保持平衡的 + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 插入重复节点 */ + testInsert(avlTree, 7); + + /* 删除节点 */ + // 请关注删除节点后,AVL 树是如何保持平衡的 + testRemove(avlTree, 8); // 删除度为 0 的节点 + testRemove(avlTree, 5); // 删除度为 1 的节点 + testRemove(avlTree, 4); // 删除度为 2 的节点 + + /* 查询节点 */ + TreeNode *node = avlTree.search(7); + cout << "\n查找到的节点对象为 " << node << ",节点值 = " << node->val << endl; +} diff --git a/chapter_tree/binary_search_tree.cpp b/chapter_tree/binary_search_tree.cpp new file mode 100644 index 0000000..420d0d1 --- /dev/null +++ b/chapter_tree/binary_search_tree.cpp @@ -0,0 +1,170 @@ +/** + * File: binary_search_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二叉搜索树 */ +class BinarySearchTree { + private: + TreeNode *root; + + public: + /* 构造方法 */ + BinarySearchTree() { + // 初始化空树 + root = nullptr; + } + + /* 析构方法 */ + ~BinarySearchTree() { + freeMemoryTree(root); + } + + /* 获取二叉树根节点 */ + TreeNode *getRoot() { + return root; + } + + /* 查找节点 */ + TreeNode *search(int num) { + TreeNode *cur = root; + // 循环查找,越过叶节点后跳出 + while (cur != nullptr) { + // 目标节点在 cur 的右子树中 + if (cur->val < num) + cur = cur->right; + // 目标节点在 cur 的左子树中 + else if (cur->val > num) + cur = cur->left; + // 找到目标节点,跳出循环 + else + break; + } + // 返回目标节点 + return cur; + } + + /* 插入节点 */ + void insert(int num) { + // 若树为空,则初始化根节点 + if (root == nullptr) { + root = new TreeNode(num); + return; + } + TreeNode *cur = root, *pre = nullptr; + // 循环查找,越过叶节点后跳出 + while (cur != nullptr) { + // 找到重复节点,直接返回 + if (cur->val == num) + return; + pre = cur; + // 插入位置在 cur 的右子树中 + if (cur->val < num) + cur = cur->right; + // 插入位置在 cur 的左子树中 + else + cur = cur->left; + } + // 插入节点 + TreeNode *node = new TreeNode(num); + if (pre->val < num) + pre->right = node; + else + pre->left = node; + } + + /* 删除节点 */ + void remove(int num) { + // 若树为空,直接提前返回 + if (root == nullptr) + return; + TreeNode *cur = root, *pre = nullptr; + // 循环查找,越过叶节点后跳出 + while (cur != nullptr) { + // 找到待删除节点,跳出循环 + if (cur->val == num) + break; + pre = cur; + // 待删除节点在 cur 的右子树中 + if (cur->val < num) + cur = cur->right; + // 待删除节点在 cur 的左子树中 + else + cur = cur->left; + } + // 若无待删除节点,则直接返回 + if (cur == nullptr) + return; + // 子节点数量 = 0 or 1 + if (cur->left == nullptr || cur->right == nullptr) { + // 当子节点数量 = 0 / 1 时, child = nullptr / 该子节点 + TreeNode *child = cur->left != nullptr ? cur->left : cur->right; + // 删除节点 cur + if (cur != root) { + if (pre->left == cur) + pre->left = child; + else + pre->right = child; + } else { + // 若删除节点为根节点,则重新指定根节点 + root = child; + } + // 释放内存 + delete cur; + } + // 子节点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个节点 + TreeNode *tmp = cur->right; + while (tmp->left != nullptr) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // 递归删除节点 tmp + remove(tmp->val); + // 用 tmp 覆盖 cur + cur->val = tmpVal; + } + } +}; + +/* Driver Code */ +int main() { + /* 初始化二叉搜索树 */ + BinarySearchTree *bst = new BinarySearchTree(); + // 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树 + vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + for (int num : nums) { + bst->insert(num); + } + cout << endl << "初始化的二叉树为\n" << endl; + printTree(bst->getRoot()); + + /* 查找节点 */ + TreeNode *node = bst->search(7); + cout << endl << "查找到的节点对象为 " << node << ",节点值 = " << node->val << endl; + + /* 插入节点 */ + bst->insert(16); + cout << endl << "插入节点 16 后,二叉树为\n" << endl; + printTree(bst->getRoot()); + + /* 删除节点 */ + bst->remove(1); + cout << endl << "删除节点 1 后,二叉树为\n" << endl; + printTree(bst->getRoot()); + bst->remove(2); + cout << endl << "删除节点 2 后,二叉树为\n" << endl; + printTree(bst->getRoot()); + bst->remove(4); + cout << endl << "删除节点 4 后,二叉树为\n" << endl; + printTree(bst->getRoot()); + + // 释放内存 + delete bst; + + return 0; +} diff --git a/chapter_tree/binary_tree.cpp b/chapter_tree/binary_tree.cpp new file mode 100644 index 0000000..8146d6a --- /dev/null +++ b/chapter_tree/binary_tree.cpp @@ -0,0 +1,43 @@ +/** + * File: binary_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* Driver Code */ +int main() { + /* 初始化二叉树 */ + // 初始化节点 + TreeNode *n1 = new TreeNode(1); + TreeNode *n2 = new TreeNode(2); + TreeNode *n3 = new TreeNode(3); + TreeNode *n4 = new TreeNode(4); + TreeNode *n5 = new TreeNode(5); + // 构建节点之间的引用(指针) + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + cout << endl << "初始化二叉树\n" << endl; + printTree(n1); + + /* 插入与删除节点 */ + TreeNode *P = new TreeNode(0); + // 在 n1 -> n2 中间插入节点 P + n1->left = P; + P->left = n2; + cout << endl << "插入节点 P 后\n" << endl; + printTree(n1); + // 删除节点 P + n1->left = n2; + delete P; // 释放内存 + cout << endl << "删除节点 P 后\n" << endl; + printTree(n1); + + // 释放内存 + freeMemoryTree(n1); + + return 0; +} diff --git a/chapter_tree/binary_tree_bfs.cpp b/chapter_tree/binary_tree_bfs.cpp new file mode 100644 index 0000000..587514b --- /dev/null +++ b/chapter_tree/binary_tree_bfs.cpp @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 层序遍历 */ +vector levelOrder(TreeNode *root) { + // 初始化队列,加入根节点 + queue queue; + queue.push(root); + // 初始化一个列表,用于保存遍历序列 + vector vec; + while (!queue.empty()) { + TreeNode *node = queue.front(); + queue.pop(); // 队列出队 + vec.push_back(node->val); // 保存节点值 + if (node->left != nullptr) + queue.push(node->left); // 左子节点入队 + if (node->right != nullptr) + queue.push(node->right); // 右子节点入队 + } + return vec; +} + +/* Driver Code */ +int main() { + /* 初始化二叉树 */ + // 这里借助了一个从数组直接生成二叉树的函数 + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); + cout << endl << "初始化二叉树\n" << endl; + printTree(root); + + /* 层序遍历 */ + vector vec = levelOrder(root); + cout << endl << "层序遍历的节点打印序列 = "; + printVector(vec); + + return 0; +} diff --git a/chapter_tree/binary_tree_dfs.cpp b/chapter_tree/binary_tree_dfs.cpp new file mode 100644 index 0000000..778c2a0 --- /dev/null +++ b/chapter_tree/binary_tree_dfs.cpp @@ -0,0 +1,69 @@ +/** + * File: binary_tree_dfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +// 初始化列表,用于存储遍历序列 +vector vec; + +/* 前序遍历 */ +void preOrder(TreeNode *root) { + if (root == nullptr) + return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + vec.push_back(root->val); + preOrder(root->left); + preOrder(root->right); +} + +/* 中序遍历 */ +void inOrder(TreeNode *root) { + if (root == nullptr) + return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root->left); + vec.push_back(root->val); + inOrder(root->right); +} + +/* 后序遍历 */ +void postOrder(TreeNode *root) { + if (root == nullptr) + return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root->left); + postOrder(root->right); + vec.push_back(root->val); +} + +/* Driver Code */ +int main() { + /* 初始化二叉树 */ + // 这里借助了一个从数组直接生成二叉树的函数 + TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); + cout << endl << "初始化二叉树\n" << endl; + printTree(root); + + /* 前序遍历 */ + vec.clear(); + preOrder(root); + cout << endl << "前序遍历的节点打印序列 = "; + printVector(vec); + + /* 中序遍历 */ + vec.clear(); + inOrder(root); + cout << endl << "中序遍历的节点打印序列 = "; + printVector(vec); + + /* 后序遍历 */ + vec.clear(); + postOrder(root); + cout << endl << "后序遍历的节点打印序列 = "; + printVector(vec); + + return 0; +} diff --git a/test/output/priority_queue_demo.exe b/test/output/priority_queue_demo.exe new file mode 100644 index 0000000..6e9c2df Binary files /dev/null and b/test/output/priority_queue_demo.exe differ diff --git a/test/priority_queue_demo.cpp b/test/priority_queue_demo.cpp new file mode 100644 index 0000000..cf7ebe6 --- /dev/null +++ b/test/priority_queue_demo.cpp @@ -0,0 +1,28 @@ +#include +#include + +struct Person { + std::string name; + int age; +}; + +struct CompareByAge { + bool operator()(const Person& p1, const Person& p2) const { + return p1.age < p2.age; + } +}; + +int main() { + std::priority_queue, CompareByAge> pq; + + pq.push({"Alice", 25}); + pq.push({"Bob", 30}); + pq.push({"Charlie", 20}); + + while (!pq.empty()) { + std::cout << pq.top().name << " "; + pq.pop(); + } + + return 0; +} \ No newline at end of file diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt new file mode 100644 index 0000000..775a558 --- /dev/null +++ b/utils/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(utils + common.hpp print_utils.hpp + list_node.hpp tree_node.hpp + vertex.hpp) \ No newline at end of file diff --git a/utils/common.hpp b/utils/common.hpp new file mode 100644 index 0000000..c72dabd --- /dev/null +++ b/utils/common.hpp @@ -0,0 +1,28 @@ +/** + * File: common.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.hpp" +#include "print_utils.hpp" +#include "tree_node.hpp" +#include "vertex.hpp" + +using namespace std; diff --git a/utils/list_node.hpp b/utils/list_node.hpp new file mode 100644 index 0000000..b68d49c --- /dev/null +++ b/utils/list_node.hpp @@ -0,0 +1,59 @@ +/** + * File: list_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* Definition for a singly-linked list node */ +struct ListNode +{ + int val; // ǽڵֵ + ListNode *next; // ָһڵָ + ListNode(int x) : val(x), next(nullptr) + { // 캯: ʼڵֵΪx, ָһڵָΪ + } +}; + +/* Generate a linked list with a vector */ // һvectorһ +ListNode *vecToLinkedList(vector list) +{ + ListNode *dum = new ListNode(0); // һڵ dum (ֵΪ0) + ListNode *head = dum; // һָڵָ head + for (int val : list) + { + head->next = new ListNode(val); // һ½ڵ, ֵΪval, ָ븳head->next + head = head->next; // headָһڵ + } + return dum->next; // ͷڵ +} + +/* Get a list node with specific value from a linked list */ +ListNode *getListNode(ListNode *head, int val) +{ + // , ҵֵΪvalĽڵ + while (head != nullptr && head->val != val) + { + head = head->next; // ָһڵ + } + return head; // ҵĽڵ, ûҵ, nullptr +} + +/* Free the memory allocated to a linked list */ +void freeMemoryLinkedList(ListNode *cur) +{ + // ͷڴ + ListNode *pre; + while (cur != nullptr) // ǰڵ㲻Ϊ + { + pre = cur; // preָǰڵ + cur = cur->next; // ǰڵָһڵ + delete pre; // ͷpreָĽڵ + } +} diff --git a/utils/print_utils.hpp b/utils/print_utils.hpp new file mode 100644 index 0000000..6686644 --- /dev/null +++ b/utils/print_utils.hpp @@ -0,0 +1,289 @@ +/** + * File: print_utils.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com), LoneRanger(836253168@qq.com) + */ + +#pragma once + +#include "list_node.hpp" +#include "tree_node.hpp" +#include +#include +#include +#include + +/* Find an element in a vector */ // һ vector вԪ +template +int vecFind(const vector &vec, T ele) +{ + int j = INT_MAX; + for (int i = 0; i < vec.size(); i++) + { + if (vec[i] == ele) // ҵԪʱԪص± + { + j = i; + } + } + return j; +} + +/* Concatenate a vector with a delim */ // һ vector ָķָӳַ +template +string strJoin(const string &delim, const T &vec) // delim Ƿָvec vector(ʹ&vecʾݵ, ǿ, Ч) +{ + ostringstream s; // һ + for (const auto &i : vec) + { + if (&i != &vec[0]) + { + s << delim; // ָӵ + } + s << i; // Ԫӵ + } + return s.str(); // .str() ijԱڷַʾ +} + +/* Repeat a string for n times */ // һַظ n +string strRepeat(string str, int n) +{ + ostringstream os; // һ + for (int i = 0; i < n; i++) + os << str; // ַظ n + return os.str(); // ַʾ +} + +/* Print an Array */ // ӡһ +template +void printArray(T *arr, int n) // arr ׵ַn ij +{ + cout << "["; + for (int i = 0; i < n - 1; i++) + { + cout << arr[i] << ", "; // ӡԪ + } + if (n >= 1) // 鳤ȴڵ 1 ʱ + cout << arr[n - 1] << "]" << endl; // ӡһԪ һ ']' + else + cout << "]" << endl; // 鳤Ϊ 0 ʱֱӴӡһ ']' +} + +/* Get the Vector String object */ // ȡһ vector ַʾ +template +string getVectorString(vector &list) // list vector +{ + return "[" + strJoin(", ", list) + "]"; // ʹ strJoin vector öӳַַ '[' ']' +} + +/* Print a vector */ // ӡһ vector +template +void printVector(vector list) +{ + cout << getVectorString(list) << '\n'; // ʹ getVectorString ȡ vector ַʾӡ +} + +/* Print a vector matrix */ // ӡһ vector +template +void printVectorMatrix(vector> &matrix) // matrix һά vector, ÿԪضһ vector, ڱʾ () +{ + cout << "[" << '\n'; + for (vector &list : matrix) // matrix еÿһ vector Ԫ + cout << " " + getVectorString(list) + "," << '\n'; // ʹ getVectorString ȡ vector ַʾӡ + cout << "]" << '\n'; +} + +/* Print a linked list */ // ӡһ +void printLinkedList(ListNode *head) +{ + vector list; // һ vector ڴ洢Ԫ + while (head != nullptr) // : ָ벻Ϊʱ, + { + list.push_back(head->val); // Ԫӵ vector + head = head->next; // ָָһԪ + } + + cout << strJoin(" -> ", list) << '\n'; // ʹ strJoin vector üͷӳַӡ +} + +/** + * This tree printer is borrowed from TECHIE DELIGHT // ӡǴ TECHIE DELIGHT õ + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +struct Trunk // ɽṹ +{ + Trunk *prev; // ָһ + string str; // ɵַʾ + Trunk(Trunk *prev, string str) // 캯 cpp е struct Ҳй캯 + { + this->prev = prev; + this->str = str; + } +}; + +/* Helper function to print branches of the binary tree */ // ڴӡĸ +void showTrunks(Trunk *p) +{ + if (p == nullptr) // Ϊʱֱӷ + { + return; + } + + // ݹõ˳ӡ˳෴ + showTrunks(p->prev); // ݹ showTrunks + cout << p->str; // ӡɵַʾ +} + +/* Print a binary tree */ // ӡһ +void printTree(TreeNode *root, Trunk *prev, bool isRight) +{ + if (root == nullptr) + { + return; + } + + string prev_str = " "; // ʼ prev_str Ϊո + Trunk trunk(prev, prev_str); // һɶ + + printTree(root->right, &trunk, true); // ݹ printTree ӡ + + if (!prev) // Ϊʱ + { + trunk.str = ""; // ɵַʾΪ "" + } + else if (isRight) // ɲΪʱ + { + trunk.str = "/"; // ɵַʾΪ "/" + prev_str = " |"; // prev_str Ϊ " |" + } + else // ɲΪҲʱ + { + trunk.str = "\\"; // ɵַʾΪ "\" + prev->str = prev_str; // prev_str Ϊ prev->str + } + + showTrunks(&trunk); // showTrunks ӡ + cout << " " << root->val << endl; // ӡڵֵ + + if (prev) // ɲΪʱ + { + prev->str = prev_str; // prev->str Ϊ prev_str + } + trunk.str = " |"; // ɵַʾΪ " |" + + printTree(root->left, &trunk, false); // ݹ printTree ӡ +} + +/* The interface of the tree printer */ // ӡĽӿ (: ĿΪ˷) +void printTree(TreeNode *root) +{ + printTree(root, nullptr, false); // printTree (ذ汾) +} + +/* Print a stack */ // ӡһջ +template +void printStack(stack stk) +{ + // Reverse the input stack // תջ + stack tmp; // һʱջ + while (!stk.empty()) // ջΪʱ (ջ) + { + tmp.push(stk.top()); // ջԪӵʱջ + stk.pop(); // ջԪ + } + + // Generate the string to print // Ҫӡַ + ostringstream s; // һ + bool flag = true; // һ־λ (ĬΪ true), ǷǵһԪ + while (!tmp.empty()) + { + if (flag) + { + s << tmp.top(); // ־λΪ true ʱջԪӵ + flag = false; // ־λΪ false + } + else // ־λΪ false ʱ + s << ", " << tmp.top(); // ջԪӵ + tmp.pop(); // ջԪ + } + cout << "[" + s.str() + "]" << '\n'; // ʹַʾӡ +} + +/* Print a queue */ // ӡһ +template +void printQueue(queue queue) // queue Ƕ +{ + // Generate the string to print // Ҫӡַ + ostringstream s; + bool flag = true; + while (!queue.empty()) + { + if (flag) + { + s << queue.front(); // ־λΪ true ʱеĶԪӵ (true ʾǵһԪ) + flag = false; + } + else + s << ", " << queue.front(); // ־λΪ false ʱеĶԪǰһţӵ + queue.pop(); // Ԫ + } + cout << "[" + s.str() + "]" << '\n'; // ʹַʾӡ +} + +/* Print a deque */ // ӡһ˫˶ +template +void printDeque(deque deque) // deque ˫˶( deck) +{ + // Generate the string to print // Ҫӡַ + ostringstream s; + bool flag = true; + while (!deque.empty()) // ˫˶вΪʱ + { + if (flag) + { + s << deque.front(); + flag = false; + } + else + s << ", " << deque.front(); + deque.pop_front(); // Ԫ + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* Print a HashMap */ // ӡһϣ +// ģ TKey TValue ֵָԵ +template +void printHashMap(unordered_map map) // map ǹϣ +{ + for (auto kv : map) // auto ؼԶƵ + { + cout << kv.first << " -> " << kv.second << '\n'; // .first ʾ.second ʾֵ + } +} + +/* Expose the underlying storage of the priority_queue container */ // ¶ȶĵײ洢 +template +S &Container(priority_queue &pq) // pq ȶ +{ + struct HackedQueue : private priority_queue // һ˽м̳еȶ + { + static S &Container(priority_queue &pq) // һ̬Ա Container + { + return pq.*&HackedQueue::c; // ȶеĵײ洢 + } + }; + return HackedQueue::Container(pq); // þ̬Ա Container +} + +/* Print a Heap (PriorityQueue) */ // ӡһѣȶУ +template +void printHeap(priority_queue &heap) // heap ȶ +{ + vector vec = Container(heap); // ȡȶеĵײ洢 + cout << "ѵʾ"; + printVector(vec); + cout << "ѵ״ʾ" << endl; + TreeNode *root = vectorToTree(vec); // ½һ rootȶеĵײ洢תΪ + printTree(root); + freeMemoryTree(root); +} diff --git a/utils/tree_node.hpp b/utils/tree_node.hpp new file mode 100644 index 0000000..de54044 --- /dev/null +++ b/utils/tree_node.hpp @@ -0,0 +1,120 @@ +/** + * File: tree_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* ڵṹ */ +struct TreeNode +{ + int val{}; // ڵֵ val {} ʾʼΪ 0 nullptr ({} C++11 : ֵʼ) + int height = 0; // ڵĸ߶ + TreeNode *parent{}; // ڵ {} ʾʼΪ nullptr + TreeNode *left{}; // {} ʾʼΪ nullptr + TreeNode *right{}; // Һ {} ʾʼΪ nullptr + TreeNode() = default; // ĬϹ캯 + explicit TreeNode(int x, TreeNode *parent = nullptr) : val(x), parent(parent) + { + } // 캯: ʼڵֵΪx, ڵΪparent +}; + +// лο +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// ʾ +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// ʾ +// / 15 +// / 7 +// / 3 +// | \ 6 +// | \ 12 +// 1 +// \ 2 +// | / 9 +// \ 4 +// \ 8 + +/* блΪݹ */ +/* + vectorToTreeDFS һݹ麯ڽһ͵ arr תΪһöĸڵ㡣 + IJ arr iarr һ洢ֵi ǵǰڵе + ȼ鵱ǰ i ǷԽ߶ӦֵǷΪ INT_MAXǵĻʾǰڵΪսڵ㣬 nullptr + ǰڵ㲻Ϊգᴴһµ TreeNode 󣬲 arr[i] ֵڵ val Ա + Ȼ󣬺ݹ vectorToTreeDFS ǰڵΪ 2 * i + 1Ϊ 2 * i + 2ݹõĿǽ arr еԪתΪĽڵ㣬ڵ֮ĸӹϵ + 󣬺ظڵ㣬ת̡ + ʹDFSķʽͨݹδÿڵ㣬Ķṹ +*/ +TreeNode *vectorToTreeDFS(vector &arr, int i) +{ + if (i < 0 || i >= arr.size() || arr[i] == INT_MAX) + { + return nullptr; + } + TreeNode *root = new TreeNode(arr[i]); + root->left = vectorToTreeDFS(arr, 2 * i + 1); // ݹ鹹 + root->right = vectorToTreeDFS(arr, 2 * i + 2); // ݹ鹹 + return root; // ظڵ +} + +/* блΪ */ +TreeNode *vectorToTree(vector arr) +{ + return vectorToTreeDFS(arr, 0); +} + +/* лΪбݹ */ +/* +treeToVecorDFS һݹ麯ڽĽڵֵDFS˳洢һС + +IJ + +rootָǰڵָ룬ʾǰݹĽڵ㡣 +iʾǰڵеλá +res洢ڵֵá +ʵ߼£ + +ȣ鵱ǰڵǷΪաΪգʾѾҶӽڵսڵ㣬ֱӷء +Ȼͨһѭ res չ㹻ijȣԱܹ洢ǰڵֵѭ i ڵ res ĴС + res УΪ i λõֵΪǰڵֵ +ݹ treeToVecorDFS ֱǰڵӽڵӽڵ㡣ӽڵ㣬λΪ 2 * i + 1ӽڵ㣬λΪ 2 * i + 2 +ǽĽڵֵ˳洢һУںݴ +*/ +void treeToVecorDFS(TreeNode *root, int i, vector &res) +{ + if (root == nullptr) + return; + while (i >= res.size()) + { + res.push_back(INT_MAX); // ʼΪ INT_MAX + } + res[i] = root->val; + treeToVecorDFS(root->left, 2 * i + 1, res); + treeToVecorDFS(root->right, 2 * i + 2, res); +} + +/* лΪб */ +vector treeToVecor(TreeNode *root) +{ + vector res; + treeToVecorDFS(root, 0, res); + return res; +} + +/* ͷŶڴ */ +void freeMemoryTree(TreeNode *root) +{ + if (root == nullptr) + return; + + // Ҫεݹͷڴ棬ͷŸڵڴ + freeMemoryTree(root->left); + freeMemoryTree(root->right); + delete root; +} diff --git a/utils/vertex.hpp b/utils/vertex.hpp new file mode 100644 index 0000000..d40316d --- /dev/null +++ b/utils/vertex.hpp @@ -0,0 +1,42 @@ +/** + * File: vertex.hpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#pragma once // ֹͷļظ + +#include + +using namespace std; + +/* */ +struct Vertex +{ + int val; + Vertex(int x) : val(x) + { + } +}; + +/* ֵб vals ضб vets */ +vector valsToVets(vector vals) +{ + vector vets; + for (int val : vals) + { + vets.push_back(new Vertex(val)); + } + return vets; +} + +/* 붥б vets ֵб vals */ +vector vetsToVals(vector vets) +{ + vector vals; + for (Vertex *vet : vets) + { + vals.push_back(vet->val); + } + return vals; +}