From 5d07c6b992dafd63f084d2cd2da92f29544f66df Mon Sep 17 00:00:00 2001 From: flykhan Date: Wed, 21 Feb 2024 09:31:34 +0800 Subject: [PATCH] init --- .gitignore | 68 +---- CMakeLists.txt | 20 ++ chapter_array_and_linkedlist/CMakeLists.txt | 4 + chapter_array_and_linkedlist/array.cpp | 113 +++++++ chapter_array_and_linkedlist/linked_list.cpp | 89 ++++++ chapter_array_and_linkedlist/list.cpp | 72 +++++ chapter_array_and_linkedlist/my_list.cpp | 171 +++++++++++ chapter_backtracking/CMakeLists.txt | 10 + chapter_backtracking/n_queens.cpp | 65 ++++ chapter_backtracking/permutations_i.cpp | 54 ++++ chapter_backtracking/permutations_ii.cpp | 56 ++++ .../preorder_traversal_i_compact.cpp | 39 +++ .../preorder_traversal_ii_compact.cpp | 46 +++ .../preorder_traversal_iii_compact.cpp | 47 +++ .../preorder_traversal_iii_template.cpp | 76 +++++ chapter_backtracking/subset_sum_i.cpp | 57 ++++ chapter_backtracking/subset_sum_i_naive.cpp | 54 ++++ chapter_backtracking/subset_sum_ii.cpp | 62 ++++ .../CMakeLists.txt | 5 + .../iteration.cpp | 76 +++++ .../recursion.cpp | 78 +++++ .../space_complexity.cpp | 107 +++++++ .../time_complexity.cpp | 168 ++++++++++ .../worst_best_time_complexity.cpp | 45 +++ chapter_divide_and_conquer/CMakeLists.txt | 3 + .../binary_search_recur.cpp | 46 +++ chapter_divide_and_conquer/build_tree.cpp | 51 ++++ chapter_divide_and_conquer/hanota.cpp | 66 ++++ chapter_dynamic_programming/CMakeLists.txt | 10 + .../climbing_stairs_backtrack.cpp | 43 +++ .../climbing_stairs_constraint_dp.cpp | 37 +++ .../climbing_stairs_dfs.cpp | 32 ++ .../climbing_stairs_dfs_mem.cpp | 39 +++ .../climbing_stairs_dp.cpp | 49 +++ chapter_dynamic_programming/coin_change.cpp | 70 +++++ .../coin_change_ii.cpp | 68 +++++ chapter_dynamic_programming/edit_distance.cpp | 136 +++++++++ chapter_dynamic_programming/knapsack.cpp | 109 +++++++ .../min_cost_climbing_stairs_dp.cpp | 53 ++++ chapter_dynamic_programming/min_path_sum.cpp | 116 +++++++ .../unbounded_knapsack.cpp | 64 ++++ chapter_graph/CMakeLists.txt | 5 + chapter_graph/graph_adjacency_list.cpp | 90 ++++++ chapter_graph/graph_adjacency_list_test.cpp | 49 +++ chapter_graph/graph_adjacency_matrix.cpp | 127 ++++++++ chapter_graph/graph_bfs.cpp | 59 ++++ chapter_graph/graph_dfs.cpp | 55 ++++ chapter_greedy/CMakeLists.txt | 3 + chapter_greedy/coin_change_greedy.cpp | 60 ++++ chapter_greedy/fractional_knapsack.cpp | 56 ++++ chapter_greedy/max_capacity.cpp | 39 +++ chapter_greedy/max_product_cutting.cpp | 39 +++ chapter_hashing/CMakeLists.txt | 6 + chapter_hashing/array_hash_map.cpp | 110 +++++++ chapter_hashing/array_hash_map_test.cpp | 52 ++++ chapter_hashing/built_in_hash.cpp | 29 ++ chapter_hashing/hash_map.cpp | 46 +++ chapter_hashing/hash_map_chaining.cpp | 150 +++++++++ chapter_hashing/hash_map_open_addressing.cpp | 171 +++++++++++ chapter_hashing/simple_hash.cpp | 66 ++++ chapter_heap/CMakeLists.txt | 3 + chapter_heap/heap.cpp | 66 ++++ chapter_heap/my_heap.cpp | 155 ++++++++++ chapter_heap/top_k.cpp | 38 +++ chapter_searching/CMakeLists.txt | 4 + chapter_searching/binary_search.cpp | 59 ++++ chapter_searching/binary_search_edge.cpp | 66 ++++ chapter_searching/binary_search_insertion.cpp | 66 ++++ chapter_searching/hashing_search.cpp | 53 ++++ chapter_searching/linear_search.cpp | 49 +++ chapter_searching/two_sum.cpp | 54 ++++ chapter_sorting/CMakeLists.txt | 6 + chapter_sorting/bubble_sort.cpp | 56 ++++ chapter_sorting/bucket_sort.cpp | 44 +++ chapter_sorting/counting_sort.cpp | 77 +++++ chapter_sorting/heap_sort.cpp | 54 ++++ chapter_sorting/insertion_sort.cpp | 31 ++ chapter_sorting/merge_sort.cpp | 58 ++++ chapter_sorting/quick_sort.cpp | 168 ++++++++++ chapter_sorting/radix_sort.cpp | 65 ++++ chapter_sorting/selection_sort.cpp | 34 +++ chapter_stack_and_queue/CMakeLists.txt | 9 + chapter_stack_and_queue/array_deque.cpp | 156 ++++++++++ chapter_stack_and_queue/array_queue.cpp | 129 ++++++++ chapter_stack_and_queue/array_stack.cpp | 85 ++++++ chapter_stack_and_queue/deque.cpp | 46 +++ chapter_stack_and_queue/linkedlist_deque.cpp | 194 ++++++++++++ chapter_stack_and_queue/linkedlist_queue.cpp | 120 ++++++++ chapter_stack_and_queue/linkedlist_stack.cpp | 109 +++++++ chapter_stack_and_queue/queue.cpp | 41 +++ chapter_stack_and_queue/stack.cpp | 41 +++ chapter_tree/CMakeLists.txt | 6 + chapter_tree/array_binary_tree.cpp | 137 +++++++++ chapter_tree/avl_tree.cpp | 233 ++++++++++++++ chapter_tree/binary_search_tree.cpp | 170 +++++++++++ chapter_tree/binary_tree.cpp | 43 +++ chapter_tree/binary_tree_bfs.cpp | 42 +++ chapter_tree/binary_tree_dfs.cpp | 69 +++++ test/output/priority_queue_demo.exe | Bin 0 -> 276125 bytes test/priority_queue_demo.cpp | 28 ++ utils/CMakeLists.txt | 4 + utils/common.hpp | 28 ++ utils/list_node.hpp | 59 ++++ utils/print_utils.hpp | 289 ++++++++++++++++++ utils/tree_node.hpp | 120 ++++++++ utils/vertex.hpp | 42 +++ 106 files changed, 7132 insertions(+), 60 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 chapter_array_and_linkedlist/CMakeLists.txt create mode 100644 chapter_array_and_linkedlist/array.cpp create mode 100644 chapter_array_and_linkedlist/linked_list.cpp create mode 100644 chapter_array_and_linkedlist/list.cpp create mode 100644 chapter_array_and_linkedlist/my_list.cpp create mode 100644 chapter_backtracking/CMakeLists.txt create mode 100644 chapter_backtracking/n_queens.cpp create mode 100644 chapter_backtracking/permutations_i.cpp create mode 100644 chapter_backtracking/permutations_ii.cpp create mode 100644 chapter_backtracking/preorder_traversal_i_compact.cpp create mode 100644 chapter_backtracking/preorder_traversal_ii_compact.cpp create mode 100644 chapter_backtracking/preorder_traversal_iii_compact.cpp create mode 100644 chapter_backtracking/preorder_traversal_iii_template.cpp create mode 100644 chapter_backtracking/subset_sum_i.cpp create mode 100644 chapter_backtracking/subset_sum_i_naive.cpp create mode 100644 chapter_backtracking/subset_sum_ii.cpp create mode 100644 chapter_computational_complexity/CMakeLists.txt create mode 100644 chapter_computational_complexity/iteration.cpp create mode 100644 chapter_computational_complexity/recursion.cpp create mode 100644 chapter_computational_complexity/space_complexity.cpp create mode 100644 chapter_computational_complexity/time_complexity.cpp create mode 100644 chapter_computational_complexity/worst_best_time_complexity.cpp create mode 100644 chapter_divide_and_conquer/CMakeLists.txt create mode 100644 chapter_divide_and_conquer/binary_search_recur.cpp create mode 100644 chapter_divide_and_conquer/build_tree.cpp create mode 100644 chapter_divide_and_conquer/hanota.cpp create mode 100644 chapter_dynamic_programming/CMakeLists.txt create mode 100644 chapter_dynamic_programming/climbing_stairs_backtrack.cpp create mode 100644 chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp create mode 100644 chapter_dynamic_programming/climbing_stairs_dfs.cpp create mode 100644 chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp create mode 100644 chapter_dynamic_programming/climbing_stairs_dp.cpp create mode 100644 chapter_dynamic_programming/coin_change.cpp create mode 100644 chapter_dynamic_programming/coin_change_ii.cpp create mode 100644 chapter_dynamic_programming/edit_distance.cpp create mode 100644 chapter_dynamic_programming/knapsack.cpp create mode 100644 chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp create mode 100644 chapter_dynamic_programming/min_path_sum.cpp create mode 100644 chapter_dynamic_programming/unbounded_knapsack.cpp create mode 100644 chapter_graph/CMakeLists.txt create mode 100644 chapter_graph/graph_adjacency_list.cpp create mode 100644 chapter_graph/graph_adjacency_list_test.cpp create mode 100644 chapter_graph/graph_adjacency_matrix.cpp create mode 100644 chapter_graph/graph_bfs.cpp create mode 100644 chapter_graph/graph_dfs.cpp create mode 100644 chapter_greedy/CMakeLists.txt create mode 100644 chapter_greedy/coin_change_greedy.cpp create mode 100644 chapter_greedy/fractional_knapsack.cpp create mode 100644 chapter_greedy/max_capacity.cpp create mode 100644 chapter_greedy/max_product_cutting.cpp create mode 100644 chapter_hashing/CMakeLists.txt create mode 100644 chapter_hashing/array_hash_map.cpp create mode 100644 chapter_hashing/array_hash_map_test.cpp create mode 100644 chapter_hashing/built_in_hash.cpp create mode 100644 chapter_hashing/hash_map.cpp create mode 100644 chapter_hashing/hash_map_chaining.cpp create mode 100644 chapter_hashing/hash_map_open_addressing.cpp create mode 100644 chapter_hashing/simple_hash.cpp create mode 100644 chapter_heap/CMakeLists.txt create mode 100644 chapter_heap/heap.cpp create mode 100644 chapter_heap/my_heap.cpp create mode 100644 chapter_heap/top_k.cpp create mode 100644 chapter_searching/CMakeLists.txt create mode 100644 chapter_searching/binary_search.cpp create mode 100644 chapter_searching/binary_search_edge.cpp create mode 100644 chapter_searching/binary_search_insertion.cpp create mode 100644 chapter_searching/hashing_search.cpp create mode 100644 chapter_searching/linear_search.cpp create mode 100644 chapter_searching/two_sum.cpp create mode 100644 chapter_sorting/CMakeLists.txt create mode 100644 chapter_sorting/bubble_sort.cpp create mode 100644 chapter_sorting/bucket_sort.cpp create mode 100644 chapter_sorting/counting_sort.cpp create mode 100644 chapter_sorting/heap_sort.cpp create mode 100644 chapter_sorting/insertion_sort.cpp create mode 100644 chapter_sorting/merge_sort.cpp create mode 100644 chapter_sorting/quick_sort.cpp create mode 100644 chapter_sorting/radix_sort.cpp create mode 100644 chapter_sorting/selection_sort.cpp create mode 100644 chapter_stack_and_queue/CMakeLists.txt create mode 100644 chapter_stack_and_queue/array_deque.cpp create mode 100644 chapter_stack_and_queue/array_queue.cpp create mode 100644 chapter_stack_and_queue/array_stack.cpp create mode 100644 chapter_stack_and_queue/deque.cpp create mode 100644 chapter_stack_and_queue/linkedlist_deque.cpp create mode 100644 chapter_stack_and_queue/linkedlist_queue.cpp create mode 100644 chapter_stack_and_queue/linkedlist_stack.cpp create mode 100644 chapter_stack_and_queue/queue.cpp create mode 100644 chapter_stack_and_queue/stack.cpp create mode 100644 chapter_tree/CMakeLists.txt create mode 100644 chapter_tree/array_binary_tree.cpp create mode 100644 chapter_tree/avl_tree.cpp create mode 100644 chapter_tree/binary_search_tree.cpp create mode 100644 chapter_tree/binary_tree.cpp create mode 100644 chapter_tree/binary_tree_bfs.cpp create mode 100644 chapter_tree/binary_tree_dfs.cpp create mode 100644 test/output/priority_queue_demo.exe create mode 100644 test/priority_queue_demo.cpp create mode 100644 utils/CMakeLists.txt create mode 100644 utils/common.hpp create mode 100644 utils/list_node.hpp create mode 100644 utils/print_utils.hpp create mode 100644 utils/tree_node.hpp create mode 100644 utils/vertex.hpp 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 0000000000000000000000000000000000000000..6e9c2df9aba2155936f25be5b7f0061b47351f24 GIT binary patch literal 276125 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 literal 0 HcmV?d00001 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; +}