├── .gitignore ├── Lab5 ├── data │ ├── data_no_lcs.txt │ └── data.txt ├── report │ ├── assets │ │ ├── 1.png │ │ ├── 2.png │ │ └── 3.png │ └── 算法第五次实验实验报告.md └── src │ └── LCS.cpp ├── Lab7 ├── data │ ├── test1.txt │ ├── test2.txt │ └── test3.txt ├── report │ ├── assets │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ └── 算法第七次实验实验报告.md ├── CMakeLists.txt └── src │ ├── Schedule.h │ ├── main.cpp │ └── Schedule.cpp ├── Lab3 ├── data │ └── insert.txt ├── report │ ├── assets │ │ ├── 0.png │ │ ├── LNR.png │ │ ├── LOT.png │ │ ├── NLR.png │ │ └── template.png │ └── 算法第三次实验.md └── src │ └── RBT.cpp ├── HWs ├── hw10.pdf ├── hw11.pdf ├── hw12.pdf ├── hw2.pdf ├── hw3.pdf ├── hw4.pdf ├── hw5.pdf ├── hw6.pdf ├── hw7.pdf ├── hw8.pdf └── hw9.pdf ├── Lab1 ├── report │ ├── assets │ │ ├── 1.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── 15.png │ │ ├── 2.png │ │ ├── 21.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ └── 8.png │ └── 算法第一次实验.md └── src │ ├── qsort_vector.cpp │ └── qsort.c ├── Lab2 ├── report │ ├── assets │ │ └── 1.png │ └── 算法第二次实验.md └── src │ └── ClosestPointPair.cpp ├── Lab4 ├── report │ ├── assets │ │ ├── 1.png │ │ ├── 2.png │ │ └── 3.png │ └── 算法第四次实验实验报告.md ├── data │ └── insert.txt └── src │ ├── IntervalTree.cpp │ └── IntervalTree.h ├── Lab6 ├── report │ ├── assets │ │ ├── 1.png │ │ └── 2.png │ └── 算法第六次实验实验报告.md ├── CMakeLists.txt ├── src │ ├── main.cpp │ ├── Huffman.h │ └── Huffman.cpp └── data │ └── table.txt ├── Lab8 ├── report │ ├── assets │ │ ├── 1.png │ │ ├── 2.png │ │ └── 3.png │ └── 算法第八次实验实验报告.md ├── data │ └── data.txt ├── CMakeLists.txt └── src │ ├── SparseMatrix.h │ ├── main.cpp │ ├── BFS.cpp │ └── SparseMatrix.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode -------------------------------------------------------------------------------- /Lab5/data/data_no_lcs.txt: -------------------------------------------------------------------------------- 1 | abc 2 | def -------------------------------------------------------------------------------- /Lab7/data/test1.txt: -------------------------------------------------------------------------------- 1 | 10 3 2 | 47 20 28 44 21 45 30 39 28 33 -------------------------------------------------------------------------------- /Lab7/data/test2.txt: -------------------------------------------------------------------------------- 1 | 15 5 2 | 98 84 50 23 32 99 22 76 72 61 81 39 76 53 37 -------------------------------------------------------------------------------- /Lab3/data/insert.txt: -------------------------------------------------------------------------------- 1 | 20 2 | 12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17 -------------------------------------------------------------------------------- /Lab5/data/data.txt: -------------------------------------------------------------------------------- 1 | abcdefghigkllmnopqrssstuvwxyz 2 | aabcdefghigklmnopopqrsttuvwxyz -------------------------------------------------------------------------------- /Lab7/data/test3.txt: -------------------------------------------------------------------------------- 1 | 19 8 2 | 39 39 23 45 100 69 21 81 39 55 20 86 34 53 58 99 36 45 46 -------------------------------------------------------------------------------- /HWs/hw10.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw10.pdf -------------------------------------------------------------------------------- /HWs/hw11.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw11.pdf -------------------------------------------------------------------------------- /HWs/hw12.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw12.pdf -------------------------------------------------------------------------------- /HWs/hw2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw2.pdf -------------------------------------------------------------------------------- /HWs/hw3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw3.pdf -------------------------------------------------------------------------------- /HWs/hw4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw4.pdf -------------------------------------------------------------------------------- /HWs/hw5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw5.pdf -------------------------------------------------------------------------------- /HWs/hw6.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw6.pdf -------------------------------------------------------------------------------- /HWs/hw7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw7.pdf -------------------------------------------------------------------------------- /HWs/hw8.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw8.pdf -------------------------------------------------------------------------------- /HWs/hw9.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/HWs/hw9.pdf -------------------------------------------------------------------------------- /Lab1/report/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/1.png -------------------------------------------------------------------------------- /Lab1/report/assets/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/11.png -------------------------------------------------------------------------------- /Lab1/report/assets/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/12.png -------------------------------------------------------------------------------- /Lab1/report/assets/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/13.png -------------------------------------------------------------------------------- /Lab1/report/assets/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/14.png -------------------------------------------------------------------------------- /Lab1/report/assets/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/15.png -------------------------------------------------------------------------------- /Lab1/report/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/2.png -------------------------------------------------------------------------------- /Lab1/report/assets/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/21.png -------------------------------------------------------------------------------- /Lab1/report/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/3.png -------------------------------------------------------------------------------- /Lab1/report/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/4.png -------------------------------------------------------------------------------- /Lab1/report/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/5.png -------------------------------------------------------------------------------- /Lab1/report/assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/6.png -------------------------------------------------------------------------------- /Lab1/report/assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/7.png -------------------------------------------------------------------------------- /Lab1/report/assets/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab1/report/assets/8.png -------------------------------------------------------------------------------- /Lab2/report/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab2/report/assets/1.png -------------------------------------------------------------------------------- /Lab3/report/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab3/report/assets/0.png -------------------------------------------------------------------------------- /Lab4/report/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab4/report/assets/1.png -------------------------------------------------------------------------------- /Lab4/report/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab4/report/assets/2.png -------------------------------------------------------------------------------- /Lab4/report/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab4/report/assets/3.png -------------------------------------------------------------------------------- /Lab5/report/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab5/report/assets/1.png -------------------------------------------------------------------------------- /Lab5/report/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab5/report/assets/2.png -------------------------------------------------------------------------------- /Lab5/report/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab5/report/assets/3.png -------------------------------------------------------------------------------- /Lab6/report/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab6/report/assets/1.png -------------------------------------------------------------------------------- /Lab6/report/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab6/report/assets/2.png -------------------------------------------------------------------------------- /Lab7/report/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab7/report/assets/1.png -------------------------------------------------------------------------------- /Lab7/report/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab7/report/assets/2.png -------------------------------------------------------------------------------- /Lab7/report/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab7/report/assets/3.png -------------------------------------------------------------------------------- /Lab7/report/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab7/report/assets/4.png -------------------------------------------------------------------------------- /Lab8/report/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab8/report/assets/1.png -------------------------------------------------------------------------------- /Lab8/report/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab8/report/assets/2.png -------------------------------------------------------------------------------- /Lab8/report/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab8/report/assets/3.png -------------------------------------------------------------------------------- /Lab3/report/assets/LNR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab3/report/assets/LNR.png -------------------------------------------------------------------------------- /Lab3/report/assets/LOT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab3/report/assets/LOT.png -------------------------------------------------------------------------------- /Lab3/report/assets/NLR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab3/report/assets/NLR.png -------------------------------------------------------------------------------- /Lab3/report/assets/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melmaphother/USTC-2023-Algorithm-Labs/HEAD/Lab3/report/assets/template.png -------------------------------------------------------------------------------- /Lab8/data/data.txt: -------------------------------------------------------------------------------- 1 | A,B,C,D,E,F,G,H,I 2 | A-B 3 | A-C 4 | B-D 5 | B-E 6 | B-G 7 | C-H 8 | C-G 9 | C-F 10 | D-E 11 | D-G 12 | E-H 13 | D-H 14 | D-I 15 | E-I 16 | F-H 17 | F-I 18 | H-G 19 | H-I -------------------------------------------------------------------------------- /Lab4/data/insert.txt: -------------------------------------------------------------------------------- 1 | 30 2 | 18 23 3 | 45 46 4 | 30 34 5 | 2 17 6 | 13 18 7 | 9 18 8 | 32 43 9 | 43 45 10 | 21 24 11 | 6 15 12 | 49 50 13 | 14 22 14 | 11 22 15 | 38 43 16 | 15 16 17 | 19 24 18 | 36 42 19 | 8 13 20 | 7 14 21 | 42 50 22 | 48 49 23 | 0 1 24 | 4 15 25 | 24 25 26 | 34 43 27 | 10 17 28 | 31 40 29 | 17 18 30 | 16 25 31 | 33 37 -------------------------------------------------------------------------------- /Lab6/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # minimum required version of cmake 2 | cmake_minimum_required (VERSION 3.8) 3 | 4 | # information of the project 5 | project (Lab6) 6 | 7 | # set the C++ standard 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | # set the output path 11 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}) 12 | 13 | # set the object 14 | add_executable(main src/main.cpp src/Huffman.cpp) 15 | 16 | # set the output path of object file 17 | set_target_properties(main PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) -------------------------------------------------------------------------------- /Lab7/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # minimum required version of cmake 2 | cmake_minimum_required (VERSION 3.8) 3 | 4 | # information of the project 5 | project (Lab7) 6 | 7 | # set the C++ standard 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | # set the output path 11 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}) 12 | 13 | # set the object 14 | add_executable(main src/main.cpp src/Schedule.cpp) 15 | 16 | # set the output path of object file 17 | set_target_properties(main PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) -------------------------------------------------------------------------------- /Lab8/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # minimum required version of cmake 2 | cmake_minimum_required (VERSION 3.8) 3 | 4 | # information of the project 5 | project (Lab8) 6 | 7 | # set the C++ standard 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | # set the output path 11 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}) 12 | 13 | # set the object 14 | add_executable(main src/main.cpp src/SparseMatrix.cpp) 15 | 16 | # set the output path of object file 17 | set_target_properties(main PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) -------------------------------------------------------------------------------- /Lab8/src/SparseMatrix.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct Edge { 8 | public: 9 | Edge(int r, int c); 10 | bool operator<(const Edge &e) const; 11 | 12 | private: 13 | int row; 14 | int col; 15 | }; 16 | 17 | class SparseMatrix { 18 | public: 19 | SparseMatrix(std::string data_path, std::string mode); 20 | void bfs(); 21 | 22 | public: 23 | int get_num_vertices(); 24 | int get_num_edges(); 25 | 26 | private: 27 | int bfs(int start_vertex); 28 | 29 | private: 30 | std::unordered_map> adj_list; 31 | std::unordered_set vertices; 32 | std::set edges; 33 | std::unordered_map visited; 34 | int num_vertices; 35 | int num_edges; 36 | std::string mode; 37 | }; 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2023-Algorithm-Labs 2 | 3 | > 本仓库为徐云老师 2023 年算法基础课的实验作业仓库 4 | 5 | ## Labs 6 | 7 | | 实验编号 | 实验内容 | 难度 | 8 | | -------- | -------------- | -------------------------- | 9 | | Lab1 | 快排 | 可以折腾一下 | 10 | | Lab2 | 最邻近点 | 抄书 | 11 | | Lab3 | 红黑树 | 抄书,慎用smart_pointer | 12 | | Lab4 | 区间树 | 抄书改编 Lab3 | 13 | | Lab5 | LCS | copilot 秒了 | 14 | | Lab6 | Huffman 编码 | 抄书 | 15 | | Lab7 | 最佳调度 | 剪枝还蛮有意思 | 16 | | Lab8 | BFS 及存储优化 | 百度网盘我不好评价。。。。 | 17 | 18 | 锐评: 19 | 20 | 1. 实验不如实验报告花时间 21 | 2. 用了 C++ 11 以后的很多东西,比如智能指针,结构化绑定,练习了一下类的构造和写法,CMakeLists 的配置、写法和用法。不用这些的话每次实验也就平均 200 行。 22 | 23 | ## Homeworks 24 | 25 | 锐评: 26 | 27 | 1. 作业有的题确实挺有难度,不过这本书的答案快被传烂了,相比较而言还是数据隐私那种一道题一篇论文更刺激。 28 | 2. 本课程作业全部以 Markdown (2 到 4 次作业) 和 Latex (5 到 12 次作业) 完成,只传了 pdf 版,仅供参考,要 tex 和 md 的与我 profiles 的联系方式联系。 29 | 30 | -------------------------------------------------------------------------------- /Lab6/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Huffman.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | std::pair getCmdOption(char **begin, char **end, 9 | const std::string &option) { 10 | char **itr = std::find(begin, end, option); 11 | if (itr != end && ++itr != end) { return std::make_pair(true, *itr); } 12 | return std::make_pair(false, ""); 13 | } 14 | 15 | bool cmdOptionExists(char **begin, char **end, const std::string &option) { 16 | return std::find(begin, end, option) != end; 17 | } 18 | 19 | int main(int argc, char **argv) { 20 | std::string data_path = "..\\data\\original.txt"; 21 | std::string store_path = "..\\data\\table.txt"; 22 | auto help_info = cmdOptionExists(argv, argv + argc, "-h"); 23 | auto input_info = getCmdOption(argv, argv + argc, "-f"); 24 | auto output_info = getCmdOption(argv, argv + argc, "-o"); 25 | if (help_info) { 26 | std::cout << "-h : help message" << std::endl; 27 | std::cout << "-f [file path] : input file path" << std::endl; 28 | std::cout << "-o [file path] : output file path" << std::endl; 29 | } 30 | if (input_info.first) data_path = input_info.second; 31 | if (output_info.first) store_path = output_info.second; 32 | 33 | HuffmanCode huffman(data_path, store_path); 34 | huffman.CalcCompressRatio(); 35 | return 0; 36 | } -------------------------------------------------------------------------------- /Lab8/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "SparseMatrix.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | std::pair getCmdOption(char **begin, char **end, 8 | const std::string &option) { 9 | char **itr = std::find(begin, end, option); 10 | if (itr != end && ++itr != end) { return std::make_pair(true, *itr); } 11 | return std::make_pair(false, ""); 12 | } 13 | 14 | bool cmdOptionExists(char **begin, char **end, const std::string &option) { 15 | return std::find(begin, end, option) != end; 16 | } 17 | 18 | int main(int argc, char **argv) { 19 | std::string data_path = "../data/twitter_small.txt"; 20 | std::string mode = "small"; 21 | auto help_info = cmdOptionExists(argv, argv + argc, "-h"); 22 | auto input_info = getCmdOption(argv, argv + argc, "-f"); 23 | auto mode_info = getCmdOption(argv, argv + argc, "-m"); 24 | if (help_info) { 25 | std::cout << "-h : help message" << std::endl; 26 | std::cout << "-f [file path] : input file path" << std::endl; 27 | std::cout << "-m [small/large] : input file mode" << std::endl; 28 | } 29 | if (input_info.first) data_path = input_info.second; 30 | if (mode_info.first) mode = mode_info.second; 31 | 32 | SparseMatrix sm(data_path, mode); 33 | 34 | // std::cout << sm.get_num_vertices() << ' ' << sm.get_num_edges() 35 | // << std::endl; 36 | 37 | sm.bfs(); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /Lab6/src/Huffman.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | extern std::map freq; 8 | extern std::map code; 9 | 10 | class IO { 11 | friend class HuffmanCode; 12 | 13 | private: 14 | std::ifstream in; 15 | std::ofstream out; 16 | 17 | public: 18 | void read(const std::string &data_path); 19 | void write(const std::string &store_path, 20 | const std::map &code); 21 | }; 22 | 23 | typedef struct HuffmanNode { 24 | char character; 25 | unsigned freq; 26 | HuffmanNode *left, *right; 27 | HuffmanNode(char character, unsigned freq) 28 | : character(character), freq(freq), left(nullptr), right(nullptr) {} 29 | } *pHuffmanNode; 30 | 31 | struct compare { 32 | //* default is "less", l->freq > r->freq represents the priority of l is 33 | //* less than r 34 | bool operator()(pHuffmanNode l, pHuffmanNode r) { 35 | return (l->freq > r->freq); 36 | } 37 | }; 38 | 39 | class HuffmanCode { 40 | private: 41 | std::priority_queue, compare> 42 | minHeap; 43 | pHuffmanNode root; 44 | IO io; 45 | 46 | public: 47 | HuffmanCode(const std::string &data_path, const std::string &store_path); 48 | void CalcCompressRatio(); 49 | 50 | private: 51 | void BuildHuffmanTree(); 52 | void GenerateCode(pHuffmanNode &root, std::string str = ""); 53 | }; 54 | -------------------------------------------------------------------------------- /Lab7/src/Schedule.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | class Schedule { 7 | public: 8 | Schedule(std::string data_path); 9 | std::pair>> 10 | GetBestSchedule(); 11 | unsigned InitByGreedy(); 12 | void BackTrace(unsigned task_index, unsigned curr_spend); 13 | 14 | private: 15 | std::vector task_time; 16 | unsigned num_tasks; 17 | unsigned num_machines; 18 | 19 | private: 20 | //* best_schedule[num_tasks] indicates which machine the i−th task is 21 | //* assigned to. 22 | //! task_index start from 1, best_schedule[i] == 0 means no arrangment 23 | std::vector best_schedule; 24 | //* curr_schedule[task_index] indicates the temporary arrangement of which 25 | //* machine the task_index−th task is assigned to. 26 | std::vector curr_schedule; 27 | //* machine_time[machine_index] indicates the total time of the tasks in the 28 | //* queue of the machine_index-th machine. 29 | //! machine_index start from 1 30 | std::vector machine_time; 31 | //* machine_max_task_time[machine_index] indicates the max time of the tasks 32 | //* in the queue of the machine_index-th machine. 33 | //* as I pre-sort the task_time, so the max time of the tasks in each machine 34 | //* is the first task in the queue. 35 | //* So if the machine_max_task_time[machine_index] is larger than its previous 36 | //* it is a duplicate case. 37 | //* This method can reduce the time complexity from O(n) to O(n/k!). 38 | std::vector machine_max_task_time; 39 | //* best_spend indicates the time spend in the best arrangement. 40 | unsigned best_spend; 41 | //* curr_spend indicates the time spend in the current arrangement. 42 | //* which is the longest time of the machine queue. 43 | unsigned curr_spend; 44 | }; 45 | -------------------------------------------------------------------------------- /Lab4/src/IntervalTree.cpp: -------------------------------------------------------------------------------- 1 | #include "IntervalTree.h" 2 | #include 3 | 4 | typedef std::pair Key_Type; 5 | 6 | // #ifndef DEBUG 7 | // #define DEBUG 8 | // #endif 9 | 10 | template 11 | void InsertAllNodes(RBTree &RBT, const std::string Insert_path) { 12 | std::ifstream f; 13 | int num, low, high; 14 | T key; 15 | f.open(Insert_path, std::ios::in); 16 | if (f.is_open()) { 17 | f >> num; 18 | for (int i = 0; i < num; i++) { 19 | f >> low >> high; 20 | key = std::make_pair(low, high); 21 | RBT.RBInsert(key); 22 | } 23 | } else { 24 | std::cout << "Can not open" << Insert_path << std::endl; 25 | } 26 | f.close(); 27 | } 28 | template void Search(RBTree &RBT) { 29 | bool isFirst = true; 30 | while (true) { 31 | std::string message = isFirst ? "Input interval: [like: 12 14] ": "Input intercal: "; 32 | isFirst = false; 33 | std::cout << message; 34 | int low, high; 35 | std::cin >> low >> high; 36 | getchar(); // fflush 37 | 38 | auto interval = std::make_pair(low, high); 39 | std::vector result; 40 | RBT.IntervalSearch(interval, result); 41 | if (result.empty()) { 42 | std::cout << "No result" << std::endl; 43 | } else { 44 | std::cout << "The result is as follows:" << std::endl; 45 | } 46 | for (auto &i : result) { std::cout << i; } 47 | 48 | std::cout << "Continue? [Y/n] "; 49 | char c; 50 | std::cin >> c; 51 | if (c == 'n') break; 52 | } 53 | } 54 | 55 | int main(int argc, char **argv) { 56 | RBTree RBT; 57 | std::string Insert_path = "insert.txt"; 58 | std::string LNR_path = "Result/LNR.txt"; 59 | std::string NLR_path = "Result/NLR.txt"; 60 | std::string LOT_path = "Result/LOT.txt"; 61 | InsertAllNodes(RBT, Insert_path); 62 | RBT.Display(LNR_path, NLR_path, LOT_path); 63 | Search(RBT); 64 | return 0; 65 | } -------------------------------------------------------------------------------- /Lab2/report/算法第二次实验.md: -------------------------------------------------------------------------------- 1 | # 算法第二次实验报告 2 | 3 | > 姓名: 王道宇 学号: PB21030794 4 | 5 | ## 实验目标 6 | 7 | 在给定的 10000 个数据点中利用最邻近点对分治算法求解出最邻近点以及它们之间的距离。 8 | 9 | ## 算法思想 10 | 11 | 最邻近点对分治算法的优化过程本质上是将原先暴力搜索算法中 $O({n \choose 2})$ 的时间复杂度一步一步降低。 12 | 13 | 首先通过分治算法将递推关系式变为 $T(n) = 2T(\frac{n}{2}) + O(nlog(n)) $,再通过数学推导,将递推关系式改为 $T(n) = 2T(\frac{n}{2})+O(n)$。亦即将复杂度从 $T(nlog^2(n))$ 降到 $T(nlog(n))$ 14 | 15 | 优化的核心思想就是预先进行一次预排序,之后在 $O(n)$ 时间内生成已排序的子数组。 16 | 17 | 这样的结果就是在总的时间复杂度中加上 $O(nlog(n))$,可见对总时间复杂度没有影响。 18 | 19 | #### 优化方向 20 | 21 | - 数据结构的设计: 22 | 23 | 对于点,使用元组记录它的 编号、横坐标、纵坐标,并使用标准命名空间中的 get 函数取元组对应的值; 24 | 25 | 对于点对,使用元组记录它的 第一个点、第二个点、距离,并使用标准命名空间中的 get 函数取元组对应的值。 26 | 27 | ```cpp 28 | typedef tuple Point; 29 | typedef tuple PointPair; 30 | 31 | #define serial 0 32 | #define loc_X 1 33 | #define loc_Y 2 34 | 35 | #define _p1 0 36 | #define _p2 1 37 | #define _dist 2 38 | ``` 39 | 40 | 获取元组的元素的值就可以用 get(Point_1) 的方式,较为简便和优雅。 41 | 42 | - 类的设计 43 | 44 | 在构造函数中使用文件路径作为参数实例化对象,将文件中的数据加载进 vector 中 45 | 46 | 类中实现了两种方式求最邻近点,分别是采用分治的最邻近点算法以及暴力搜索法 47 | 48 | 类中的私有行为包括预排序、递归、合并(照着书写?),以及调用 sort 函数的自定义比较函数。 49 | 50 | - 一些优化 51 | 52 | 可以发现,在递归中,按横坐标排序的 vector X 一直不需要变化,而是按纵坐标排序的 vector Y 需要按照对应的横坐标范围需要改变。所以在临时变量的设计上,可以只用 X 的左右两个边界表示当前需要处理的 X 中的范围,而只新创建 vector Y 的临时子序列。这样可以节省栈空间。函数定义如下: 53 | 54 | ```cpp 55 | /** 56 | * @brief Recursion 57 | * 58 | * @param Points_X_Sort sorted vector X (still initial X) 59 | * @param Points_Y_Sort sorted vector Y (slice Y) 60 | * @param p begin of vector X 61 | * @param r end of vector X 62 | * @return PointPair closest PointPair 63 | */ 64 | PointPair ClosestPair::ClosestPointPair(vector &Points_X_Sort, 65 | vector &Points_Y_Sort, 66 | unsigned p, unsigned r); 67 | ``` 68 | 69 | ## 实验结果 70 | 71 | 由于需要与暴力搜索比较,所以同时记录了程序运行时间,按照实验要求输出的结果如下: 72 | 73 | ![](assets/1.png) 74 | 75 | 可见,两种方式的输出最邻近点对是一样的,而使用分治算法的程序运行时间远远低于暴力搜索法。 -------------------------------------------------------------------------------- /Lab8/src/BFS.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class Graph { 9 | public: 10 | Graph(std::string data_path) { 11 | std::ifstream f(data_path); 12 | assert(f.is_open()); 13 | std::string line; 14 | std::getline(f, line); 15 | num_vertices = 0; 16 | for (int i = 0; i < line.size(); i++) { 17 | if (line[i] != ',') { num_vertices++; } 18 | } 19 | graph.resize(num_vertices); 20 | for (int i = 0; i < num_vertices; i++) { 21 | graph[i].resize(num_vertices); 22 | } 23 | visited.resize(num_vertices); 24 | while (std::getline(f, line)) { 25 | int i = line[0] - 'A'; 26 | int j = line[2] - 'A'; 27 | graph[i][j] = 1; 28 | graph[j][i] = 1; 29 | } 30 | f.close(); 31 | } 32 | 33 | void print_graph() { 34 | std::cout << "Graph:" << std::endl; 35 | for (int i = 0; i < num_vertices; i++) { 36 | for (int j = 0; j < num_vertices; j++) { 37 | std::cout << graph[i][j] << " "; 38 | } 39 | std::cout << std::endl; 40 | } 41 | std::cout << std::endl; 42 | } 43 | 44 | void bfs(int start) { 45 | std::cout << "BFS:" << std::endl; 46 | std::queue q; 47 | q.push(start); 48 | visited[start] = 1; 49 | while(!q.empty()) { 50 | int v = q.front(); 51 | q.pop(); 52 | std::cout << static_cast(v + 'A') << " "; 53 | for(int i = 0; i < num_vertices; i++){ 54 | if(graph[v][i] == 1 && visited[i] == 0){ 55 | q.push(i); 56 | visited[i] = 1; 57 | } 58 | } 59 | } 60 | } 61 | 62 | ~Graph(){ 63 | graph.clear(); 64 | visited.clear(); 65 | } 66 | 67 | private: 68 | std::vector> graph; 69 | std::vector visited; 70 | int num_vertices; 71 | }; 72 | 73 | int main(){ 74 | Graph g("../data/data.txt"); 75 | g.print_graph(); 76 | g.bfs(0); 77 | return 0; 78 | } -------------------------------------------------------------------------------- /Lab7/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Schedule.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | std::pair getCmdOption(char **begin, char **end, 10 | const std::string &option) { 11 | char **itr = std::find(begin, end, option); 12 | if (itr != end && ++itr != end) { return std::make_pair(true, *itr); } 13 | return std::make_pair(false, ""); 14 | } 15 | 16 | bool cmdOptionExists(char **begin, char **end, const std::string &option) { 17 | return std::find(begin, end, option) != end; 18 | } 19 | 20 | int main(int argc, char **argv) { 21 | std::string data_path = "..\\data\\test1.txt"; 22 | auto help_info = cmdOptionExists(argv, argv + argc, "-h"); 23 | auto input_info = getCmdOption(argv, argv + argc, "-f"); 24 | if (help_info) { 25 | std::cout << "-h : help message" << std::endl; 26 | std::cout << "-f [file path] : input file path" << std::endl; 27 | } 28 | if (input_info.first) data_path = input_info.second; 29 | 30 | auto start = std::chrono::high_resolution_clock::now(); 31 | 32 | Schedule schedule(data_path); 33 | auto best_schedule = schedule.GetBestSchedule(); 34 | 35 | auto end = std::chrono::high_resolution_clock::now(); 36 | auto duration = std::chrono::duration_cast(end - start); 37 | std::cout << "Duration of get best schedule is " << duration.count() << "us" << std::endl; 38 | 39 | std::cout << "Best spend: " << best_schedule.first << std::endl; 40 | std::cout << "Best schedule: " << std::endl; 41 | for (auto [machine, tasks] : best_schedule.second) { 42 | std::cout << "Machine " << machine << ": "; 43 | auto total_time = 0; 44 | for (auto [task_index, task_time] : tasks) { total_time += task_time; } 45 | std::cout << "Total time: " << total_time << " "; 46 | for (auto [task_index, task_time] : tasks) { 47 | std::cout << '(' << task_index << ", " << task_time << ')' << " "; 48 | } 49 | std::cout << std::endl; 50 | } 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /Lab4/report/算法第四次实验实验报告.md: -------------------------------------------------------------------------------- 1 | # 算法第四次实验实验报告 2 | 3 | > 姓名:王道宇 学号:PB21030794 4 | 5 | ## 实验内容 6 | 7 | 实现区间树数据结构的维护,并且在区间树中实现查找功能。 8 | 9 | ## 算法思想设计 10 | 11 | - 区间树相比于红黑树的改进点 12 | 13 | 由于我在红黑树中实现了模板,所以对于区间树只需要改变调用者的调用参数即可。 14 | 15 | 对于区间树,其结点中需要新建一个域:max,其关键字 key 值是一个区间,它具有上界和下界,基于这种结构,可以使用 pair 来表述这种关键字。具体数据结构如下: 16 | 17 | ```cpp 18 | typedef std::pair Key_Type; 19 | ``` 20 | 21 | 对于区间树,还有一个需要注意的点就是:在程序中要维护 max 域的性质: 22 | 23 | ```cpp 24 | x->max = max(x.high, x->left->max, x->right->max) 25 | ``` 26 | 27 | 一共有两处需要维护其性质: 28 | 29 | - 插入结点时遍历一条从根到叶的路径的过程中 30 | 31 | 遍历时,遍历的当前结点需要更新其 max 域的值。 32 | 33 | ```cpp 34 | x->max = max(x->max, z->key.second); 35 | ``` 36 | 37 | - 左旋右旋时 38 | 39 | 以左旋为例,需要考虑 x 是否有左子树和右子树,并且需要判断右子树是否有左子树: 40 | 41 | ```cpp 42 | if (x->left) { 43 | if (x->right) { 44 | if (x->right->left) { 45 | x->max = max(x->key.second, x->left->max, x->right->left->max); 46 | } else { 47 | x->max = max(x->key.second, x->left->max); 48 | } 49 | } else { 50 | x->max = max(x->key.second, x->left->max); 51 | } 52 | } else { 53 | if (x->right) { 54 | if (x->right->left) { 55 | x->max = max(x->key.second, x->right->left->max); 56 | } else { 57 | x->max = x->key.second; 58 | } 59 | } else { 60 | x->max = x->key.second; 61 | } 62 | } 63 | if (x->right) { x->right->max = max(x->right->max, x->max); } 64 | ``` 65 | 66 | 67 | - 查找功能的实现 68 | 69 | 由于程序要求我们查找出所有与目标区间有重叠的区间,故在遍历完左区间之后需要再次遍历右区间,相当于中序遍历的顺序: 70 | 71 | ```cpp 72 | template 73 | inline bool RBTree::overlap(const T &x, const T &interval) { 74 | return !(x.second < interval.first || interval.second < x.first); 75 | } 76 | 77 | template 78 | void RBTree::IntervalSearch(const RBNode *root, const T &interval, 79 | std::vector &result) { 80 | if (overlap(root->key, interval)) { result.push_back(root->key); } 81 | if (root->left && root->left->max >= interval.first) { 82 | IntervalSearch(root->left.get(), interval, result); 83 | } 84 | if (root->right && root->right->max >= interval.first) { 85 | IntervalSearch(root->right.get(), interval, result); 86 | } 87 | } 88 | ``` 89 | 90 | 这里使用 inline 关键字表征内联函数,避免调用函数的额外开销。在 Search 时,使用递归并标定了递归进行的条件。 91 | 92 | **下面分析该算法的复杂度:** 93 | 94 | 假设重叠区间数为 k,该算法最多检查每个节点两次并执行恒定的操作时间检查,因此运行时间不能超过 O(n)。 如果进行递归调用树的一个分支,那么该分支必然包含与需要查找的区间重叠的区间,所以运行时间也不能超过 O(k log n),因为高度最多为 n 并且重叠区间数为 k 。 95 | 96 | ## 实验结果 97 | 98 | - 区间树插入结果,仅展示先序遍历和层序遍历的结果: 99 | 100 | - 中序遍历 101 | 102 | ![](assets/1.png) 103 | 104 | - 层序遍历 105 | 106 | ![](assets/2.png) 107 | 108 | - 搜索结果: 109 | 110 | 这里展示三种情况:查询区间与红黑树中部分区间重叠;查询区间为一个点;查询区间不符合要求 111 | 112 | ![](assets/3.png) -------------------------------------------------------------------------------- /Lab7/src/Schedule.cpp: -------------------------------------------------------------------------------- 1 | #include "Schedule.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | Schedule::Schedule(std::string data_path) { 8 | std::ifstream in(data_path, std::ios::in); 9 | assert(in.is_open()); 10 | in >> num_tasks >> num_machines; 11 | assert(num_tasks > 0 && num_machines > 0 && num_machines < num_tasks); 12 | task_time.resize(num_tasks + 1, 0); 13 | for (unsigned i = 1; i <= num_tasks; ++i) { 14 | in >> task_time[i]; 15 | assert(task_time[i] > 0); 16 | } 17 | best_schedule.resize(num_tasks + 1, 0); 18 | curr_schedule.resize(num_tasks + 1, 0); 19 | machine_time.resize(num_machines + 1, 0); 20 | machine_max_task_time.resize(num_machines + 1, INT_MAX); 21 | best_spend = INT_MAX; 22 | curr_spend = 0; 23 | } 24 | 25 | std::pair>> 26 | Schedule::GetBestSchedule() { 27 | best_spend = InitByGreedy(); 28 | BackTrace(1, 0); 29 | std::map> best_schedule_map; 30 | for (size_t task_index = 1; task_index <= num_tasks; ++task_index) { 31 | auto machine_index = best_schedule[task_index]; 32 | // std::cout << "Task " << task_index << " Time " << 33 | // task_time[task_index] << " assigned to machine " << machine_index << 34 | // std::endl; 35 | best_schedule_map[machine_index][task_index] = task_time[task_index]; 36 | } 37 | return std::make_pair(best_spend, best_schedule_map); 38 | } 39 | 40 | unsigned Schedule::InitByGreedy() { 41 | std::vector greedy_machine_time(num_machines + 1, 0); 42 | unsigned greedy_spend = INT_MAX; 43 | sort(task_time.begin() + 1, task_time.end(), std::greater()); 44 | for (auto i = 1; i <= num_machines; i++) { 45 | greedy_machine_time[i] += task_time[i]; 46 | } 47 | greedy_spend = task_time[0]; 48 | for (auto i = num_machines + 1; i <= num_tasks; i++) { 49 | auto min_index = std::min_element(greedy_machine_time.begin() + 1, 50 | greedy_machine_time.end()); 51 | *min_index += task_time[i]; 52 | greedy_spend = std::max(greedy_spend, *min_index); 53 | } 54 | // std::cout << "Greedy spend: " << greedy_spend << std::endl; 55 | return greedy_spend; 56 | } 57 | 58 | void Schedule::BackTrace(unsigned task_index, unsigned curr_spend) { 59 | if (task_index == num_tasks + 1) { 60 | if (curr_spend < best_spend) { 61 | best_spend = curr_spend; 62 | best_schedule = curr_schedule; 63 | } 64 | return; 65 | } 66 | for (auto k = 1; k <= num_machines; k++) { 67 | if (std::max(curr_spend, machine_time[k] + task_time[task_index]) < 68 | best_spend) { 69 | if (machine_time[k] == 0) { 70 | if (task_time[task_index] > machine_max_task_time[k - 1]) { 71 | break; 72 | } 73 | machine_max_task_time[k] = task_time[task_index]; 74 | } 75 | 76 | curr_schedule[task_index] = k; 77 | machine_time[k] += task_time[task_index]; 78 | 79 | auto new_spend = std::max(curr_spend, machine_time[k]); 80 | BackTrace(task_index + 1, new_spend); 81 | 82 | machine_time[k] -= task_time[task_index]; 83 | curr_schedule[task_index] = 0; 84 | 85 | if (machine_time[k] == 0) machine_max_task_time[k] = 0; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Lab6/src/Huffman.cpp: -------------------------------------------------------------------------------- 1 | #include "Huffman.h" 2 | #include 3 | #include 4 | 5 | #define sep 20 6 | 7 | std::map freq; 8 | std::map code; 9 | 10 | void IO::read(const std::string &data_path) { 11 | this->in.open(data_path, std::ios::in); 12 | assert(this->in.is_open()); 13 | char c; 14 | while (this->in.get(c)) { 15 | if (c != EOF) { 16 | if (c < 33 || c > 126) { continue; } 17 | freq[c]++; 18 | } 19 | } 20 | this->in.close(); 21 | } 22 | 23 | void IO::write(const std::string &store_path, 24 | const std::map &code) { 25 | this->out.open(store_path, std::ios::out); 26 | this->out.setf(std::ios::left); 27 | this->out.width(sep); 28 | this->out << "Character"; 29 | this->out.setf(std::ios::left); 30 | this->out.width(sep); 31 | this->out << "Frequency"; 32 | this->out.setf(std::ios::left); 33 | this->out.width(sep); 34 | this->out << "Code" << std::endl; 35 | assert(this->out.is_open()); 36 | for (const auto &[character, str] : code) { 37 | this->out.setf(std::ios::left); 38 | this->out.width(sep); 39 | this->out << character; 40 | this->out.setf(std::ios::left); 41 | this->out.width(sep); 42 | this->out << freq[character]; 43 | this->out.setf(std::ios::left); 44 | this->out.width(sep); 45 | this->out << str << std::endl; 46 | } 47 | this->out.close(); 48 | } 49 | 50 | HuffmanCode::HuffmanCode(const std::string &data_path, 51 | const std::string &store_path) { 52 | io.read(data_path); 53 | BuildHuffmanTree(); 54 | io.write(store_path, code); 55 | } 56 | 57 | void HuffmanCode::BuildHuffmanTree() { 58 | for (const auto &[character, freq] : freq) { 59 | this->minHeap.push(new HuffmanNode(character, freq)); 60 | } 61 | while (this->minHeap.size() > 1) { 62 | auto left = this->minHeap.top(); 63 | this->minHeap.pop(); 64 | auto right = this->minHeap.top(); 65 | this->minHeap.pop(); 66 | auto new_node = new HuffmanNode('\0', left->freq + right->freq); 67 | new_node->left = left; 68 | new_node->right = right; 69 | this->minHeap.push(new_node); 70 | } 71 | this->root = this->minHeap.top(); 72 | GenerateCode(this->root); 73 | } 74 | 75 | void HuffmanCode::GenerateCode(pHuffmanNode &root, std::string str) { 76 | std::string left_str = str + "0"; 77 | std::string right_str = str + "1"; 78 | if (root->left == nullptr && root->right == nullptr) { 79 | code[root->character] = str; 80 | return; 81 | } 82 | if (root->left != nullptr) GenerateCode(root->left, left_str); 83 | if (root->right != nullptr) GenerateCode(root->right, right_str); 84 | } 85 | 86 | void HuffmanCode::CalcCompressRatio() { 87 | size_t origin = 0; 88 | size_t compressed = 0; 89 | size_t size = freq.size(); 90 | assert(size > 0); 91 | size_t bits = static_cast(std::ceil(std::log2(size))); 92 | for (const auto &[character, freq] : freq) { origin += freq * bits; } 93 | for (const auto &[character, str] : code) { 94 | compressed += freq[character] * str.size(); 95 | } 96 | std::cout << "origin: " << origin << std::endl; 97 | std::cout << "compressed: " << compressed << std::endl; 98 | std::cout << "ratio: " << static_cast(compressed) / origin 99 | << std::endl; 100 | } -------------------------------------------------------------------------------- /Lab6/report/算法第六次实验实验报告.md: -------------------------------------------------------------------------------- 1 | # 算法第六次实验实验报告 2 | 3 | > 姓名: 王道宇 4 | > 5 | > 学号: PB21030794 6 | 7 | ## 实验目的 8 | 9 | 使用小根堆实现一篇文章出现字符的 Huffman 编码。 10 | 11 | ## 算法思想设计 12 | 13 | 首先介绍两个 C++ 中的 STL: map 和 priority_queue: 14 | 15 | 1. map 16 | 17 | map内部实现了一个红黑树,该结构具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素,因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行这样的操作,故红黑树的效率决定了map的效率。 18 | 19 | - 优点 20 | 21 | 有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作红黑树,内部实现一个红黑书使得map的很多操作在 log(n) 的时间复杂度下就可以实现,因此效率非常的高。 22 | 23 | - 缺点 24 | 25 | 空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点,孩子节点以及红/黑性质,使得每一个节点都占用大量的空间。 26 | 27 | 2. priority_queue 28 | 29 | priority_queue 定义了一个元素有序排列的队列。默认队列头部的元素优先级最高。因为它是一个队列,所以只能访问第一个元素,这也意味着优先级最高的元素总是第一个被处理。但是如何定义“优先级”完全取决于调用者,比如在程序中为了实现小根堆,重载了 () 运算符: 30 | 31 | ```cpp 32 | struct compare { 33 | //* default is "less", l->freq > r->freq represents the priority of l is 34 | //* less than r 35 | bool operator()(pHuffmanNode l, pHuffmanNode r) { 36 | return (l->freq > r->freq); 37 | } 38 | }; 39 | ``` 40 | 41 | 形成 Huffman 树时,首先从优先队列中 pop 出两个最小值,将其关键字加和之后再重建新结点插入有限队列,这样可以保证优先队列中元素的相对关系。最终剩下的最后一个结点即为 Huffman 树的根节点: 42 | 43 | ```cpp 44 | while (this->minHeap.size() > 1) { 45 | auto left = this->minHeap.top(); 46 | this->minHeap.pop(); 47 | auto right = this->minHeap.top(); 48 | this->minHeap.pop(); 49 | auto new_node = new HuffmanNode('\0', left->freq + right->freq); 50 | new_node->left = left; 51 | new_node->right = right; 52 | this->minHeap.push(new_node); 53 | } 54 | this->root = this->minHeap.top(); 55 | ``` 56 | 57 | 在形成 Huffman 树之后,可以通过自顶向下的遍历操作将所有 Huffman 结点的编码值求出,并保存在一个 map 中: 58 | 59 | ```cpp 60 | void HuffmanCode::GenerateCode(pHuffmanNode &root, std::string str) { 61 | std::string left_str = str + "0"; 62 | std::string right_str = str + "1"; 63 | if (root->left == nullptr && root->right == nullptr) { 64 | code[root->character] = str; 65 | return; 66 | } 67 | if (root->left != nullptr) GenerateCode(root->left, left_str); 68 | if (root->right != nullptr) GenerateCode(root->right, right_str); 69 | } 70 | ``` 71 | 72 | 最终通过编码 map 将结果输出到文件即可。 73 | 74 | 求压缩率时,首先确定文件的定长编码,文件中只需考虑 33 到 126 的字符,所以只需要 7 位定长编码;而 Huffman 编码的长度由词频和 Huffman 编码的长度共同确定。 75 | 76 | ```cpp 77 | size_t bits = static_cast(std::ceil(std::log2(size))); 78 | for (const auto &[character, freq] : freq) { origin += freq * bits; } 79 | for (const auto &[character, str] : code) { 80 | compressed += freq[character] * str.size(); 81 | } 82 | ``` 83 | 84 | ## 实验结果 85 | 86 | 文件目录如下: 87 | 88 | ```make 89 | . 90 | ├─data 91 | ├─report 92 | │ └─assets 93 | └─src 94 | └─Huffman.cpp 95 | └─Huffman.h 96 | └─main.cpp 97 | ``` 98 | 99 | 为了正常编译运行程序,请按如下方式操作: 100 | 101 | 注意本项目需要 –std=c++17 的支持。 102 | 103 | 在项目根目录执行: 104 | 105 | ```bash 106 | mkdir build 107 | ``` 108 | 109 | ```bash 110 | cd build 111 | ``` 112 | 113 | 在 build 文件夹下执行: 114 | 115 | Windows: 116 | 117 | ```bash 118 | cmake .. -G "MinGW Makefiles" 119 | ``` 120 | 121 | ```bash 122 | mingw32-make or make 123 | ``` 124 | 125 | Linux: 126 | 127 | ```bash 128 | cmake .. 129 | ``` 130 | 131 | ```bash 132 | make 133 | ``` 134 | 135 | 生成的可执行文件在 build 文件夹下,直接执行: 136 | 137 | ```bash 138 | .\main.exe [-f ../data/original.txt] [-o ../data/table.txt] 139 | ``` 140 | 141 | 即可。 142 | 143 | 1. 编码输出文件 144 | 145 | ![](assets/1.png) 146 | 147 | 2. 压缩率 148 | 149 | ![](assets/2.png) 150 | 151 | -------------------------------------------------------------------------------- /Lab8/src/SparseMatrix.cpp: -------------------------------------------------------------------------------- 1 | #include "SparseMatrix.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | Edge::Edge(int r, int c) : row(r), col(c) {} 13 | 14 | bool Edge::operator<(const Edge &e) const { 15 | return row < e.row || (row == e.row && col < e.col); 16 | } 17 | 18 | SparseMatrix::SparseMatrix(std::string data_path, std::string mode) { 19 | this->mode = mode; 20 | assert(mode == "small" || mode == "large"); 21 | std::ifstream f(data_path); 22 | assert(f.is_open()); 23 | int row, col; 24 | char sep; 25 | int count = 0; 26 | int sep_count = mode == "small" ? 500000 : 5000000; 27 | std::cout << "Read file begin..." << std::endl; 28 | while (true) { 29 | if (mode == "small") { 30 | f >> row >> col; 31 | } else if (mode == "large") { 32 | f >> row >> sep >> col; 33 | } 34 | if (f.fail()) break; 35 | adj_list[row].insert(col); 36 | vertices.insert(row); 37 | vertices.insert(col); 38 | // edges.insert(std::move(Edge(row, col))); 39 | count++; 40 | // if (count % sep_count == 0) { 41 | // std::cout << count << ' ' << row << ' ' << col << std::endl; 42 | // } 43 | } 44 | num_vertices = vertices.size(); 45 | for (auto it = vertices.begin(); it != vertices.end(); it++) { 46 | visited[*it] = false; 47 | } 48 | num_edges = edges.size(); 49 | f.close(); 50 | std::cout << "Read file end..." << std::endl; 51 | } 52 | 53 | void SparseMatrix::bfs() { 54 | std::cout << "BFS begin..." << std::endl; 55 | 56 | auto start = std::chrono::high_resolution_clock::now(); 57 | int count = 0; 58 | for (auto [vertex, isvisited] : visited) { 59 | if (isvisited == false) { 60 | // std::cout << "BFS start from " << vertex << std::endl; 61 | count += bfs(vertex); 62 | } 63 | } 64 | auto end = std::chrono::high_resolution_clock::now(); 65 | 66 | std::cout << "BFS end..." << std::endl; 67 | 68 | std::cout << "Total number of vertices visited: " << count << std::endl; 69 | 70 | bool flag = true; 71 | for (auto [vertex, isvisited] : visited) { 72 | if (isvisited == false) { 73 | std::cout << "Vertex " << vertex << " is not visited." << std::endl; 74 | flag = false; //* once there is a vertex not visited, the graph 75 | //* is not searched completed. 76 | } 77 | } 78 | if (flag) { 79 | std::cout << "Graph is searched completed." << std::endl; 80 | } else { 81 | std::cout << "Graph is not searched completed." << std::endl; 82 | } 83 | 84 | if (mode == "small") { 85 | auto duration = 86 | std::chrono::duration_cast(end - start); 87 | std::cout << "Duration of bfs is " << duration.count() << "ms" 88 | << std::endl; 89 | } else { 90 | auto duration = 91 | std::chrono::duration_cast(end - start); 92 | std::cout << "Duration of bfs is " << duration.count() << "s" 93 | << std::endl; 94 | } 95 | } 96 | 97 | int SparseMatrix::bfs(int start_vertex) { 98 | int count = 0; 99 | std::queue q; 100 | q.push(start_vertex); 101 | visited[start_vertex] = true; 102 | while (!q.empty()) { 103 | int v = q.front(); 104 | q.pop(); 105 | count++; 106 | for (auto it = adj_list[v].begin(); it != adj_list[v].end(); it++) { 107 | if (visited[*it] == false) { 108 | q.push(*it); 109 | visited[*it] = true; 110 | } 111 | } 112 | } 113 | return count; 114 | } 115 | 116 | int SparseMatrix::get_num_vertices() { return num_vertices; } 117 | 118 | int SparseMatrix::get_num_edges() { return num_edges; } -------------------------------------------------------------------------------- /Lab8/report/算法第八次实验实验报告.md: -------------------------------------------------------------------------------- 1 | # 算法第八次实验实验报告 2 | 3 | > 姓名: 王道宇 4 | > 5 | > 学号: PB21030794 6 | 7 | ## 实验目的 8 | 9 | 1. 针对图,根据给定的数据选择合适的存储方式(邻接矩阵和邻接表中的一种)进行存储 10 | 2. 进行图的广度优先遍历(BFS)。 11 | 12 | ## 算法思想设计 13 | 14 | ### BFS 15 | 16 | 经典的连通图 BFS 利用队列,维护是否已经访问过的状态 visited,对图的顶点及其邻接点遍历并不断入队,直到所有的结点均访问完毕。经典的 BFS 伪代码如下: 17 | 18 | ```cpp 19 | BFS(start){ 20 | queue q; 21 | q.push(start); 22 | visited[start] = 1; 23 | while(!q.empty()) { 24 | v = q.front(); 25 | q.pop(); 26 | print(v); 27 | for(each adjacent vertex 'i' of v){ 28 | if(visited[i] == 0){ 29 | q.push(i); 30 | visited[i] = 1; 31 | } 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | 非连通图的 BFS 仍然需要利用队列,但是此时需要对所有的未访问顶点调用 BFS 。伪代码如下: 38 | 39 | ```cpp 40 | BFS(){ 41 | for(each vertex of vertices){ 42 | if(visited[i] == 0){ 43 | BFS(i); 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ### 存储结构分析 50 | 51 | 三份数据的特点和结构为: 52 | 53 | 1. data 54 | 55 | 顶点数:9,边数:18,无向连通图 56 | 57 | 2. twitter_small 58 | 59 | 顶点数:81306,边数: 1768149,有向非**强连通图**,有较多重边 60 | 61 | 3. twitter_large 62 | 63 | 顶点数:11316811,边数:85331846,有向非**强连通图**,实际上**无重边** 64 | 65 | 当图不为稀疏图时,使用邻接矩阵较为方便,可以比较直观地看出顶点间地关系,同时,查看两点之间是否有边相连只需要 O(1) 的复杂度。所以,对于第一份数据,可以直接使用**邻接矩阵**存储。 66 | 67 | 当图为稀疏图时,比如 twitter_small 和 twitter_large 这两份数据,其 $\frac{E}{V^2}$ 达到了 $2.67\times10^{-4}$ 以及 $6.66\times10^{-7}$ ,那么这两份数据很适合使用**邻接表**存储。 68 | 69 | ### 复杂度分析 70 | 71 | 1. 数据结构设计 72 | 73 | 在设计邻接表时,使用了如下的数据结构: 74 | 75 | ```cpp 76 | std::unordered_map> adj_list; 77 | std::unordered_set vertices; 78 | std::set edges; 79 | std::unordered_map visited; 80 | int num_vertices; 81 | int num_edges; 82 | ``` 83 | 84 | - adj_list 85 | 86 | 其键为顶点编号,其值为一个无序集合,保存了这个顶点所有邻边的编号。 87 | 88 | - vertices 89 | 90 | 直接使用无序集合保存编号即可。 91 | 92 | - edges 93 | 94 | 每一条边都由如下的结构体构成: 95 | 96 | ```cpp 97 | struct Edge { 98 | public: 99 | Edge(int r, int c); 100 | private: 101 | int row; 102 | int col; 103 | }; 104 | ``` 105 | 106 | 由于不知道怎么写 unordered_set 的哈希函数,只能使用 set 作为替代,重载了 set 的 < 运算符,定义了 edge之间的偏序关系如下: 107 | 108 | ```cpp 109 | bool Edge::operator<(const Edge &e) const { 110 | return row < e.row || (row == e.row && col < e.col); 111 | } 112 | ``` 113 | 114 | 由于 set 在判断两个值(假设为 a,b)是否相等时会调用该函数,调用方式为:若 a < b 或 b < a 均不成立,那么认为 a 与 b 不相等。可见,按照上述的重载函数,两次比较可以排除除了 $a.row = b.row$ 并且 $a.col = b.col$ 的所有情况,换句话说,当插入重边时,set 保证了过滤所有的重边。考虑 edge 的数量在 BFS 时并没有作用,所以最终在读文件和建图时为了**处理速率**并未使用,只保留了其实现如下: 115 | 116 | ```cpp 117 | edges.insert(std::move(Edge(row, col))); 118 | ``` 119 | 120 | 其中 move 函数将左值转为右值,加快速率并且转移控制权,避免内存堆积。 121 | 122 | - visited 123 | 124 | 其键为顶点编号,其值为 bool 型变量 true 或 false,代表是否已对该顶点访问过。 125 | 126 | 其中 unordered_map 和 unordered_set 均使用 hash 函数,插入删除和查找的时间复杂度均为 $O(1)$。 127 | 128 | 2. 空间复杂度 129 | 130 | 假设顶点数为 n,边数为 m 131 | 132 | - adj_list :$O(n + m)$ 133 | - vertices:$O(n)$ 134 | - edges:$O(2m)$ 135 | - visited:$O(n)$ 136 | 137 | 总共空间复杂度为:$O(n + m)$ 138 | 139 | 3. 时间复杂度 140 | 141 | 通过摊还分析,对每个顶点调用 BFS 并不会导致最终时间复杂度为顶点数乘 BFS 的时间复杂度,考虑到顶点的 visited 状态会实时更新,所以时间复杂度其实是顶点数加 BFS 的时间复杂度。 142 | 143 | 考虑 BFS 的时间复杂度,由于需要遍历一次且仅有一次所有的顶点和边,所以 BFS 的时间复杂度为 $O(m + n)$ 。 144 | 145 | 所有最终总的时间度为:$O(m + n)$ 。 146 | 147 | ## 实验结果 148 | 149 | - data 150 | 151 | ![](assets/3.png) 152 | 153 | - twitter_small 154 | 155 | ![](assets/2.png) 156 | 157 | - twitter_large 158 | 159 | ![](assets/1.png) 160 | 161 | 最终遍历的顶点数与给定的顶点数相同,说明 BFS 的过程正确。 162 | -------------------------------------------------------------------------------- /Lab5/report/算法第五次实验实验报告.md: -------------------------------------------------------------------------------- 1 | # 算法第五次实验实验报告 2 | 3 | > 姓名: 王道宇 学号:PB21030794 4 | 5 | ## 实验目的 6 | 7 | 1. 编程实现最长公共子序列( `LCS` )算法,并理解其核心思想。 8 | 9 | 2. 时间复杂度 $O(mn)$,空间复杂度 $O(mn)$,求出 `LCS` 及其长度。 10 | 3. 时间复杂度 $O(mn)$,空间复杂度 $O(2*min(m,n))$,求出 `LCS` 的长度。 11 | 4. 时间复杂度 $O(mn)$,空间复杂度 $O(min(m,n) + 1)$,求出 `LCS` 的长度。 12 | 13 | ## 算法思想设计 14 | 15 | 求最长公共子序列的过程实际上是动态规划的过程。动态规划的状态转移方程为: 16 | $$ 17 | dp[i][j] = 18 | \begin{cases} 19 | dp[i-1][j-1] + 1 ,\quad text1[i-1] = text2[j-1]\\ 20 | \max\{dp[i-1][j],dp[i][j-1]\},\quad text1[i-1]\neq text2[j-1] 21 | \end{cases} 22 | $$ 23 | 24 | 1. 为了保证输入的两个字符串长度有顺序之分,在构造函数中规约了两个字符串为较长的在上,较短的在下。 25 | 26 | 2. 朴素的动态规划算法: 27 | 28 | 在朴素的动态规划算法中,我们使用一个大小为 $m\cdot n$ 大小的表保存每个 $dp$ 表项。算法自下而上,自左向右地遍历整个表。 29 | 30 | 虽然朴素动态规划算法需要较大地空间保存每个表项,然而,如果需要最大公共子序列的内容,最少也需要 $O(mn)$ 大小的空间用于保存足够的信息,否则在回溯时无法将最大公共子序列的内容记录下来。 31 | 32 | - 遍历过程: 33 | 34 | 在遍历中实际上不需要使用书中给出的方法:用另一个表保存箭头指向。因为这个信息在回溯时可以再次将对应位置进行比较来确定回溯的指向。所以在遍历中只需要将表项的内容按照状态转移方程填写完成即可。具体代码如下: 35 | 36 | ```cpp 37 | std::vector> dp(m + 1, std::vector(n + 1, 0)); 38 | for (int i = 1; i <= m; i++) { 39 | for (int j = 1; j <= n; j++) { 40 | if (this->text1[i - 1] == this->text2[j - 1]) { 41 | dp[i][j] = dp[i - 1][j - 1] + 1; 42 | } else { 43 | dp[i][j] = std::max(dp[i - 1][j], dp[i][j - 1]); 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | 这里设计了一个二维 $vector$ 作为 $dp$ 表项,同时将其初始化为 0 50 | 51 | - 回溯过程: 52 | 53 | 回溯时,从表中最右下角开始,比较对应位置字符串中的字符,如果相等,那么将该字符压入结果字符串中回溯方向为左上的表项;如果不等,那么将表项左边和上边的表项比较,较大的表项即为回溯的方向。具体代码如下: 54 | 55 | ```cpp 56 | std::string lcs; 57 | int i = m, j = n; 58 | while (i > 0 && j > 0) { 59 | if (this->text1[i - 1] == this->text2[j - 1]) { 60 | lcs.push_back(this->text1[i - 1]); 61 | i--; 62 | j--; 63 | } else if (dp[i - 1][j] > dp[i][j - 1]) { 64 | i--; 65 | } else { 66 | j--; 67 | } 68 | } 69 | ``` 70 | 71 | 回溯结束后,变量 $lcs$ 中不是最终结果,需要将其反转之后再进行返回,其中返回值是一个 $pair$ ,包括最长子序列和其长度: 72 | 73 | ```cpp 74 | std::reverse(lcs.begin(), lcs.end()); 75 | return std::make_pair(lcs, dp[m][n]); 76 | ``` 77 | 78 | 3. 缩减空间复杂度至 $O(2*min(m,n))$: 79 | 80 | 可以看到,动态转移方程的每一项只与其上一行以及其左边的一个元素有关,所以我们可以只使用两个长为 $min(m,n)$ 的 $vector$ 即可。 81 | 82 | 一旦缩减至该空间复杂度,就不可能在有限时间回溯出最大公共序列,所以在此只输出最大公共序列的长度,具体代码如下: 83 | 84 | ```cpp 85 | int m = this->text1.size(); 86 | int n = this->text2.size(); 87 | std::vector dp_pre(n + 1, 0), dp_now(n + 1, 0); 88 | for (int i = 1; i <= m; i++) { 89 | for (int j = 1; j <= n; j++) { 90 | if (this->text1[i - 1] == this->text2[j - 1]) { 91 | dp_now[j] = dp_pre[j - 1] + 1; 92 | } else { 93 | dp_now[j] = std::max(dp_pre[j], dp_now[j - 1]); 94 | } 95 | } 96 | /* 97 | * Direct assignment operations between STL composed of 98 | * statically allocated data types are overloaded in c++ 99 | */ 100 | dp_pre = dp_now; 101 | } 102 | return dp_now[n]; 103 | ``` 104 | 105 | 4. 缩减空间复杂度至 $O(min(m,n) + 1)$: 106 | 107 | 可以看到,动态转移方程的每一项只与其上方的、左边的、左上的三个元素有关,所以我们可以只使用一个长为 $min(m,n)$ 的 $vector$ 以及一个额外临时变量 $pre$ 保存左上的一个元素即可。所有修改均为原址改动。具体代码如下: 108 | 109 | ```cpp 110 | int m = this->text1.size(); 111 | int n = this->text2.size(); 112 | std::vector dp_now(n + 1, 0); 113 | int pre, now; 114 | for (int i = 1; i <= m; i++) { 115 | pre = 0; 116 | dp_now[0] = 0; // * not need but for clear 117 | for (int j = 1; j <= n; j++) { 118 | if (this->text1[i - 1] == this->text2[j - 1]) { 119 | now = pre + 1; 120 | } else { 121 | now = std::max(dp_now[j], dp_now[j - 1]); 122 | } 123 | pre = dp_now[j]; 124 | dp_now[j] = now; 125 | } 126 | } 127 | ``` 128 | 129 | 130 | 131 | ## 实验结果 132 | 133 | 1. 有公共子序列 134 | 135 | ![](assets/2.png) 136 | 137 | 2. 无公共子序列 138 | 139 | ![](assets/3.png) 140 | -------------------------------------------------------------------------------- /Lab6/data/table.txt: -------------------------------------------------------------------------------- 1 | Character Frequency Code 2 | ! 112 0011001011 3 | " 391 00110001 4 | ' 233 001110000 5 | ( 18 1011100101101 6 | ) 18 1011100101110 7 | , 1938 001101 8 | - 292 101110011 9 | . 1512 1110011 10 | 0 2 0011001010011011 11 | 1 5 00110010100000 12 | 2 1 0011001010000100 13 | 3 3 001100101000011 14 | 4 4 001100101000110 15 | 5 2 0011001010001011 16 | 6 2 0011001010011010 17 | 7 1 0011001010001010 18 | 9 4 001100101001100 19 | : 40 111001011010 20 | ; 56 00110010101 21 | ? 67 10111001010 22 | A 312 111001000 23 | B 280 101110001 24 | C 160 1110010011 25 | D 42 111001011011 26 | E 95 0011000010 27 | F 188 001100000 28 | G 18 1011100101100 29 | H 141 1011100100 30 | I 261 001110011 31 | J 93 11100101111 32 | K 1 0011001010000101 33 | L 45 111001011101 34 | M 197 001100100 35 | N 245 001110010 36 | O 80 11100101100 37 | P 50 00110000111 38 | Q 4 001100101000111 39 | R 49 00110000110 40 | S 339 111001010 41 | T 430 00110011 42 | U 15 0011001010010 43 | V 9 00110010100111 44 | W 120 0011100010 45 | X 3 001100101000100 46 | Y 21 1011100101111 47 | a 10834 1101 48 | b 2029 001111 49 | c 2663 101111 50 | d 6281 0000 51 | e 17214 100 52 | f 2878 00010 53 | g 2761 111000 54 | h 8491 0111 55 | i 7886 0110 56 | j 120 0011100011 57 | k 985 0011101 58 | l 5847 11101 59 | m 3494 00100 60 | n 9253 1010 61 | o 9890 1100 62 | p 2149 101100 63 | q 153 1110010010 64 | r 7643 0100 65 | s 7685 0101 66 | t 11515 1111 67 | u 3428 00011 68 | v 1192 1011101 69 | w 3518 00101 70 | x 274 101110000 71 | y 2173 101101 72 | z 45 111001011100 73 | -------------------------------------------------------------------------------- /Lab1/src/qsort_vector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | using namespace std; 12 | 13 | #define K 4 14 | 15 | class Qsort { 16 | public: 17 | vector LoadData(string data_path); 18 | int GetSize() { return data_size; } 19 | void StoreData(vector &data, string store_path); 20 | 21 | public: 22 | int GetRandom(double Min, double Max); 23 | pair Partition(vector &data, int p, int r); 24 | pair Random_Partition(vector &data, int p, int r); 25 | pair Midium_Partition(vector &data, int p, int r); 26 | 27 | public: 28 | void InsertSort(vector &data, int p, int r); 29 | void QuickSort(vector &data, int p, int r); 30 | bool Verify(vector &data, int p, int r); 31 | 32 | private: 33 | vector data; 34 | int data_size; 35 | }; 36 | 37 | vector Qsort::LoadData(string data_path) { 38 | ifstream f; 39 | f.open(data_path, ios::in); 40 | int item; 41 | int count = 0; 42 | if (f.is_open()) { 43 | f >> data_size; 44 | while (!f.eof()) { 45 | f >> item; 46 | data.push_back(item); 47 | count++; 48 | if (count == data_size) { break; } 49 | } 50 | } 51 | f.close(); 52 | // cout << count << ' ' << data.size() << endl; 53 | return this->data; 54 | } 55 | 56 | void Qsort::StoreData(vector &data, string store_path) { 57 | ofstream f; 58 | f.open(store_path, ios::out); 59 | if (f.is_open()) { 60 | for (auto i = data.begin(); i != data.end(); ++i) { 61 | f << *i; 62 | if ((i - data.begin()) % 17 == 0 && i != data.begin()) 63 | f << '\n'; 64 | else 65 | f << ' '; 66 | } 67 | } 68 | f.close(); 69 | } 70 | 71 | int Qsort::GetRandom(double Min = 1, double Max = 100000) { 72 | static random_device rand_dev; 73 | static mt19937 rng(rand_dev.entropy() > 0 ? rand_dev() : time(nullptr)); 74 | static uniform_real_distribution distrib; 75 | 76 | return static_cast( 77 | distrib(rng, uniform_real_distribution::param_type{Min, Max})); 78 | } 79 | 80 | pair Qsort::Partition(vector &data, int p, int r) { 81 | int lptr = p, rptr = r + 1; 82 | int i = lptr; 83 | int base = data[p]; 84 | while (i < rptr) { 85 | if (data[i] < base) { 86 | swap(data[i], data[lptr + 1]); 87 | lptr++; 88 | i++; 89 | } else if (data[i] > base) { 90 | swap(data[i], data[rptr - 1]); 91 | rptr--; 92 | } else { 93 | i++; 94 | } 95 | } 96 | swap(data[p], data[lptr]); 97 | return make_pair(lptr, rptr); 98 | } 99 | 100 | pair Qsort::Random_Partition(vector &data, int p, int r) { 101 | int ran = GetRandom(static_cast(p), static_cast(r)); 102 | swap(data[ran], data[p]); 103 | return Partition(data, p, r); 104 | } 105 | 106 | pair Qsort::Midium_Partition(vector &data, int p, int r) { 107 | if (r - p + 1 < 3) return make_pair(p, r); 108 | int arr[3] = {data[p], data[(p + r) / 2], data[r]}; 109 | int index[3] = {p, (p + r) / 2, r}; 110 | for (int i = 0; i < 3; i++) { 111 | for (int j = i + 1; j < 3; j++) { 112 | if (arr[j] < arr[i]) { 113 | swap(arr[i], arr[j]); 114 | swap(index[i], index[j]); 115 | } 116 | } 117 | } 118 | swap(data[index[1]], data[p]); 119 | return Partition(data, p, r); 120 | } 121 | 122 | void Qsort::InsertSort(vector &data, int p, int r) { 123 | for (int i = p + 1; i <= r; i++) { 124 | int elem = data[i]; 125 | int j; 126 | for (j = i; j > p && data[j - 1] > elem; j--) { data[j] = data[j - 1]; } 127 | data[j] = elem; 128 | } 129 | } 130 | 131 | void Qsort::QuickSort(vector &data, int p, int r) { 132 | if (p > r) return; 133 | if (r - p + 1 <= K) { 134 | InsertSort(data, p, r); 135 | return; 136 | } 137 | // auto ret = Random_Partition(data, p, r); 138 | auto ret = Midium_Partition(data, p, r); 139 | QuickSort(data, p, ret.first - 1); 140 | QuickSort(data, ret.second, r); 141 | } 142 | 143 | bool Qsort::Verify(vector &data, int p, int r) { 144 | bool flag = true; 145 | if (p > r) return false; 146 | for (int i = p; i < r; i++) { 147 | if (data[i] > data[i + 1]) { 148 | flag = false; 149 | break; 150 | } 151 | } 152 | return flag; 153 | } 154 | 155 | int main() { 156 | Qsort q; 157 | auto arr = q.LoadData("data.txt"); 158 | clock_t start, end; 159 | start = clock(); 160 | q.QuickSort(arr, 0, q.GetSize() - 1); 161 | end = clock(); 162 | cout << "duration of quicksort is " << end - start << "ms" << endl; 163 | bool flag = q.Verify(arr, 0, q.GetSize() - 1); 164 | cout << "flag = " << flag << endl; 165 | if (flag) q.StoreData(arr, "sorted.txt"); 166 | 167 | 168 | auto arr1 = q.LoadData("data.txt"); 169 | start = clock(); 170 | sort(arr1.begin(), arr1.end()); 171 | end = clock(); 172 | cout << "duration of sort is " << end - start << "ms" << endl; 173 | } 174 | -------------------------------------------------------------------------------- /Lab7/report/算法第七次实验实验报告.md: -------------------------------------------------------------------------------- 1 | # 算法第七次实验实验报告 2 | 3 | > 姓名: 王道宇 4 | > 5 | > 学号: PB21030794 6 | 7 | ## 实验目的 8 | 9 | 设有 $n$ 个任务由 $k$ 个可并行工作的机器来完成,完成任务 $i$ 需要时间为 $t_i$ 。试设计一 10 | 个算法找出完成这 $n$ 个任务的最佳调度,使完成全部任务的时间最早。(要求给出调度方案)。 11 | 12 | 在本实验中我们只考虑 $n > k$ 的情况。因为 $n < k$ 时最佳调度方案就是将 $n$ 个任务放在 $n$ 个机器中完成,最短时间为最长任务的时间。 13 | 14 | ## 算法思想设计 15 | 16 | 本题应当使用回溯法来解决,下文将说明为何不能使用贪心法解决。 17 | 18 | 假设使用贪心法解决,首先将所有给定的任务按需要时间从大到小排序,选取贪心策略为开始将最长时间的 $k$ 个任务放入 $k$ 个机器中,之后始终选取当前最短时间的机器放入接下来的任务。然而这种方式并不一定能得到最优解,比如下例: 19 | 20 | 任务队列为 $\{16, 14, 12, 11, 10, 9, 8\} $,机器数为 $3$ ,如果使用贪心策略,那么任务安排序列为 $ \{1, 2, 3, 3, 2, 1, 3\}$,这样 $3$ 台机器完成各自任务的时间为 $25, 24, 31$,完成全部任务的最小时间为 $31$ 。然而最佳调度序列应当为 $\{1, 1, 2, 2, 3, 3, 3\}$,这样 $3$ 台机器完成各自任务的时间为 $30, 23, 27$,完成全部任务的最小时间为 $30$ 。 21 | 22 | 虽然贪心解不是最优解,它的部分思想还是可以沿用至回溯法中,如果使用朴素的回溯法,其时间复杂度来到了 $O(k^n)$ ,而这对于较大的 $n$ 和 $k$ 来说是不可接受的,所以在回溯的过程中需要进行剪枝操作,朴素的剪枝策略如下: 23 | 24 | - 维护一个最优花费时间 $best\_spend$ ,初始化为最大整数,当前的花费 $curr\_spend$ 若大于最优花费,则不进行下面的遍历操作。若当前遍历的结果小于最优花费时间,则更新 $besk\_spend$。 25 | 26 | 朴素的回溯过程如下: 27 | 28 | ```cpp 29 | void Schedule::BackTrace(unsigned task_index, unsigned curr_spend) { 30 | if (task_index == num_tasks + 1) { // 已经回溯完毕 31 | if (curr_spend < best_spend) { 32 | best_spend = curr_spend; 33 | best_schedule = curr_schedule; 34 | } 35 | return; 36 | } 37 | for (auto k = 1; k <= num_machines; k++) { 38 | if (std::max(curr_spend, machine_time[k] + task_time[task_index]) < 39 | best_spend) { 40 | curr_schedule[task_index] = k; 41 | machine_time[k] += task_time[task_index]; 42 | // 回溯 43 | auto new_spend = std::max(curr_spend, machine_time[k]); 44 | BackTrace(task_index + 1, new_spend); 45 | // 恢复现场 46 | machine_time[k] -= task_time[task_index]; 47 | curr_schedule[task_index] = 0; 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | 可以看到,减小时间复杂度的关键就是如何更快的达到剪枝的条件。基于部分贪心法的思想,设计了以下三种优化方案: 54 | 55 | 1. 贪心解做初始解 56 | 57 | 首先我们知道,贪心解虽然不是最优解,但是它**一定不是最坏解**,**甚至不是次坏解**,所以 $best\_spend$ 可以不初始化为最大整数。先进行一遍贪心策略,将贪心解作为 $besk\_spend$ 的初始值,可以让后来的剪枝更快。 58 | 59 | 2. 预排序 60 | 61 | 通过预排序将所有的任务时间从大到小排序,优先分配时间较长的任务,这样可以让机器的总时间更快的到达 $besk\_spend$,剪枝的速度更快。 62 | 63 | 以下是求贪心解加预排序的代码:首先进行预排序,再分配前 $k$ 个时间最长的任务,最后将所有的任务按照当前拥有最短时间的机器分配。 64 | 65 | ```cpp 66 | unsigned Schedule::InitByGreedy() { 67 | std::vector greedy_machine_time(num_machines + 1, 0); 68 | unsigned greedy_spend = INT_MAX; 69 | sort(task_time.begin() + 1, task_time.end(), std::greater()); 70 | for (auto i = 1; i <= num_machines; i++) { 71 | greedy_machine_time[i] += task_time[i]; 72 | } 73 | greedy_spend = task_time[0]; 74 | for (auto i = num_machines + 1; i <= num_tasks; i++) { 75 | auto min_index = std::min_element(greedy_machine_time.begin() + 1, 76 | greedy_machine_time.end()); 77 | *min_index += task_time[i]; 78 | greedy_spend = std::max(greedy_spend, *min_index); 79 | } 80 | // std::cout << "Greedy spend: " << greedy_spend << std::endl; 81 | return greedy_spend; 82 | } 83 | ``` 84 | 85 | 3. 去除重复机器排序 86 | 87 | 可以看到,机器之间是没有任何区别的,所以回溯的过程中会遇到分配相同而仅仅是机器排序不同的情况,所以我们定义一个标准,由于我们的任务进行了预排序,所以分配的任务一定是从时间较大的到时间较小的(或相同的)。而让机器第一个进入的任务时间长度不大于其前一个机器的任务时间长度,即可**约束**所有机器的第一个任务一定是按机器的顺序从大到小排列的。这样的简单约束甚至可以让复杂度从 $O(m)$ 降低到 $O(m/k!)$ 。而实现这种约束也很简单,只要对每个机器维护一个值,表示第一个进入机器的任务时间,让该值不大于其前一个机器的该值即可。具体代码如下: 88 | 89 | ```cpp 90 | if (machine_time[k] == 0) { 91 | if (task_time[task_index] > machine_max_task_time[k - 1]) { 92 | break; 93 | } 94 | machine_max_task_time[k] = task_time[task_index]; 95 | } 96 | ... // 回溯过程 97 | if (machine_time[k] == 0) machine_max_task_time[k] = 0; 98 | ``` 99 | 100 | 下面以 text3.txt 数据为例(其中 $n = 19$,$k = 8$),通过程序计时来表示各个优化的程度: 101 | 102 | 1. 不做优化时: 103 | 104 | 完全无法运行出结果,因为 $8^{19} \approx 10^{17} $,而这是实际不可接收的。 105 | 106 | 2. 贪心解做初始解 + 预排序: 107 | 108 | ![](assets/4.png) 109 | 110 | 使用了约 $5.4s$,是可用水平,然而当 $n$ 和 $k$ 再增大后,该时间也不可接收。 111 | 112 | 3. 再去除重复机器排序 113 | 114 | ![](assets/1.png) 115 | 116 | 使用了约 $1.5ms$,可以看到 $\frac{5.4s}{1.5ms} \approx 3600$,提升幅度巨大。 117 | 118 | ## 实验结果 119 | 120 | 文件目录如下: 121 | 122 | ```makefile 123 | . 124 | ├─data 125 | ├─report 126 | │ └─assets 127 | └─src 128 | └─Schedule.cpp 129 | └─Schedule.h 130 | └─main.cpp 131 | ``` 132 | 133 | 为了正常编译运行程序,请按如下方式操作: 134 | 135 | 注意本项目需要 –std=c++17 的支持。 136 | 137 | 在项目根目录执行: 138 | 139 | ```bash 140 | mkdir build 141 | ``` 142 | 143 | ```bash 144 | cd build 145 | ``` 146 | 147 | 在 build 文件夹下执行: 148 | 149 | Windows: 150 | 151 | ```bash 152 | cmake .. -G "MinGW Makefiles" 153 | ``` 154 | 155 | ```bash 156 | mingw32-make or make 157 | ``` 158 | 159 | Linux: 160 | 161 | ```bash 162 | cmake .. 163 | ``` 164 | 165 | ```bash 166 | make 167 | ``` 168 | 169 | 生成的可执行文件在 build 文件夹下,直接执行: 170 | 171 | ```bash 172 | .\main.exe [-f ../data/text1.txt] 173 | ``` 174 | 175 | 即可。 176 | 177 | 同时,程序内部使用了计时工具,可以输出得到最佳调度的时间,时间单位为微秒(us)。可以看到所有给定的数据,均可在 2 毫秒以内给出结果,说明优化后的性能较好。 178 | 179 | 1. text1.txt 180 | 181 | ![](assets/3.png) 182 | 183 | 2. text2.txt 184 | 185 | ![](assets/2.png) 186 | 187 | 3. text3.txt 188 | 189 | ![](assets/1.png) -------------------------------------------------------------------------------- /Lab5/src/LCS.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class LCS { 9 | public: 10 | LCS(std::string data_path); 11 | std::pair LCSNaive(); 12 | int LCSSaveSpace(); 13 | int LCSSaveSpaceFurther(); 14 | 15 | public: 16 | std::string getText1() const { return this->text1; } 17 | std::string getText2() const { return this->text2; } 18 | 19 | private: 20 | std::string text1; 21 | std::string text2; 22 | }; 23 | 24 | LCS::LCS(std::string data_path) { 25 | std::ifstream data_file(data_path); 26 | std::string line1, line2; 27 | if (data_file.is_open()) { 28 | std::getline(data_file, line1); 29 | std::getline(data_file, line2); 30 | this->text1 = line1.size() >= line2.size() ? line1 : line2; // * longer 31 | this->text2 = line1.size() >= line2.size() ? line2 : line1; //* shorter 32 | data_file.close(); 33 | } else { 34 | std::cout << "Unable to open file"; 35 | } 36 | } 37 | 38 | std::pair LCS::LCSNaive() { 39 | int m = this->text1.size(); 40 | int n = this->text2.size(); 41 | std::vector> dp(m + 1, std::vector(n + 1, 0)); 42 | 43 | for (int i = 1; i <= m; i++) { 44 | for (int j = 1; j <= n; j++) { 45 | if (this->text1[i - 1] == this->text2[j - 1]) { 46 | dp[i][j] = dp[i - 1][j - 1] + 1; 47 | } else { 48 | dp[i][j] = std::max(dp[i - 1][j], dp[i][j - 1]); 49 | } 50 | } 51 | } 52 | 53 | std::string lcs; 54 | int i = m, j = n; 55 | while (i > 0 && j > 0) { 56 | if (this->text1[i - 1] == this->text2[j - 1]) { 57 | lcs.push_back(this->text1[i - 1]); 58 | i--; 59 | j--; 60 | } else if (dp[i - 1][j] > dp[i][j - 1]) { 61 | i--; 62 | } else { 63 | j--; 64 | } 65 | } 66 | 67 | std::reverse(lcs.begin(), lcs.end()); 68 | return std::make_pair(lcs, dp[m][n]); 69 | } 70 | 71 | int LCS::LCSSaveSpace() { 72 | int m = this->text1.size(); 73 | int n = this->text2.size(); 74 | std::vector dp_pre(n + 1, 0), dp_now(n + 1, 0); 75 | for (int i = 1; i <= m; i++) { 76 | for (int j = 1; j <= n; j++) { 77 | if (this->text1[i - 1] == this->text2[j - 1]) { 78 | dp_now[j] = dp_pre[j - 1] + 1; 79 | } else { 80 | dp_now[j] = std::max(dp_pre[j], dp_now[j - 1]); 81 | } 82 | } 83 | /* 84 | * Direct assignment operations between STL composed of 85 | * statically allocated data types are overloaded in c++ 86 | */ 87 | dp_pre = dp_now; 88 | } 89 | return dp_now[n]; 90 | } 91 | 92 | int LCS::LCSSaveSpaceFurther() { 93 | int m = this->text1.size(); 94 | int n = this->text2.size(); 95 | std::vector dp_now(n + 1, 0); 96 | int pre, now; 97 | for (int i = 1; i <= m; i++) { 98 | pre = 0; 99 | dp_now[0] = 0; // * not need but for clear 100 | for (int j = 1; j <= n; j++) { 101 | if (this->text1[i - 1] == this->text2[j - 1]) { 102 | now = pre + 1; 103 | } else { 104 | now = std::max(dp_now[j], dp_now[j - 1]); 105 | } 106 | pre = dp_now[j]; 107 | dp_now[j] = now; 108 | } 109 | } 110 | return dp_now[n]; 111 | } 112 | 113 | void ConsolePrint(LCS &lcs) { 114 | std::cout.setf(std::ios::left); 115 | std::cout.width(25); 116 | std::cout << "Text1: "; 117 | std::cout << lcs.getText1() << std::endl; 118 | std::cout.width(25); 119 | std::cout << "Text2: "; 120 | std::cout << lcs.getText2() << std::endl; 121 | auto lcs_pair = lcs.LCSNaive(); 122 | std::cout.width(25); 123 | std::cout << "Naive dp: "; 124 | if (lcs_pair.first.size() > 0) { 125 | std::cout << "lcs: " << lcs_pair.first << std::endl; 126 | std::cout.width(25); 127 | std::cout << " "; 128 | } 129 | std::cout << "length: " << lcs_pair.second << std::endl; 130 | std::cout.width(25); 131 | std::cout << "Save space dp: "; 132 | std::cout << "length: " << lcs.LCSSaveSpace() << std::endl; 133 | std::cout.width(25); 134 | std::cout << "Further save space dp: "; 135 | std::cout << "length: " << lcs.LCSSaveSpaceFurther() << std::endl; 136 | } 137 | 138 | std::pair getCmdOption(char **begin, char **end, 139 | const std::string &option) { 140 | char **itr = std::find(begin, end, option); 141 | if (itr != end && ++itr != end) { return std::make_pair(true, *itr); } 142 | return std::make_pair(false, ""); 143 | } 144 | 145 | bool cmdOptionExists(char **begin, char **end, const std::string &option) { 146 | return std::find(begin, end, option) != end; 147 | } 148 | 149 | int main(int argc, char **argv) { 150 | std::string data_path = "data.txt"; 151 | if (cmdOptionExists(argv, argv + argc, "-h")) { 152 | std::cout << "-h : help message" << std::endl; 153 | std::cout << "-f [file path] : input file path" << std::endl; 154 | } 155 | auto help_info = cmdOptionExists(argv, argv + argc, "-h"); 156 | auto input_info = getCmdOption(argv, argv + argc, "-f"); 157 | if (help_info) { 158 | std::cout << "-h : help message" << std::endl; 159 | std::cout << "-f [file path] : input file path" << std::endl; 160 | } 161 | if (input_info.first) data_path = input_info.second; 162 | LCS lcs(data_path); 163 | ConsolePrint(lcs); 164 | } -------------------------------------------------------------------------------- /Lab3/report/算法第三次实验.md: -------------------------------------------------------------------------------- 1 | # 算法第三次实验实验报告 2 | 3 | > 姓名:王道宇 学号:PB21030794 4 | 5 | ## 实验内容 6 | 7 | 实现红黑树的插入算法,并保证红黑树的性质。 8 | 9 | 按照书本内容,实现类似 LeftRotate,RightRotate,RBInsert,RBInsertFixup 的函数,并在程序的最后使用树的中序遍历、前序遍历以及层序遍历的方式遍历整棵树并写入文件。 10 | 11 | ## 算法思想设计 12 | 13 | 1. **数据结构设计** 14 | 15 | - **节点设计** 16 | 17 | 一个红黑树的节点应当有以下五个信息:关键字 key、颜色 RED or BLACK、左孩子结点的指针、右孩子结点的指针、父结点的指针。 18 | 19 | 树的key有以下两个可能需要考虑的性质: 20 | 21 | - key的类型可能具有多样性,比如key可以是一个整形变量,也可以是**任何可以进行自定义相互比较**的数据类型。 22 | - key很可能不像整形变量那样只需分配在栈上,如果需要程序手动分配内存,那么如何在程序结束之后释放内存便成为了需要注意的问题。(虽然不管的话操作系统自动帮助释放) 23 | 24 | 使用几个技巧解决这些问题: 25 | 26 | - 使用 C++ 中模板的概念,将 key 的类型定义为模板,这样由实例化类时指定类型作为 key 的类型。 27 | 28 | - 重载 < 运算符:使用重载函数自定义 key 之间的大小比较。但是其实重载也不够 oop,因为不能要求调用者再去修改类中重载运算符的函数。更好的方式是仿照 sort 函数,定义比较函数的入口,这样只要求调用者自定义比较函数,实例化红黑树时传入函数指针作为比较依据。 29 | 30 | 不过该程序目前只使用了朴素的 < 运算符,因为实验只要求整形数据的相互比较。 31 | 32 | - 考虑到可能需要的释放内存操作,我并没有像 C++ STL 中 set 的红黑树底层实现中使用裸指针作为结点指针的方式,而是使用了 C++11 之后引入的智能指针(smart pointer)的概念,选取 unique_ptr 作为部分结点指针,并选取 C++14 之后引入的 make_unique 函数进行结点指针的初始化。 33 | 34 | 考虑到 unique_ptr 的特殊性,一块内存由一个指针管理,它有几个重要的性质: 35 | 36 | - 程序离开指针的作用域时,会自动将指针指向的内容销毁。 37 | - 传递参数时 unique_ptr 只接受右值引用的方式传递,所以需要使用 std 中的move函数将变量转成右值引用。 38 | - 由于树错综复杂的结点指向关系,只将左右孩子结点定义为 unique_ptr,而将结点的父指针定义为一个裸指针。因为父指针指向的结点或是 root 结点,或是已经被其他左右孩子结点指针指向的内容,所以父结点指针只起搜索的作用,不允许修改结点的内容。其次,在 Insert 的过程中要进行结点的上溯,所以必须使用裸指针保证上溯时指针的拷贝传递而不是 move。 39 | 40 | 最终设计的结点 **结构体** 如下: 41 | 42 | ```cpp 43 | enum Color { RED, BLANK }; 44 | 45 | template struct RBNode { 46 | T key; 47 | Color color; 48 | //* Automatically assigned the initial value 'nullptr' 49 | std::unique_ptr> left; 50 | std::unique_ptr> right; 51 | RBNode *parent; //* it doesn't 52 | explicit RBNode(const T &key) 53 | : key(key), color(Color::RED), parent(nullptr) {} 54 | }; 55 | ``` 56 | 57 | 实际实现还有一些需要的注意点: 58 | 59 | - unique_ptr 定义时会自动初始化为 nullptr,而裸指针不会,所以需要在构造函数中初始化为 nullptr 60 | - 使用了列表化的构造函数的写法,其中 color 会自动初始化为 RED,对应了 Insert 时将结点置为红的操作。 61 | - 使用 explicit 保证避免构造函数的隐式类型转换。 62 | 63 | - **树的设计** 64 | 65 | 树中有两个关键结点:root 和 nil,然而我认为书本在这个位置有一定的误导性。书本说为了减小存储所需空间,本来所有叶子结点的左右孩子(即 nil 结点)可以合成一个结点 T.nil 。但是在实际实现时,可以考虑以下问题: 66 | 67 | - 如果将 nil 结点视为正常结点,使用上述构造函数构造,那么 nil 结点无法定义父结点,因为所有树的叶子结点都是它的父结点。 68 | - nil 结点的key值无法定义。 69 | 70 | 考虑到我们使用指针去保证树的链接,所以直接使用 nullptr 空指针去代替 nil 结点即可,同时 root 结点由于被声明为 unique_ptr,它也会被初始化为 nullptr ,这是可能会有一个问题:这样无法满足红黑树的性质 2: nil 结点为黑色,因为它只是一个空指针,没有 color 这个属性。然而这个问题也很好解决:当搜索到空指针时,认为该指针指向的 nil 结点即可,如果需要使用 nil 结点的颜色,就认为该空指针 “对应” 了黑色。这种假设在树为空时也是成立的:当树为空时,root 结点也是 nullptr,它的 “颜色” 也刚好为黑(红黑树性质 1:根节点为黑)。 71 | 72 | 红黑树类被声明为: 73 | 74 | ```cpp 75 | template class RBTree { 76 | public: 77 | void RBInsert(const T &key); 78 | void Display(const string LNR_path, const string NLR_path, 79 | const string LOT_path); 80 | 81 | private: 82 | void LeftRotate(unique_ptr> &&x); 83 | void RightRotate(unique_ptr> &&x); 84 | 85 | private: 86 | void RBInsert(unique_ptr> &&z); 87 | void RBInsertFixup(unique_ptr> &&z); 88 | 89 | private: 90 | unique_ptr> root; 91 | }; 92 | 93 | ``` 94 | 95 | 其中重载了 RBInsert 函数,分别代表使用一个 key 插入和使用一个结点的右值引用插入,逻辑是调用者只需要使用 public 的RBInsert(const T &key) 函数即可,程序内部会将其转换为一个结点实体进行插入。 96 | 97 | 2. **对数据结构的一些补充** 98 | 99 | 在介绍完结点和树的数据结构之后可以看到:对于非平凡树而言,所有指针中只有一部分是 unique_ptr,这些指针包括: 100 | 101 | - root 结点 102 | - 所有非叶子结点的左右孩子 103 | 104 | 这也意味着以下结论: 105 | 106 | - 所有叶子结点的左右孩子都是 nullptr 107 | - 所有非根结点,其所占内存都由其父结点的或是左孩子结点指针,或是右孩子结点指针管理,**其本身没有定义一个裸结点指针管理其内存**。这样做是为了保证 unique_ptr 的指向唯一性,避免多个指针管理(修改)同一块内存,便于unique_ptr 的内存管理。 108 | - 所有 parent 指针都只有搜索和上溯的作用,不允许裸指针操作内存。 109 | 110 | 3. **算法设计** 111 | 112 | 分别按照 PPT 中实现左旋、右旋、插入、调整算法即可。不做赘述。 113 | 114 | ## 实验结果 115 | 116 | 1. 编译命令: 117 | 118 | ```bash 119 | g++ -Wall RBT.cpp -o RBT 120 | ``` 121 | 122 | 2. 打印结果: 123 | 124 | 打印调整算法中的 case: 125 | 126 | ![](assets/0.png) 127 | 128 | 3. 文件输出结果: 129 | 130 | - 中序遍历 LNR 131 | 132 | ![](assets/LNR.png) 133 | 134 | - 前序遍历 NLR 135 | 136 | ![](assets/NLR.png) 137 | 138 | - 层序遍历 LOT 139 | 140 | ![](assets/LOT.png) 141 | 142 | 143 | 4. 正确性检查: 144 | 145 | 可以根据[红黑树在线生成网站](https://www.cs.usfca.edu/~galles/visualization/RedBlack.html)生成的模板进行比对,可以看到结果是正确的。 146 | 147 | ![](assets/template.png) 148 | 149 | ## 困难以及解决 150 | 151 | 在使用 unique_ptr 时,发现了一个问题,当两个 unique_ptr 被判断相等时,对一个 unique_ptr 所指向的内容做修改时,另一个unique_ptr 将会与之同步更改,这就是 unique_ptr 的性质。 152 | 153 | 比如在左右旋程序中需要判断该点是其父结点的左孩子还是右孩子: 154 | 155 | ```cpp 156 | else if (x == xp->left){} 157 | ``` 158 | 159 | 但是接下来,需要做一系列操作: 160 | 161 | ```cpp 162 | xp->left = move(y); 163 | xp->left->left = move(x); 164 | xp->left->left->parent = xp->left.get(); 165 | ``` 166 | 167 | 但是由于 x 与 xp->left 相等,故第一行过后,x 本身也同步指向了xp->left 的值,而非维持原来的值。 168 | 169 | 所以需要事先保留 x 所指向的值: 170 | 171 | ```cpp 172 | RBNode *x_tmp = x.release(); 173 | xp->left = move(y); 174 | xp->left->left = unique_ptr>(x_tmp); 175 | xp->left->left->parent = xp->left.get(); 176 | ``` 177 | 178 | 其中第三行是使用 unique_ptr 的通过裸指针的默认有参构造函数,通过unique-ptr 的 pointer 模板创建 RBNode 的指针,而参数即为RBNode型指针,也就是之前我们保留的 x.release()。 179 | 180 | 并且,我们使用 release 而非 get 是因为:此时的 x 是函数的右值引用的形参,所以将 x 对其指向的内存释放所有权。 181 | -------------------------------------------------------------------------------- /Lab1/src/qsort.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int *LoadData(char *data_path); 8 | void StoreData(int *data, int data_size, char *store_path); 9 | 10 | int GetRandom(int Min, int Max); 11 | int *Partition(int *data, int p, int r); 12 | int *Random_Partition(int *data, int p, int r); 13 | int *Medium_Partition(int *data, int p, int r); 14 | 15 | void InsertSort(int *data, int p, int r); 16 | void QuickSort_Medium_Partition(int *data, int p, int r, int K); 17 | void QuickSort_Random_Partition(int *data, int p, int r, int K); 18 | void QuickSort_Partition(int *data, int p, int r, int K); 19 | bool Verify(int *data, int p, int r); 20 | 21 | void swap(int *a, int *b) { 22 | int tmp = *a; 23 | *a = *b; 24 | *b = tmp; 25 | } 26 | 27 | int *LoadData(char *data_path) { 28 | FILE *f = fopen(data_path, "r"); 29 | int data_size; 30 | int count = 0; 31 | int *data = NULL; 32 | if (f == NULL) { 33 | printf("No such file or dictionary!\n"); 34 | } else { 35 | fscanf(f, "%d", &data_size); 36 | data = (int *)malloc(data_size * sizeof(int)); 37 | while (fscanf(f, "%d", &data[count]) == 1) { 38 | count++; 39 | if (count == data_size) break; 40 | } 41 | } 42 | fclose(f); 43 | return data; 44 | } 45 | 46 | void StoreData(int *data, int data_size, char *store_path) { 47 | FILE *f = fopen(store_path, "w"); 48 | if (f != NULL) { 49 | for (int i = 0; i < data_size; ++i) { 50 | fprintf(f, "%d", data[i]); 51 | if (i % 17 == 0 && i != 0) 52 | fprintf(f, "\n"); 53 | else 54 | fprintf(f, " "); 55 | } 56 | } 57 | fclose(f); 58 | } 59 | 60 | int GetRandom(int Min, int Max) { 61 | srand(time(NULL)); // initialize generator of random number 62 | return rand() % (Max + 1 - Min) + Min; 63 | } 64 | 65 | int *Partition(int *data, int p, int r) { 66 | int lptr = p, rptr = r + 1; 67 | int i = lptr; 68 | int base = data[p]; 69 | while (i < rptr) { 70 | if (data[i] < base) { 71 | swap(data + i, data + lptr + 1); 72 | lptr++; 73 | i++; 74 | } else if (data[i] > base) { 75 | swap(data + i, data + rptr - 1); 76 | rptr--; 77 | } else { 78 | i++; 79 | } 80 | } 81 | swap(data + p, data + lptr); 82 | int *ret = (int *)malloc(2 * sizeof(int)); 83 | ret[0] = lptr; 84 | ret[1] = rptr; 85 | return ret; 86 | } 87 | 88 | int *Random_Partition(int *data, int p, int r) { 89 | int ran = GetRandom(p, r); 90 | swap(data + ran, data + p); 91 | return Partition(data, p, r); 92 | } 93 | 94 | int *Medium_Partition(int *data, int p, int r) { 95 | int arr[5] = {data[p], data[(3 * p + r) / 4], data[(p + r) / 2], 96 | data[(p + 3 * r) / 4], data[r]}; 97 | int index[5] = {p, (3 * p + r) / 4, (p + r) / 2, (p + 3 * r) / 4, r}; 98 | for (int i = 0; i < 5; i++) { 99 | for (int j = i + 1; j < 5; j++) { 100 | if (arr[j] < arr[i]) { 101 | swap(arr + i, arr + j); 102 | swap(index + i, index + j); 103 | } 104 | } 105 | } 106 | swap(data + index[2], data + p); 107 | return Partition(data, p, r); 108 | } 109 | 110 | void InsertSort(int *data, int p, int r) { 111 | for (int i = p + 1; i <= r; i++) { 112 | int elem = data[i]; 113 | int j; 114 | for (j = i; j > p && data[j - 1] > elem; j--) { data[j] = data[j - 1]; } 115 | data[j] = elem; 116 | } 117 | } 118 | 119 | void QuickSort_Medium_Partition(int *data, int p, int r, int K) { 120 | if (p > r) return; 121 | if (r - p + 1 <= K) { 122 | InsertSort(data, p, r); 123 | return; 124 | } 125 | // int* ret = Random_Partition(data, p, r); 126 | int *ret = Medium_Partition(data, p, r); 127 | QuickSort_Medium_Partition(data, p, ret[0] - 1, K); 128 | QuickSort_Medium_Partition(data, ret[1], r, K); 129 | } 130 | 131 | void QuickSort_Random_Partition(int *data, int p, int r, int K) { 132 | if (p > r) return; 133 | if (r - p + 1 <= K) { 134 | InsertSort(data, p, r); 135 | return; 136 | } 137 | int *ret = Random_Partition(data, p, r); 138 | QuickSort_Random_Partition(data, p, ret[0] - 1, K); 139 | QuickSort_Random_Partition(data, ret[1], r, K); 140 | } 141 | 142 | void QuickSort_Partition(int *data, int p, int r, int K) { 143 | if (p > r) return; 144 | if (r - p + 1 <= K) { 145 | InsertSort(data, p, r); 146 | return; 147 | } 148 | int *ret = Partition(data, p, r); 149 | QuickSort_Partition(data, p, ret[0] - 1, K); 150 | QuickSort_Partition(data, ret[1], r, K); 151 | } 152 | 153 | bool Verify(int *data, int p, int r) { 154 | bool flag = true; 155 | if (p > r) return false; 156 | for (int i = p; i < r; i++) { 157 | if (data[i] > data[i + 1]) { 158 | flag = false; 159 | break; 160 | } 161 | } 162 | return flag; 163 | } 164 | 165 | int compare(const void *a, const void *b) { return (*(int *)a - *(int *)b); } 166 | 167 | int main(int argc, char **argv) { 168 | int *arr = NULL; 169 | clock_t start, end; 170 | clock_t duration_medium = 0, duration_random = 0, duration = 0, 171 | c_lib_duration = 0; 172 | char *data_path = "../Data/ExtraData/1.in"; 173 | char *store_path = "../Data/sorted.txt"; 174 | int K = 32; 175 | if (argc < 1) { 176 | return 0; 177 | } else { 178 | for (int i = 1; i < argc; i++) { 179 | if (strcmp(argv[i], "-f") == 0) { 180 | data_path = argv[i + 1]; 181 | } else if (strcmp(argv[i], "-K") == 0 || strcmp(argv[i], "-k") == 0) { 182 | K = atoi(argv[i + 1]); 183 | } 184 | else if (strcmp(argv[i], "-o") == 0){ 185 | store_path = argv[i + 1]; 186 | } 187 | else if (strcmp(argv[i], "-h") == 0) { 188 | printf("-h : help message\n"); 189 | printf("-f : file path\n"); 190 | printf("-o : store path\n"); 191 | printf( 192 | "-K/-k : 2 ~ 128, length of sequence when stop recursion\n"); 193 | } 194 | } 195 | } 196 | if (K < 2 ) { 197 | printf("please modify you parameter '-K'\n"); 198 | return 0; 199 | } 200 | for (size_t i = 0; i < 100; i++) { 201 | arr = LoadData(data_path); 202 | start = clock(); 203 | QuickSort_Medium_Partition(arr, 0, _msize(arr) / sizeof(int) - 1, K); 204 | end = clock(); 205 | duration_medium += end - start; 206 | } 207 | for (size_t i = 0; i < 100; i++) { 208 | arr = LoadData(data_path); 209 | start = clock(); 210 | QuickSort_Random_Partition(arr, 0, _msize(arr) / sizeof(int) - 1, K); 211 | end = clock(); 212 | duration_random += end - start; 213 | } 214 | for (size_t i = 0; i < 100; i++) { 215 | arr = LoadData(data_path); 216 | start = clock(); 217 | QuickSort_Partition(arr, 0, _msize(arr) / sizeof(int) - 1, K); 218 | end = clock(); 219 | duration += end - start; 220 | } 221 | for (size_t i = 0; i < 100; i++) { 222 | arr = LoadData(data_path); 223 | start = clock(); 224 | qsort(arr, _msize(arr) / sizeof(int), sizeof(arr[0]), compare); 225 | end = clock(); 226 | c_lib_duration += end - start; 227 | } 228 | printf("Average duration of my quicksort (medium partition) is %lf ms\n", 229 | (double)duration_medium / 100); 230 | printf("Average duration of my quicksort (random partition) is %lf ms\n", 231 | (double)duration_random / 100); 232 | printf("Average duration of my quicksort (static partition) is %lf ms\n", 233 | (double)duration / 100); 234 | printf("Average duration of c-lib-qsort is %lf ms\n", 235 | (double)c_lib_duration / 100); 236 | 237 | // test and store 238 | arr = LoadData(data_path); 239 | QuickSort_Medium_Partition(arr, 0, _msize(arr) / sizeof(int) - 1, K); 240 | bool flag = Verify(arr, 0, _msize(arr) / sizeof(int) - 1); 241 | flag == true ? printf("Already sorted!") : printf("Not sorted!"); 242 | if (flag) StoreData(arr, _msize(arr) / sizeof(int), store_path); 243 | } 244 | -------------------------------------------------------------------------------- /Lab1/report/算法第一次实验.md: -------------------------------------------------------------------------------- 1 | # 算法第一次实验报告 2 | 3 | > 学号:PB21030794 姓名:王道宇 4 | 5 | ## 实验内容 6 | 7 | 编程实现**快速排序**,并且实现**快速排序**的优化。 8 | 9 | ## 算法思想介绍 10 | 11 | > 由于老师要求,快速排序计时结果需要与 C Standard Library 中 qsort 函数对比。然而笔者开始实现快速排序时使用的是C++ 语言编写,所以后来又移植了 C 语言版本。在算法思想介绍阶段使用 C++ 版本介绍,实验结果展示时使用 C 语言版本。 12 | 13 | #### 传统快速排序思想的一些问题 14 | 15 | - 在传统快速排序中,算法的性能与每次 PARTITION 的分组结果密切相关。但 PARTITION 使用的是固定基准,如果分组不够合理将导致递归调用过深,时间复杂度将达到 $O(n^2)$ ,这是不可接收的。 16 | - 传统快速排序将递归终点定为排序序列的长度为 2 ,然而在序列长度较短时,使用快速排序所带来的性能提升相较于调用递归函数的开销可能是微乎其微的。 17 | - 当需要排序的序列中有大量元素重复时,传统快速排序使用一个元素作为 pivot value 显然是不够合理的。 18 | 19 | #### 相较于问题的一系列改进 20 | 21 | - 为了找到每次 PARTITION 的最优结果,我们的任务是找到序列中尽量接近中位数的值作为 pivot value。以下是两种方法: 22 | 23 | 1. 随机基准法:在每次 PARTITION 时,从序列中随机找到一个数作为 pivot value。这种方式从期望上不会落入最差情况。 24 | 2. 多数取中法:取序列中特定位置的部分值排序,取中位数作为 pivot value。这种方法简单直观,并且开销较小,**实验结果中也用了这种方法**。最终结果中,由于选取的其它参数的影响,将取值定为取五个数的中位数。选取位置为序列的三个四等分点以及起始结尾。 25 | 26 | **但是实际上,如果序列足够的乱,固定基准也不会比上述两种方式慢。** 27 | 28 | - 为了实现尽量少的调用递归函数的开销,选取 K,使得当序列的长度小于 K 时,使用插入排序代替快速排序。结果中也会分析不同 K 对运行时间的影响。 29 | 30 | - 在选取 pivot value 时,可以将只选一个值改为选择与该 pivot value 相等的一段区间,这样可以有效减少递归深度。具体实现时,需要维护序列的五个指针,分别是最左的头指针 p 、最右的尾指针 r 、第一个与 pivot value 相等的值的位置 lptr 、最后一个与 pivot value 相等的值的位置 rptr 以及遍历指针 i 。pivot的初始位置在序列的最左,即 p ,lptr 的初始位置在 p,rptr 的初始位置在 r + 1,遍历指针 i 的初始位置在 p 。在遍历过程中,遍历指针右移,当遍历的当前值比 pivot 小时,将 lptr 的后一个位置与当前值互换,并且 lptr 同步右移;当遍历的当前值比 pivot 大时,将 rptr 的前一个位置与当前值互换,将 rptr 左移,但是遍历指针不右移(因为交换过来的值还没有遍历);当遍历的当前值与 pivot 相等时,将遍历指针右移然而 lptr 不右移。这样,在遍历的过程中 (lptr,i) 的元素均与 pivot 相等。遍历的终止条件是 i >= rptr,表示当前值就是所有与 pivot 相等的元素的下一个了。遍历结束之后,将 lptr 位置的值与序列开始的 pivot 互换,这样 [lptr, rptr) 即为最终所有与 pivot 相等的元素。具体代码如下: 31 | 32 | ```cpp 33 | pair Qsort::Partition(vector &data, int p, int r) { 34 | int lptr = p, rptr = r + 1; 35 | int i = lptr; 36 | int pivot = data[p]; 37 | while (i < rptr) { 38 | if (data[i] < pivot) { 39 | swap(data[i], data[lptr + 1]); 40 | lptr++; 41 | i++; 42 | } else if (data[i] > pivot) { 43 | swap(data[i], data[rptr - 1]); 44 | rptr--; 45 | } else { 46 | i++; 47 | } 48 | } 49 | swap(data[p], data[lptr]); 50 | return make_pair(lptr, rptr); 51 | } 52 | ``` 53 | 54 | 返回值为 lptr 与 rptr 的 pair 。 55 | 56 | ## 实验结果 57 | 58 | > 运行环境:Windows 11,Intel Core i7-13700H (Laptop) 59 | > 60 | > 也尝试了本机的 Ubuntu 23.04 Laptop 系统以及挂载在Windows wsl2 下的 Ubuntu 22.04,用于测试时间,结果大致相同,故以Windows 为结果展示。 61 | 62 | > 实验仓库为:https://github.com/Melmaphother/2023-Algorithm-Labs 但目前未公开。由于实验报告的篇幅限制,无法将所有截图全部展示,如果助教需要检查除提交外的内容,可以与我联系 ~~ 63 | 64 | #### 关于实验结果的约定: 65 | 66 | - 为了与 C Standard Library 中的 qsort 函数对比,将算法移植到了 C 上。基本的方法和优化并没有改动。 67 | 68 | - 为了避免每次运行结果的浮动,使用 100 次循环,分别测试编写的 QuickSort 函数和 qsort 函数,运行函数之前重新从文件中读入原始数据。 69 | 70 | - 为了检测排序结果是否正确,程序内部编写了 Verify 函数,遍历序列检测是否为真。 71 | 72 | - 为了避免递归的额外开销,程序编译时使用了 gcc -O1 优化。编译命令为: 73 | 74 | ```bash 75 | gcc -Wall -O1 qsort.c -o qsort 76 | ``` 77 | 78 | - 对于原始 1e5 条数据,探究了 K 对程序运行时间的影响,并且选择了相较而言最优的 K。 79 | 80 | - 对于额外的三份数据,也做了运行时间的对比,但没有再探究最合适的 K 值了(感觉没什么必要),使用的是上一条数据对应的 K 值。 81 | 82 | - 运行结果的形式:分为三行,第一行是实验中实现的快速排序运行 100 次的平均时间,第二行是使用qsort 运行 100 次的平均时间,第三行是一个序列是否已正确排序的信息:使用 Verify 函数,如果已经正确排序,则输出 "Already sorted!"。 83 | 84 | #### 如何得到实验结果 85 | 86 | 实验中使用了多个参数以及其默认值: 87 | 88 | - -h:打印帮助信息,并且按照程序设定的默认值运行程序。但是默认值可能跟检查时的数据集文件路径不相同,建议使用下文 -f 参数。 89 | - -f:数据的路径,可以是相对路径或绝对路径 90 | - -o:排序完成之后的保持路径,可以是相对路径或绝对路径 91 | - -K 或 -k:递归程序的终点,也就是何时开始使用插入排序而非递归的快速排序。 92 | 93 | 一个建议的输入样例: 94 | 95 | Windows下: 96 | 97 | ```bash 98 | ./qsort.exe -f 0.in -K 64 99 | ``` 100 | 101 | Linux 下: 102 | 103 | ```bash 104 | ./qsort -f 0.in -K 64 105 | ``` 106 | 107 | #### 实验结果: 108 | 109 | - 第一份数据:初始 1e5 条数据 110 | 111 | 对每个 K 测试 1 ~ 3 次,时间单位为 ms 112 | 113 | | K And PARTITION | Medium Partition | Random Partition | Static Partition | C Library `qsort` | 114 | | --------------- | ---------------- | ---------------- | ---------------- | ----------------- | 115 | | 2 | 7.80 ~ 7.88 | 7.89 ~ 8.27 | 7.52 | 7.78 ~ 7.88 | 116 | | 8 | 6.51 ~ 6.57 | 6.44 ~ 6.65 | 6.06 ~ 6.13 | 7.45 ~ 7.66 | 117 | | 16 | 5.95 ~ 5.99 | 5.91 ~ 6.23 | 5.65 ~ 5.84 | 7.30 ~ 7.56 | 118 | | 32 | 5.62 ~ 5.67 | 5.32 ~ 5.58 | 5.34 ~ 5.52 | 7.33 ~ 7.42 | 119 | | 48 | 5.46 ~ 5.51 | 5.33 ~ 5.52 | 5.28 ~ 5.36 | 7.37 ~ 7.48 | 120 | | 64 | 5.36 ~ 5.42 | 5.06 ~ 5.42 | 5.28 ~ 5.33 | 7.26 ~ 7.38 | 121 | | 96 | 5.32 ~ 5.36 | 5.26 ~ 5.38 | 5.22 ~ 5.29 | 7.33 ~ 7.50 | 122 | | 128 | 5.32 ~ 5.33 | 5.47 ~ 5.54 | 5.23 ~ 5.41 | 7.10 ~ 7.33 | 123 | 124 | 运行截图为: 125 | 126 | ![](assets/1.png) 127 | 128 | 可以发现,随着 K 增大,在正常范围内,平均运行时间会先呈减小趋势,再慢慢趋于稳定。在这个例子中最短的运行时间应当是 K = 64 时。 129 | 130 | 同时可以看到,在数据集较小并且随机性较好时,三种 PARTITION 方法几乎都处在同一时间,并且在取到合适的 K 时可以显著地比 C Library 中的 qsort 快。 131 | 132 | 可以发现,RANDOM PARTITION 方式时间跨度较大,稳定性较弱,相比较而言,MEDIUM PARTITION 方式时间跨度较小,稳定性较好。 133 | 134 | - 第二份数据:1e6 条数据,特征为数据随机,取 K 为 32 135 | 136 | 对每个 K 测试 1 次,时间单位为 ms 137 | 138 | | K And PARTITION | Medium Partition | Random Partition | Static Partition | C Library `qsort` | 139 | | --------------- | ---------------- | ---------------- | ---------------- | ----------------- | 140 | | 2 | 151.88 | 151.80 | 131.39 | 140.24 | 141 | | 8 | 130.77 | 123.14 | 114.51 | 138.39 | 142 | | 16 | 120.18 | 124.90 | 162.55 | 149.59 | 143 | | 32 | 137.29 | 239.09 | 242.95 | 379.01 | 144 | | 64 | 166.11 | 218.13 | 135.92 | 265.34 | 145 | | 128 | 212.85 | 241.78 | 249.32 | 341.68 | 146 | 147 | 以 K = 32 的数据为例,运行截图为: 148 | 149 | ![](assets/2.png) 150 | 151 | 可以看到,在数据数量较大时,MEDIUM 和 RANDOM 方法在很多情况下要更优于 STATIC 方法,因为分治数据得到了更好的平均。 152 | 153 | - 第三份数据:1e6 条数据,特征为有大量重复元素 154 | 155 | 对每个 K 测试 1 次,时间单位为 ms 156 | 157 | | K And PARTITION | Medium Partition | Random Partition | Static Partition | C Library `qsort` | 158 | | --------------- | ---------------- | ---------------- | ---------------- | ----------------- | 159 | | 2 | 35.69 | 33.88 | 34.72 | 45.28 | 160 | | 8 | 37.37 | 36.74 | 35.87 | 48.14 | 161 | | 16 | 38.12 | 37.24 | 36.49 | 46.03 | 162 | | 32 | 35.34 | 35.71 | 37.45 | 47.54 | 163 | | 48 | 35.07 | 33.61 | 34.55 | 45.96 | 164 | | 64 | 36.17 | 33.89 | 34.75 | 45.62 | 165 | | 128 | 35.55 | 33.22 | 34.93 | 45.52 | 166 | 167 | 以 K = 16, 8 的数据为例,运行截图为: 168 | 169 | ![](assets/14.png) 170 | 171 | 在有大量重复元素的前提下,三种方式都比 qsort 要快,并且幅度较大。 172 | 173 | - 第四份数据:1e6 条数据,特征为序列近似有序 174 | 175 | 对每个 K 测试 1 次,时间单位为 ms 176 | 177 | | K And PARTITION | Medium Partition | Random Partition | Static Partition | C Library `qsort` | 178 | | --------------- | ---------------- | ---------------- | ---------------- | ----------------- | 179 | | 32 | 18.42 | 210.32 | 1285.13 | 164.15 | 180 | | 64 | 23.69 | 257.56 | 370.80 | 24.40 | 181 | | 128 | 30.09 | 92.91 | 279.31 | 31.75 | 182 | 183 | 运行截图为: 184 | 185 | ![](assets/21.png) 186 | 187 | 可以看到,在序列近似有序的情况下,STATIC PARTITION 方法将会落入 $O(n^2)$ 的陷阱中,运行时间显著比 MEDIUM PARTITION 方法长。同时,在这种情况下,RANDOM PARTITION 方法只能是中规中矩的水平。 -------------------------------------------------------------------------------- /Lab2/src/ClosestPointPair.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | 16 | typedef tuple Point; 17 | typedef tuple PointPair; 18 | 19 | #define serial 0 20 | #define loc_X 1 21 | #define loc_Y 2 22 | 23 | #define _p1 0 24 | #define _p2 1 25 | #define _dist 2 26 | 27 | class ClosestPair { 28 | public: 29 | ClosestPair(string data_path); 30 | static double Distance(Point &P1, Point &P2); 31 | PointPair FindClosestPair(); 32 | PointPair NormalFindClosestPair(); 33 | 34 | private: 35 | void PreSort(); 36 | PointPair ClosestPointPair(vector &Point_X_Sort, 37 | vector &Point_Y_Sort, unsigned p, 38 | unsigned r); 39 | PointPair Merge(vector &Points); 40 | 41 | private: 42 | static bool CompLocX(const Point &P1, const Point &P2); 43 | static bool CompLocY(const Point &P1, const Point &P2); 44 | 45 | private: 46 | vector Points; 47 | unsigned PointSize; 48 | vector Points_X_Sort; 49 | vector Points_Y_Sort; 50 | }; 51 | 52 | ClosestPair::ClosestPair(string data_path) { 53 | ifstream f; 54 | double _loc_X, _loc_Y; 55 | unsigned _serial; 56 | // load data into vector 57 | f.open(data_path, ios::in); 58 | if (f.is_open()) { 59 | while (!f.eof()) { 60 | f >> _serial >> _loc_X >> _loc_Y; 61 | // cout << _serial << ' ' << _loc_X << ' ' << _loc_Y << endl; 62 | Points.push_back(make_tuple(_serial, _loc_X, _loc_Y)); 63 | } 64 | } 65 | // Points.pop_back(); 66 | PointSize = Points.size(); 67 | f.close(); 68 | } 69 | 70 | double ClosestPair::Distance(Point &P1, Point &P2) { 71 | double DistPowX = pow(get(P1) - get(P2), 2); 72 | double DistPowY = pow(get(P1) - get(P2), 2); 73 | return sqrt(DistPowX + DistPowY); 74 | } 75 | 76 | PointPair ClosestPair::FindClosestPair() { 77 | PreSort(); 78 | // base on the sorted array X and array Y, find the closest point pair 79 | PointPair closest_pair = 80 | ClosestPointPair(Points_X_Sort, Points_Y_Sort, 0, PointSize); 81 | if (get(get<_p1>(closest_pair)) > 82 | get(get<_p2>(closest_pair))) 83 | return make_tuple(get<_p2>(closest_pair), get<_p1>(closest_pair), 84 | get<_dist>(closest_pair)); 85 | else 86 | return closest_pair; 87 | } 88 | 89 | PointPair ClosestPair::NormalFindClosestPair() { 90 | Point point1 = make_tuple(INT_MAX, DBL_MAX, DBL_MAX); 91 | Point point2 = make_tuple(INT_MAX, DBL_MAX, DBL_MAX); 92 | double closest_dist = DBL_MAX; 93 | for (size_t i = 0; i < PointSize; i++) { 94 | for (size_t j = i + 1; j < PointSize; j++) { 95 | double dist = Distance(Points[i], Points[j]); 96 | if (dist < closest_dist) { 97 | point1 = Points[i]; 98 | point2 = Points[j]; 99 | closest_dist = dist; 100 | } 101 | } 102 | } 103 | return make_tuple(point1, point2, closest_dist); 104 | } 105 | 106 | PointPair ClosestPair::ClosestPointPair(vector &Points_X_Sort, 107 | vector &Points_Y_Sort, 108 | unsigned p, unsigned r) { 109 | unsigned points_size = r - p; 110 | if (points_size < 0) 111 | exit(-1); 112 | else if (points_size == 0 || points_size == 1) { 113 | Point NonePoint = make_tuple(INT_MAX, DBL_MAX, DBL_MAX); 114 | PointPair NonePair = make_tuple(NonePoint, NonePoint, DBL_MAX); 115 | return NonePair; 116 | } else if (points_size <= 3) { 117 | Point point1 = make_tuple(INT_MAX, DBL_MAX, DBL_MAX); 118 | Point point2 = make_tuple(INT_MAX, DBL_MAX, DBL_MAX); 119 | double closest_dist = DBL_MAX; 120 | for (int i = p; i < r; i++) { 121 | for (int j = i + 1; j < r; j++) { 122 | double dist = Distance(Points_X_Sort[i], Points_X_Sort[j]); 123 | if (dist < closest_dist) { 124 | point1 = Points_X_Sort[i]; 125 | point2 = Points_X_Sort[j]; 126 | closest_dist = dist; 127 | } 128 | } 129 | } 130 | return make_tuple(point1, point2, closest_dist); 131 | } 132 | // * Divide and Recursion 133 | unsigned m = (p + r - 1) / 2; 134 | double loc_X_div = get(Points_X_Sort[m]); 135 | vector YL, YR; 136 | for (unsigned i = 0; i < points_size; i++) { 137 | if (get(Points_Y_Sort[i]) <= loc_X_div) 138 | //* if Point_Y_Sort.loc_X > Point_X_Sort[middle].loc_X, add this 139 | //* point to YL 140 | YL.push_back(Points_Y_Sort[i]); 141 | 142 | else 143 | YR.push_back(Points_Y_Sort[i]); 144 | } 145 | 146 | PointPair pair1 = ClosestPointPair(Points_X_Sort, YL, p, m + 1); 147 | PointPair pair2 = ClosestPointPair(Points_X_Sort, YR, m + 1, r); 148 | // * Compare ret 149 | PointPair closest_pair = 150 | get<_dist>(pair1) < get<_dist>(pair2) ? pair1 : pair2; 151 | double closest_dist = get<_dist>(closest_pair); //* 一般不会为 DBL_MAX 152 | // * Merge 153 | double l_loc_X_limit = (closest_dist == DBL_MAX) 154 | ? get(Points_X_Sort[p]) 155 | : loc_X_div - closest_dist; 156 | double r_loc_X_limit = (closest_dist == DBL_MAX) 157 | ? get(Points_X_Sort[r]) 158 | : loc_X_div + closest_dist; 159 | vector Points_Y_Sort_limit; 160 | for (int i = 0; i < points_size; i++) { 161 | if (get(Points_Y_Sort[i]) >= l_loc_X_limit && 162 | get(Points_Y_Sort[i]) <= r_loc_X_limit) { 163 | Points_Y_Sort_limit.push_back(Points_Y_Sort[i]); 164 | } 165 | } 166 | PointPair merge_closest_pair = Merge(Points_Y_Sort_limit); 167 | double merge_closest_dist = get<_dist>(merge_closest_pair); 168 | 169 | return (closest_dist <= merge_closest_dist) ? closest_pair 170 | : merge_closest_pair; 171 | } 172 | 173 | void ClosestPair::PreSort() { 174 | Points_X_Sort = Points; 175 | Points_Y_Sort = Points; 176 | sort(Points_X_Sort.begin(), Points_X_Sort.end(), this->CompLocX); 177 | sort(Points_Y_Sort.begin(), Points_Y_Sort.end(), this->CompLocY); 178 | } 179 | 180 | PointPair ClosestPair::Merge(vector &Points) { 181 | unsigned points_size = Points.size(); 182 | if (points_size == 0) { 183 | Point NonePoint = make_tuple(INT_MAX, DBL_MAX, DBL_MAX); 184 | PointPair NonePair = make_tuple(NonePoint, NonePoint, DBL_MAX); 185 | return NonePair; 186 | } 187 | Point point1 = make_tuple(INT_MAX, DBL_MAX, DBL_MAX); 188 | Point point2 = make_tuple(INT_MAX, DBL_MAX, DBL_MAX); 189 | double closest_dist = DBL_MAX; 190 | for (int i = 0; i < Points.size(); i++) { 191 | for (int j = i + 1; j < Points.size() && j < i + 7; j++) { 192 | double dist = Distance(Points[i], Points[j]); 193 | if (dist < closest_dist) { 194 | point1 = Points[i]; 195 | point2 = Points[j]; 196 | closest_dist = dist; 197 | } 198 | } 199 | } 200 | return make_tuple(point1, point2, closest_dist); 201 | } 202 | 203 | bool ClosestPair::CompLocX(const Point &P1, const Point &P2) { 204 | return get(P1) < get(P2); 205 | } 206 | 207 | bool ClosestPair::CompLocY(const Point &P1, const Point &P2) { 208 | return get(P1) < get(P2); 209 | } 210 | 211 | int main() { 212 | string data_path = "data.txt"; 213 | ClosestPair points(data_path); 214 | 215 | clock_t start, end; 216 | 217 | start = clock(); 218 | PointPair point_pair = points.FindClosestPair(); 219 | end = clock(); 220 | cout << "duration of find closest pair is " << end - start << "ms" << endl; 221 | 222 | Point point1 = get<_p1>(point_pair); 223 | Point point2 = get<_p2>(point_pair); 224 | double dist = get<_dist>(point_pair); 225 | cout << "The closest point pair is" << endl; 226 | cout << get(point1) << ' ' << get(point1) << ' ' 227 | << get(point1) << endl; 228 | cout << get(point2) << ' ' << get(point2) << ' ' 229 | << get(point2) << endl; 230 | cout << "The distance of the point pair is " << dist << endl << endl; 231 | 232 | start = clock(); 233 | PointPair normal_point_pair = points.NormalFindClosestPair(); 234 | end = clock(); 235 | cout << "duration of normal find closest pair is " << end - start << "ms" << endl; 236 | 237 | Point normal_point1 = get<_p1>(normal_point_pair); 238 | Point normal_point2 = get<_p2>(normal_point_pair); 239 | double normal_dist = get<_dist>(normal_point_pair); 240 | cout << "The closest point pair is" << endl; 241 | cout << get(normal_point1) << ' ' << get(normal_point1) 242 | << ' ' << get(normal_point1) << endl; 243 | cout << get(normal_point2) << ' ' << get(normal_point2) 244 | << ' ' << get(normal_point2) << endl; 245 | cout << "The distance of the point pair is " << normal_dist << endl; 246 | } -------------------------------------------------------------------------------- /Lab3/src/RBT.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | enum Color { RED, BLANK }; 10 | 11 | template struct RBNode { 12 | T key; 13 | Color color; 14 | //* Automatically assigned the initial value 'nullptr' 15 | std::unique_ptr> left; 16 | std::unique_ptr> right; 17 | RBNode *parent; //* it doesn't 18 | explicit RBNode(const T &key) 19 | : key(key), color(Color::RED), parent(nullptr) {} 20 | }; 21 | 22 | template class RBTree { 23 | public: 24 | void RBInsert(const T &key); 25 | void Display(const string LNR_path, const string NLR_path, 26 | const string LOT_path); 27 | 28 | private: 29 | void LeftRotate(unique_ptr> &&x); 30 | void RightRotate(unique_ptr> &&x); 31 | 32 | private: 33 | void RBInsert(unique_ptr> &&z); 34 | void RBInsertFixup(unique_ptr> &&z); 35 | 36 | private: 37 | unique_ptr> root; 38 | }; 39 | 40 | template void RBTree::RBInsert(const T &key) { 41 | unique_ptr> new_node = make_unique>(key); 42 | RBInsert(move(new_node)); 43 | } 44 | 45 | template 46 | void RBTree::Display(const string LNR_path, const string NLR_path, 47 | const string LOT_path) { 48 | ofstream f1, f2, f3; 49 | f1.open(LNR_path, ios::out); 50 | if (f1.is_open()) { 51 | if (root) { 52 | stack *> Stack1; 53 | RBNode *Node = root.get(); 54 | while (!Stack1.empty() || Node) { 55 | while (Node) { 56 | Stack1.push(Node); 57 | Node = Node->left.get(); 58 | } 59 | Node = Stack1.top(); 60 | string Node_color = Node->color ? "BLACK" : "RED"; 61 | f1 << Node->key << ' ' << Node_color << endl; 62 | Stack1.pop(); 63 | Node = Node->right.get(); //* be care that this is not 64 | //* push(Node->right.get()) 65 | } 66 | } 67 | } else { 68 | cout << "Can not open " << LNR_path << endl; 69 | } 70 | f1.close(); 71 | f2.open(NLR_path, ios::out); 72 | if (f2.is_open()) { 73 | if (root) { 74 | stack *> Stack2; 75 | RBNode *Node = root.get(); 76 | Stack2.push(Node); 77 | while (!Stack2.empty()) { 78 | Node = Stack2.top(); 79 | string Node_color = Node->color ? "BLACK" : "RED"; 80 | f2 << Node->key << ' ' << Node_color << endl; 81 | Stack2.pop(); 82 | if (Node->right) { Stack2.push(Node->right.get()); } 83 | if (Node->left) { Stack2.push(Node->left.get()); } 84 | } 85 | } 86 | } else { 87 | cout << "Can not open " << NLR_path << endl; 88 | } 89 | f2.close(); 90 | f3.open(LOT_path, ios::out); 91 | if (f3.is_open()) { 92 | if (root) { 93 | queue *> Queue1; 94 | RBNode *Node = root.get(); 95 | Queue1.push(Node); 96 | while (!Queue1.empty()) { 97 | Node = Queue1.front(); 98 | string Node_color = Node->color ? "BLACK" : "RED"; 99 | f3 << Node->key << ' ' << Node_color << endl; 100 | Queue1.pop(); 101 | if (Node->left) { Queue1.push(Node->left.get()); } 102 | if (Node->right) { Queue1.push(Node->right.get()); } 103 | } 104 | } 105 | } else { 106 | cout << "Can not open " << LOT_path << endl; 107 | } 108 | f3.close(); 109 | } 110 | 111 | template void RBTree::LeftRotate(unique_ptr> &&x) { 112 | unique_ptr> y = move(x->right); 113 | x->right = move(y->left); 114 | if (x->right) x->right->parent = x.get(); //* warning 115 | y->parent = x->parent; 116 | auto xp = x->parent; //* avoid that after move(x), we cann't find x 117 | if (!xp) { //* x is root 118 | RBNode *x_tmp = x.release(); 119 | root = move(y); 120 | root->left = unique_ptr>(x_tmp); 121 | root->left->parent = root.get(); 122 | } else if (x == xp->left) { 123 | RBNode *x_tmp = x.release(); 124 | xp->left = move(y); 125 | /* 126 | * 1. 127 | * We can not use "xp->left->left = move(x);" 128 | * assume unique_ptr p1, p2; 129 | * The problem is that when p1 = p2, if we change what p1 point to, 130 | * p2 will change at the same time. So we should save what p2 point to 131 | * as p2_tmp, then modify what p1 point to. 132 | * 2. 133 | * Also, we can not use 134 | * "xp->left->left = make_unique>(*x_tmp);" 135 | * because there is no "default constructor" in struct RBNode 136 | * 3. However, we can use 137 | * default copy constructor of unique_ptr, like: 138 | * unique_ptr>(x_tmp); 139 | */ 140 | xp->left->left = unique_ptr>(x_tmp); 141 | xp->left->left->parent = xp->left.get(); 142 | } else { 143 | RBNode *x_tmp = x.release(); 144 | xp->right = move(y); 145 | xp->right->left = unique_ptr>(x_tmp); 146 | xp->right->left->parent = xp->right.get(); 147 | } 148 | } 149 | 150 | template void RBTree::RightRotate(unique_ptr> &&x) { 151 | unique_ptr> y = move(x->left); 152 | x->left = move(y->right); 153 | if (x->left) x->left->parent = x.get(); //* warning 154 | y->parent = x->parent; 155 | auto xp = x->parent; 156 | if (!xp) { //* x is root 157 | RBNode *x_tmp = x.release(); 158 | root = move(y); 159 | root->right = unique_ptr>(x_tmp); 160 | root->right->parent = root.get(); 161 | } else if (x == xp->left) { 162 | RBNode *x_tmp = x.release(); 163 | xp->left = move(y); 164 | xp->left->right = unique_ptr>(x_tmp); 165 | xp->left->right->parent = xp->left.get(); 166 | } else { 167 | RBNode *x_tmp = x.release(); 168 | xp->right = move(y); 169 | xp->right->right = unique_ptr>(x_tmp); 170 | xp->right->right->parent = xp->right.get(); 171 | } 172 | } 173 | 174 | template void RBTree::RBInsert(unique_ptr> &&z) { 175 | RBNode *y = nullptr; 176 | RBNode *x = root.get(); 177 | while (x) { 178 | y = x; 179 | if (z->key < x->key) { 180 | x = x->left.get(); 181 | } else { 182 | x = x->right.get(); 183 | } 184 | } 185 | z->parent = y; 186 | if (!y) { 187 | root = move(z); 188 | RBInsertFixup(move(root)); 189 | } else if (z->key < y->key) { 190 | y->left = move(z); 191 | RBInsertFixup(move(y->left)); 192 | } else { 193 | y->right = move(z); 194 | RBInsertFixup(move(y->right)); 195 | } 196 | } 197 | 198 | template void RBTree::RBInsertFixup(unique_ptr> &&z) { 199 | RBNode *zp = z->parent; 200 | RBNode *z_tmp = z.get(); 201 | while (zp && zp->color == Color::RED) { 202 | RBNode *zpp = zp->parent; 203 | if (zp == zpp->left.get()) { //* case 1, 2, 3 204 | RBNode *y = zpp->right.get(); 205 | if (y && y->color == Color::RED) { //* case 1 206 | cout << "case 1 "; 207 | y->color = Color::BLANK; 208 | zp->color = Color::BLANK; 209 | zpp->color = Color::RED; 210 | z_tmp = zpp; 211 | zp = zpp->parent; 212 | } else { //* case 2, 3 213 | bool is_3 = true; 214 | if (z_tmp == zp->right.get()) { //* case 2 215 | cout << "case 2 "; 216 | is_3 = false; 217 | LeftRotate(move(zpp->left)); 218 | zp = zpp->left.get(); 219 | } 220 | if (is_3) cout << "case 3 "; 221 | zp->color = Color::BLANK; //* case 3 222 | zpp->color = Color::RED; 223 | RBNode *zppp = zpp->parent; 224 | if (!zppp) { //* zpp is root 225 | RightRotate(move(root)); 226 | } else if (zpp == zppp->left.get()) { 227 | RightRotate(move(zppp->left)); 228 | } else { 229 | RightRotate(move(zppp->right)); 230 | } 231 | } 232 | } else { //* case 4, 5, 6 233 | RBNode *y = zpp->left.get(); 234 | if (y && y->color == Color::RED) { //* case 4 235 | cout << "case 4 "; 236 | y->color = Color::BLANK; 237 | zp->color = Color::BLANK; 238 | zpp->color = Color::RED; 239 | z_tmp = zpp; 240 | zp = zpp->parent; 241 | } else { //* case 5, 6 242 | bool is_6 = true; 243 | if (z_tmp == zp->left.get()) { //* case 5 244 | cout << "case 5 "; 245 | is_6 = false; 246 | RightRotate(move(zpp->right)); 247 | zp = zpp->right.get(); 248 | } 249 | if (is_6) cout << "case 6 "; 250 | zp->color = Color::BLANK; //* case 6 251 | zpp->color = Color::RED; 252 | RBNode *zppp = zpp->parent; 253 | if (!zppp) { //* zpp is root 254 | LeftRotate(move(root)); 255 | } else if (zpp == zppp->left.get()) { 256 | LeftRotate(move(zppp->left)); 257 | } else { 258 | LeftRotate(move(zppp->right)); 259 | } 260 | } 261 | } 262 | } 263 | root->color = Color::BLANK; 264 | } 265 | 266 | template 267 | void InsertAllNodes(RBTree &RBT, const string Insert_path) { 268 | ifstream f; 269 | int num; 270 | T key; 271 | f.open(Insert_path, ios::in); 272 | if (f.is_open()) { 273 | f >> num; 274 | // cout << num << endl; 275 | for (int i = 0; i < num; i++) { 276 | f >> key; 277 | cout << key << ' '; 278 | RBT.RBInsert(key); 279 | cout << endl; 280 | } 281 | } else { 282 | cout << "Can not open" << Insert_path << endl; 283 | } 284 | f.close(); 285 | } 286 | 287 | int main(int argc, char **argv) { 288 | RBTree RBT; 289 | string Insert_path = "insert.txt"; 290 | string LNR_path = "Result/LNR.txt"; 291 | string NLR_path = "Result/NLR.txt"; 292 | string LOT_path = "Result/LOT.txt"; 293 | InsertAllNodes(RBT, Insert_path); 294 | RBT.Display(LNR_path, NLR_path, LOT_path); 295 | return 0; 296 | } -------------------------------------------------------------------------------- /Lab4/src/IntervalTree.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | enum Color { RED, BLANK }; 9 | 10 | template struct RBNode { 11 | T key; 12 | Color color; 13 | int max; 14 | //* Automatically assigned the initial value 'nullptr' 15 | std::unique_ptr> left; 16 | std::unique_ptr> right; 17 | RBNode *parent; //* it doesn't 18 | explicit RBNode(const T &key) 19 | : key(std::make_pair(key.first, key.second)), color(Color::RED), 20 | max(key.second), parent(nullptr) {} 21 | }; 22 | 23 | std::ofstream &operator<<(std::ofstream &out, RBNode> *p) { 24 | out << '[' << p->key.first << ", " << p->key.second << ']' 25 | << " max: " << p->max << ' '; 26 | return out; 27 | } 28 | 29 | bool operator<(std::pair &key1, std::pair &key2) { 30 | return key1.first < key2.first; 31 | } 32 | 33 | std::ostream &operator<<(std::ostream &out, std::pair &key) { 34 | out << '[' << key.first << ", " << key.second << ']' << std::endl; 35 | return out; 36 | } 37 | 38 | inline int max(const int a, const int b) { return a > b ? a : b; } 39 | 40 | inline int max(const int a, const int b, const int c) { 41 | return a > b ? (a > c ? a : c) : (b > c ? b : c); 42 | } 43 | 44 | template class RBTree { 45 | public: 46 | void RBInsert(const T &key); 47 | void Display(const std::string LNR_path, const std::string NLR_path, 48 | const std::string LOT_path); 49 | void IntervalSearch(const T &interval, std::vector &result); 50 | 51 | private: 52 | void LeftRotate(std::unique_ptr> &&x); 53 | void RightRotate(std::unique_ptr> &&x); 54 | 55 | private: 56 | void RBInsert(std::unique_ptr> &&z); 57 | void RBInsertFixup(std::unique_ptr> &&z); 58 | 59 | private: 60 | bool overlap(const T &x, const T &interval); 61 | void IntervalSearch(const RBNode *root, const T &interval, 62 | std::vector &result); 63 | 64 | private: 65 | std::unique_ptr> root; 66 | }; 67 | 68 | template void RBTree::RBInsert(const T &key) { 69 | std::unique_ptr> new_node = std::make_unique>(key); 70 | RBInsert(std::move(new_node)); 71 | Display("Result/LNR.txt", "Result/NLR.txt", "Result/LOT.txt"); 72 | } 73 | 74 | template 75 | void RBTree::Display(const std::string LNR_path, const std::string NLR_path, 76 | const std::string LOT_path) { 77 | std::ofstream f1, f2, f3; 78 | f1.open(LNR_path, std::ios::out); 79 | if (f1.is_open()) { 80 | if (root) { 81 | std::stack *> stack1; 82 | RBNode *Node = root.get(); 83 | while (!stack1.empty() || Node) { 84 | while (Node) { 85 | stack1.push(Node); 86 | Node = Node->left.get(); 87 | } 88 | Node = stack1.top(); 89 | std::string Node_color = Node->color ? "BLACK" : "RED"; 90 | f1 << Node << ' ' << Node_color << std::endl; 91 | stack1.pop(); 92 | Node = Node->right.get(); //* be care that this is not 93 | //* push(Node->right.get()) 94 | } 95 | } 96 | } else { 97 | std::cout << "Can not open " << LNR_path << std::endl; 98 | } 99 | f1.close(); 100 | f2.open(NLR_path, std::ios::out); 101 | if (f2.is_open()) { 102 | if (root) { 103 | std::stack *> stack2; 104 | RBNode *Node = root.get(); 105 | stack2.push(Node); 106 | while (!stack2.empty()) { 107 | Node = stack2.top(); 108 | std::string Node_color = Node->color ? "BLACK" : "RED"; 109 | f2 << Node << ' ' << Node_color << std::endl; 110 | stack2.pop(); 111 | if (Node->right) { stack2.push(Node->right.get()); } 112 | if (Node->left) { stack2.push(Node->left.get()); } 113 | } 114 | } 115 | } else { 116 | std::cout << "Can not open " << NLR_path << std::endl; 117 | } 118 | f2.close(); 119 | f3.open(LOT_path, std::ios::out); 120 | if (f3.is_open()) { 121 | if (root) { 122 | std::queue *> Queue1; 123 | RBNode *Node = root.get(); 124 | Queue1.push(Node); 125 | while (!Queue1.empty()) { 126 | Node = Queue1.front(); 127 | std::string Node_color = Node->color ? "BLACK" : "RED"; 128 | f3 << Node << ' ' << Node_color << std::endl; 129 | Queue1.pop(); 130 | if (Node->left) { Queue1.push(Node->left.get()); } 131 | if (Node->right) { Queue1.push(Node->right.get()); } 132 | } 133 | } 134 | } else { 135 | std::cout << "Can not open " << LOT_path << std::endl; 136 | } 137 | f3.close(); 138 | } 139 | 140 | template 141 | void RBTree::IntervalSearch(const T &interval, std::vector &result) { 142 | if (interval.first > interval.second) return; 143 | IntervalSearch(root.get(), interval, result); 144 | } 145 | 146 | template 147 | void RBTree::LeftRotate(std::unique_ptr> &&x) { 148 | if (x->left) { 149 | if (x->right) { 150 | if (x->right->left) { 151 | x->max = max(x->key.second, x->left->max, x->right->left->max); 152 | } else { 153 | x->max = max(x->key.second, x->left->max); 154 | } 155 | } else { 156 | x->max = max(x->key.second, x->left->max); 157 | } 158 | } else { 159 | if (x->right) { 160 | if (x->right->left) { 161 | x->max = max(x->key.second, x->right->left->max); 162 | } else { 163 | x->max = x->key.second; 164 | } 165 | } else { 166 | x->max = x->key.second; 167 | } 168 | } 169 | if (x->right) { x->right->max = max(x->right->max, x->max); } 170 | std::unique_ptr> y = std::move(x->right); 171 | x->right = std::move(y->left); 172 | if (x->right) x->right->parent = x.get(); //* warning 173 | y->parent = x->parent; 174 | auto xp = x->parent; //* avoid that after std::move(x), we cann't find x 175 | if (!xp) { //* x is root 176 | RBNode *x_tmp = x.release(); 177 | root = std::move(y); 178 | root->left = std::unique_ptr>(x_tmp); 179 | root->left->parent = root.get(); 180 | } else if (x == xp->left) { 181 | RBNode *x_tmp = x.release(); 182 | xp->left = std::move(y); 183 | /* 184 | * 1. 185 | * We can not use "xp->left->left = std::move(x);" 186 | * assume unique_ptr p1, p2; 187 | * The problem is that when p1 = p2, if we change what p1 point to, 188 | * p2 will change at the same time. So we should save what p2 point to 189 | * as p2_tmp, then modify what p1 point to. 190 | * 2. 191 | * Also, we can not use 192 | * "xp->left->left = std::make_unique>(*x_tmp);" 193 | * because there is no "default constructor" in struct RBNode 194 | * 3. However, we can use 195 | * default copy constructor of unique_ptr, like: 196 | * unique_ptr>(x_tmp); 197 | */ 198 | xp->left->left = std::unique_ptr>(x_tmp); 199 | xp->left->left->parent = xp->left.get(); 200 | } else { 201 | RBNode *x_tmp = x.release(); 202 | xp->right = std::move(y); 203 | xp->right->left = std::unique_ptr>(x_tmp); 204 | xp->right->left->parent = xp->right.get(); 205 | } 206 | } 207 | 208 | template 209 | void RBTree::RightRotate(std::unique_ptr> &&x) { 210 | if (x->right) { 211 | if (x->left) { 212 | if (x->left->right) { 213 | x->max = max(x->key.second, x->right->max, x->left->right->max); 214 | } else { 215 | x->max = max(x->key.second, x->right->max); 216 | } 217 | } else { 218 | x->max = max(x->key.second, x->right->max); 219 | } 220 | } else { 221 | if (x->left) { 222 | if (x->left->right) { 223 | x->max = max(x->key.second, x->left->right->max); 224 | } else { 225 | x->max = x->key.second; 226 | } 227 | } else { 228 | x->max = x->key.second; 229 | } 230 | } 231 | if (x->left) { x->left->max = max(x->left->max, x->max); } 232 | std::unique_ptr> y = std::move(x->left); 233 | x->left = std::move(y->right); 234 | if (x->left) x->left->parent = x.get(); //* warning 235 | y->parent = x->parent; 236 | auto xp = x->parent; 237 | if (!xp) { //* x is root 238 | RBNode *x_tmp = x.release(); 239 | root = std::move(y); 240 | root->right = std::unique_ptr>(x_tmp); 241 | root->right->parent = root.get(); 242 | } else if (x == xp->left) { 243 | RBNode *x_tmp = x.release(); 244 | xp->left = std::move(y); 245 | xp->left->right = std::unique_ptr>(x_tmp); 246 | xp->left->right->parent = xp->left.get(); 247 | } else { 248 | RBNode *x_tmp = x.release(); 249 | xp->right = std::move(y); 250 | xp->right->right = std::unique_ptr>(x_tmp); 251 | xp->right->right->parent = xp->right.get(); 252 | } 253 | } 254 | 255 | template void RBTree::RBInsert(std::unique_ptr> &&z) { 256 | RBNode *y = nullptr; 257 | RBNode *x = root.get(); 258 | while (x) { 259 | x->max = max(x->max, z->key.second); //* important 260 | y = x; 261 | if (z->key < x->key) { 262 | x = x->left.get(); 263 | } else { 264 | x = x->right.get(); 265 | } 266 | } 267 | z->parent = y; 268 | if (!y) { 269 | root = std::move(z); 270 | RBInsertFixup(std::move(root)); 271 | } else if (z->key < y->key) { 272 | y->left = std::move(z); 273 | RBInsertFixup(std::move(y->left)); 274 | } else { 275 | y->right = std::move(z); 276 | RBInsertFixup(std::move(y->right)); 277 | } 278 | } 279 | 280 | template 281 | void RBTree::RBInsertFixup(std::unique_ptr> &&z) { 282 | RBNode *zp = z->parent; 283 | RBNode *z_tmp = z.get(); 284 | while (zp && zp->color == Color::RED) { 285 | RBNode *zpp = zp->parent; 286 | if (zp == zpp->left.get()) { //* case 1, 2, 3 287 | RBNode *y = zpp->right.get(); 288 | if (y && y->color == Color::RED) { //* case 1 289 | #ifdef DEBUG 290 | std::cout << "case 1 "; 291 | #endif 292 | y->color = Color::BLANK; 293 | zp->color = Color::BLANK; 294 | zpp->color = Color::RED; 295 | z_tmp = zpp; 296 | zp = zpp->parent; 297 | } else { //* case 2, 3 298 | bool is_3 = true; 299 | if (z_tmp == zp->right.get()) { //* case 2 300 | #ifdef DEBUG 301 | std::cout << "case 2 "; 302 | #endif 303 | is_3 = false; 304 | LeftRotate(std::move(zpp->left)); 305 | zp = zpp->left.get(); 306 | } 307 | if (is_3) { 308 | #ifdef DEBUG 309 | std::cout << "case 3 "; 310 | #endif 311 | } 312 | zp->color = Color::BLANK; //* case 3 313 | zpp->color = Color::RED; 314 | RBNode *zppp = zpp->parent; 315 | if (!zppp) { //* zpp is root 316 | RightRotate(std::move(root)); 317 | } else if (zpp == zppp->left.get()) { 318 | RightRotate(std::move(zppp->left)); 319 | } else { 320 | RightRotate(std::move(zppp->right)); 321 | } 322 | } 323 | } else { //* case 4, 5, 6 324 | RBNode *y = zpp->left.get(); 325 | if (y && y->color == Color::RED) { //* case 4 326 | #ifdef DEBUG 327 | std::cout << "case 4 "; 328 | #endif 329 | y->color = Color::BLANK; 330 | zp->color = Color::BLANK; 331 | zpp->color = Color::RED; 332 | z_tmp = zpp; 333 | zp = zpp->parent; 334 | } else { //* case 5, 6 335 | bool is_6 = true; 336 | if (z_tmp == zp->left.get()) { //* case 5 337 | #ifdef DEBUG 338 | std::cout << "case 5 "; 339 | #endif 340 | is_6 = false; 341 | RightRotate(std::move(zpp->right)); 342 | zp = zpp->right.get(); 343 | } 344 | if (is_6) { 345 | #ifdef DEBUG 346 | std::cout << "case 6 "; 347 | #endif 348 | } 349 | zp->color = Color::BLANK; //* case 6 350 | zpp->color = Color::RED; 351 | RBNode *zppp = zpp->parent; 352 | if (!zppp) { //* zpp is root 353 | LeftRotate(std::move(root)); 354 | } else if (zpp == zppp->left.get()) { 355 | LeftRotate(std::move(zppp->left)); 356 | } else { 357 | LeftRotate(std::move(zppp->right)); 358 | } 359 | } 360 | } 361 | } 362 | root->color = Color::BLANK; 363 | } 364 | 365 | template 366 | inline bool RBTree::overlap(const T &x, const T &interval) { 367 | return !(x.second < interval.first || interval.second < x.first); 368 | } 369 | 370 | template 371 | void RBTree::IntervalSearch(const RBNode *root, const T &interval, 372 | std::vector &result) { 373 | if (overlap(root->key, interval)) { result.push_back(root->key); } 374 | if (root->left && root->left->max >= interval.first) { 375 | IntervalSearch(root->left.get(), interval, result); 376 | } 377 | if (root->right && root->right->max >= interval.first) { 378 | IntervalSearch(root->right.get(), interval, result); 379 | } 380 | } --------------------------------------------------------------------------------