├── .gitignore ├── CODING_GUIDELINES.md ├── LICENSE.txt ├── README.md ├── awesome-resources.md ├── code-style ├── README.md ├── google_norm.cc └── google_norm.h ├── notes ├── README.md ├── pics │ ├── 二叉树 │ │ ├── 二叉搜索树.png │ │ ├── 二叉查找树时间复杂度.png │ │ ├── 完全二叉树.png │ │ ├── 树的基本概念.png │ │ ├── 满二叉树.png │ │ ├── 递归树.png │ │ ├── 递归树_全排列.png │ │ ├── 递归树_归并排序.png │ │ ├── 递归树_快速排序.png │ │ ├── 递归树_快速排序_复杂度.png │ │ └── 递归树_斐波那契数列.png │ ├── 哈希表 │ │ └── LRU.png │ ├── 图 │ │ ├── 图.jpg │ │ ├── 图_带权图.jpg │ │ ├── 图_广度优先搜索.jpg │ │ ├── 图_微博.jpg │ │ ├── 图_微博2.jpg │ │ ├── 图_邻接矩阵.jpg │ │ └── 图_邻接表.jpg │ ├── 堆 │ │ ├── 堆.png │ │ ├── 堆化_从下向上.jpg │ │ ├── 堆化_从下向上.png │ │ ├── 堆化_删除堆顶1.jpg │ │ ├── 堆化_删除堆顶1.png │ │ ├── 堆化_删除堆顶2.jpg │ │ ├── 堆化_删除堆顶2.png │ │ ├── 建堆.jpg │ │ ├── 建堆2.jpg │ │ ├── 建堆时间复杂度.jpg │ │ ├── 建堆节点高度.jpg │ │ ├── 建堆节点高度1.jpg │ │ ├── 建堆节点高度2.jpg │ │ └── 排序.jpg │ ├── 复杂度 │ │ ├── 复杂度效果图.png │ │ └── 复杂度量级.png │ ├── 字符串匹配 │ │ ├── RK1.jpg │ │ └── RK2.jpg │ └── 跳表 │ │ └── 跳表结构.png ├── 二分查找.md ├── 哈希算法.md ├── 图.md ├── 堆&应用.md ├── 复杂度分析.md ├── 字符串匹配.md ├── 排序.md ├── 散列表.md ├── 数组&链表.md ├── 栈.md ├── 树.md ├── 算法思想.md ├── 跳表.md ├── 递归.md └── 队列.md ├── pics ├── cc文件格式.png ├── logo2.png ├── qq.png ├── 函数注释说明.png └── 注释格式.png ├── practice ├── README.md └── array │ ├── 1_两个数之和.md │ └── 26_在有序数组中移除重复数字.md ├── src ├── README.md ├── array │ ├── array.hpp │ └── array.test.cc ├── backtracking │ ├── backtracking.hpp │ └── backtracking.test.cc ├── binary_search │ ├── binary_search.hpp │ └── binary_search.test.cc ├── divide_and_conquer │ ├── divide_and_conquer.hpp │ └── divide_and_conquer.test.cc ├── dynamic_programming │ ├── dynamic_program.hpp │ └── dynamic_program.test.cc ├── graph │ ├── graph.hpp │ └── graph.test.cc ├── hash_table │ ├── hash_table.hpp │ ├── hash_table.test.cc │ ├── lru_hash.hpp │ └── lru_hash.test.cc ├── heap │ ├── heap.hpp │ └── heap.test.cc ├── internal │ ├── macros.h │ └── temp.h ├── list │ ├── single_list.hpp │ └── single_list.test.cc ├── queue │ ├── queue.cc │ ├── queue.hpp │ └── queue.test.cc ├── recursion │ ├── recursion.hpp │ └── recursion.test.cc ├── skip_list │ ├── skip_list.hpp │ └── skip_list.test.cc ├── sort │ ├── sort.hpp │ └── sort.test.cc ├── stack │ ├── stack.hpp │ └── stack.test.cc ├── stl_examples │ ├── use_hashmap.cc │ ├── use_map.cc │ └── use_sort.cc ├── tree │ ├── binary_search_tree.hpp │ └── binary_search_tree.test.cc └── utils │ ├── glib_exception.h │ ├── string_common.hpp │ ├── tic_toc.hpp │ └── types.h └── template ├── README.md ├── other_type.h ├── template.hpp ├── template.md └── template.test.cc /.gitignore: -------------------------------------------------------------------------------- 1 | src/**/*.out 2 | src/**/*.gch 3 | 4 | practice/**/*.gch 5 | practice/**/*.out 6 | 7 | code-style/*.gch 8 | 9 | practice/others 10 | 11 | template/*.gch 12 | 13 | #目前还不完善,待完善后在更新 14 | CONTRIBUTION.md 15 | 16 | others/ 17 | books/ 18 | TODO.md 19 | 20 | -------------------------------------------------------------------------------- /CODING_GUIDELINES.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## :memo: C++ 代码规范 4 | 5 | 简单描述一下项目代码中用到的 C++ 代码规范。基本上是按照 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) 进行调整的。中文版本 [点击这里](https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/)。 6 | 7 | **当前规范文档会持续更新 ......**

8 | 9 | 10 | 11 | ### 目录 12 | 13 | --- 14 | 15 | * [1 命名](#1-命名) 16 | * [1)一般命名规则](#1一般命名规则) 17 | * [2)文件命名](#2文件命名) 18 | * [3)类型命名](#3类型命名) 19 | * [4)变量&常量命名](#4变量常量命名) 20 | * [5)函数命名](#5函数命名) 21 | * [6)枚举命名](#6枚举命名) 22 | * [7)宏命名](#7宏命名) 23 | * [8)命名空间命名](#8命名空间命名) 24 | * [2 格式](#2-格式) 25 | * [1)空格 or 制表符(tab)](#1空格-or-制表符tab) 26 | * [2)水平空格](#2水平空格) 27 | * [3 函数设置](#3-函数设置) 28 | * [1)参数传递](#1参数传递) 29 | * [2)其他注意](#2其他注意)

30 | 31 | 32 | 33 | ### 1 命名 34 | 35 | --- 36 | 37 | #### 1)一般命名规则 38 | 39 | - 名字要有一些含义 40 | - 不能使用一些缩写,除非大家都知道该缩写是什么意思。比如 `T` 是模板参数,都是知道的。还有一个单词不能删除其中内部的几个字母。 41 | 42 | **Good** 43 | 44 | ```c++ 45 | int price_count_reader; // 不使用缩写。 46 | int num_errors; // num 大家都知道含义 47 | int num_dns_connections; // 都知道 dns 代表什么 48 | ``` 49 | 50 | **Bad** 51 | 52 | ```c++ 53 | int n; // 无意义 54 | int nerr; // 缩写有歧义 55 | int n_comp_conns; // 缩写有歧义 56 | int wgc_connections; // wgc 只有几个人知道什么意思 57 | int pc_reader; // 除了个人电脑可以用 pc 外。其他东西也可以用其简写 58 | int cstmr_id; // 删除了单词其中的几个字母 59 | ``` 60 | 61 | 62 | 63 | #### 2)文件命名 64 | 65 | - 文件和目录名字应该是全小写,单词与单词之间,要加下划线 `_` 进行连接。 66 | - 所有测试文件以 `.test.cc` 作为拓展名。头文件(包含具体实现)以 `.hpp` 结尾。头文件仅仅有类型/函数/变量声明,要以 `.h` 作为拓展后缀。其他的源文件同样要以 `.cc` 作为拓展名。 67 | 68 | **例子** 69 | 70 | - 测试源文件:`single_list.test.cc` 71 | - 一般源文件:`use_map.cc` 72 | - 头文件(含有实现):`single_list.hpp` 73 | - 头文件(不含实现):`types.h` 74 | - 目录:`list`、`stl_examples` 75 | 76 | 77 | 78 | #### 3)类型命名 79 | 80 | 所有类型{类、结构体、类型别名、枚举}都有相同的命名规则。都是单词的首字母要大写并且没有下划线。 81 | 82 | ```c++ 83 | // classes and structs 84 | class UrlTable { ... }; 85 | class UrlTableTester { ... }; 86 | struct UrlTableProperties { ... }; 87 | 88 | // typedefs 89 | typedef hash_map PropertiesMap; 90 | 91 | // using aliases 92 | using PropertiesMap = hash_map; 93 | 94 | // enums 95 | enum UrlTableErrors { GoogleName }; 96 | ``` 97 | 98 | 99 | 100 | #### 4)变量&常量命名 101 | 102 | - 一般变量的名字,都是要小写,且下划线链接。对于全局静态存储期变量直接单词首字母大写,且单词之间不需要下划线连接。外部看来相当于一个函数的调用。 103 | - 前缀含有 `const` `constexpr` 的全局常量。并且其生存期是静态存储期。那么名字必须是全部大写且前面加上一个 `k` 标志。方便从名字中推断出,其是静态存储期的常量。 104 | 105 | ```c++ 106 | // 普通临时变量,普通函数形式参数,结构体成员变量等等 107 | int table_name; 108 | const int table_name; 109 | static int talbe_name; 110 | 111 | // 静态全局/非静态全局 112 | static int TableName; 113 | int TableName; // 假设是一个全局变量 114 | 115 | // 静态全局/非静态全局常量 116 | const int kTableName = 5; // 全局常量 117 | constexpr int kTableName = 5; 118 | const static int kTableName = 5; // 全局常量/局部常量 119 | ``` 120 | 121 | - 类内部成员变量&常量 122 | 123 | private 变量,全部是小写单词加下划线连接,且末尾有 `_` 下划线。public 变量,全部是大写字母且没有下划线链接。 124 | 125 | ```c++ 126 | class xx { 127 | public: 128 | const int TableName = 5; // 为了让外部看起来更像是一个接口函数 129 | static int TableName; 130 | 131 | private: 132 | int table_name_; 133 | const int table_name_; 134 | constexpr int table_name_ = 8; 135 | }; 136 | ``` 137 | 138 | - 结构体内部变量&常量 139 | 140 | 不管什么情况全部都是小写,注意没有后缀 `_`。 141 | 142 | ```c++ 143 | struct xxx { 144 | const int table_name = 5; 145 | int table_name; 146 | static int table_name; 147 | }; 148 | ``` 149 | 150 | 151 | 152 | 153 | #### 5)函数命名 154 | 155 | 函数命名与静态存储期的变量命名规则一致,首字母大写,无下划线连接。 156 | 157 | ```c++ 158 | AddTableEntry() 159 | DeleteUrl() 160 | OpenFileOrDie() 161 | ``` 162 | 163 | 需要注意的是,对于成员函数来说,特殊情况是,该成员函数是取值函数、设值函数、返回当前的某个状态。必须要按照普通变量的命名规则。就是全部小写加下划线连接。对于取值设值函数,函数名字最好与其对应的成员名字对应起来。 164 | 165 | ```c++ 166 | class Foo { 167 | public: 168 | 169 | Foo(int b) : Bar(), baz_(b) {} 170 | // 设值函数与 baz_ 成员变量对应起来 171 | void set_baz(int baz) { baz_ = baz; } 172 | // 状态函数 173 | bool empty() { return is_empty; } 174 | // 与 count_ 对应起来 175 | int get_count() { return count_; } 176 | private: 177 | int baz_; 178 | bool is_empty; 179 | int count_; 180 | } 181 | ``` 182 | 183 | 184 | 185 | #### 6)枚举命名 186 | 187 | 枚举值的命名是首字母全大写,且单词之间下划线连接。 188 | 189 | 例子 190 | 191 | ```c++ 192 | enum UrlTableErrors { 193 | OK, 194 | OUT_OF_MEMORY, 195 | MALFORMED_INPUT 196 | }; 197 | 198 | enum TraversalOrder: short { 199 | PRE_ORDER, 200 | IN_ORDER, 201 | POST_ORDER 202 | }; 203 | ``` 204 | 205 | 206 | 207 | #### 7)宏命名 208 | 209 | 建议一般少用宏,但是如果用了宏,那么单词全部大写,且单词之前用下划线分隔。 210 | 211 | ```c++ 212 | #define MY_MACRO_THAT_SCARES; 213 | ``` 214 | 215 | 216 | 217 | #### 8)命名空间命名 218 | 219 | 命名空间都是小写字母,顶层命名空间一般是项目的名字或者是某个团队负责的某个部分定义的命名空间,避免与标准库或者知名的顶层命名空间冲突。比如 std 命名空间,自己不要使用。对于子目录中有多个文件,在这些文件中除了加上顶层的命名空间外,还需要加上以目录名作为命名空间名。尽量不要使用 `internal` 这种命名空间,如果用到的话,可以前面加上文件的名字 `stack_internal` 这种命名空间即可消除潜在的冲突。 220 | 221 |

222 | 223 | ### 2 格式 224 | 225 | --- 226 | 227 | #### 1)空格 or 制表符(tab) 228 | 229 | 使用 soft tabs ,代表 4 个空格。在自己的编译器或者编辑器中进行设置。这个是仿照 python 代码的格式来设置的。比较清晰。 230 | 231 | 232 | 233 | #### 2)水平空格 234 | 235 | 对于下面介绍的两个规则,在实际使用时,如果能够代码的排版更清晰,可以适当调整下面的空格规则。比如操作运算符的赋值操作哪里,在代码块中举的例子。就是为了代码的美观,适当牺牲了空格规则。但是整体上仍然是按照下面的要求进行书写代码。 236 | 237 | - 一般规则 238 | 239 | - 每行的结尾没有空格 240 | 241 | - 分号前面应该没有空格 242 | 243 | ```c++ 244 | int i = 0; 245 | ``` 246 | 247 | - 在半开的花括号之前一定要有空格 248 | 249 | ```c++ 250 | void f(bool b) { 251 | //... 252 | } 253 | ``` 254 | 255 | - 花括号初始化列表内部的值与花括号之间可有可无空格。但是你自己的代码必须要一致 256 | 257 | ```c++ 258 | int x[] = { 0 }; 259 | int x[] = {0}; 260 | ``` 261 | 262 | - 类的集成和初始化列表的空格如下 263 | 264 | ```c++ 265 | class Foo : public Bar { 266 | public: 267 | // For inline function implementations, put spaces between the braces 268 | // and the implementation itself. 269 | Foo(int b) : Bar(), baz_(b) {} // No spaces inside empty braces. 270 | void Reset() { baz_ = 0; } // Spaces separating braces from implementation. 271 | ... 272 | } 273 | ``` 274 | 275 | - 操作运算符 276 | 277 | - 赋值 278 | 279 | ```c++ 280 | int x = 0; 281 | int num_black = x; 282 | // 当然对于上面这两行可以进行如下对齐,使得格式更清晰 283 | int x = 0; 284 | int num_black = x; 285 | ``` 286 | 287 | - 其他二元操作符与操作数之间一般会有空格,可以适当的没有空格。下面的的形式都可以,看着清晰即可 288 | 289 | ```c++ 290 | v = w * x + y / z; 291 | v = w*x + y/z; 292 | v = w * (x + z); 293 | ``` 294 | 295 | - 一元操作符之间不会有空格 296 | 297 | ```c++ 298 | x = -5; 299 | ++y; 300 | z = !y; // or z = noty 301 | ``` 302 | 303 |

304 | 305 | ### 3 函数设置 306 | 307 | --- 308 | 309 | - 封装有意义的操作 310 | - 保持函数体短小简洁。最好每个函数不要超过 40 行。 311 | - 一个函数应该执行单一的逻辑操作。 312 | - 如果函数太过短小,可以用 Lambda 表达式进行代替。 313 | 314 | 315 | 316 | #### 1)参数传递 317 | 318 | 对于输入参数,按值传递一个内置类型的变量是比较少消耗资源的。除非我们在函数内部需要修改该值,那么此时我们才会用引用类型。对于类类型或者一个数组(占用空间比较大)变量,直接用 const 类型的引用即可。减少了拷贝的开销。 319 | 320 | **Good** 321 | 322 | ```c++ 323 | void f1(const string& s); // 引用的方式要比直接拷贝开销小 324 | 325 | void f2(int x); // 内置类型的拷贝资源消耗少于引用 326 | // 如果是想在 f2 函数中修改这个变量 x,可以使用引用 327 | void f2(int &x); 328 | ``` 329 | 330 | **Bad** 331 | 332 | ```c++ 333 | void f3(string s); // 此时拷贝类,实际上开销大于直接上面的传递引用 334 | 335 | void f4(const int& x); // 开销大于上面的 f2(int x) 336 | ``` 337 | 338 | 339 | 340 | #### 2)其他注意 341 | 342 | - 形式参数顺序,要按照先输入,后输出的顺序进行设置。函数内部不改变该值,那么需要在加上 `const ` 前缀,此时是否加上引用,要按照上面参数传递规则。 343 | 344 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GLib. Copyright (c) 2019 gcj. mail: ncuneugcj@outlook.com 2 | github: https://github.com/saber 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 3. The original author of the work must be notified of any 14 | redistribution of source code or in binary form. 15 | 4. Neither the name of copyright holders nor the names of its 16 | contributors may be used to endorse or promote products derived 17 | from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS 23 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | > 记录学习《数据结构与算法之美》专栏的实现代码(C++)& 笔记。 4 | 5 | [![](https://img.shields.io/github/issues/saber/algorithm.svg)](https://github.com/saber/algorithm/issues) [![](http://img.shields.io/github/forks/saber/algorithm.svg)](https://github.com/saber/algorithm/network) [![](http://img.shields.io/github/starts/saber/algorithm.svg)](http://img.shields.io/github/saber/algorithm/stargazers) [![](https://travis-ci.org/saber/algorithm.svg?branch=master)](https://travis-ci.org/saber/algorithm) [![](https://img.shields.io/github/release/saber/algorithm.svg)](https://github.com/saber/algorithm/releases)[![jaywcjlove/sb](https://jaywcjlove.github.io/sb/lang/chinese.svg)](README.md)

6 | 7 | --- 8 | 9 | 10 | 11 | ## :open_file_folder: src 12 | 13 | 专栏中讲过的数据结构与算法的 C++ 实现。内部包含了详细的代码注释、复杂度分析以及基本实现思路等等。下面图片是其中单链表 `.hpp` 注释例子。代码风格参照 [Google 代码风格 — 笔记](./code-style/)。一份在学习 Google 代码规范时,以代码形式简单整理的笔记。 14 | 15 | 查看该 Repo 当前包含的数据结构与算法相关的代码,**戳这里**​ :point_right: :point_right: [代码](/src)。 16 | 17 | **代码持续更新中 ......** 18 | 19 |

20 | 21 | 22 | 23 | ## :notebook: notes 24 | 25 | 为方便阅读学习过程中的笔记,笔记已经按照中文排版格式进行排版。排版格式参照 [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines)。 26 | 27 | 阅读笔记,**戳这里**​ :point_right: :point_right: [笔记](notes/)。 28 | 29 | **笔记持续更新中 ......**

30 | 31 | 32 | 33 | ## :open_file_folder: code-style 34 | 35 | 该文件夹包含了代码规范笔记,这些笔记是 Google 代码规范的浓缩形式,该笔记与上面记录数据结构与算法的笔记不同,是以代码形式呈现的笔记。本 Repo 包含的代码,都是根据这些规范进行书写。当然这份规范笔记还有许多不足,会随着笔者的 C++ 水平的提升而进行改进。同时自己也简单整理了一份规范文档,适用于本项目的源码。**戳这里** :point_right: :point_right: [CODING_GUIDELINES.md](CODING_GUIDELINES.md)。这份文档配合 `template` 文件夹内部的两个文件 `template.h` `template.test.cc` 一起查看,效果更佳。 36 | 37 | 更多的代码风格,请参照 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)。中文版本 [点击这里](https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/)。 38 | 39 | **代码规范持续更新中 ......**

40 | 41 | 42 | 43 | ## :pencil2: awesome-resources.md 44 | 45 | 学习过程中收集的关于数据结构与算法的资源,以及大佬经验。 46 | 47 | **戳这里** :point_right: :point_right: [awesome-resources.md](awesome-resources.md)。 48 | 49 | **资源持续更新中 ......**

50 | 51 | 52 | 53 | ## :open_file_folder: practice 54 | 55 | 本文件夹包含了数据结构与算法对应的练习题及笔记,**戳这里**:point_right: :point_right: [实战](practice/) 。笔记内容清晰标记了复杂度、实现思路及具体实现代码,题目分类来源于 [LeetCode](https://leetcode.com/problemset/all/)。 56 | 57 | **持续更新中 ......**

58 | 59 | 60 | 61 | ## :open_file_folder: template 62 | 63 | 包含了当前项目中,一份源码规范的简单模板。是该 Repo 中源文件和头文件遵循的一些简单规则,并且以代码形式展现出来。除了上面 `code-style` 标题介绍的代码规范笔记。同时自己也简单整理了一份规范文档。**戳这里** :point_right: :point_right: [CODING_GUIDELINES.md](CODING_GUIDELINES.md)。这份文档配合 `template` 文件夹内部的两个文件 `template.h` `template.test.cc` 一起查看,效果更佳。 64 | 65 | **持续更新中 ......**

66 | 67 | 68 | 69 | ## :black_nib: 计划 70 | 71 | 本 Repo 中, `src`文件夹包含的代码实现,基本上是按照如下列表顺序进行更新。已经完成的会进行特殊标注,除了下面一些数据结构与算法,还会包含一些专栏中涉及到的高级算法实现。如果你觉得某个/些算法也可以加入其中,不妨创建一个 `issue` ,我会把他添加到这里。 72 | 73 | ##### 数组 74 | 75 | - [x] 实现一个支持动态扩容的数组 76 | - [x] 实现两个有序数组合并为一个有序数组 77 | - [ ] 实现一个大小固定的有序数组,支持动态增删改操作 78 | 79 | ##### 链表 80 | - [x] 实现单链表,支持增删操作 81 | - [ ] 实现循环链表、双向链表,支持增删操作 82 | - [x] 实现单链表反转 83 | - [ ] 实现部分单链表翻转 84 | - [ ] 实现两个有序的链表合并为一个有序链表 85 | - [x] 实现求链表的中间结点 86 | - [x] 链表检测环 87 | - [x] 单链表实现 LRU 缓存淘汰策略 88 | 89 | ##### 栈 90 | - [x] 用数组实现一个顺序栈 91 | 92 | - [x] 栈在表达式求值中的应用,及其在消消乐中的应用 93 | 94 | - [ ] 用链表实现一个链式栈 95 | - [ ] 编程模拟实现一个浏览器的前进、后退功能 96 | 97 | ##### 队列 98 | - [x] 用数组实现一个顺序队列 99 | - [ ] 用链表实现一个链式队列 100 | 101 | - [x] 数组实现一个循环队列 102 | 103 | ##### 递归 104 | - [x] 编程实现斐波那契数列求值f(n)=f(n-1)+f(n-2) 105 | 106 | - [x] 最终推荐人检测环 A-B-C-A 107 | 108 | - [ ] 编程实现求阶乘n! 109 | - [ ] 编程实现一组数据集合的全排列 110 | 111 | ##### 排序 112 | - [x] 实现归并排序、快速排序、插入排序、冒泡排序、选择排序 113 | 114 | - [x] 编程实现O(n)时间复杂度内找到一组数据的第K大元素 115 | 116 | ##### 二分查找 117 | 118 | - [x] 实现一个有序数组的二分查找算法 119 | - [x] 实现模糊二分查找算法(第一个等于给定值、最后一个等于给定值、第一个大于等于给定值、最后一个小于等于给定值) 120 | - [x] 循环有序数组查询指定值 121 | 122 | ##### 跳表 123 | 124 | - [x] 跳表实现 125 | 126 | ##### 散列表 127 | - [x] 实现一个基于链表法解决冲突问题的散列表 128 | - [x] 实现一个 LRU 缓存淘汰算法 129 | 130 | ##### 字符串 131 | - [ ] 实现一个字符集,只包含a~z这26个英文字母的Trie树 132 | - [ ] 实现朴素的字符串匹配算法 133 | 134 | ##### 二叉树 135 | - [x] 实现一个二叉查找树,并且支持插入、删除、查找操作 136 | - [ ] 实现查找二叉查找树中某个节点的后继、前驱节点 137 | - [x] 实现二叉树前、中、后序以及按层遍历 138 | 139 | ##### 堆 140 | - [x] 实现一个小顶堆、大顶堆(默认就是一个简单版的优先级队列) 141 | - [ ] 优先级队列 142 | - [x] 实现堆排序 143 | - [ ] 利用优先级队列合并K个有序数组 144 | - [ ] 求一组动态数据集合的最大Top K 145 | 146 | ##### 图 147 | - [ ] 实现有向图、有权图、无权图的邻接矩阵和邻接表表示方法 148 | - [x] 实现无向图邻接表表示方法 149 | - [x] 实现图的深度优先搜索(递归+非递归)、广度优先搜索 150 | - [ ] 实现Dijkstra算法、A*算法 151 | - [ ] 实现拓扑排序的Kahn算法、DFS算法 152 | 153 | ##### 回溯 154 | - [x] 利用回溯算法求解八皇后问题 155 | - [x] 利用回溯算法求解0-1背包问题 156 | - [x] [不同路径问题](https://leetcode-cn.com/problems/unique-paths-iii/) 157 | 158 | ##### 分治 159 | - [x] 利用分治算法求一组数据的逆序对个数 160 | 161 | ##### 动态规划 162 | - [ ] 0-1背包问题 163 | - [ ] 最小路径和 164 | - [ ] 编程实现莱文斯坦最短编辑距离 165 | - [ ] 编程实现查找两个字符串的最长公共子序列 166 | - [ ] 编程实现一个数据序列的最长递增子序列

167 | 168 | 169 | 170 | ### ~~如何贡献自己的代码&笔记~~(暂时不开放贡献,还没有完善好一些东西,后期会开放......) 171 | 172 | --- 173 | 174 | ~~按照下面的步骤,即可贡献自己的代码&笔记。在此之前,你需要了解贡献规则,**戳这里** :point_right::point_right: [贡献](./CONTRIBUTION.md)。~~ 175 | 176 | ~~1)**Fork** 当前项目~~ 177 | 178 | ~~2)创建一个分支,增加一份「代码 & 笔记」,但是不包含当前项目中已经存在的,你可在上面计划列表中寻找一个未完成的目标。~~ 179 | 180 | ~~3)最后一步是 **pull request**(确保内容格式符合贡献规则)~~

181 | 182 | 183 | 184 | ## :memo: 后记 185 | 186 | ### QQ 群 187 | 188 | 专栏推荐的课本资源大概 1GB,因为 Git 上传限制以及 CSDN 同时不能自己设置 0 积分。如果想获取该资源,直接到 CSDN 平台下载([资源链接1](https://download.csdn.net/download/geself/11152111) ,[资源链接2](https://download.csdn.net/download/geself/11152094),[资源链接3](https://download.csdn.net/download/geself/11152117)),可能会花费你的很多积分,本来想设置成 0 积分免费分享给大家。目前由于 CSDN 改版,这个积分会由 CSDN 平台算法自动制定,个人没有权限设置 0 积分。如果没有积分,可以免费下载上述资源,也就是申请加入下面的群,到 qq 群进行下载。建立 qq 群的目的有两个,一方面,该平台不限制文件时间,可以随时下载资源。另一方面,这也为大家提供一个学习交流平台,在这里你可以自由地讨论技术问题。其中一些聊天记录,都可以拓展自己的视野,相比于微信群,qq 群能够长久保存,方便你及时回顾,寻找当前遇到问题的解决方案。当然,如果你有更合适的免费资源上传和下载的地方。希望能够及时告知,我会把资源放在上面(百度云除外,下载/上传都太慢......)。 189 | 190 | **Note**:为了保证群内不出现微商等闲杂人员,申请加群时,要备注成 saber/algorithm 等字眼,方便筛选。 191 | 192 |

193 | 194 | 195 | 196 | ### Repo 总览 197 | 198 | ``` 199 | . 200 | ├── awesome-resources.md 201 | ├── code-style 202 | │   ├── google_norm.cc 203 | │   ├── google_norm.h 204 | │   └── README.md 205 | ├── CODING_GUIDELINES.md 206 | ├── LICENSE.txt 207 | ├── notes 208 | │   ├── pics 209 | │   ├── README.md 210 | │   ├── 递归.md 211 | │   ├── 堆&应用.md 212 | │   ├── 队列.md 213 | │   ├── 二分查找.md 214 | │   ├── 复杂度分析.md 215 | │   ├── 哈希算法.md 216 | │   ├── 排序.md 217 | │   ├── 散列表.md 218 | │   ├── 数组&链表.md 219 | │   ├── 树.md 220 | │   ├── 跳表.md 221 | │   └── 栈.md 222 | ├── pics 223 | │   ├── cc文件格式.png 224 | │   ├── logo2.png 225 | │   ├── qq.png 226 | │   ├── 函数注释说明.png 227 | │   └── 注释格式.png 228 | ├── practice 229 | │   ├── array 230 | │   └── README.md 231 | ├── README.md 232 | ├── src 233 | │   ├── array 234 | │   ├── binary_search 235 | │   ├── hash_table 236 | │   ├── heap 237 | │   ├── internal 238 | │   ├── list 239 | │   ├── queue 240 | │   ├── README.md 241 | │   ├── recursion 242 | │   ├── skip_list 243 | │   ├── sort 244 | │   ├── stack 245 | │   ├── stl_examples 246 | │   ├── tree 247 | │   └── utils 248 | └── template 249 | ├── other_type.h 250 | ├── README.md 251 | ├── template.hpp 252 | ├── template.md 253 | └── template.test.cc 254 | ``` 255 | 256 | -------------------------------------------------------------------------------- /awesome-resources.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # awesome-algorithms-resources[![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) 4 | 5 | 记录一些学习数据结构与算法过程中,搜索到的好用资源。对于一些英文资源可以点击这里 [Awesome Algorithms](https://github.com/tayllan/awesome-algorithms)。 6 | 7 | **持续更新中 ......**

8 | 9 | 10 | 11 | ## 目录 12 | 13 | * [一、经验(磨刀不误砍柴工)](#一经验磨刀不误砍柴工) 14 | * [二、资源](#二资源) 15 | * [1)网站](#1网站) 16 | * [2)在线视频课程](#2在线视频课程) 17 | * [3)书](#3书) 18 | * [4)博客](#4博客) 19 | * [5)Github 项目](#5github-项目) 20 | * [6)在线评测网站](#6在线评测网站) 21 | * [三、复杂度分析](#三复杂度分析) 22 | * [四、数据结构](#四数据结构) 23 | * [1)链表](#1链表) 24 | * [2)栈](#2栈) 25 | * [3)队列](#3队列) 26 | * [五、算法](#五算法) 27 | * [1)递归](#1递归) 28 | * [2)排序](#2排序)

29 | 30 | 31 | ## 一、经验(磨刀不误砍柴工) 32 | 33 | - [怎么才算真正掌握了一个数据结构或算法?](https://mp.weixin.qq.com/s/t8z4KQMrTrR3NljtWJm2zg) 34 | - [我的算法学习之路](http://www.cnblogs.com/figure9/archive/2014/05/05/3708351.html) 35 | - [左程云:程序员该如何学习算法?](https://www.nowcoder.com/discuss/61529) 36 | - [如何学习数据结构,知乎回答:](https://www.zhihu.com/question/21318658) 37 | - [如何学习数据结构? - 黑毛大猪蹄子的回答 - 知乎]( 38 | https://www.zhihu.com/question/21318658/answer/154739001) 39 | - [如何学习数据结构? - 孟蛋蛋的回答 - 知乎]( 40 | https://www.zhihu.com/question/21318658/answer/42690576) 41 | - [如何学习数据结构? - 小鱼二的回答 - 知乎]( 42 | https://www.zhihu.com/question/21318658/answer/26295370) 43 | - [本人偏向于学习C++,如何全面的学习《算法》(第四版)这本用java写的书?](https://www.zhihu.com/question/30170425) 44 | - [程序员如何提升算法思维?](https://zhuanlan.zhihu.com/p/54929463) 45 | - [LeetCode 100 题](https://leetcode.com/problemset/top-100-liked-questions/) — 适合准备校招

46 | 47 | 48 | 49 | ## 二、资源 50 | 51 | ### 1)网站 52 | 53 | 下面是一些学习数据结构的在线网站 54 | 55 | - [数据结构与算法之美](https://time.geekbang.org/column/article/39922) — 极客时间专栏,更多好专栏可以点击 [极客时间](https://time.geekbang.org/) 56 | - [可视化学习数据结构与算法](https://visualgo.net/en) — 包含了各种算法的可视化操作 57 | - [GeeksforGeeks](https://www.geeksforgeeks.org/) — 英文版一些算法的实现 58 | - [VisuAlgo](https://visualgo.net/en) — 一个动图显示各种算法原理的网站

59 | 60 | 61 | 62 | ### 2)在线视频课程 63 | 64 | - [浙大数据结构](https://mooc.study.163.com/course/1000033001?_trace_c_p_k2_=8cea2b8fb208415bbfde896a4361caf7#/info) 65 | 66 | - [清华大学数据结构](http://www.xuetangx.com/courses/course-v1:TsinghuaX+30240184X+sp/about) — 邓俊辉 67 | 68 | - [普林斯顿-算法第四版](https://www.coursera.org/lecture/algorithms-part1/analysis-of-algorithms-introduction-xaxyP) 69 | 70 | - [算法与数据结构 — b 站](https://www.bilibili.com/video/av41612881/?p=1) — 针对互联网笔试

71 | 72 | 73 | 74 | ### 3)书 75 | 76 | - 入门 77 | - 《算法图解》 — 主打图解,通俗易懂 78 | - 《大话数据结构》— 生动形象 79 | - 《数据结构与算法分析:C++ 语言描述》 80 | - 《数据结构与算法分析:C 语言描述》 81 | - 面试相关 82 | - 《剑指 offer》— 包含了常见的经典的面试题,适用于一般公司 83 | - 《编程珠玑》— 针对海量数据的处理技巧,可以开拓视野 84 | - 《编程之美》— 微软工程师出品,针对 Google、Facebook 大公司 85 | - 进阶 86 | - 《算法第四版 - Algorithms》— 更适合初级进阶,内容不全,没有动态规划,偏重讲解算法 87 | - 《算法导论》— 复杂度证明、推倒等数学公式多

88 | 89 | 90 | 91 | ### 4)博客 92 | 93 | - [数据结构与算法系列](http://www.cnblogs.com/skywang12345/p/3603935.html) — 包含了基本数据结构与排序算法 c/c++/Java 实现版本 94 | - [《算法》第4版 导读](https://juejin.im/post/5bebccfe6fb9a049fd0f6590#heading-0) 95 | - [台部落](https://www.twblogs.net/c/5b7a8baa2b7177392c96436d/) — 台湾地区的一个包含算法的博客集,需要用 chrome 自动转化成简体中文 96 | - [磊磊落落的博客](https://leileiluoluo.com/posts/tag/%E7%AE%97%E6%B3%95) 97 | -

98 | 99 | 100 | 101 | ### 5)Github 项目 102 | 103 | 包含 c/c++ 的一些实现。学习过程中可以参考他人的代码。 104 | 105 | - [数据结构与算法专栏---Github--50 个代码实现](https://github.com/wangzheng0822/algo/blob/master/README.md) — 极客时间王争课程代码实现 106 | 107 | - [xtaci/algorithm ](https://github.com/xtaci/algorithms) 108 | - [PetarV/Algorithms](https://github.com/PetarV-/Algorithms) 109 | - [faheel/Algos](https://github.com/faheel/Algos)

110 | 111 | 112 | 113 | ### 6)在线评测网站 114 | 115 | - [LeetCode](https://leetcode.com/) 116 | 117 | - [LintCode](https://www.lintcode.com/problem/) — LeetCode 中文网 118 | - [牛客网](https://www.nowcoder.com/) — 附带各大互联网笔试题 119 | - [PAT](https://www.patest.cn/practice) — 浙大的一个练习网站

120 | 121 | 122 | 123 | ## 三、复杂度分析 124 | 125 | - [十分钟搞定时间复杂度(算法的时间复杂度)](https://www.jianshu.com/p/f4cca5ce055a)

126 | 127 | 128 | 129 | ## 四、数据结构 130 | 131 | ### 1)链表 132 | 133 | - [搞懂单链表常见面试题](https://juejin.im/post/5aa299c1518825557b4c5806) — 非常全的关于考察单链表的知识,会了这些,可以适用于所有关于链表(单链表、双链表、循环链表)的题目。 134 | - [看图理解单链表翻转](https://blog.csdn.net/feliciafay/article/details/6841115) 135 | 136 | 137 | 138 | ### 2)栈 139 | 140 | 141 | 142 | ### 3)队列 143 | 144 | ### 4)散列表 145 | 146 | - [哈希表全解](http://www.nowamagic.net/academy/detail/3008085)—介绍了哈希表的原理及应用例子 147 | - [HashMap的loadFactor为什么是0.75?](https://www.jianshu.com/p/64f6de3ffcc1) 148 | - [C++ STL 之哈希表 | unordered_map](https://www.sczyh30.com/posts/C-C/cpp-stl-hashmap/)—可以仿照例子,进行书写哈希表底层实现代码 149 | - [hashCode 方法及 equals 方法的规范](https://www.sczyh30.com/posts/Java/java-hashcode-equal/)—自己实现的类,如果想用哈希表算法,需要对 hashCode 和 equals 进行重载,原理可以参考这个,对于 c++ 例子,可以参考上面 「C++ STL 之哈希表」

150 | 151 | ### 5)树 152 | 153 | - [清晰理解红黑树的演变---红黑的含义](https://blog.csdn.net/chen_zhang_yu/article/details/52415077) 154 | - <<算法四>> 155 | - [TreeMap红黑树源码详解](https://blog.csdn.net/abcdef314159/article/details/77193888) 156 | - stl 源码分析红黑树源码部分 157 | - [学习 《算法导论》第13章 红黑树 总结二](https://blog.csdn.net/wan198809/article/details/48602921) 158 | - 159 | 160 | 161 | 162 | ## 五、算法 163 | 164 | ### 1)递归 165 | 166 | 167 | 168 | ### 2)排序 169 | 170 | - [知无涯之std::sort源码剖析](http://feihu.me/blog/2014/sgi-std-sort/) 171 | - [动态图解十大经典排序算法](https://mp.weixin.qq.com/s/HQg3BzzQfJXcWyltsgOfCQ) — java 实现,可以重点看下原理 172 | 173 | ### 3)哈希算法 174 | 175 | - [一致性哈希算法](http://www.zsythink.net/archives/1182) 176 | - [memcached 分布式算法](https://kb.cnblogs.com/page/42734/) — 一个翻译的日本博客,重点看一下一致性哈希算法原理 177 | - [什么是一致性哈希](https://mp.weixin.qq.com/s/yimfkNYF_tIJJqUIzV7TFA) — 图解算法系列 178 | - [分布式算法(一致性Hash算法)](https://www.cnblogs.com/moonandstar08/p/5405991.html) — 含有 Java 实现代码 179 | - [一致性哈希算法与高可用集群代理](https://leileiluoluo.com/posts/consistent-hashing-and-high-available-cluster-proxy.html) — 含有 java 代码 180 | - [一致性Hash算法背景](https://www.cnblogs.com/haippy/archive/2011/12/10/2282943.html) — 含有链接各种实现代码 181 | 182 | ### 4)回溯算法 183 | 184 | - [小白带你学--回溯算法](https://www.jianshu.com/p/dd3c3f3e84c0) 185 | - [回溯算法经典框架](https://segmentfault.com/a/1190000018319044?utm_source=tag-newest) 186 | - [算法之八皇后问题详解暨终极极限挑战](https://blog.csdn.net/u014028063/article/details/82594744) 187 | - 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /code-style/README.md: -------------------------------------------------------------------------------- 1 | ## :pencil2: 内容说明 2 | 3 | 该文件夹包含了代码规范 `google_norm.cc` 和 `google_norm.h`,文件内容是 Google 代码规范的浓缩,同样也是一种以代码形式呈现的笔记。本 Repo 包含的代码,都是根据这些规范进行书写。当然这份规范笔记还有许多不足,会随着笔者的 C++ 水平的提升而进行改进。同时自己也简单整理了一份规范文档,适用于本项目的源码。**戳这里** :point_right: :point_right: [CODING_GUIDELINES.md](../CODING_GUIDELINES.md)。这份文档配合上层目中 `template` 文件夹内部的两个文件 `template.h` `template.test.cc` 一起阅读,效果更佳。 4 | 5 | 更多的代码风格,请参照 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Header_Files)。中文版本参考 [点击这里](https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/)。 6 | 7 | **持续更新中 ......** 8 | 9 | -------------------------------------------------------------------------------- /code-style/google_norm.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 saber 3 | * File: queue.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/11 7 | * Description: queue simple implementation 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | // #include 头文件包含顺序:相关头文件(项目中的)、C 库、C++库、其他库.h比如第三方 opencv、Eigen 等等库 13 | // 对应的头文件包含的,在当前 cc 文件中就不用包含了!!!为了头文件没有冗余性, 14 | // 对于具有平台特性的代码需要条件编译,需要放在其他 include 之后 15 | #include "kit/google/google_norm.h" // 自己的头文件 16 | **留白** 17 | #include // c 库 18 | **留白** 19 | //#include // c++ 库 20 | **留白** 21 | //#include // 比如这个在头文件中包含了,那么在这里就不用包含了。 22 | #include "glog/logging.h" // 其他库头文件 23 | **留白** 24 | #include "kit/system/file_dir.h" // 本项目中的头文件 ... 25 | 26 | // 宏的名字,注意全大写,然后下划线链接。 27 | #ifdef DISASTER_CXX11 28 | #include 29 | #endif 30 | 31 | // 对于使用 gflags 需要在命名空间外部定义变量 32 | DEFINE_FLAG(bool, someflag, false, "dummy flag"); 33 | 34 | namespace kit { 35 | namespace google { 36 | 37 | // 宏命名(尽量少使用宏,尽可能用常量代替 const constexpr) 38 | #define My_MACRO_SMALL_CHILDREN // 采用全大写,且下划线链接! 39 | 40 | // 静态存储期的常量用 k 开头,其他静态存储期变量直接用函数命名规则单词首字母大写,且最好不要定义一个全局类变量!!!会出现动态初始化的 bug 41 | // 全局变量要注释说明含义和用途。以及作为全局变量的原因。 42 | 43 | const int kGlobalConst = 8; // 这个名字要改成具有某些含义的名字,而不是现在这样说变量的属性 44 | constexpr int kGlobalExpr = 9; 45 | // 全局的且对外的变量按照函数命名规则,开头大写单词, 46 | int GlobalVar; // 全局变量 47 | static int GoogleStatic = 4; // 全局静态,仅在文件作用域内使用,直接按照常量 k 方式命名 48 | 49 | // 预处理指令不要缩进,从行首开始 50 | #if DISASTER_PENDING 51 | ..... 52 | #endif 53 | 54 | // 构造函数说明.................................................................. 55 | // 如果所有变量能放在同一行: 56 | GoogleNorm::GoogleNorm(int var) : some_var_(var) { 57 | DoSomething(); 58 | } 59 | 60 | // 如果不能放在同一行, 61 | // 必须置于冒号后, 并缩进 4 个空格 62 | GoogleNorm::GoogleNorm(int var) 63 | : some_var_(var), some_other_var_(var + 1) { 64 | DoSomething(); 65 | } 66 | 67 | // 如果初始化列表需要置于多行, 将每一个成员放在单独的一行 68 | // 并逐行对齐 69 | GoogleNorm::GoogleNorm(int var) 70 | : some_var_(var), // 4 space indent 71 | some_other_var_(var + 1) { // lined up 72 | DoSomething(); 73 | } 74 | 75 | // 右大括号 } 可以和左大括号 { 放在同一行 76 | // 如果这样做合适的话 77 | GoogleNorm::GoogleNorm(int var) 78 | : some_var_(var) {} 79 | 80 | //构造函数结尾................................................................... 81 | void GoogleNorm::MemberFunc2() { 82 | 83 | } 84 | ***留白一行**** 85 | // 两个函数定义之间留白一个空行 86 | void GoogleNorm::MemberFunc(const 值或引用(输入参数),非 const 指针或引用) { 87 | // 变量初始化建议 括号,普通非数组的变量用 = 号 88 | // 将函数变量尽可能置于最小作用域内, 并在变量声明时进行初始化. 89 | vector v(100, 1); 90 | int pi(3.14); // 优先使用 int pi = 3.14; 91 | vector x; // 注意留白处理 92 | y = static_cast(x); 93 | vector x; // 留白处理 94 | 95 | while (1) { // 注意空格处理 96 | 97 | } 98 | // 空循环体 99 | while (1) continue; 100 | // 函数返回值:如果单参数不需要括号 return aa;逻辑判断的可以用括号改善阅读环境 101 | return (some_long_condition && 102 | another_condition); 103 | } 104 | 105 | // 定义处,注释描述函数实现。声明处描述函数功能。 106 | // 重点放在函数的实现大致步骤。比如前半部分加锁,后半部分不需要。 107 | 108 | // 弥补方法: 109 | // 如果函数参数意义不明显,比如用到了字面值常量,如 23 等等,可以用一个 constexpr 常量来代替 110 | // 对于 bool 类型参数,可以用 enum 来代替,然后 enum ChoiceArg { kHave , kNoHave }; 这样用 enum 更清晰 111 | // 一个函数不要超过 40 行 112 | void GoogleTest(输入参数, 输出参数) { // 函数名字每个单词大写,剩下小写。 113 | // 开头不要加空行 114 | // 普通变量,小写加下划线 115 | int price_count_reader; 116 | const int local_const = 3; 117 | static int local_static = 4; 118 | 119 | // 对于代码中巧妙的、晦涩的、有趣的、重要的地方加上注释 120 | // example: 这是一个 for 循环。用了 ... 技巧。。。 121 | for (int i = 0; i < 2; ++i) { 122 | // 比较晦涩的地方,要在行尾加入注释。空两个空格 123 | printf("i = %d\n", i); //example: this is a printf,no except 124 | 125 | // 把 && 逻辑处理符号放在后面 ,适当加入括号,方便阅读 if ((this_one_thing > this_other_thing) && ....) 126 | if (this_one_thing > this_other_thing && 127 | a_third_thing == a_fourth_thing && 128 | yet_another && last_one) { 129 | 130 | } 131 | } 132 | // 注意使用基于范围的 for 循环 133 | double data[7]; 134 | for (double& d:data) fin>>d; 135 | 136 | // 空循环体处理 137 | for (int i = 0; i < kGlobalConst; ++i) {} 138 | 139 | // 注意空格的处理 140 | if (a == 5) { 141 | ; 142 | } else if (a == 4) { 143 | ; 144 | } 145 | 146 | // 简短的 if 可以写在一行(有 else 不可以) 147 | if (b == 3) return ; 148 | // 注意空格处理 149 | switch (var) { 150 | case 0: { 151 | break; 152 | } 153 | case 1: { 154 | break; 155 | } 156 | default: { 157 | // 若 default 用不执行,可以使用这条语句 158 | assert(false); 159 | } 160 | } 161 | // 函数结尾不要加空行 162 | } 163 | // 如果某个函数有多个配置选项,可以像刚刚说的那样,定义一个类或结构体保存配置选项。然后用该类作为形式参数,这样可以后期随意添加选项 164 | // 比如: const DecimalNumber product = CalculateProduct(values, 7, false, nullptr); CalculateProduct() 有 4 个配置选项参数,可以定义个类或者结构体,然后进行配置 165 | // 然后传入这个类的实例 166 | // 修改后: class ProductOptions {...}; 167 | // ProductOptions options; options.set_precision_decimals(7); options.set_use_cache(ProductOptions::kDontUseCache); 168 | // CalculateProduct(values, options, nullptr); 169 | 170 | } // namespace google 171 | } // namespace kit 172 | -------------------------------------------------------------------------------- /code-style/google_norm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: queue.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/11 7 | * Description: queue simple implementation 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | // 注释风格按照 // 只有在文件开头的说明使用 /**/,自己要求 // 后面有一个空格 13 | // 利用项目源代码树的全路径。项目 kit 此时 kit/src/google/google_norm.h 14 | #ifndef KIT_GOOGLE_GOOGLE_NORM_H_ 15 | #define KIT_GOOGLE_GOOGLE_NORM_H_ 16 | **留白** 17 | // #include 头文件包含顺序:相关头文件(项目中的)、C 库、C++库、其他库.h,比如第三方 opencv、Eigen 等等库 18 | // 该项目中的其他头文件,对于每个类的头文件需要按照字母顺序进行排序。 19 | // 但是对于头文件来说,我们仅仅列出头文件中需要包含的一些库,然后按照上面的顺序进行排列,假设下面的头文件,在该文件内都使用了。 20 | #include // C 库头文件 21 | 22 | #include // C++ 头文件 23 | 24 | #include 25 | #include // 其他第三方库 26 | 27 | #include "kit/.../..." // 利用了项目内的其他头文件的某个类型或函数 28 | 29 | 30 | // 鼓励在 .cc 文件内使用匿名命名空间或 static 声明(只在声明处,定义就不要了)。 31 | namespace kit { // 小写字母,最高级命名空间取决于项目名称,不要使用缩写,但是大家都知道的缩写可以用。 32 | namespace google { // 对应于 kit/google 文件夹 33 | // 可以在这里加上 别名 34 | using Uint8Color = std::array; 35 | 36 | extern const int kGlobalVar; // 全局常量 37 | 38 | 39 | // TODO 对临时的,短期的解决方案,但不完美的代码使用这个注释,或者写一些 "将来某一天要做的事情"即 未完成事。或明确的事项 40 | // DEPRECATED 弃用注释,不是强制弃用,格式如下: 41 | 42 | // DEPRECATED(写说明,帮助其他人修复其调用点) ,或者一般做法,可以将一个弃用函数改造成为一个内联函数,这一函数将调用新的接口。 43 | void FuncTodo() { 44 | // pass 45 | } 46 | // 改造后的内联函数 47 | inline FuncTodo() { 48 | // pass 49 | // 调用新的接口 50 | FuncOne(); 51 | } 52 | 53 | // 函数调用时,尽可能易读性:比如: my_widget.Transform(x1, x2, x3, y1, y2, y3, y4, z1, z2, z3);可以改为: 54 | // my_widget.Transform(x1, x2, x3, 55 | // y1, y2, y3, 56 | // z1, z2, z3); 57 | 58 | // 属性和展开为属性的宏,写在函数声明或定义的最前面,即返回类型之前 59 | // 宏的名字,注意全大写,然后下划线链接。 60 | #define MUST_USE_RESULT // 起到提示作用,可以想着用 61 | 62 | 63 | // 函数声明:注释函数功能,标注: 对于比较明显的就不需要说明了。 64 | // 输入输出, 65 | // 函数是否分配了必须由调用者释放的空间。 66 | // 参数是否可以为空指针,或者在函数内部进行空指针检测,提示错误!用 glog CHECK()宏。 67 | void GoogleTest(); // 尽量使用这种在命名空间中的函数,不要在命名空间外部定义全局函数,而 static 函数要用在类中 68 | 69 | 70 | // 类型名称的每个单词首字母均大写, 不包含下划线 71 | using VectorInt = std::vector; 72 | typedef std::vector VectorInt; 73 | enum GoogleChoice { GLOG_CHOICE, GFLAGS_CHOICE }; // 枚举值,采用常量的命名方式。 74 | 75 | // 内部变量命名规则与普通变量命名规则一致,小写加下划线链接,但是不需要结尾加下划线 76 | // 自己规定为前置下划线 + 小写字母组合:恰好区分 class 77 | struct GoogleSection { 78 | int data_member; // 变量:不需要下划线结尾,当做普通变量处理,小写字母加下划线 79 | int data_2; 80 | static int struct_test; // 不用 k 开头,不用加下划线,因为是变量。这种基本上用不到 81 | const int data = 3; 82 | }; 83 | // 列表初始化 84 | GoogleSection cc{2,3}; 85 | 86 | // 函数代码比较少 10 行的函数声明为内联函数,且声明和定义都放在头文件中,否则编译失败。 87 | // 对于包含 for()等循环 switch 的情况,最好不要用内联,除非,for()等循环 switch 用的少,或基本很少执行。 88 | // 当然对于有些虽然声明为内联,但是编译器有时不会看做内联。比如递归函数、虚函数 89 | inline void TestInline(int test_num) { 90 | int test_num_buf = test_num + 2; 91 | return; // 建议 void 返回格式 92 | } 93 | // 类注释:在类前面,描述类的功能 94 | //和用法,比如 Example:...写下简单用法 95 | //除非它的功能相当明显。一般情况下可以不说明,用分行 // 注释法 96 | // 是否可多线程访问实例,需要说明。 97 | class GoogleNorm { // 对应文件名字 google_norm.h google_norm.cc 98 | public: 99 | // 属于 static const constexpr 的数据成员要放在 public 后面,剩下其他变量全部是 private 100 | constexpr static const char* ConfigurationFileActionName = // k 开头 // 因为是 static 常量 101 | "intensity_to_color"; 102 | static const MemberStaticConst = 3; 103 | 104 | GoogleNorm() = default; 105 | // 构造函数一般不注释,切记,读代码的人知道构造/析构函数的功能,需要注明函数对参数做了什么,是否取得指针所有权,析构清理了什么 106 | GoogleNorm(int i ) {} 107 | GoogleNorm(const GoogleNorm &other) : section_cout_(other.section_cout_) { 108 | int * temp = new (nothrow) int[other.ClassStatic](); 109 | // 拷贝元素 110 | temp 111 | } 112 | ~GoogleNorm() = default; 113 | //or 114 | // 析构函数,注意释放内存,以及变量对齐问题,显示整齐 115 | ~GoogleNorm() { 116 | if (nullptr != queue_) { 117 | delete[] queue_; 118 | queue_ = nullptr; 119 | capacity_ = 0; 120 | size_ = 0; 121 | head_ = 0; 122 | tail_ = 0; 123 | } 124 | } 125 | 126 | // 属性宏或展开为属性的宏,写在函数声明或定义的最前面。 表示提醒调用者 127 | MUST_USE_RESULT bool IsOk(); 128 | 129 | void get_const(){ return ClassStatic_; } // 单函数内部要留白左右一个空格 130 | 131 | void get_section() const { return section_cout_; } // 取值函数和设值函数要全小写加下划线。最好与实际成员变量对应 132 | 133 | // 声明:注释函数功能:对于明显的可以不用说明。比如上面取值和设值函数就不用加注释 134 | // 输入输出, 135 | // 函数是否分配了必须由调用者释放的空间。 136 | // 参数是否可以为空指针,或者在函数内部进行空指针检测,提示错误!用 glog CHECK()宏。 137 | void MemberFunc(); // 普通函数 单词首字母大写! 138 | 139 | protected: 140 | 141 | private: // 类成员变量要加下划线,一律小写字母加下划线,结尾加下划线, 对于归属类的 static 变量,直接用函数命名方式,对于 static 常量,则用常量命名方式 142 | static int ClassStatic = 3; 143 | // 空格前置 风格一致 144 | char *c; // char &c; 145 | // 类作用域的常量,const constexpr 不要用 k 打头了,直接用函数的命名方式 146 | const int days_in_week_ = 8; 147 | // 数据成员注释,一般不用,但是对于变量可以接受 NULL or -1 这种值时,需要注释代表的含义 148 | int section_cout_; // 类的成员 变量(静态、非静态),下划线结尾 149 | int *p_; // 一段内存 150 | }; 151 | 152 | } // namespace google 153 | } // namespace kit 154 | **留白一行** 155 | #endif //KIT_GOOGLE_GOOGLE_NORM_H_ 156 | -------------------------------------------------------------------------------- /notes/README.md: -------------------------------------------------------------------------------- 1 | ### :pencil2: 内容说明 2 | 3 | --- 4 | 5 | 本文件夹包含了一些笔记 — 学习整理极客时间付费专栏《数据结构与算法之美》。订阅该专栏,可以 [点击这里](https://time.geekbang.org/column/intro/126)。 6 | 7 | 笔记中涉及的理论代码,可以查看该 Repo 中 `src` 目录代码,**戳这里** :point_right: :point_right: [代码](../src/)。

8 | 9 | 10 | 11 | ### :memo: 注意事项 12 | 13 | --- 14 | 15 | GitHub 目前无法查看 MarkDown 文件中的 LaTex 数学公式,导致笔记在线观看效果不好。 16 | 17 | 推荐解决方案: 18 | 19 | - 克隆当前 Repo ,用 Typora 软件或者其他相关的 Markdown 软件进行离线查看。 20 | 21 | Note:在用 Typora 离线观看时,在每个笔记前面,可以自己加上 `[TOC]` ,展示更好的目录跳转。 22 | 23 | - 安装 Google 插件 [Github With Mathjax](https://chrome.google.com/webstore/detail/github-with-mathjax/ioemnmodlmafdkllaclgeombjnmnbima) ,实现在线查看。如果安装失败可以 [点击这里](https://github.com/orsharir/github-mathjax/issues/24),查看解决方案。 24 | 25 | 根本上解决方案(嫌麻烦): 26 | 27 | - 在线编辑并且生成一张图片链接,然后插入到 `.md` 文件中进行引用。 28 | - 使用公式编辑器,生成一张图片,在文件中引用。

29 | 30 | 31 | 32 | ### :page_with_curl: 笔记目录 33 | 34 | --- 35 | 36 | #### 1)数据结构相关 37 | 38 | - [数组&链表](数组&链表.md) 39 | - [栈](栈.md) 40 | - [队列](队列.md) 41 | - [跳表](跳表.md) 42 | - [哈希表/散列表](散列表.md) 43 | - [树](树.md) 44 | - [堆&应用](堆&应用.md) 45 | - [图](图.md) 46 | 47 | 48 | 49 | #### 2)算法思想相关 50 | 51 | - [复杂度分析](复杂度分析.md) 52 | - [递归](递归.md) 53 | - [排序](排序.md) 54 | - [二分查找](二分查找.md) 55 | - [字符串匹配](字符串匹配.md) 56 | - [算法思想](算法思想.md) 57 | 58 | -------------------------------------------------------------------------------- /notes/pics/二叉树/二叉搜索树.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/二叉搜索树.png -------------------------------------------------------------------------------- /notes/pics/二叉树/二叉查找树时间复杂度.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/二叉查找树时间复杂度.png -------------------------------------------------------------------------------- /notes/pics/二叉树/完全二叉树.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/完全二叉树.png -------------------------------------------------------------------------------- /notes/pics/二叉树/树的基本概念.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/树的基本概念.png -------------------------------------------------------------------------------- /notes/pics/二叉树/满二叉树.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/满二叉树.png -------------------------------------------------------------------------------- /notes/pics/二叉树/递归树.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/递归树.png -------------------------------------------------------------------------------- /notes/pics/二叉树/递归树_全排列.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/递归树_全排列.png -------------------------------------------------------------------------------- /notes/pics/二叉树/递归树_归并排序.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/递归树_归并排序.png -------------------------------------------------------------------------------- /notes/pics/二叉树/递归树_快速排序.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/递归树_快速排序.png -------------------------------------------------------------------------------- /notes/pics/二叉树/递归树_快速排序_复杂度.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/递归树_快速排序_复杂度.png -------------------------------------------------------------------------------- /notes/pics/二叉树/递归树_斐波那契数列.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/二叉树/递归树_斐波那契数列.png -------------------------------------------------------------------------------- /notes/pics/哈希表/LRU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/哈希表/LRU.png -------------------------------------------------------------------------------- /notes/pics/图/图.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/图/图.jpg -------------------------------------------------------------------------------- /notes/pics/图/图_带权图.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/图/图_带权图.jpg -------------------------------------------------------------------------------- /notes/pics/图/图_广度优先搜索.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/图/图_广度优先搜索.jpg -------------------------------------------------------------------------------- /notes/pics/图/图_微博.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/图/图_微博.jpg -------------------------------------------------------------------------------- /notes/pics/图/图_微博2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/图/图_微博2.jpg -------------------------------------------------------------------------------- /notes/pics/图/图_邻接矩阵.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/图/图_邻接矩阵.jpg -------------------------------------------------------------------------------- /notes/pics/图/图_邻接表.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/图/图_邻接表.jpg -------------------------------------------------------------------------------- /notes/pics/堆/堆.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/堆.png -------------------------------------------------------------------------------- /notes/pics/堆/堆化_从下向上.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/堆化_从下向上.jpg -------------------------------------------------------------------------------- /notes/pics/堆/堆化_从下向上.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/堆化_从下向上.png -------------------------------------------------------------------------------- /notes/pics/堆/堆化_删除堆顶1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/堆化_删除堆顶1.jpg -------------------------------------------------------------------------------- /notes/pics/堆/堆化_删除堆顶1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/堆化_删除堆顶1.png -------------------------------------------------------------------------------- /notes/pics/堆/堆化_删除堆顶2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/堆化_删除堆顶2.jpg -------------------------------------------------------------------------------- /notes/pics/堆/堆化_删除堆顶2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/堆化_删除堆顶2.png -------------------------------------------------------------------------------- /notes/pics/堆/建堆.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/建堆.jpg -------------------------------------------------------------------------------- /notes/pics/堆/建堆2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/建堆2.jpg -------------------------------------------------------------------------------- /notes/pics/堆/建堆时间复杂度.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/建堆时间复杂度.jpg -------------------------------------------------------------------------------- /notes/pics/堆/建堆节点高度.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/建堆节点高度.jpg -------------------------------------------------------------------------------- /notes/pics/堆/建堆节点高度1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/建堆节点高度1.jpg -------------------------------------------------------------------------------- /notes/pics/堆/建堆节点高度2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/建堆节点高度2.jpg -------------------------------------------------------------------------------- /notes/pics/堆/排序.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/堆/排序.jpg -------------------------------------------------------------------------------- /notes/pics/复杂度/复杂度效果图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/复杂度/复杂度效果图.png -------------------------------------------------------------------------------- /notes/pics/复杂度/复杂度量级.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/复杂度/复杂度量级.png -------------------------------------------------------------------------------- /notes/pics/字符串匹配/RK1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/字符串匹配/RK1.jpg -------------------------------------------------------------------------------- /notes/pics/字符串匹配/RK2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/字符串匹配/RK2.jpg -------------------------------------------------------------------------------- /notes/pics/跳表/跳表结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/notes/pics/跳表/跳表结构.png -------------------------------------------------------------------------------- /notes/二分查找.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
二分查找
4 | 5 | * [一、什么是二分查找](#一什么是二分查找) 6 | * [二、二分查获找的思想](#二二分查获找的思想) 7 | * [三、二分查找的复杂度 O(log(n))](#三二分查找的复杂度-ologn) 8 | * [四、二分查找的实现方法(递归 VS 非递归)](#四二分查找的实现方法递归-vs-非递归) 9 | * [1)容易出错的地方](#1容易出错的地方) 10 | * [2)实现方法](#2实现方法) 11 | * [五、二分查找的局限性](#五二分查找的局限性) 12 | * [1)依赖的是顺序表结构 — 数组](#1依赖的是顺序表结构--数组) 13 | * [2)针对有序的数组](#2针对有序的数组) 14 | * [3)针对插入和删除操作不频繁的场合](#3针对插入和删除操作不频繁的场合) 15 | * [4)针对数据量不能太小](#4针对数据量不能太小) 16 | * [5)数据量不能太大(最多占内存1/5)](#5数据量不能太大最多占内存15) 17 | * [六、二分查找的变种](#六二分查找的变种) 18 | * [七、二分查找的应用例子](#七二分查找的应用例子) 19 | * [1)如何在 100 万个整数中快速找某个整数](#1如何在-100-万个整数中快速找某个整数) 20 | * [2)求一个数的平方根,要求精确到指定位数](#2求一个数的平方根要求精确到指定位数) 21 | * [3)如何快速定位 IP 对应的省份地址](#3如何快速定位-ip-对应的省份地址) 22 | * [八、思考](#八思考) 23 | * [1)为什么不能用链表方式来存储数据,进行二分查找?](#1为什么不能用链表方式来存储数据进行二分查找) 24 | * [2)二分查找 vs 散列表 vs 二叉树](#2二分查找-vs-散列表-vs-二叉树) 25 | 26 | 27 | 28 | #### 一、什么是二分查找 29 | 30 | --- 31 | 32 | 是一种针对有序数据集合的查找算法。是一种最省内存的方式实现的快速查找的方法。 33 | 34 | 35 | 36 | #### 二、二分查获找的思想 37 | 38 | --- 39 | 40 | 针对有序数据集合,每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半。直到要查找的元素或者区间被缩小为 0 时,此时表示没有找到。 41 | 42 | 43 | 44 | #### 三、二分查找的复杂度 O(log(n)) 45 | 46 | --- 47 | 48 | 与二分查找复杂度相同的还有堆、二叉树操作。这种复杂度,有时候比常数阶复杂度的算法实际执行时间要快。因为常数阶 $O(1)$ 可能对应的是 $O(1000)$ 。 49 | 50 | 51 | 52 | #### 四、二分查找的实现方法(递归 VS 非递归) 53 | 54 | --- 55 | 56 | ##### 1)容易出错的地方 57 | 58 | - 终止条件 59 | - 区间上下界更新方法 60 | - 返回值选择 61 | 62 | 63 | 64 | ##### 2)实现方法 65 | 66 | - 非递归方法 67 | - 循环退出条件:low <= high 68 | - mid 的取值:mid = low + (high - low)/2 ,不用 (high + low)/2 的原因,当 high 和 low 都很大时,此时两者之和可能会有溢出的风险。因此改为前一种方式。 69 | - low 和 high 的更新:low = mid + 1, high = mid - 1。这里的 1 实际上就是步长。针对不同的问题,这里的步长可能不同。 70 | - 递归方法 71 | - 终止条件:low > high 72 | - 递推公式:f(区间) = f(区间/2) 73 | 74 | 75 | 76 | #### 五、二分查找的局限性 77 | 78 | --- 79 | 80 | ##### 1)依赖的是顺序表结构 — 数组 81 | 82 | 如果利用链表来实现二分查找,那么在获取指定索引的元素值时,对于数组来说,复杂度为 $O(1)$,但是对于链表,复杂度变为 $O(n)$,也就是说,利用链表的方式来来实现二分,那么每次折半时,链表都会比数组的方法多了 $O(n)$ 的额外操作。复杂度会更高。 83 | 84 | 85 | 86 | ##### 2)针对有序的数组 87 | 88 | 二分查找只能用在排好序数据上,也就是在使用二分查找之前,一定要将数据进行排序。那么边际成本就是排序的复杂度。在排序中最快的复杂度也就是 $O(nlog(n))$ ,此时适合一次排序,多次查找的场合。 89 | 90 | 91 | 92 | ##### 3)针对插入和删除操作不频繁的场合 93 | 94 | 如果一组数据在排序完成后,除了查找操作外,还会进行一系列删除和插入操作。如果能够保证插入和删除操作之后,数据仍然是有序的,那么使用二分查获找总是可以的。或者插入和删除操作很少,那么在查找之前,需要排序数据这种边际成本可以容忍。但是,当一组数据总是会频繁动态的插入和删除,同时插入和删除的操作不能保证数据仍然是有序的。那么此时就不适合二分查找了。因为每次使用二分查找都需要排序数据。频繁插入和删除,会导致排序算法的执行次数大大增加。这样查找的边际成本太大。此时应该使用二叉树这种。适合动态数据,查找复杂度也同时是 $O(nlogn)$。 95 | 96 | 97 | 98 | ##### 4)针对数据量不能太小 99 | 100 | 当数据量小的时候,比如只有 10 个。我们可以直接一次性进行遍历即可找到某个值,那么时间复杂度是 $O(n)$ ,数据量小,会使复杂度接近常数阶。但是这个时候利用二分法,那么需要排序一次。然后在查找。那么实际执行时间,二分可能会大于直接顺序遍历。 101 | 102 | 特例:当数据中会有比较耗时的比较操作时。比如一组数据的比较操作是字符串。那么此时我们应该减少比较次数。假设排序的操作不会比较字符串。那么此时还是优先选择二分查找的方法。保证比较的操作次数少于循环遍历方法。如果排序操作也会比较字符串,且查找不太频繁。那么还是优先使用循环遍历。 103 | 104 | 105 | 106 | ##### 5)数据量不能太大(最多占内存1/5) 107 | 108 | 因为二分算法是基于顺序表的。那么就需要申请很大的连续空间,一般来说,如果数据量太大,计算机内存空间会很难提供那么的大空间存储数据。 109 | 110 | 111 | 112 | #### 六、二分查找的变种 113 | 114 | --- 115 | 116 | - 查找第一个值等于给定值的元素 117 | - 查找最后一个值等于给定值的元素 118 | - 查找第一个大于等于给定值的元素 119 | - 查找最后一个小于等于给定值的元素 120 | 121 | 122 | 123 | #### 七、二分查找的应用例子 124 | 125 | --- 126 | 127 | ##### 1)如何在 100 万个整数中快速找某个整数 128 | 129 | 100 万个整数大小占据 80M 空间,符合内存限制,可以使用二分查找方法。虽然二叉树和散列表方式也是可找到。但是这两种方法,需要耗费额外的空间。因此二分方法是最省内存空间的方法。且复杂度也是 $O(log(n))$ 130 | 131 | 132 | 133 | ##### 2)求一个数的平方根,要求精确到指定位数 134 | 135 | - 利用二分思路,一位一位进行确定。 136 | - newton 方法,用循环迭代的方式找到精确解。是平方阶收敛 137 | 138 | 139 | 140 | ##### 3)如何快速定位 IP 对应的省份地址 141 | 142 | 将 IP 地址表的初始地址(终止地址不作处理)转化为 int 类型数据,然后排序(小--->大)。之后查找最后一个小于等于给定值的元素。判断给定值是否在找到的初始地址对应的地址区间。如果给定值在该区间,那么读取对应的省份即可。否则失败。 143 | 144 | 145 | 146 | #### 八、思考 147 | 148 | --- 149 | 150 | ##### 1)为什么不能用链表方式来存储数据,进行二分查找? 151 | 152 | 对于链表方法来说,每次除了比较操作外,还需要进行 $O(n)$ 的获取指定索引值的操作。那么依次是 153 | 154 | $n/2 \quad n/2^2 \quad n/2^3 ...\quad n/2^k $ 此时 k = log(n),这个是等比数列,求和后可以发现,此时链表实现的二分查找复杂度是 $O(n)$ ,我们知道即使循环遍历一遍数据也是 $O(n)$,如果用链表的话,还需要排序,链表遍历的赋值操作等等,造成实际执行时间和空间复杂度会远高于基于数组的二分法和循环遍历方法。 155 | 156 | 157 | 158 | ##### 2)二分查找 vs 散列表 vs 二叉树 159 | 160 | 一般精确查找的地方,会用散列表和二叉树代替二分查找。因为内存不够的情况比较少见。而对于二叉树来说,更适合「近似」查找问题。比如上面说的二分查找的变种。 161 | 162 | -------------------------------------------------------------------------------- /notes/哈希算法.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
哈希算法
4 | 5 | - [一、什么是哈希算法](#一、什么是哈希算法) 6 | - [二、设计哈希算法要求](#二、设计哈希算法要求) 7 | - [三、哈希算法应用](#三、哈希算法应用) 8 | 9 | 10 | 11 | #### 一、什么是哈希算法 12 | 13 | --- 14 | 15 | 将任意长度的二进制值串映射为固定长度的二进制值串,映射规则称为**哈希算法**。而映射后的二进制值串称为**哈希值**。 16 | 17 | 18 | 19 | #### 二、设计哈希算法要求 20 | 21 | --- 22 | 23 | - 从哈希值不能反向推导出原始数据 24 | - 对输入数据非常敏感,原始数据变化一个 Bit,最后得到的哈希值也是大不相同 25 | - 散列冲突的概率非常小,不同的原始数据,哈希值相同的概率非常小 26 | - 哈希算法的执行效率要尽量高,针对较长的文本,也能快速计算哈希值 27 | 28 | 29 | 30 | #### 三、哈希算法应用 31 | 32 | --- 33 | 34 | - **安全加密** — 从哈希值不能反向推导出原始数据。散列冲突的概率非常小,MD5 一般为 1/(2^128) 35 | 36 | - **唯一标识** — 以图片库搜索某张图片为例,从图片上获取一些特定位置像素,通过哈希算法形成唯一标识码,作为图片唯一标识(信息摘要)。然后以散列表形式存储标识码及图像本身到数据库。之后根据唯一标识可以在散列表中快速找到相同 mapped value 的图片,对于映射到相同索引的图片可以进行所有像素的比对。 37 | 38 | - **数据校验** — 下载 2GB 电影,分成 100 块下载,每块根据其内容计算哈希值,下载成功后再次进行哈希值比对,如果比对正确,则说明下载过程中网络传输没有出现错误。 39 | 40 | - **散列函数** — 本质与哈希算法一致,散列函数是哈希算法的特例,散列函数侧重于执行效率,以及散列值的均匀性。 41 | 42 | - **负载均衡** — 要求在同一个客户端上,一次会话中的所有请求都路由到同一个服务器上,可以通过哈希算法,对客户端 IP 地址或者会话 ID 计算哈希值,将哈希值与服务器列表的大小进行取模计算,最终得到的值就是客户端对应的服务器编号。这实际上就是散列表的实现原理! 43 | 44 | - **数据分片** — 以大数据「关键词日志」为例,利用多个机器并行处理大数据,将每个数据计算哈希值,将其与机器个数 n 取模。此时每个数据都会分配到对应的机器上。之后同一个关键词会被相同的机器处理,然后每台机器统计每个关键词的数量即可(里面有不重复的关键词对应同一台机器,也就是所谓的冲突)。在**唯一标识**哪里,如果图片集是 1 亿张,那么无法在一台机器上建立散列表,此时需要将图片分给多个机器,这里也按照数据分片思想,每张图片的标识符与计算机台数 n 取模(这里的 n 可以大概估算出来),之后分配到对应的机器,分别建立散列表。 45 | 46 | **NOTE**:海量数据处理问题,都可以采用多机分布式处理。借助分片思路,可以突破单机内存、CPU 等资源的限制。上面哈希值 n 取模的方法,其实有一个弊端,如果数据量动态变大,那么增加一台机器时,此时需要重新计算所有数据分配到哪个机器上。这样会造成雪崩效应。下面**分布式存储**方案,很好的解决了这个问题。数据分片方法仅仅适用于机器资源固定的场合。 47 | 48 | - **分布式存储** — 数据哈希值的范围划分成 m 个区间,每个机器负责 m/k 个区间,其中 k 是当前机器个数。此时在增加一台机器,那么只需要拷贝某几个区间的值到新的机器上。这种方法称为**一致性哈希**。解决了分布式系统的扩容、缩容导致数据大量搬移的难题。 49 | 50 | 51 | 52 | **问题**:你会直接存储用户密码这么重要的数据吗?仅仅 MD5 加密一下存储就够了吗? 53 | 54 | > 实际上这样做是不够的,因为 MD5 虽然加密后无法反向推导。但是只要获得了加密后的文件。通过彩虹表(简单密码的 MD5 值)一一 比对,仍然能够破解一些简单的密码。这就是所谓的字典攻击。 55 | > 56 | > 可以通过引入一个 salt。也就是类似与注册时,会收到一个验证码,最终密码 = 你的密码 + 验证码,通过将「最终密码」进行 MD5 加密,此时得到的密文破解的难度会大大增加。 57 | 58 | -------------------------------------------------------------------------------- /notes/图.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | * [一、什么是图](#一、什么是图) 6 | * [二、为什么要用图](#二、为什么要用图) 7 | * [三、如何使用图结构](#三、如何使用图结构) 8 | * [1 如何存储图](#1 如何存储图) 9 | * [1 邻接矩阵](#1-邻接矩阵) 10 | * [2 邻接表](#2-邻接表) 11 | * [2 图的应用例子](#2-图的应用例子) 12 | * [四、图的暴力搜索算法](#四、图的暴力搜索算法) 13 | * [1 广度优先搜索](#1-广度优先搜索) 14 | * [2 深度优先搜索](#2-深度优先搜索) 15 | * [3 适用范围](#3-适用范围) 16 | 17 | 18 | 19 | ### 一、什么是图 20 | 21 | --- 22 | 23 | 如何存储微博、微信等这些社交网络的好友关系? 24 | 25 | 26 | 27 | 图是一种非线性表数据结构,顶点与顶点以及之间的连线构成一个图结构,根据边是否有方向,分为有向图和无向图。 28 | 29 | 度(degree):跟顶点相连接的边的条数 30 | 31 |
32 | 33 |

34 |
本图片来自极客时间《数据结构与算法之美》专栏

35 | 36 | 入度(In-degree):指向该顶点的边的条数 37 | 38 | 出度(Out-degree):从该顶点出发的边的条数 39 | 40 | 带权图(weighted graph):每条边都有一个权重,比如可以表示 QQ 好友间的亲密度 41 | 42 |
43 | 44 |

45 |
本图片来自极客时间《数据结构与算法之美》专栏

46 | 47 | 48 | 49 | ### 二、为什么要用图 50 | 51 | --- 52 | 53 | 图中边和顶点可以表示应用场景的两个对象以及对象之间的联系,抽象的表达可以解决许多类似的问题,比如 QQ 微信 微博的的社交关系就用图来表示。 54 | 55 | 56 | 57 | ### 三、如何使用图结构 58 | 59 | --- 60 | 61 | #### 1 如何存储图 62 | 63 | ##### 1 邻接矩阵 64 | 65 | 用一个二维数组来表示,数组元素 $a[i][j]$ 表示顶点 i 和顶点 j 之前的联系(根据图的分类:有向图、无向图、带权无向图)。如下图所示,无向图会浪费空间,但是简单直接,一些问题可以转换为矩阵的乘法! 66 | 67 |
68 | 69 |

70 |
本图片来自极客时间《数据结构与算法之美》专栏

71 | 72 | ##### 2 邻接表 73 | 74 |
75 | 76 |

77 |
本图片来自极客时间《数据结构与算法之美》专栏

78 | 79 | 1 对应的链表表示,1 指向了 2,不过这样查找速度比较慢,可以将链表替换成平衡二叉搜索树(实际使用可以用红黑树),在查询的时候就有 logn 的速度找到 2 和 4 之间是否有联系。还可以用跳表或者动态的有序数组使用二分搜索,都可以有 logn 的速度进行快速查询! 80 | 81 | 82 | 83 | #### 2 图的应用例子 84 | 85 | 比如微博的关系图,我们先根据微博期望支持的操作,来选定什么样的存储结构 86 | 87 | - 判断用户 A 是否关注了用户 B 88 | 89 | - 判断用户 A 是否是用户 B 的粉丝 90 | - 用户 A 关注用户 B 91 | - 用户 A 取消关注用户 B 92 | - 根据名称的首字母排序,分页获取用户的粉丝列表 93 | - 根据用户名称的首字母排序,分页获取用户的关注列表 94 | 95 | 96 | 97 | 根据这些操作,可以选定用邻接表方式(假定在内存中处理这些数据),根据 A 关注 B,A 是否是 B 的粉丝,此时就需要两个邻接表,一个是存储顶点向外指向的关系,另一种是存储指向该顶点的关系。后面又说根据首字母排序,那么说明邻接表每个位置不能用链表存储,应该使用比较快的查询、插入、删除的动态数据结构,比如可以用跳表,复杂度为 $O(logn)$,并且还是有序的存储。如果数据量太大,可以根据哈希算法分片到不同的机器上,然后根据顶点编号计算哈希值,之后到对应的机器上获取即可,如下图 98 | 99 |
100 | 101 |

102 |
本图片来自极客时间《数据结构与算法之美》专栏

103 | 104 | 如果不在内存中存放这些数据,我们也可以用数据库来存储这些数据,然后直接建立一个表,如下图所示,这里以逆邻接表为例,直接记录了 1 对应了 4,然后 2 对应了 1, 4。这样空间占用比较高,适合存储永久型数据。 105 | 106 |
107 | 108 |

109 |
本图片来自极客时间《数据结构与算法之美》专栏

110 | 111 | ### 四、图的暴力搜索算法 112 | 113 | --- 114 | 115 | 给你一个用户,如何找出这个用户的所有三度(其中包含一度、二度和三度)好友关系? 116 | 117 | 下面两种方法既可以用在无向图,也可以用在有向图上。都是不带权重的图 118 | 119 | 120 | 121 | #### 1 广度优先搜索 122 | 123 | 一种层层推进的搜索策略,因为内部用邻接表来表示,所以每一层表示一个顶点对应的所有邻接表顶点。实现过程中需要记录访问过的顶点,以及搜索的路径,还有一个队列,保存与当前顶点相连的下一层顶点。 124 | 125 |
126 | 127 |

128 |
本图片来自极客时间《数据结构与算法之美》专栏

129 | 130 | **时间复杂度**:最坏情况,每个顶点都需要进入队列一次,每条边也需要访问一次,那么复杂度为 O(V+E),对于一个连通图中(任意两个顶点都是联通的),边数一定大于顶点数 - 1,所以广度优先算法也可以协成 O(V)。 131 | 132 | 133 | 134 | **空间复杂度**:空间消耗主要在 visited 数组、queue 队列、prev 数组上,所以空间复杂度为 O(V)。 135 | 136 | 137 | 138 | #### 2 深度优先搜索 139 | 140 | 利用一种回溯的思想,就是以深度优先的方式搜索整个解空间,在搜索过程中用剪枝函数,避免无效的搜索。这里深度优先就用了一个数组,来记录该顶点是不是被访问过。相当于剪枝函数,解空间应该是从起点能够终点的搜索可达路径。 141 | 142 | 143 | 144 | **时间复杂度**:最坏情况下,每条边被访问了两次(包含回退),因此时间复杂度是 O(E)。 145 | 146 | **空间复杂度**:与广度优先一致,要包含 visited、prev 数组和递归调用栈,递归调用深度最大不会超过顶点的个数,总空间复杂度就是 O(V)。 147 | 148 | 149 | 150 | #### 3 适用范围 151 | 152 | 这两种搜索算法是图上最基本的最常用的,但是因为是穷举法,所以仅仅适用于状态空间不大的情况(图不大),广度优先需要借助队列来实现,深度优先需要借助栈来实现。两者的时间复杂度都是 O(E),空间复杂度都是 O(V)。 -------------------------------------------------------------------------------- /notes/堆&应用.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
堆&应用
4 | 5 | * [一、什么是堆](#一什么是堆) 6 | * [二、为什么用堆](#二为什么用堆) 7 | * [三、如何实现堆](#三如何实现堆) 8 | * [1 堆化(heapify)及其方法](#1-堆化heapify及其方法) 9 | * [2 删除堆顶元素](#2-删除堆顶元素) 10 | * [3 堆化的时间复杂度](#3-堆化的时间复杂度) 11 | * [四、堆的应用——排序](#四堆的应用排序) 12 | * [1 建堆](#1-建堆) 13 | * [2 建堆复杂度](#2-建堆复杂度) 14 | * [3 排序](#3-排序) 15 | * [4 整个排序时间复杂度](#4-整个排序时间复杂度) 16 | * [5 为什么快排要比堆排序性能好?](#5-为什么快排要比堆排序性能好) 17 | * [6 思考题](#6-思考题) 18 | 19 | - [五、堆的应用——优先级队列](#五、堆的应用——优先级队列) 20 | - [1 合并有序小文件](#1 合并有序小文件) 21 | - [2 高性能定时器](#2 高性能定时器) 22 | - [六、堆的应用——求 Top K](#六、堆的应用——求 Top K) 23 | - [1 静态数据](#1 静态数据) 24 | - [2 动态数据](# 2 动态数据) 25 | - [七、堆的应用——求中位数](#七、堆的应用——求中位数) 26 | - [1 静态数据](#1 静态数据) 27 | - [2 动态数据](#2 动态数据) 28 | - [八、课后思考及习题](#八、课后思考及习题) 29 | 30 | 31 | 32 | #### 一、什么是堆 33 | 34 | --- 35 | 36 | 在实际的软件开发中,快速排序的性能要比堆排序好,这是为什么呢? 37 | 38 | 不是稳定的排序算法! 39 | 40 | 41 | 42 | 一种特殊的树 43 | 44 | - 满足完全二叉树 45 | - 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值。根据大于等于或者小于等于可以分为大顶堆和小顶堆 46 | 47 |
48 | 49 |

50 |
本图片来自极客时间《数据结构与算法之美》专栏

51 | 52 | #### 二、为什么用堆 53 | 54 | --- 55 | 56 | 数据结构简单(数组),不需要存储指针,节省了大量的空间,并且仅通过数组下标就可以找到左右儿子节点。其应用场景多,经典有堆排序(原地的、时间复杂度为 $O(nlogn)$) 57 | 58 | 59 | 60 | #### 三、如何实现堆 61 | 62 | --- 63 | 64 | 需要了解关于堆的两个内容。 65 | 66 | - **堆都支持那些操作**:堆中插入元素(需要堆化)及从堆顶删除一个元素(同样需要堆化) 67 | - **如何存储一个堆**:数组,1 为树根节点,2xi 是左子节点,2xi+1是右节点,i/2 是父节点,此时的堆浪费一个空间,如果选取 0 作为根节点,那么 2xi + 1是左叶子节点,2xi + 2 是右节点。这个操作起来就不是那么好,所以选择浪费一个空间即可! 68 | 69 | 70 | 71 | **Note** 下面方法以大顶堆为例 72 | 73 | 74 | 75 | ##### 1 堆化(heapify)及其方法 76 | 77 | - 从下向上 78 | 79 | 将新来数据放在数组最后,然后和其父节点值进行比较,如果大于父节点值,那么两者进行值交换,然后再次与父节点进行比较,直到到达堆顶或者其值小于父节点值。 80 | 81 |
82 | 83 |

84 |
本图片来自极客时间《数据结构与算法之美》专栏

85 | 86 | - 从上向下 87 | 88 | 与上面类似,只不过以当前节点为关注节点,依次与自己的两个儿子节点进行比较,如果该节点的值小于最大儿子节点值,那么两者进行交换,直到满足到达叶子节点或者不满足交换条件为止。 89 | 90 | 91 | 92 | ##### 2 删除堆顶元素 93 | 94 | 如果直接删除堆顶元素,然后从上向下堆化会出现数组中的某个空间空洞,此时整个树就不是完全二叉树。如下 95 | 96 |
97 | 98 |

99 |
本图片来自极客时间《数据结构与算法之美》专栏

100 | 101 | 解决方法,就是把堆的最后一个节点值替换到堆顶,然后再次进行从上到下的堆化即可。 102 | 103 |
104 | 105 |

106 |
本图片来自极客时间《数据结构与算法之美》专栏

107 | 108 | ##### 3 堆化的时间复杂度 109 | 110 | 包含 n 个节点完全二叉树高度为 $log_2n$ ,堆化过程是按照节点路径依次进行比较的,所以堆化时间复杂度与树的高度成正比 $O(logn)$。 111 | 112 | 113 | 114 | #### 四、堆的应用——排序 115 | 116 | --- 117 | 118 | **结论**:时间复杂度为 $O(nlogn)$,空间复杂度为 O(1)。该排序算法时间复杂度非常稳定。 119 | 120 | 121 | 122 | 需要如下两个步骤 123 | 124 | - 建堆 125 | - 排序 126 | 127 | 128 | 129 | ##### 1 建堆 130 | 131 | - 把数组的 index=1 的位置作为堆顶,然后从第二个元素开始将这些元素插入这个堆,堆化方法是从下向上。 132 | - 从后向前处理数据,在第一个不是叶子节点的位置开始(n/2),从上向上下进行堆化。(n/2 + 1 开始到 n 都是叶子节点) 133 | 134 |
135 | 136 | 137 |

138 |
本图片来自极客时间《数据结构与算法之美》专栏

139 | 140 | ##### 2 建堆复杂度 141 | 142 | 因为每个节点的交换次数与该节点的高度成正比,那么我们只需要把数组中的每个节点的高度全部加起来就可以得到这个树的总共的交换次数。那么时间复杂度就可以得到了。 143 | 144 |
145 | 146 |

147 |
本图片来自极客时间《数据结构与算法之美》专栏

148 | 149 | 从图可以看出,最后第二层节点对应高度为 1,节点个数为 $2^{h-1}$,用每层节点个数乘以对应高度求和,把公式两边乘以 2 然后减去 S1 就得到最后的和 150 | 151 |
152 | 153 | 154 | 155 |

156 |
本图片来自极客时间《数据结构与算法之美》专栏

157 | 158 | 此时将 $h = log_2n$ 代入 S 的公式中,就可得到建堆的时间复杂度为 $O(n)$。 159 | 160 | 161 | 162 | ##### 3 排序 163 | 164 | 由堆的特性我们知道,最顶部的数据是最大数据,那么把堆顶数据和最后一个数据交换,然后对剩下的 1-n-1 个元素进行堆化,从新建立一个新的大小为 n-1 的堆,实际上就是对第 1 个堆顶元素进行从上到下的一次堆化,这样再次把最大的元素放到数组的倒数第 2 个位置,之后在堆化一次,建立新的堆。直到将要堆化的数组大小为 1。 165 | 166 |
167 | 168 |

169 |
本图片来自极客时间《数据结构与算法之美》专栏

170 | 171 | ##### 4 整个排序时间复杂度 172 | 173 | 建堆 $O(n)$ ,排序是 $O(nlogn)$,因为每次堆化都是在堆顶开始,一次堆化的时间复杂度为 $logn$,那么需要对 n 个元素都做一遍堆化,那么复杂度为 $O(nlogn)$ 。 174 | 175 | 176 | 177 | ##### 5 为什么快排要比堆排序性能好? 178 | 179 | - 堆排序数据的访问方式没有快速排序友好(CPU 缓存方面) 180 | - 对于同样的数据,排序过程中,堆排序算法的数据交换次数要多余快速排序(建堆过程会打乱逆序度) 181 | 182 | 但是快排只能适合静态数据! 183 | 184 | 185 | 186 | ##### 6 思考题 187 | 188 | 1. 堆排序过程中,建堆时,对于完全二叉树,下标从 n/2 + 1 到 n 的都是叶子节点,这个结论是如何推导出来的? 189 | 190 | 因为最大叶子节点下标为 n,那么其父节点为 n/2,所以在 n/2 + 1 之后的节点都是叶子节点。 191 | 192 | 2. 堆的其他应用? 193 | - 从最大数量级数据中筛选出 top n 条数据。 194 | - 优先级队列(c++ 中的 priority_queue) 195 | 196 | 197 | 198 | #### 五、堆的应用——优先级队列 199 | 200 | --- 201 | 202 | 假设现在我们有一个包含 10 亿个搜索关键词的日志文件,如何能快速获取到热门榜 Top 10 的搜索关键词呢? 203 | 204 | 先将日志文件分片存储在 10 个文件夹,根据哈希算法(计算每个数据的哈希值),按照得到的哈希值 /10 将一些关键词放到 10 个文件夹中,然后对每个文件夹子统计 TOP k,最后对 10 个文件中的 TOPK 进行合并即可! 205 | 206 | 207 | 208 | 应用场景非常多:赫夫曼编码、图的最短路径、最小生成树算法。 209 | 210 | ##### 1 合并有序小文件 211 | 212 | 每个小文件依次从头取出元素,放入到优先级队列中,取出队列头部元素,就是当前最小元素。然后对应文件索引移动到下一个位置。 213 | 214 | 215 | 216 | ##### 2 高性能定时器 217 | 218 | 按照时间大小放入到堆中,堆顶元素时间最短,因此只需要计算当前时间和堆顶时间间隔即可。定时器不用轮询,不用遍历任务列表,性能就会提高。 219 | 220 | 221 | 222 | #### 六、堆的应用——求 Top K 223 | 224 | --- 225 | 226 | ##### 1 静态数据 227 | 228 | 数据集合事先确定,那么为了获取前 k 大(小)元素,我们只需要维护一个大小为 k 的堆(优先级队列),然后遍历数组,每个元素与堆顶(小顶堆)元素比较,如果小于堆顶元素,那么继续取出下一个元素,如果大于堆顶元素,那么移除堆顶元素,之后将数组中该元素插入到堆中。这样遍历一遍就得到了前 K 大元素。 229 | 230 | 一次堆化需要 $O(logk)$,假设最坏情况下, n 个元素都入堆一次,那么时间复杂度为 $O(nlogk)$。 231 | 232 | 233 | 234 | ##### 2 动态数据 235 | 236 | 一个数据集合有两个操作,一个添加数据,另一个是询问前 k 大数据。可以在开始的时候就维护一个 k 大小的堆,然后每次插入数据都做一次静态数据的处理操作,那么就会实时返回一个前 k 大的数据。 237 | 238 | 239 | 240 | #### 七、堆的应用——求中位数 241 | 242 | --- 243 | 244 | 中位数概念:奇数对应 n/2 + 1,偶数对应 n/2 245 | 246 | ##### 1 静态数据 247 | 248 | 直接排序后获取中位数即可 249 | 250 | 251 | 252 | ##### 2 动态数据 253 | 254 | 需要维护两个堆:一个大堆,一个小堆。大堆维护前半部分数据,小堆维护后半部分数据,大堆堆顶元素小于小堆堆顶元素,每次来一个数据先与大堆和小堆的堆顶元素比较,如果该元素小于等于大堆堆顶元素,那么插入到大顶堆,需要 $O(logk)$,否则需要加入到小顶堆中。此时两个堆元素个数不符合该条件 255 | 256 | - 奇数,大顶堆元素个数为 n/2 + 1,小顶堆元素个数为 n/2 257 | - 偶数,大顶堆元素个数为 n/2,小顶堆元素个数为 n/2 258 | 259 | 那么需要把一个堆中的堆顶元素移动到另一个堆中。维护其符合上面的条件。插入数据需要堆化,时间复杂度为 O(logn),求取中位数可以直接获取大堆的堆顶元素即可。其实也可以用一个小堆来存储中位数个数据,每来一个数据就会向堆中插入,其复杂度为 O(logn),获取中位数其实就是获取堆顶元素! 260 | 261 | 262 | 263 | #### 八、课后思考及习题 264 | 265 | --- 266 | 267 | 有一个访问量非常大的新闻网站,我们希望将点击量排名 Top 10 的新闻摘要,滚动显示在网站首页 banner 上,并且每隔 1 小时更新一次。如果你是负责开发这个功能的工程师,你会如何来实现呢? 268 | 269 | 270 | 271 | Leetcode 272 | 273 | - Leetcode 973:对应 Top k 274 | - 295:数据流的中位数 -------------------------------------------------------------------------------- /notes/复杂度分析.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
复杂度分析
4 | 5 | 6 | * [一、什么是复杂度分析](#一什么是复杂度分析) 7 | * [二、为什么要进行复杂度分析](#二为什么要进行复杂度分析) 8 | * [三、如何进行复杂度分析](#三如何进行复杂度分析) 9 | * [1)大 O 复杂度表示法](#1大-o-复杂度表示法) 10 | * [2)时间复杂度基本分析方法](#2时间复杂度基本分析方法) 11 | * [3)复杂度量级统计及其直观曲线图](#3复杂度量级统计及其直观曲线图) 12 | * [4)理解下面的概念](#4理解下面的概念) 13 | * [5)高级时间复杂度分析方法](#5高级时间复杂度分析方法) 14 | 15 | 16 | 17 | #### 一、什么是复杂度分析 18 | 19 | --- 20 | 21 | 是衡量算法执行效率的一个考量指标和衡量手段。是一种理论分析,实际应用中需要真实运行测试。 22 | 23 | 24 | 25 | #### 二、为什么要进行复杂度分析 26 | 27 | --- 28 | 29 | 能够在实际运行代码之前,预测到该算法的执行快慢以及空间的占用率,当然实际测试也是必不可少。 30 | 31 | 最重要的是,能够在写代码的过程中,提前写好理论最优且更高质量的代码。 32 | 33 | 34 | 35 | #### 三、如何进行复杂度分析 36 | 37 | --- 38 | 39 | ##### 1)大 O 复杂度表示法 40 | 41 | - **`时间复杂度`**:渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系, 42 | 43 | 常用形式: $O(1)\quad O(logn) \quad O(n) \quad O(nlogn) \quad O(n^2) \quad O(2^n) \quad O(n!)$ 44 | 45 | - **`空间复杂度`**:渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系(指除了原本的数据存储空间外,算法运行还需要额外的存储空间 )。常用形式:$ O(1) \quad O(n) \quad O(n^2)$ 46 | 47 | $T(n) = O(2n^2 + 2n + 3) = O(n^2)$ 类似这样的化简即为复杂度大 O 表示法。 n 是每行代码的执行次数。 48 | 49 | **Note**:所有代码的执行时间 T(n) 与每行代码的执行次数成正比。即每行代码代表一个 unit time。该表示方法不代表真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势。这也是为什么不同量级复杂度在 n 不同的时候,会有相反的表现。比如 $O(n) $ 与 $O(nlog(n))$ ,在 n 小的时候,在不忽略系数的情况下。很可能 $Onlog(n)$ 代表的算法对应的实际执行时间,要大于 $O(n^2)$ 代表的算法对应的实际执行时间。 50 | 51 | 52 | 53 | ##### 2)时间复杂度基本分析方法 54 | 55 | - 只关注循环执行次数最多的一段代码 56 | - 加法法则:总复杂度 = 量级最大的那段代码的复杂度 57 | - 乘法法则:嵌套代码的复杂度 = 嵌套内外代码复杂度的乘积 58 | 59 | 60 | 61 | ##### 3)复杂度量级统计及其直观曲线图 62 | 63 |
64 | 65 | 66 |

67 |
本图片来自极客时间《数据结构与算法之美》专栏

68 | 69 | 70 | 71 | ##### 4)理解下面的概念 72 | 73 | - $O(1$) :代表常量级,代码执行时间不随 n 的增大而增大,一般情况下,只要算法中不存在循环、递归、即使有成千上万行的代码,其时间复杂度也是 $O(1)$。 74 | - $O(logn)$、$O(nlogn)$ 75 | - $O(m+n)$、$O(m*n)$:对于 m + n 这种情况,在不知道 m n 谁大的时候,必须写成这种形式。 76 | 77 | 78 | 79 | ##### 5)高级时间复杂度分析方法 80 | 81 | 除了上面 2)介绍的 [基本复杂度分析](#2时间复杂度基本分析方法) 外,还有如下 4 种复杂度。对于下面前三个复杂度分析,实际中一般情况下不会用到,只有在同一块代码在不同的情况下出现了复杂度有**量级**的差距,才会使用这三种复杂度表示法来区分。 82 | 83 | - `最好情况时间复杂度`(best case time complexity):理想情况下,执行这段代码的时间复杂度。 84 | 85 | - `最坏情况时间复杂度`(worst case):在最糟糕的情况下,执行这段代码的时间复杂度。 86 | 87 | - `平均情况时间复杂度`(average case):加权平均时间复杂度或者期望时间复杂度。 88 | 89 | - `均摊时间复杂度`(amortized): 利用摊还分析法得到的复杂度。这个一般应用在连续时间的有规律的复杂度分析上且大部分情况下复杂度都很低,只有个别情况复杂度比较高,而且这些操作之间存在连贯的时序关系。分析方法例子:如果出现一个 O(n) 复杂度操作后,会连续出现 n-1 次 O(1) 复杂度操作。那么此时可以把 O(n) 耗时多的那次操作均摊到接下来的 n-1 次耗时少的操作上。均摊下来就是 O(1) 复杂度 。 90 | 91 | 结论:一般情况下,能够应用均摊时间复杂度分析的场合,均摊时间复杂度就等于最好情况时间复杂度。 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /notes/字符串匹配.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
字符串匹配
4 | 5 | RK 算法是如何借助哈希算法来实现高效字符串匹配的呢? 6 | 7 | <对于查找功能是重要功能的软件来说,比如一些文本编辑器,它们的 8 | 9 | 10 | 11 | ### 一、什么是字符串匹配 12 | 13 | --- 14 | 15 | 在一个主串中查找模式串,通常来说是一个串跟一个串进行匹配。比如 BF 和 RK 算法。也有多模式串的匹配算法,就是在一个串中同时查找多个串。这种是 Trie 树和 AC 自动机。 16 | 17 | 18 | 19 | #### 1 BF 算法 20 | 21 | 一种暴力简单的字符串匹配算法,就在主串的起始位置开始,一直遍历,每次切换起始位置后,都会与模式串一一对比,假设主串长度为 n,模式串为 m,那么 BF 算法的时间复杂度为 $O(n*m)$。尽管复杂度高,但是在实际应用中用的比较多,一般在串的长度比较小时,效率比较高! 22 | 23 | 24 | 25 | #### 2 RK 算法 26 | 27 | 在 BF 算法的基础上,减少主串的子串与模式串一一对比消耗的时间,这里用哈希算法来改进,整体就是把子串映射为一个 hash 值,然后与模式串的哈希值进行比较,这样在比较环节就需要遍历一次,其复杂度为 $O(n)$,通常计算子串的哈希值都是要遍历每个子串的元素,如果这样做的话,整体复杂度本身并没有改变,仍然是 $O(n*m)$,我们的目的就是要改进哈希计算时不要把每个子串都遍历一遍。那么需要一个有技巧的 hash 算法,就是把子串的字符按照 a~z 26 个字母,变为 26 进制,这样的哈希算法执行后,每个子串都对应一个唯一的哈希值,可以发现找到的子串 i 与子串 i+1 之间会有部分重叠,只需要计算子串 i 的哈希值,然后通过递推方法就可以得到子串 i+1 的 hash 值。此时,在按照上面新的哈希算法计算哈希值时,就不需要遍历每个子串的元素了,降低了复杂度,此时计算每个子串的哈希值的复杂度就变为了 $O(n)$。所以整体上,先计算哈希值,然后在遍历每个哈希值一一比较,此时整体复杂度为 $O(n)$ ,下图是上面刚刚讲解的 26 进制的例子 28 | 29 |
30 | 31 | 32 |

33 |
本图片来自极客时间《数据结构与算法之美》专栏

34 | 35 | 在上面的基础上还会出现一个问题,如果按照 26 进制来计算的话,如果字符串的长度超出 26,或者没有超出 26,但是得到 10 进制值时会超出整型的表示范围,那么我们不得不找到新的哈希算法(可能会增加哈希算法的冲突概率)来降低这种风险。比如举个简单例子,可以把子串的值全部加起来,这个算法当做哈希算法,其值作为哈希值,这样整体表达的整型范围小很多,那么增加了哈希冲突如何解决?我们可以对冲突的子串一一对比每个子元素,这可能也是唯一解决哈希冲突的方法,那么最坏情况下,假设每个子串都哈希冲突,那么我们每个子串都需要一一对比子元素,那么此时的时间复杂度其实就是 $O(n*m)$,但是这样的哈希冲突遇到的概率极低,所以整体来说 RK 算法的效率仍然要好于 BF 算法(当然针对一般的场景,如果主串太小,比如长度为 3,模式串为 2,那么直接用 BF 即可,效率还高)。 36 | 37 | 38 | 39 | #### 3 BM(Boyer-Moore)算法 40 | 41 | 对于查找功能是重要功能的软件来说,比如一些文本编辑器,它们的查找功能都是用哪种算法来实现的呢?有没有比 BF 算法和 RK 算法更加高效的字符串匹配算法呢? 42 | 43 | 44 | 45 | ##### 1 核心思想 46 | 47 | 在模式串和主串匹配时,如果主串中的某个字符 c 在模式串中是不存在的,那么模式串向后滑动的时候,只要该字符 c 与模式串有重合,肯定无法匹配,那么可以把模式串往后多滑动几位。这样的效率就会提升很快。 -------------------------------------------------------------------------------- /notes/散列表.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
散列表
4 | 5 | * [一、什么是散列表](#一什么是散列表) 6 | * [二、设计散列函数及冲突解决方法](#二设计散列函数及冲突解决方法) 7 | * [1 设计散列函数](#1-设计散列函数) 8 | * [2 散列冲突解决方案](#2-散列冲突解决方案) 9 | * [1)开放寻址法](#1开放寻址法) 10 | * [2)链表法(常用)](#2链表法常用) 11 | * [三、工业级散列表特性及设计方法](#三工业级散列表特性及设计方法) 12 | * [1)工业级散列表特性](#1工业级散列表特性) 13 | * [2)如何实现一个散列表](#2如何实现一个散列表) 14 | * [四、散列表和链表结合的例子](#四散列表和链表结合的例子) 15 | * [五、一些技巧和结论](#五一些技巧和结论) 16 | 17 | 18 | 19 | #### 一、什么是散列表 20 | 21 | --- 22 | 23 | 散列表是数组的一种扩展,由数组演化而来。用数组支持下标随机访问数据的特性(访问的复杂度为 $O(1)$)。 24 | 25 | 散列函数(Hash 函数):将 **关键字** or **键(key)**转化为数组下标的映射方法。 26 | 27 | 散列函数计算得到的值叫做**散列值**。 28 | 29 | 30 | 31 | #### 二、设计散列函数及冲突解决方法 32 | 33 | --- 34 | 35 | ##### 1 设计散列函数 36 | 37 | 1)构造散列函数基本要求: 38 | 39 | - 散列函数计算得到的散列值是一个非负整数 40 | - 如果 key1 = key2,那 hash(key1) == hash(key2); 41 | - 如果 key1 != key2,那 hash(key1) != hash(key2); 42 | 43 | 44 | 45 | 2)设计散列函数注意事项 46 | 47 | - 散列函数不能太复杂,减少计算散列值的时间。 48 | - 散列函数生成的值尽可能随机且均匀分布—避免或最小化散列冲突,均匀槽位数据量 49 | 50 | 51 | 52 | 3)散列函数常用设计方法举例 53 | 54 | - 学生运动会分析参赛编号特征,将编号中的后两位作为散列值,叫做数据分析法—根据实际数据,直接选取散列值。 55 | 56 | - Word 拼写检查功能。以 "nice" 举例,散列值如下 57 | 58 | ```c++ 59 | hash("nice")=(("n" - "a") * 26*26*26 + ("i" - "a")*26*26 + ("c" - "a")*26+ ("e"-"a")) / 78978 60 | 61 | ``` 62 | 63 | - string 类哈希码原理举例 64 | 65 | ```c++ 66 | // 处理 string 对象哈希码的原理,目前内部设计原理不清楚 67 | size_t HashCode(const std::string &key) const { 68 | size_t string_code = 0; 69 | if (key.size() > 0) { 70 | for (size_t i = 0; i < key.size(); i++) { 71 | string_code = string_code * 31 + key[i]; 72 | } 73 | } 74 | return string_code; 75 | } 76 | 77 | //! \brief 一个散列函数,返回底层数组索引 78 | size_t HashFunction(const KeyType &key) const { 79 | size_t hash = HashCode(key); 80 | return (hash ^ (hash >> 16)) & (capacity_ - 1); 81 | } 82 | ``` 83 | 84 | 85 | ##### 2 散列冲突解决方案 86 | 87 | ###### 1)开放寻址法 88 | 89 | 如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。常用探测方法有线性探测(步长为 1)、二次探测(步长为 2)、双重散列(多组散列函数)。 90 | 91 | 92 | 93 | 不管哪种探测方法,当散列表中空闲位置不多时,散列冲突的概率会大大提高。导致查找操作接近 $O(n)$ 。一般情况下,尽可能保证散列表中有一定比例的空闲槽位。用装载因子(表中元素个数/散列表长度)表示空位的多少。可以动态扩容装载因子。但是要避免低效率的扩充容量,一般来说,动态扩容,我们在数组中用到过,当数组容量过大时,我们申请 2 倍的内存空间。然后将数据拷贝进去,但是对于散列表来说,当拷贝时,可能会占用太多的时间。如果此代码服务于用户,那么在拷贝的时候明显会有卡顿。 94 | 95 | 96 | 97 | 为了解决上面的一次性扩容,可以非一次性插入所有数据,即分批进行数据插入。因此这个也适用于动态数组的扩容。 98 | 99 | 100 | 101 | **优点:** 102 | 103 | - 可以有效的利用 CPU 缓存加快查询速度。 104 | 105 | **缺点:** 106 | 107 | - 所有数据存储在一个数组中,冲突的代价更高。浪费更多的内存空间(因为装载因子的上限不能太大) 108 | 109 | **适用情况:**当数据量小、装载因子小的时候,适合采用开放寻址法。比如 ThreadLocalMap 利用开放寻址法解决散列冲突。 110 | 111 | 112 | 113 | ###### 2)链表法(常用) 114 | 115 | 在每个槽位对应一条链表。相同散列值的元素都放到相同槽位对应的链表中。查找和删除操作的时间复杂度。假设有 m 个槽位,n 个数据,那么每个槽位对应的链表长度平均为 k = n/m,所以时间复杂度为 $O(k)$ 116 | 117 | 118 | 119 | **缺点:** 120 | 121 | - 消耗内存,执行效率低于数组。对 CPU 缓存不友好。 122 | 123 | **优点:** 124 | 125 | - 灵活,支持更多优化方法,可以将链表改为更加高效的数据结构—跳表、红黑树。即使退化成单个槽位的查询,那么效率也是 $O(logn)$ ,有效避免了散列碰撞攻击。 126 | - 适合存储大对象、大数据量 127 | 128 | 129 | 130 | #### 三、工业级散列表特性及设计方法 131 | 132 | --- 133 | 134 | ##### 1)工业级散列表特性 135 | 136 | - 支持快速的查询、插入、删除操作 137 | - 内存占用合理,不能浪费过多的内存空间 138 | - 性能稳定,极端情况下,散列表的性能也不会退化到无法接受的情况 139 | 140 | 141 | 142 | ##### 2)如何实现一个散列表 143 | 144 | - 设计一个合适的散列函数 145 | - 定义装载因子阈值,并且设计动态扩容策略 146 | - 选择合适的散列冲突解决方法 147 | 148 | 149 | 150 | #### 四、散列表和链表结合的例子 151 | 152 | --- 153 | 154 | - LRU 缓存淘汰算法:哈希表 + 双链表 155 | 156 | - 往缓存中添加一个数据:$O(1)$ 157 | - 从缓存中删除一个数据:$O(1)$ 158 | - 在缓存中查找一个数据:$O(1)$ 159 | 160 | 原理如下: 161 | 162 |
163 | 164 |

165 |
本图片来自极客时间《数据结构与算法之美》专栏

166 | 167 | - Redis 有序集合:哈希表 + 跳表 168 | - Java LinkedHashMap:哈希表 + 双链表,与 LRU 缓存淘汰算法一致 169 | 170 | 171 | 172 | **问题:**为什么要用散列表和双链表一块使用? 173 | 174 | >散列表支持高效的数据插入、删除、查找操作。但是内部是无序的。如果我们想要顺序遍历数据。那么可以通过维护另一个双链表(or 跳表)结构来记录顺序。 175 | 176 | 177 | 178 | #### 五、一些技巧和结论 179 | 180 | --- 181 | 182 | - A % B = A & (B-1) 成立条件:B 是 2 的指数。 183 | 184 | - 数组占据随机访问的优势,却有需要连续内存的缺点。 185 | 186 | 链表具有可不连续存储的优势,但访问查找是线性的。 187 | 188 | 散列表和链表、跳表的混合使用,是为了结合数组和链表的优势,规避它们的不足。 189 | 190 | 我们可以得出数据结构和算法的重要性排行榜:连续空间 > 时间 > 碎片空间。 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /notes/数组&链表.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
数组&链表
4 | 5 | * [第一部分 数组](#第一部分-数组) 6 | * [一、什么是数组](#一什么是数组) 7 | * [二、数组相比于容器的优劣](#二数组相比于容器的优劣) 8 | * [1)优势](#1优势) 9 | * [2)劣势](#2劣势) 10 | * [第二部分 链表](#第二部分-链表) 11 | * [一、什么是链表与数组有什么不同](#一什么是链表与数组有什么不同) 12 | * [二、为什么用链表及其种类](#二为什么用链表及其种类) 13 | * [1)用链表的原因](#1用链表的原因) 14 | * [2)链表种类及其定义](#2链表种类及其定义) 15 | * [3)链表删除操作两种方式](#3链表删除操作两种方式) 16 | * [三、链表经典的应用场景 —— LRU 缓存淘汰策略](#三链表经典的应用场景--lru-缓存淘汰策略) 17 | * [四、链表 vs 数组、单链表 vs 双链表,及其注意事项](#四链表-vs-数组单链表-vs-双链表及其注意事项) 18 | * [1)数组 vs 链表](#1数组-vs-链表) 19 | * [2)单向链表 vs 双向链表](#2单向链表-vs-双向链表) 20 | * [3)轻松写出正确的链表代码](#3轻松写出正确的链表代码) 21 | 22 | 23 | 24 | ### 第一部分 数组 25 | 26 | #### 一、什么是数组 27 | 28 | --- 29 | 30 | 是一种线性表数据结构。它用一组**连续**的内存空间,来存储一组具有**相同类型**的数据。 31 | 32 | 33 | 34 | #### 二、数组相比于容器的优劣 35 | 36 | --- 37 | 38 | ##### 1)优势 39 | 40 | - 支持随机访问,根据下标访问的时间复杂度为 $O(1)$。 41 | - 相比容器,数组性能更高。 42 | 43 | 44 | 45 | ##### 2)劣势 46 | 47 | - 低效的「插入」和「删除」 48 | 49 | > 在插入和删除某个数据时,会有数据的移动操作。 50 | > 51 | > 插入操作时间复杂度为 $O(n)$。移动数据采取的技巧是,将插入数据移动到数组最后。 52 | > 53 | > 删除操作时间复杂度为 $O(n)$。为了减少移动操作,可以直接记录哪个数据被删除, 54 | > 55 | > 当数组中每隔更多的空间时,进行一次整体的数据删除。大大减少了数据搬移带来的时间损耗。 56 | 57 | - 数组的访问越界问题 58 | 59 | > 当访问的存储空间已经越界时,可能程序仍然能够运行,这样会造成未知的 bug。 60 | > 61 | > 一般会用容器来替代。 62 | 63 | **Note**:对于数组以及 c++ vector 容器的选择问题,对于业务开发,用容器即可。但是对于性能要求特别高的地方,或者追求简洁的表达方式。可以直接操作数组。但是要求自己分配申请内存和删除。 64 | 65 | 66 | 67 | ### 第二部分 链表 68 | 69 | #### 一、什么是链表与数组有什么不同 70 | 71 | --- 72 | 73 | 一组用指针将**零散的内存块**串联起来的数据结构。相比于数组的**连续内存空间**,链表结构是离散的空间,具有更灵活的性质。 74 | 75 | 76 | 77 | #### 二、为什么用链表及其种类 78 | 79 | --- 80 | 81 | ##### 1)用链表的原因 82 | 83 | 在程序运行时需要一块大的内存空间,存储 1GB 大小的数据,假设此时数组已经满了,如果新来数据时,我们需要申请 1.5GB 的内存空间,然后将 1GB 数据拷贝到 1.5GB 空间中。此时需要耗费很长时间,当然,如果此时没有连续的 1.5GB 空间,数组就无法适用,对于链表来说,我们就可以直接申请一块小的空间存储数据。 84 | 85 | 86 | 87 | ##### 2)链表种类及其定义 88 | 89 | - 单链表(带有/不带有头指针):增删查找复杂度为 $O(n)$。 90 | - 循环链表(尾 ---> 头,适合处理具有环形结构特点的问题):比如约瑟夫问题。 91 | - 双向链表(前后两个指针):可以在时间复杂度为 $O(1)$ 的情况下找到前驱节点。使得在某些情况下插入和删除操作都要比单链表简单高效。 92 | - 双向循环链表 93 | 94 | 95 | 96 | ##### 3)链表删除操作两种方式 97 | 98 | - 删除节点中「值等于某个给定值」的结点 99 | - 删除给定指针指向的结点 100 | 101 | 102 | 103 | #### 三、链表经典的应用场景 —— LRU 缓存淘汰策略 104 | 105 | 解决思路:维护一个单链表,越靠近链表尾部的节点是越早之前访问的。当有一个新的数据被访问时,我们从链表头开始顺序遍历链表。步骤如下: 106 | 107 | - 如果此数据已经在链表中了,那么将数据对应的节点,直接替换到链表的头部。可以是数据交换,或者是节点的交换。 108 | - 如果数据不在链表中,则此时分为两种情况 109 | - 缓存未满,则将此节点直接插入到链表的头部。 110 | - 缓存满了,则链表尾节点去除,然后将新的数据节点插入链表头部。 111 | 112 | 复杂度:$O(n)$。可以引入散列表(Hash Table)来优化。 113 | 114 | 115 | 116 | #### 四、链表 vs 数组、单链表 vs 双链表,及其注意事项 117 | 118 | --- 119 | 120 | ##### 1)数组 vs 链表 121 | 122 | > 数组:连续存储、占用连续内存,可借助 cpu 缓存机制提高访问效率。但是在声明时就要占用很大的内存。因此在申请内存时,要尽可能与自己的空间相符。 123 | > 124 | > 链表:访问效率低,可以支持动态扩容。 125 | 126 | 127 | 128 | ##### 2)单向链表 vs 双向链表 129 | 130 | > 删除给定指针指向的结点:插入和删除对于单向链表是 $O(n)$,对于双链表是 $O(1)$。对于有序链表且按值查询的效率,双向链表要高于单向链表。双向链表可以根据值的大小向前或者向后查找。 131 | > 132 | > 但是双向链表的占用空间要高于单向链表。但是会提升效率,这是一种空间换取时间的思想! 133 | 134 | 135 | 136 | ##### 3)轻松写出正确的链表代码 137 | 138 | > - 警惕指针丢失和内存泄露(在插入和删除节点时注意指针顺序,以及删除节点的释放空间问题) 139 | > - 利用哨兵简化实现难度{比如单链表建立一个头结点使得插入操作统一} 140 | > - 重点留意边界条件处理,检验在以下情况时能否顺序工作 141 | > 142 | > - 链表为空时 143 | > - 链表只有一个节点时 144 | > - 包含两个节点时 145 | > - 代码逻辑在处理第一个节点和尾节点时 146 | > - 举例画图,辅助思考 147 | > - 多写多练是重点! 148 | 149 | -------------------------------------------------------------------------------- /notes/栈.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | * [一、什么是栈](#一什么是栈) 6 | * [二、为什么需要栈](#二为什么需要栈) 7 | * [三、如何实现栈](#三如何实现栈) 8 | * [四、栈的应用](#四栈的应用) 9 | 10 | 11 | 12 | #### 一、什么是栈 13 | 14 | --- 15 | 16 | 从栈的操作特性来看,是一种“操作受限”的线性表,只允许在端插入和删除数据。 17 | 18 | 栈有如下特性: 19 | 20 | 1)栈: 后进先出,仅在一端插入和删除数据。 21 | 22 | 23 | 24 | #### 二、为什么需要栈 25 | 26 | --- 27 | 28 | 任何数据结构都是对特定应用场景的抽象,当某个数据集合只涉及在某端插入和删除数据(或者实际问题可以转化为这种方式)且满足后进者先出,先进者后出的操作特性时,我们应该首选栈这种数据结构。 29 | 30 | 31 | 32 | #### 三、如何实现栈 33 | 34 | --- 35 | 36 | 栈可以由数组和链表来实现。分别叫做顺序栈和链式栈。具体可以参考根目录下 `/src/stack/stack.hpp` 查看顺序栈的实现。 37 | 38 | **Note**:栈指针永远指向栈顶(无数据)。 39 | 40 | **复杂度分析** 41 | 42 | - 固定大小的栈,时间和空间复杂度都为 $O(1)$。 43 | - 支持动态扩容的栈 44 | - 最好时间复杂度:$O(1)$ 45 | - 最坏时间复杂度:$O(n)$ 46 | - 平均时间复杂度:$O(1)$ ,可以用摊还分析法进行分析。 47 | 48 | 49 | 50 | #### 四、栈的应用 51 | 52 | --- 53 | 54 | - 函数调用栈 55 | 56 | - 栈在表达式求值中的应用 57 | 58 | *思路*:用两个栈来实现,一个保存操作数,一个保存运算符。左到右的遍历表达式,当遇到数字,直接将其压入操作数栈,遇到运算符,就与运算符栈的栈顶元素进行比较。如果比运算符栈顶元素优先级高,那么直接将当前运算符压栈,如果比运算符栈栈顶优先级低或者相同,则从运算符栈中取出栈顶元素,从操作数中拿出两个操作数,然后进行运算。在把计算结果压入操作数栈,继续比较当前运算符和运算符栈顶元素。 59 | 60 | - 栈在括号匹配中的应用 61 | 62 | *思路*:未匹配的左括号放入栈中,然后遍历括号字符串,如果是左括号,则直接压入栈,如果是右括号,则比较栈顶元素是否和当前括号匹配。如果匹配,就把栈顶元素弹出,然后继续浏览字符串,进行下一个括号的判断。如果不匹配或者栈最后不为空,说明括号不是成对的。 63 | 64 | - 栈在浏览器的前进和后退功能上的应用 65 | 66 | *思路*:用两个栈 X,Y。X 保存首次浏览器页面,栈顶元素就是当前正在浏览的页面。当点击后退键,从 X 中拿出一个页面放在 Y 中,当点击前进时,从 Y 中拿出一个页面放在 X 中。 67 | 68 | 具体代码可以查看 `src/stack/stack.hpp` 文件。 69 | 70 | -------------------------------------------------------------------------------- /notes/算法思想.md: -------------------------------------------------------------------------------- 1 |
算法思想
2 | 3 | ### 一、贪心算法 4 | 5 | --- 6 | 7 | 如果该问题可以证明当前决策即不受之前决策影响和对后续决策的影响,那么贪心算法就可以解决最优解问题。 8 | 9 | 10 | 11 | 建模方法:在每次选择当前情况下,在对限制值同等贡献量的情况下,对期望值贡献最大的数据。 12 | 13 | 14 | 15 | ### 二、分治算法 16 | 17 | --- 18 | 19 | 解决的问题满足如下条件: 20 | 21 | - 原问题与分解成的小问题具有相同的模式 22 | - 原问题分解成的子问题可以独立求解,子问题之间没有相关性(这点与动态规划是不同的) 23 | - 具有分解终止条件,当问题足够小时,可以直接求解 24 | - 可以将子问题合并成原问题,并且合并的复杂度不能太高(重点!),否则无法起到减小算法总体复杂度的效果 25 | 26 | 27 | 28 | 一般分治算法将问题分解为多个子问题的方式,可以用多线程或者多机来处理。单核多线程能够执行的原因是,算法都包含内存访问和 CPU 计算两部分,因此在访问内存时,CPU 就会空闲,此时可以通过调度其他线程来充分利用 CPU。 29 | 30 | 31 | 32 | 例子: 33 | 34 | - 求出一组数据的有序对个数或者逆序对个数 35 | - 二维平面上有 n 个点,如何快速计算出两个距离最近的点对 36 | - 有两个 n*n 的矩阵 A B,如何快速求解两个矩阵的乘积 C = A * B 37 | 38 | 39 | 40 | ### 三、回溯算法 41 | 42 | --- 43 | 44 | 回溯算法本质上就是枚举,优点是类似于摸着石头过河查找策略,当问题的解空间维度足够大,那么我们可以一维一维的枚举所有情况,但是回溯算法中会有剪枝操作,把一些不符合条件的方案提前否定掉。当一些问题缺乏某种规律时,我们就可以一步一步的进行寻找解空间。 45 | 46 | 47 | 48 | 经典问题:8 皇后、0-1 背包、正则表达式 49 | 50 | 51 | 52 | ### 四、动态规划 53 | 54 | --- 55 | 56 | 回溯 + 备忘录 <=效率=> 动态规划 57 | 58 | 动态规划一般适用于解决「一个模型三个特征」 59 | 60 | - 多阶段决策最优解模型 61 | - 最优子结构:问题的最优解包含子问题的最优解,通过求解子问题的最优解推导出问题的最优解。 62 | - 无后效性:在推导后面阶段的状态时,只关心前面阶段的状态值,某阶段一旦确定,就不受后面阶段的影响 63 | - 重复子问题:不同的决策序列,也就是回溯算法画出的递归树中有重复的子状态。 64 | 65 | 66 | 67 | 解题思路总结: 68 | 69 | - 状态转移表法:由回溯算法+递归树推断出状态节点,根据动态规划适用于解决的问题特性,判断当前问题是否可以利用动态规划来解决,如果可以用动态会话解决,此时需要根据递归树和回溯算法确定好状态,然后建立一个状态转移表(一般是二维的,高维的无法适用),在根据最优子结构也就是状态转移方程来决定如何更新状态转移表,其实最为本质的还是要知道状态转移方程是怎么来的,此时才能知道状态状态转移表如何更新! 70 | - 状态转移方程法:就是根据状态转移方程通过递归来实现的方法,重点也是求解出状态转移方程。 71 | 72 | 这两种思路其实重点仍然是写出状态转移方程,当然可以不写出来,比如背包问题,可以直接想到状态是如何转移的,但是对于网格路径问题,我们必须要分析出状态转移方程,才知道状态转移表如何更新。所以动态规划的重点是能不能找到最优子结构。 73 | 74 | 75 | 76 | ### 五、四种算法思想 77 | 78 | --- 79 | 80 | 贪心、回溯、动态规划分为一类可以抽象成多阶段最优解模型,分治分为另一类,无法抽象成多阶段决策模型。 81 | 82 | -------------------------------------------------------------------------------- /notes/跳表.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
跳表
4 | 5 | * [一、什么是跳表](#一什么是跳表) 6 | * [二、如何理解跳表结构](#二如何理解跳表结构) 7 | * [三、跳表复杂度分析](#三跳表复杂度分析) 8 | * [四、一些问题](#四一些问题) 9 | 10 | 11 | 12 | #### 一、什么是跳表 13 | 14 | --- 15 | 16 | 一种动态的数据结构,底层是链表(加上多级索引层)。支持快速的插入、删除、查找操作。 17 | 18 | 19 | 20 | #### 二、如何理解跳表结构 21 | 22 | --- 23 | 24 | 实际上就是在单链表的基础上,建立多个索引层,通过遍历高索引层,到低索引层,最终确定要查找的值。如下图所示 25 | 26 |
27 | 28 |

29 |
本图片来自极客时间《数据结构与算法之美》专栏

30 | 31 | 32 | 33 | #### 三、跳表复杂度分析 34 | 35 | --- 36 | 37 | - 时间复杂度 38 | 39 | 假设链表节点个数为 n,那么每层索引按照间隔 2 个节点划分,第 1 级索引为 n/2,第二级索引为 n/4 依次类推,最后为 $n/2^k$ ,如果最后一层为 2 个节点,那么 $n/2^k = 2$,此时可以计算出 $k = log_2(n) - 1$ 总共有 k 层,假设查找时,每一层都会遍历,且每一层遍历 m 个节点。那么需要遍历 k 层,则时间复杂度为 $O(mk) = O(mlog(n))$,现在来确定 m 的个数,实际上,每一层最多只遍历 3 个节点,因此这里的复杂度为 $O(3log(n)) = O(log(n))$ 因此复杂度就是与基于数组的二分相同。 40 | 41 | - 空间复杂度 42 | 43 | 根据上面的分析,总共 k 层,每层对应节点数为 $n/2^k$,求和为 (n-1)。所以空间复杂度为 $O(n)$ 44 | 45 | 46 | 对于空间复杂度来说,在实际使用时,索引占用的空间仅仅是一些关键值和指向底层节点的指针,如果数据本身空间大于这些索引空间,那么索引空间其实可以忽略。跳表插入和删除复杂度与查找复杂度一致,都是 $O(logn)$。当插入数据过多时,还要考虑数据结构的动态更新问题,要用到随机函数生成的值K来表示要插入的值同时插入到k级索引中。随机函数需要自己来根据兴趣了解。 47 | 48 | 49 | 50 | #### 四、一些问题 51 | 52 | --- 53 | 54 | 1)为什么 Redis 要用跳表来实现? 55 | 56 | Redis 中的有序集合是通过跳表和散列表来实现的。虽然插入、删除、查找和迭代输出有序序列这几个操作,红黑树也能完成且时间复杂度一样,但是按照区间来查找数据这个操作跳表效率更高。 57 | 58 | -------------------------------------------------------------------------------- /notes/递归.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
递归
4 | 5 | * [一、什么是递归](#一什么是递归) 6 | * [二、为什么用递归](#二为什么用递归) 7 | * [三、什么样的问题可以用递归解决,递归有那些应用](#三什么样的问题可以用递归解决递归有那些应用) 8 | * [四、如何写递归代码](#四如何写递归代码) 9 | * [1) 关键](#1-关键) 10 | * [2) 递推公式确定方法](#2-递推公式确定方法) 11 | * [3)思维误区及其解决方案](#3思维误区及其解决方案) 12 | * [五、递归代码潜在缺点及其解决方案](#五递归代码潜在缺点及其解决方案) 13 | * [六、递归代码改写为非递归代码(递归都可以改写成循环)](#六递归代码改写为非递归代码递归都可以改写成循环) 14 | * [七、应用实例](#七应用实例) 15 | * [八、拓展思考及其建议](#八拓展思考及其建议) 16 | * [1)如何进行递归代码的调试?](#1如何进行递归代码的调试) 17 | * [2)对于递归的使用态度](#2对于递归的使用态度) 18 | * [九、复习递归知识的问题集合](#九复习递归知识的问题集合) 19 | 20 | 21 | 22 | #### 一、什么是递归 23 | 24 | --- 25 | 26 | 1、定义:一种应用非常广泛的算法或者编程技巧。 27 | 28 | 29 | 30 | #### 二、为什么用递归 31 | 32 | --- 33 | 34 | 很多数据结构和算法的编码实现都要用到递归 35 | 36 | 37 | 38 | #### 三、什么样的问题可以用递归解决,递归有那些应用 39 | 40 | --- 41 | 42 | 应用递归的问题需要满足的三个条件: 43 | 44 | - 一个问题的解可以分解为几个子问题的解 45 | - 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样 46 | - 存在递归终止条件 47 | 48 | 递归可以用在比如 DFS 深度优先搜索、前中后序二叉树遍历等等。 49 | 50 | 51 | 52 | #### 四、如何写递归代码 53 | 54 | --- 55 | 56 | ##### 1) 关键 57 | 58 | 写出递推公式,找到终止条件 59 | 60 | 61 | 62 | ##### 2) 递推公式确定方法 63 | 64 | 写递归代码的关键,就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。 65 | 66 | 67 | 68 | ##### 3)思维误区及其解决方案 69 | 70 | - 误区:看到递归时总是想把递归平铺展开,脑子里就会循环,一层一层往下调,然后在一层一层返回,试图想搞清楚计算机的每一步都是怎么执行的。 71 | 72 | - 解决方案:如果一个问题 A 可以分解为若干子问题 B、C、D,你可以假设子问题 B、C、D 已经解决,在此基础上思考如何解决问题,而且你只需要思考问题 A 与子问题 B、C、D 两层之间的关系即可,不需要一层一层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样子理解起来就简单多了。 73 | 74 | **总结:**只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。 75 | 76 | 77 | 78 | #### 五、递归代码潜在缺点及其解决方案 79 | 80 | --- 81 | 82 | - **堆栈溢出:**当递归求解的数据规模很大,调用层次很深一直压入栈,就会有堆栈溢出的风险。 83 | 84 | *解决:*可以限制最大深度方式解决这个问题,但是递归深度不好确定,或者到达一定深度如修改成非递归方法,如快排中变为子集,采用堆排序。如果知道递归深度很小。那么可以确定一个最大深度值。或者直接将递归代码改写为循环代码。或者使用尾递归优化(待定!),回归阶段不需要任何操作。 85 | 86 | - **重复计算:** 就是比如 $f(n) = f(n-1) + f(n-2)$ 这样一个递归代码 f(3) 会计算 3 次等等。 87 | 88 | _解决_: 用一个数据结构(比如散列表)来保存已经求解过的 $f(k)$。当递归调用到 $f(k)$ 时,先看下是否已经求解过了。如果是,则直接从散列表中取值返回,不需要重复计算。 89 | 90 | - **空间复杂度高:**会压栈和出栈,不断的占用空间。 91 | 92 | _解决:_ 尾递归优化、改为循环。 93 | 94 | - **时间效率低:**通过层层函数调用,保存现场的时间会增加。 95 | 96 | *解决:* 如果压栈次数多,可能会造成时间上的增多,可以改为循环实现。 97 | 98 | 99 | #### 六、递归代码改写为非递归代码(递归都可以改写成循环) 100 | 101 | --- 102 | 103 | **优点:** 解决了递归代码含有的部分缺点{堆栈溢出、重复计算、空间复杂度高}。当然也可能是全部缺点,这与非递归代码的实现有关。 104 | 105 | **关键: **根据递归的递推公式。直接改写为循环代码。 106 | 107 | 108 | 109 | #### 七、应用实例 110 | 111 | --- 112 | 113 | - 电影院求解自己在第几排:只要知道前一排的人是第几排。此时就知道我们所在第几排。这样递归下去: $f(n) = f(n-1) + 1\quad$,其中 $f(n)$ 表示当前自己所在排排数。$f(n-1)$ 表示前一排排数。$f(1) = 1$ 表示第一排的人知道自己在第一排。 114 | 115 | - n 个台阶问题: 递推公式:$f(n)=f(n-1)+f(n-2)$ 其中 $f(1) = 1 \quad f(2) = 2$ 可以这样理解,最后一步,向前迈最后一个台阶时有两种情况,要么是在 n-1 台阶处,此时已经有 $f(n-1)$ 种情况了,要么是在 n-2 台阶处,此时已经有 $f(n-2)$ 种情况。或者可以如下理解(但是感觉不通顺): 116 | 117 | > 可以根据第一步的走法,把所有走法分为两类,第一类是第一步走了1 个台阶,另一类是第一步走了 2 个台阶。所以 n 个台阶的走法就等于先走 1 阶后,n-1 个台阶的走法 加上先走 2 阶后,n-2 个台阶的走法。 118 | 119 | 120 | 121 | #### 八、拓展思考及其建议 122 | 123 | --- 124 | 125 | ##### 1)如何进行递归代码的调试? 126 | 127 | - 打印日志(可以选择 glog),查看递归值 128 | 129 | - 结合条件断点进行调试 130 | 131 | - 打印一个树型结构的日志,每进入一层,深度加 1 然后打印深度,比如深度可以用这种符号来表示 "--",在符号后面接上值,换行,这样可以很清除的看到每次调用信息。以及总深度是多少。 132 | 133 | 134 | ##### 2)对于递归的使用态度 135 | 136 | 工作中尽量少使用递归,否则代码移植到堆栈小的处理器上,可能会出现堆栈溢出的 Bug。 137 | 138 | 139 | 140 | #### 九、复习递归知识的问题集合 141 | 142 | --- 143 | 144 | 该讲内容总结为几个问题, 大家复习的时候可以先尝试回答这些问题检查自己的掌握程度: 145 | 146 | - 递归需要满足的三个条件是什么? 147 | 148 | - 写好递推代码的关键着手点是什么? 如何避免被一层层的逻辑绕进去? 149 | 150 | - 写递推代码是如何避免堆栈溢出? 151 | 152 | - 写递推代码如何避免重复计算? 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /notes/队列.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
队列
4 | 5 | * [一、什么是队列](#一什么是队列) 6 | * [二、如何实现队列](#二如何实现队列) 7 | * [1)两种实现方式](#1两种实现方式) 8 | * [2)实现关键](#2实现关键) 9 | * [三、队列变种及其应用](#三队列变种及其应用) 10 | * [1)阻塞队列](#1阻塞队列) 11 | * [2)并发队列](#2并发队列) 12 | * [3)应用](#3应用) 13 | 14 | 15 | 16 | #### 一、什么是队列 17 | 18 | --- 19 | 20 | **队列:**先进先出,一个操作受限的线性表。头部删除数据、尾部插入数据。 21 | 22 | 包含循环队列、阻塞队列、并发队列。 23 | 24 | **用途:**高性能队列 Disruptor、Linux 环形缓存,都用到了循环并发队列。 java concurrent 并发包利用 ArrayBlockingQueue 实现公平锁。 25 | 26 | 27 | 28 | #### 二、如何实现队列 29 | 30 | --- 31 | 32 | ##### 1)两种实现方式 33 | 34 | 数组 or 链表。分别称为为顺序队列和链式队列。 35 | 36 | **Note**: 实现时,head 指向第一个有效数据。 tail 指向最后一个有效数据的下一个无效数据。来数据时, 37 | 38 | 直接 queue[tail] = data.然后 tail++。 39 | 40 | 41 | 42 | ##### 2)实现关键 43 | 44 | 确定好队列空和队列满的判定条件。下面分为循环队列和普通队列的队满和队空的条件。 45 | 46 | | 队列 | 空条件 | 满条件 | 备注 | 47 | | -------- | --------------------- | ------------------------------- | ------------------------------------------- | 48 | | 普通队列 | head == tail 头尾重合 | head = 0 && tail = n (数组大小) | tail == n 时需要搬移数据,平均复杂度 $O(1)$ | 49 | | 循环队列 | head == tail 头尾重合 | (tail + 1) % n == head | tail 指向的空间不存储数据 | 50 | 51 | 52 | 53 | #### 三、队列变种及其应用 54 | 55 | --- 56 | 57 | ##### 1)阻塞队列 58 | 59 | 其实就是在队列基础上增加了阻塞操作。入队和出队都可以阻塞。简单来说,就是在队列为空的时候,从队头取数据会被阻塞(阻塞指的是,阻塞获取数据对应的线程,然后让其挂起,之后来了数据的时候在唤醒它,让它获取数据即可)。因为此时还没有数据可取,直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞(与上同理),直到队列中有空闲位置后再插入数据,然后再返回。 60 | 61 | 62 | 63 | 典型应用:「生产者和消费者模型」,一个线程生产数据放入阻塞队列,其他线程(可以是多个)从阻塞队列中获取数据。 64 | 65 | 66 | 67 | ##### 2)并发队列 68 | 69 | 保证入队和出队时线程安全的队列。最直接的方式就是在入队列和出队列前后加锁/解锁。但是这样的后果是锁粒度太大,导致的并发度太低。基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的并发队列。具体原理待定查看!。这也是基于数组的循环队列比链式队列应用更加广泛的原因。 70 | 71 | **Note**:利用循环队列实现并发队列时,需要对并发数据有一定的预测,否则会丢失太多的请求。 72 | 73 | 74 | 75 | ##### 3)应用 76 | 77 | 线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?各种处理策略又是如何实现的呢? 78 | 79 | > 有两种解决方式: 80 | > 81 | > - 非阻塞方式:直接拒绝掉任务请求。 82 | > 83 | > - 阻塞方式:将任务进行排队,即压入队列中,等到有空闲线程时,取出排队的头部任务,处理该任务。 84 | > 85 | > 基于阻塞方式的策略(先进先出)有两种实现: 86 | > 87 | > - 基于链表实现方式:**一个无界队列**。但是可能会导致过多的请求排队等待,请求处理的响应时间过长。所以,针对响应时间比较敏感的系统,基于链表实现的无限排队的线程池是不合适的。 88 | > 89 | > - 基于数组的实现方式:**有界队列**。队列的大小有限,所以线程池中排队的请求超过队列大小时,接下来的请求就会被拒绝,这种方式对响应时间敏感的系统来说,就相对更加合理。不过,设置一个合理的队列大小,也是非常有讲究的。队列太大导致等待的请求太多,队列太小会导致无法充分利用系统资源、发挥最大性能。 90 | 91 | 总结:实际上,对于大部分资源有限的场景,当没有空闲资源时,基本上都可以通过「队列」数据结构,来实现请求排队。 92 | 93 | -------------------------------------------------------------------------------- /pics/cc文件格式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/pics/cc文件格式.png -------------------------------------------------------------------------------- /pics/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/pics/logo2.png -------------------------------------------------------------------------------- /pics/qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/pics/qq.png -------------------------------------------------------------------------------- /pics/函数注释说明.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/pics/函数注释说明.png -------------------------------------------------------------------------------- /pics/注释格式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/pics/注释格式.png -------------------------------------------------------------------------------- /practice/README.md: -------------------------------------------------------------------------------- 1 | ## :pencil: 内容说明 2 | 3 | 该文件夹包含了一些相应数据结构和算法的对应练习。通过文件夹名字即可找到对应已经练习过的题目及其讲解! -------------------------------------------------------------------------------- /practice/array/1_两个数之和.md: -------------------------------------------------------------------------------- 1 |
1 两数之和
2 | 3 | ##### 题目来源 4 | 5 | 1. [英文链接](https://leetcode.com/problems/two-sum/) 6 | 2. [中文链接](https://leetcode-cn.com/problems/two-sum/) 7 | 8 | 9 | 10 | ##### 题目描述 11 | 12 | ``` 13 | 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 14 | 15 | 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 16 | ``` 17 | 18 | 19 | 20 | ##### 示例 21 | 22 | ``` 23 | Given nums = [2, 7, 11, 15], target = 9, 24 | 25 | Because nums[0] + nums[1] = 2 + 7 = 9, 26 | return [0, 1] 27 | ``` 28 | 29 | 30 | 31 | ##### 解答 32 | 33 | ```c++ 34 | 35 | class Solution { 36 | public: 37 | //! \complexity 时间复杂度:最坏为 O(n^2) 确切是 O(n^2/2) 最好是 O(1),平均复杂度为 O(n) 38 | //! 空间复杂度:O(n) 39 | //! \idea 就是遍历一遍数组,然后拿当前值与前面已经遍历过的数进行一一比较。这里为了方便, 40 | //! 会拿一个数组记录已经比较过的数字,然后从这个数组中进行循环查找 41 | vector twoSum(vector& nums, int target) { 42 | vector result; 43 | vector match; 44 | match.push_back(target - nums[0]); 45 | for (int i = 1; i < nums.size(); ++i) { 46 | // 在 match 中查找与其当前值匹配的值 47 | for (int j = 0; j < match.size(); ++j) { 48 | if (nums[i] == match[j]) { 49 | result.push_back(j); 50 | result.push_back(i); 51 | break; 52 | } 53 | } 54 | 55 | if (result.size() != 0) 56 | break; 57 | 58 | match.push_back(target - nums[i]); 59 | } 60 | return result; 61 | } 62 | 63 | //! \complexity 时间复杂度:最坏为 O(n),最好 0(1),平均复杂度为 O(n) 64 | //! 空间复杂度:O(n) 65 | //! \idea 就是遍历一遍数组,然后拿当前值与前面已经遍历过的数进行一一比较。这里为了加速, 66 | //! 会拿一个散列表记录已经比较过的数字,然后从这个数组中进行查找(是 O(1) 的复杂度) 67 | //! \important:利用了散列表查询的 O(1) 复杂度! 68 | vector twoSum(vector& nums, int target) { 69 | vector result; 70 | unordered_map match; // 第一个参数是target-nums[i],第二个参数保存对应的索引 71 | match[target - nums[0]] = 0; 72 | for (int i = 1; i < nums.size(); ++i) { 73 | // 在 match 中查找与其当前值匹配的值 74 | unordered_map::iterator iter = match.find(nums[i]); 75 | if (iter != match.end()) { 76 | result.push_back(iter->second); 77 | result.push_back(i); 78 | break; 79 | } 80 | 81 | match.insert(make_pair(target - nums[i], i)); 82 | } 83 | return result; 84 | } 85 | 86 | //! \complexity 时间复杂度:最坏为 O(n^2),平均复杂度为 O(n) 87 | //! 空间复杂度:O(n) 88 | //! \idea 先排序,用 nlogn 复杂度,然后在有序数组中使用快慢指针思想,一前一后指针向中间遍历 89 | //! 直到遇到两个数符合 target 值。如果两个数和大于 target,那么说明最大数不可能有 90 | //! 与其能合成 target 的数,所以让最大数的指针向前移动一次,如果两个数和小于 target, 91 | //! 那么说明最小数太小了,即使与当前最大的数也不能合并成 target。 92 | //! \reference https://github.com/apachecn/Interview/blob/master/docs/Algorithm/Leetcode/C%2B%2B/0001._Two_Sum.md 93 | vector twoSum(vector& nums, int target) { 94 | vector result; 95 | vector> num; 96 | for (int i = 0; i < nums.size(); i++) { 97 | num.push_back(make_pair(nums[i], i)); 98 | } 99 | sort(num.begin(), num.end()); 100 | 101 | int start = 0; 102 | int end = num.size() - 1; 103 | while (start < end) { 104 | int temp = num[start].first + num[end].first; 105 | if (temp == target) { 106 | result.push_back(num[start].second); 107 | result.push_back(num[end].second); 108 | break; 109 | } else { 110 | if (temp > target) 111 | end--; 112 | else 113 | start++; 114 | } 115 | } 116 | return result; 117 | } 118 | }; 119 | ``` 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /practice/array/26_在有序数组中移除重复数字.md: -------------------------------------------------------------------------------- 1 |
26 从有序数组中移除重复的元素
2 | 3 | ##### 题目来源 4 | 5 | 1. [英文链接](https://leetcode.com/problems/remove-duplicates-from-sorted-array/) 6 | 2. [中文链接](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/) 7 | 8 | 9 | 10 | ##### 题目描述 11 | 12 | ``` 13 | 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 14 | 15 | 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 16 | ``` 17 | 18 | 19 | 20 | ##### 示例 21 | 22 | ``` 23 | Given nums = [0,0,1,1,1,2,2,3,3,4], 24 | 25 | Your function should return length = 5, with the first five elements of nums being modified to 0, 1, 2, 3, and 4 respectively. 26 | ``` 27 | 28 | 29 | 30 | ##### 解答 31 | 32 | ```c++ 33 | class Solution { 34 | public: 35 | //! \brief 要想移除重复的元素,首先要用两个索引记录重复元素的起始位置和终点位置 36 | //! 并且题目要求数组最终存储的是不重复元素(从前向后排列),所以 37 | //! 需要一个插入原始数组的索引,来记录当前数组有效数据的位置 38 | //! 每次识别一个新元素,就把对应元素移动到 index++ 指定的位置 39 | //! 然后识别重复元素的两个索引依次在原始数组中向下遍历,直到遍历完整个数组 40 | //! \complexity 时间复杂度:O(n) 空间复杂度:O(1) 41 | //! \note 在按照上面的思路写代码时,先把最通用的情况写下来,然后针对特殊情况(索引开始位置) 42 | //! 在看如何处理并嵌入通用框架 43 | int removeDuplicates(vector& nums) { 44 | int index = 0; 45 | int start = 0; 46 | int end = 0; 47 | for (; end < nums.size(); ++end) { 48 | if (nums[start] == nums[end]) 49 | continue; 50 | else { 51 | index++; 52 | start = end; 53 | nums[index] = nums[start]; 54 | } 55 | } 56 | return (nums.size() > 0 ? index + 1 : 0); 57 | } 58 | 59 | // 快慢指针法,leetcode 给的答案 60 | int removeDuplicates(vector& nums) { 61 | if (nums.size() == 0) return 0; 62 | int i = 0; 63 | for (int j = 1; j < nums.size(); j++) { 64 | if (nums[j] != nums[i]) { 65 | i++; 66 | nums[i] = nums[j]; 67 | } 68 | } 69 | return i + 1; 70 | } 71 | // 直接利用 STL 算法 72 | // #include 73 | // unique 擦除迭代器之间的相邻重复元素 74 | // 如果数据没有排序,可以排序后在利用该算法,可以把整个数组的重复元素去除! 75 | // referenc: https://blog.csdn.net/elloop/article/details/7694505 76 | int removeDuplicates(vector& nums) { 77 | nums.erase(unique(nums.begin(),nums.end()),nums.end()); 78 | return nums.size(); 79 | } 80 | }; 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | ### :pencil2: 内容说明 2 | 3 | --- 4 | 5 | 本文件夹包含了自己在学习《数据结构与算法之美》专栏过程中实现的 c++ 代码。实现代码分别放在对应的文件夹中。每个文件夹内部,都会包含至少两个文件—— `.cc` 和 `.hpp`。`xxx.test.cc` 文件是具体数据结构/算法的测试代码。其内部详细测试了 `.hpp` 文件包含的所有功能,测试例子尽可能将一些边界条件考虑进去。`.hpp` 文件是具体数据结构/算法的实现文件,每个 `.hpp` 文件的开头部分,会由以下几个部分组成(至少包含前三个): 6 | 7 | - `\brief` 8 | 9 | 描述当前实现文件对应的功能。 10 | 11 | - `\Note` 12 | 13 | 在使用过程中,或者其他一些注意事项。 14 | 15 | - `\TODO` 16 | 17 | 该文件中还没有完成的一些功能,或者一些需要将来改进的地方。 18 | 19 | - `\TODO Reference` 20 | 21 | 为完成 `\TODO` 留下的任务,将会参考的资料。 22 | 23 | - `\Conclusion` 24 | 25 | 可能会从试验结果得出一些结论。 26 | 27 | 代码中涉及的理论,可以到该 Repo 中的 `notes` 目录,**戳这里** :point_right: :point_right: [笔记](../notes/)。

28 | 29 | 30 | 31 | **Note**:代码运行方式 32 | 33 | - Linux, ubuntu16.04 64 位,g++ 版本 5.4.0,其他平台未测试。 34 | 35 | ```shell 36 | g++ xxx.cc -std=c++11 &&./a.out 37 | ``` 38 | 39 | - Windows 系统,可以自行导入编译器。

40 | 41 | 42 | 43 | ### :memo: 注意事项 44 | 45 | --- 46 | 47 | 1)代码相应注释中,对于 `\complexity` 标注的复杂度,默认是时间复杂度。如果代码的空间复杂度比较简单,没有做过多的标注。 48 | 49 | 2)相应实现文件开头部分,简单介绍了该文件的一些功能,列出了函数名字。如果想查看具体如何使用,可以根据函数名,跳转到相应实现代码处查看。 50 | 51 | 3)每个文件夹内都会有一个相应的测试文件。测试例子可能没有包含全部边界或者其他未考虑的情况。如果您找到了测试用例的 Bug ,欢迎提供 `Issues`。

52 | 53 | 54 | 55 | ### :paperclip: Issues 56 | 57 | --- 58 | 59 | 在实现代码过程中,可能存在潜在的 Bug,希望细心的你能够在参考代码的过程中,多多发现 Bug。当然目前的实现代码还有很多不足,比如实现过于简单,没有很好的发挥 c++ 一些高级特性,这对于 c++ 新手的我来说在所难免。再比如有一些功能没有加入其中。对于 Bug ,可以直接在 GitHub 上进行 `Issues`,大家一起交流进步:smile:。

60 | 61 | 62 | 63 | ### :open_file_folder: 代码目录 64 | 65 | --- 66 | 67 | #### 1)数据结构 68 | 69 | - [数组](array/) 70 | - [链表](list/) 71 | - [栈](stack/) 72 | - [队列](queue/) 73 | - [跳表](skip_list/) 74 | - [哈希表](hash_table/) 75 | - [树](tree/) 76 | - [堆](heap/) 77 | - [图](graph/) 78 | 79 | 80 | 81 | #### 2)算法 82 | 83 | - [递归](recursion/) 84 | - [排序](sort) 85 | - [二分](binary_search) 86 | - [分治](divide_and_conquer) 87 | - [回溯](backtracking/) 88 | 89 | 90 | 91 | #### 3)工具 92 | 93 | - [工具](utils/) 94 | - [常用宏](internal/) 95 | - [标准库使用例子](stl_examples/) 96 | 97 | -------------------------------------------------------------------------------- /src/array/array.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: array.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/8 7 | * Description: dynamic array simple implementation 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #ifndef GLIB_ARRAY_HPP_ 13 | #define GLIB_ARRAY_HPP_ 14 | 15 | #include 16 | #include 17 | #include // use memcpy() 对于 stl 类类型 无法使用! 18 | #include 19 | #include // use sort() 20 | #define GUARD_ORDERED(x) // 保证有序 21 | 22 | //! \brief 简单实现了一个动态的数组 23 | //! 基本功能: 24 | //! 1)支持动态增加内存从后插入元素、下标访问功能[]、合并两个有序数组(仅支持小到大内置类型) 25 | //! 对应函数:PushBack、operator[]、Merge 26 | //! 27 | //! 2)其他普通函数:返回数组有效字节数、数组总容量、排序函数 28 | //! 对应函数:size、capacity、Sort 29 | //! 30 | //! 3)内部函数:比较两个元素大小 31 | //! 对应函数:Compare 32 | //! 33 | //! \Note 34 | //! 1)该数组不支持,赋值、移动赋值、移动拷贝功能,仅仅做了一个动态数组的简单实现 35 | //! 36 | //! \TODO 37 | //! 1)支持移动赋值、移动构造功能 38 | //! 1)实现 LRU 缓存淘汰策略——在单链表中已经实现了 39 | //! 2)实现一个大小固定的有序数组,支持动态增删改操作——实际上考核插入时的数据移动、删除时的垃圾回收策略 40 | //! 3)leetCode 题目待查找 41 | //! 42 | //! \platform 43 | //! ubuntu16.04 g++ version 5.4.0 44 | 45 | namespace glib { 46 | using namespace std; 47 | 48 | namespace array_internal { 49 | 50 | //! \brief 比较两个元素的大小 51 | //! \return 1: 1 > 2 52 | //! 0: 1 == 2 53 | //! -1: 1 < 2 54 | template 55 | int Compare(T &element1, T &element2) { 56 | if (element1 > element2) 57 | return 1; 58 | if (element1 == element2) 59 | return 0; 60 | if (element1 < element2) 61 | return -1; 62 | } 63 | } // namespace internal 64 | 65 | //! \brief 动态扩容的数组 66 | template 67 | class Array { 68 | public: 69 | 70 | Array() : size_(1), offset_(0) { // 默认申请构造一个大小空间 71 | array_ = new T[size_]; 72 | if (nullptr == array_) 73 | exit(-1); 74 | array_[0] = T(); 75 | } 76 | 77 | // 构造指定大小数组 78 | Array(size_t size) : size_(size), offset_(size_-1) { 79 | assert(size_); // 保证至少申请一个元素 80 | array_ = new T[size_](); 81 | if (nullptr == array_) 82 | exit(-1); 83 | for (size_t i = 0; i < size_; i++) 84 | array_[i] = T(); 85 | // memset(array_, T(), size_); // 仅仅适用与基本类型 86 | } 87 | 88 | Array(const Array &obj) : size_(obj.size_), offset_(obj.offset_) { 89 | array_ = new T[size_](); 90 | for (size_t i = 0; i <= offset_; i++) 91 | array_[i] = obj.array_[i]; 92 | // memcpy(array_, obj.array_, sizeof(T) * size_); // stl 类无法使用 93 | } 94 | 95 | // 删除赋值函数 96 | Array& operator=(const Array &obj) = delete; 97 | ~Array() { delete[] array_; } 98 | 99 | size_t size() const {return offset_ + 1; } // 返回数组有效字节数 100 | size_t capacity() const {return size_; } // 返回数组容量 101 | void Sort() {sort(array_, array_ + offset_ + 1); } // 从小到大排序,最后一个输入的是无效的元素 102 | 103 | //! \brief 从数组后面插入数组元素 104 | //! \note 数组满了的时候,会进行申请内存,(数组内至少有一个元素) 105 | //! \complexity best case O(1) worst case O(n) average case O(1) 106 | void PushBack(const T& element) { 107 | offset_++; 108 | if (offset_ > size_-1) { // 建立新的存储空间 109 | size_t new_size = size_ + size_/2 + 1; 110 | if (new_size <= 5) new_size = 5; 111 | T *array_temp = new T[new_size](); 112 | // memcpy(array_temp, array_, sizeof(T) * size_); 113 | for (size_t i = 0; i < size_; i++) 114 | array_temp[i] = array_[i]; 115 | delete[] array_; 116 | array_ = array_temp; 117 | size_ = new_size; 118 | } 119 | array_[offset_] = element; 120 | } 121 | 122 | //! \note 必须定义下面两个重载(注意数组越界问题) 123 | 124 | // 重载 [] ,可以想数组那样利用下标进行访问 125 | T& operator[](size_t index) { 126 | if (index >= size_) { 127 | std::cerr << "数组越界" << endl; 128 | exit(-1); 129 | } 130 | return *(array_ + index); 131 | } 132 | 133 | const T& operator[](size_t index) const { // 只能作为右值 134 | if (index >= size_) { 135 | std::cerr << "数组越界" << endl; 136 | exit(-1); 137 | } 138 | return *(array_ + index); 139 | } 140 | 141 | private: 142 | T* array_; // 数组指针 143 | size_t size_; // 数组总容量(内存空间) 144 | size_t offset_; // 数组中当前数据的有效偏移,PushBack() 后会在 offset_ + 1 处加入新元素。 145 | 146 | }; // class Array 147 | 148 | 149 | //! \brief 将两个有序数组合并为一个 150 | //! \note 假定两个有序数组都是从小到大排序好的,仅支持基本数据类型 151 | //! \complexity 时间复杂度:O(n),空间复杂度:O(n) 152 | //! \method 直接比较两个数组的最小值,谁小谁先放在临时数组中。知道所有值都放入到临时数组中 153 | //! \TODO 154 | //! 1)支持类类型的数组合并比如 std::string 155 | template 156 | Array Merge(Array &array1, Array &array2) { 157 | // 为了安全可以先排序 158 | // array1.Sort(); 159 | // array2.Sort(); 160 | GUARD_ORDERED(array1); 161 | GUARD_ORDERED(array2); 162 | 163 | // 合并两个数组 164 | int array_index1 = 0; 165 | int array_index2 = 0; 166 | Array temp(array1.size() + array2.size()); 167 | for (size_t i = 0; i < temp.size(); ++i) { 168 | if (array_index1 != array1.size() && array_index2 != array2.size()) { 169 | if (array_internal::Compare(array1[array_index1], array2[array_index2]) <= 0) 170 | temp[i] = array1[array_index1++]; 171 | else 172 | temp[i] = array2[array_index2++]; 173 | } else { 174 | if (array_index1 == array1.size()) 175 | temp[i] = array2[array_index2++]; 176 | else 177 | temp[i] = array1[array_index1++]; 178 | } 179 | } 180 | 181 | return temp; 182 | } 183 | 184 | } // namespace glib 185 | 186 | #endif // GLIB_ARRAY_HPP_ 187 | -------------------------------------------------------------------------------- /src/array/array.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: array.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/8 7 | * Description: simple test for using self-make dynamic array 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "array.hpp" 13 | #include 14 | #include 15 | using namespace std; 16 | 17 | //! \brief 数组类型的基本测试 18 | //! \run 19 | //! g++ array.test.cc -std=c++11 && ./a.out 20 | int main(int argc, char const *argv[]) { 21 | // 基本类型测试 22 | cout << "基本类型测试" << endl; 23 | glib::Array array; 24 | for (size_t i = 0; i < array.size(); i++) { 25 | // array.PushBack(i); 26 | array[i] = i; 27 | } 28 | array.PushBack(5); 29 | array.PushBack(6); 30 | array.PushBack(7); 31 | 32 | cout << "数组有效数据: " << array.size() << endl; 33 | cout << "数组容量: " << array.capacity() << endl; 34 | for (size_t i = 0; i < array.size(); i++) { 35 | cout << "array[i]: " << array[i] << endl; 36 | } // 输出 0 5 6 7 37 | cout << endl; 38 | 39 | // string 类型测试 40 | cout << "string 类型测试" << endl; 41 | glib::Array array_string(5); 42 | for (size_t i = 0; i < array_string.size(); i++) { 43 | array_string[i] = "st"; 44 | } 45 | array_string.PushBack("st2"); 46 | cout << "print" << endl; 47 | for (size_t i = 0; i < array_string.size(); i++) { 48 | cout << array_string[i] << " "; 49 | } // 输出 st st st st st st2 50 | cout << endl; 51 | cout << endl; 52 | 53 | // 测试合并两个有序数组 54 | cout << "测试合并两个有序数组" << endl; 55 | glib::Array array1(7); 56 | glib::Array array2(9); 57 | for (size_t i = 0; i < 7; i++) { 58 | array1[i] = 7 - i; 59 | } // 7 6 5 4 3 2 1 60 | for (size_t i = 0; i < 9; i++) { 61 | array2[i] = 9 - i; 62 | }// 9 8 7 6 5 4 3 2 1 63 | // 先排序 64 | array1.Sort(); 65 | array2.Sort(); 66 | 67 | auto array_merge = glib::Merge(array1, array2); 68 | for (size_t i = 0; i < array_merge.size(); i++) { 69 | cout << array_merge[i] << " "; 70 | } // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 9 71 | cout << endl; 72 | 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /src/backtracking/backtracking.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: backtracking.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/9/17 7 | * Description: test Backtracking algorithm 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "backtracking.hpp" 13 | #include 14 | #include 15 | #include 16 | #include // std::pair 17 | #include // time 18 | using namespace std; 19 | 20 | //! \brief 简单测试回溯算法(8 皇后问题、0-1 背包问题、不同路径问题 III) 21 | //! \run 22 | //! g++ backtracking.test.cc -std=c++11 && ./a.out 23 | 24 | int main(int argc, char const *argv[]) { 25 | cout << "8 皇后问题" << endl; 26 | const int N = 4; 27 | cout << glib::EightQueens(N) << endl; // 2 28 | 29 | // 0-1 背包问题 30 | cout << "测试 0-1 背包最大重量问题" << endl; 31 | vector backpack{1, 3, 5, 7, 4}; 32 | int max_capacity = 18; 33 | cout << glib::ZeroOrOneBackpack(backpack, max_capacity) << endl; 34 | 35 | // o-1 背包 给定重量的前提下,最大价值量问题 36 | // 测试数据链接:https://github.com/skyerhxx/Test-Data-of-01-knapsack-problem-/tree/master/%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE 37 | cout << "测试 0-1 背包最大价值问题" << endl; 38 | vector> backpack1{ // 30 组数据运行时间就在 10 s 左右了 39 | {71, 26}, {34, 59}, {82, 30}, {23, 19}, {1, 66}, {88, 85}, {12, 94}, 40 | {57, 8 }, {10, 3 }, {68, 44}, {5, 5 }, {33, 1 }, {37, 41}, {69, 82}, 41 | {98, 76}, {24, 1 }, {26, 12}, {83, 81}, {16, 73}, {26, 32}, {18, 74}, 42 | {43, 54}, {52, 62}, {71, 41}, {22, 19}, {65, 10}, {68, 65}, {8, 53}, 43 | {40, 56}, {40, 53}, {24, 70}, {72, 66}, {16, 58}, {34, 22}, {10, 72}, 44 | {19, 33}, {28, 96}, {13, 88}, {34, 68}, {98, 45}, {29, 44}, {31, 61}, 45 | {79, 78}, {33, 78}, {60, 6 }, {74, 66}, {44, 11}, {56, 59}, {54, 83}, 46 | {17, 48}}; // 这 50 组数据最大价值量为 1063,但是自己测试没有运行出来 47 | vector> backpack2{ 48 | {77, 92}, {22, 22}, {29, 87}, {50, 46}, {99, 90} 49 | }; 50 | vector> backpack3{ 51 | {95, 89}, {75, 59}, {23, 19}, {73, 43}, {50, 100}, 52 | {22, 72}, {6 , 44}, {57, 16}, {89, 7 }, {98, 64} 53 | }; 54 | // int max_capacity1 = 300; 55 | int max_capacity2 = 100; 56 | int max_capacity3 = 300; 57 | 58 | // cout << glib::ZeroOrOneBackpackValue(backpack1, max_capacity1) << endl; // 1063 回溯法运行不出来 59 | 60 | cout << glib::ZeroOrOneBackpackValue(backpack2, max_capacity2) << endl; // 133 61 | cout << glib::ZeroOrOneBackpackValue(backpack3, max_capacity3) << endl; // 388 62 | 63 | // 网格是否能走 64 | cout << "grid path problem" << endl; 65 | // vector> grids{{1, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 2, -1}}; // 2 66 | vector> grids{{1, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 2}}; // 4 67 | // vector> grids{{0, 1}, {2, 0}}; // 0 68 | cout << glib::GridPath(grids) << endl; 69 | 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /src/binary_search/binary_search.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: binary_search.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/5/8 7 | * Description: use and test binary_search function 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "./binary_search.hpp" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | //! \brief 简单测试二分搜索功能{二分搜索各种功能、求解某个值的根、循环数组二分求解} 23 | //! \run 24 | //! g++ binary_search.test.cc -std=c++11 && ./a.out 25 | int main(int argc, char const *argv[]) { 26 | // 二分搜索非递归测试 27 | cout << "二分搜索非递归测试" << endl; 28 | vector binary_search_array = {2, 2, 2, 2, 5, 6, 7, 8, 9, 10, 10, 10}; 29 | const auto nonrecur_iter = glib::BinarySearch(binary_search_array, 4, 30 | glib::BinarySearchOption::NON_RECURSION_FIRST_GREATER_EQUAL); 31 | if (nonrecur_iter != binary_search_array.end()) { 32 | std::vector::const_iterator const_iter = binary_search_array.begin(); 33 | cout << "find the index in array: " << distance(const_iter, nonrecur_iter) << endl; 34 | cout << "value: " << *nonrecur_iter << endl; 35 | } else { 36 | cout << "Not found this value" << endl; 37 | } 38 | cout << endl; 39 | 40 | // 二分搜索递归测试—— 对于重复数据元素目前还不适用,仅仅测试一下功能 41 | cout << "二分搜索递归测试" << endl; 42 | vector binary_search_array1 = {2, 2, 2, 2, 5, 6, 7, 8, 9, 10, 10, 10}; 43 | const auto recursion_iter = glib::BinarySearch(binary_search_array1, 6, glib::BinarySearchOption::RECURSION); 44 | if (recursion_iter != binary_search_array1.end()) { 45 | cout << "find the value in array: " << *recursion_iter << endl; 46 | } else { 47 | cout << "Not found this value" << endl; 48 | } 49 | cout << endl; 50 | 51 | // 测试利用二分查找思路,求取一个值的平方根 52 | cout << "测试利用二分查找思路,求取一个值的平方根" << endl; 53 | cout << "测试二分思路" << endl; 54 | auto start_sqrt_binary_search = chrono::system_clock::now(); 55 | cout<< setiosflags(ios::fixed) << setprecision(7) 56 | << glib::Sqrt(30, 4, 6, glib::binary_search_app::SqrtMethod::NEWTON) << endl; 57 | auto end_sqrt_binary_search = chrono::system_clock::now(); 58 | chrono::duration elaspsed_seconds_binary_search = 59 | end_sqrt_binary_search - start_sqrt_binary_search; 60 | cout << " binary_search sqrt time: " << elaspsed_seconds_binary_search.count() << endl; 61 | cout << endl; 62 | 63 | cout << "测试 Newton 思路" << endl; 64 | auto start_sqrt_newton = chrono::system_clock::now(); 65 | cout<< setiosflags(ios::fixed) << setprecision(7) 66 | << glib::Sqrt(30, 4, 6, glib::binary_search_app::SqrtMethod::NEWTON) << endl; 67 | auto end_sqrt_newton = chrono::system_clock::now(); 68 | chrono::duration elaspsed_seconds_newton = 69 | end_sqrt_newton - start_sqrt_newton; 70 | cout << " newton sqrt time: " << elaspsed_seconds_newton.count() << endl; 71 | cout << endl; 72 | 73 | // 测试循环有序数组 74 | cout << "测试循环有序数组" << endl; 75 | vector rotated_array = {1, 2, 3, 4, 5, 6}; 76 | cout << "索引: " << glib::SearchRotatedSortedArray(rotated_array, 3) << endl; // pivot:-1 索引 2 77 | vector rotated_array1 = {2, 3, 4, 5, 6, 1}; 78 | cout << "索引: " << glib::SearchRotatedSortedArray(rotated_array1, 3) << endl; // pivot:4 索引:1 79 | vector rotated_array2 = {5, 6, 1, 2, 3, 4}; 80 | cout << "索引: " << glib::SearchRotatedSortedArray(rotated_array2, 6) << endl; // pivot:1 索引:1 81 | vector rotated_array3 = {4, 5, 6, 1, 2, 3}; 82 | cout << "索引: " << glib::SearchRotatedSortedArray(rotated_array3, 2) << endl; // pivot: 2 索引:4 83 | vector rotated_array4 = {6, 1, 2, 3, 4, 5}; 84 | cout << "索引: " << glib::SearchRotatedSortedArray(rotated_array4, 6) << endl; // 索引:0 85 | 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /src/divide_and_conquer/divide_and_conquer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: divide_and_conquer.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/9/15 7 | * Description: divide and conquer algorithm simple implementation 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #ifndef GLIB_DIVIDE_AND_CONQUER_H_ 13 | #define GLIB_DIVIDE_AND_CONQUER_H_ 14 | 15 | #include // std::less_equal 模板函数 16 | #include // std::copy std::distance 17 | #include 18 | #include 19 | 20 | //! \brief 利用分治算法实现求取一组数组的逆序对(正序对)个数 21 | //! 外部调用核心函数: 22 | //! 1)求取逆序对数,并且容器元素归并排序:MergeSortCount() 23 | //! 2)对数据进行归并排序:MergeSort() 24 | //! 25 | //! \Note 26 | //! 1)这两个函数只能操作这几个容器: vector deque array 普通数组 27 | //! 28 | //! \platform 29 | //! ubuntu16.04 g++ version 5.4.0 30 | 31 | namespace glib { 32 | namespace internal { // helper function 33 | //! 两个有序数组的合并,并且求出逆序对个数 34 | template ::value_type> 39 | > 40 | void Merge(RandomAccessIterator first1, RandomAccessIterator last1, 41 | RandomAccessIterator first2, RandomAccessIterator last2, 42 | OutputIterator output, 43 | T& count, 44 | BinaryPred comp = BinaryPred()) { 45 | for (; first1 != last1 && first2 != last2; ) { 46 | if (comp(*first1, *first2)) { // 默认这里是 <= 47 | *output = *first1; 48 | ++first1; 49 | } else { 50 | count += T(std::distance(first1, last1)); 51 | *output = *first2; 52 | ++first2; 53 | } 54 | ++output; 55 | } 56 | while (first1 != last1) { 57 | *output = *first1; 58 | ++first1; 59 | ++output; 60 | } 61 | while (first2 != last2) { 62 | *output = *first2; 63 | ++first2; 64 | ++output; 65 | } 66 | } 67 | 68 | // 这里单纯是归并排序合并操作 69 | template ::value_type> 73 | > 74 | void MergeSortHelper(RandomAccessIterator first1, RandomAccessIterator last1, 75 | RandomAccessIterator first2, RandomAccessIterator last2, 76 | OutputIterator output, BinaryPred comp = BinaryPred()) { 77 | for (; first1 != last1 && first2 != last2; ) { 78 | if (comp(*first1, *first2)) { // 默认这里是 <= 79 | *output = *first1; 80 | ++first1; 81 | } else { 82 | *output = *first2; 83 | ++first2; 84 | } 85 | ++output; 86 | } 87 | while (first1 != last1) { 88 | *output = *first1; 89 | ++first1; 90 | ++output; 91 | } 92 | while (first2 != last2) { 93 | *output = *first2; 94 | ++first2; 95 | ++output; 96 | } 97 | 98 | } 99 | 100 | } // namespace internal 101 | 102 | //! \brief 求出一组数据的有序对数 103 | //! \note 区间是左闭右开,并且这里只能用随机获取迭代器(数组、vector、queue) 104 | //! \complexity O(nlogn) 空间复杂度为 O(n) 105 | template ::value_type> 109 | > 110 | void MergeSortCount(RandomAccessIterator first, RandomAccessIterator last, 111 | T& count, BinaryPred comp = BinaryPred()) { 112 | auto len = std::distance(first, last); 113 | if (1 >= len) { return; } // 这里之前 len < 1 然后出现了错误! 114 | auto mid = first + len / 2; // 这里的实现,导致只能用随机获取迭代器的容器! 115 | MergeSortCount(first, mid, count, comp); 116 | MergeSortCount(mid, last, count, comp); 117 | std::vector::value_type> merged_temp; // 临时存放合并和的结果 118 | internal::Merge(first, mid, mid, last, std::back_inserter(merged_temp), count, comp); 119 | std::copy(merged_temp.begin(), merged_temp.end(), first); 120 | } 121 | 122 | //! \brief 求出一组数据的有序对数 123 | //! \note 区间是左闭右开,并且这里只能用随机获取迭代器(数组、vector、queue) 124 | //! \complexity O(nlogn) 空间复杂度 O(n) 125 | template ::value_type> 128 | > 129 | void MergeSort(RandomAccessIterator first, RandomAccessIterator last, 130 | BinaryPred comp = BinaryPred()) { 131 | auto len = std::distance(first, last); 132 | if (1 >= len) { return; } // 这里之前 len < 1 然后出现了错误! 133 | auto mid = first + len / 2; // 这里的实现,导致只能用随机获取迭代器的容器! 134 | MergeSort(first, mid, comp); 135 | MergeSort(mid, last, comp); 136 | std::vector::value_type> merged_temp; // 临时存放合并和的结果 137 | internal::MergeSortHelper(first, mid, mid, last, std::back_inserter(merged_temp), comp); 138 | std::copy(merged_temp.begin(), merged_temp.end(), first); 139 | } 140 | 141 | } // namespace glib 142 | 143 | #endif // GLIB_DIVIDE_AND_CONQUER_H_ 144 | -------------------------------------------------------------------------------- /src/divide_and_conquer/divide_and_conquer.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File:divide_and_conquer.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/9/15 7 | * Description: test divide and conquer algorithm 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "./divide_and_conquer.hpp" 13 | #include 14 | #include 15 | #include 16 | #include 17 | using namespace std; 18 | 19 | //! \brief 简单测试分治算法求取数据的逆序对数,以及归并排序 20 | //! \run 21 | //! g++ divide_and_conquer.test.cc -std=c++11 && ./a.out 22 | //! \note 这里只能用随机获取迭代器类型,也就是只能使用 vector deque array 23 | int main(int argc, char const *argv[]) { 24 | // vector test 25 | cout << "测试 vector 是否成立" << endl; 26 | vector vec{2, 4, 3, 1, 5, 6}; 27 | int count = 0; 28 | glib::MergeSortCount(vec.begin(), vec.end(), count, std::greater_equal()); // 此时求解的为正续对数,非逆序对 29 | cout << "count: " << count << endl; 30 | for (auto ele: vec) 31 | cout << ele << " "; 32 | 33 | // test 34 | cout << endl << "测试 deque 是否成立" << endl; 35 | deque deq{2, 4, 3, 1, 5, 6}; 36 | int count2 = 0; 37 | glib::MergeSortCount(deq.begin(), deq.end(), count2); 38 | cout << "count: " << count2 << endl; 39 | for (auto ele: deq) 40 | cout << ele << " "; 41 | 42 | // test arr 43 | cout << endl << "测试数组归并排序" << endl; 44 | int arr[6] = {2, 4, 3, 1, 5, 6}; 45 | glib::MergeSort(arr, arr + 6); 46 | for (auto ele: arr) 47 | cout << ele << " "; 48 | 49 | // list queue not usefull 50 | // list lis{2, 4, 3, 1, 5, 6}; 51 | // int count1 = 0; 52 | // glib::MergeSort(lis.begin(), lis.end(), count1); 53 | // cout << "count: " << count1 << endl; 54 | // for (auto ele: lis) 55 | // cout << ele << " "; 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /src/dynamic_programming/dynamic_program.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: dynamic_program.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/9/19 7 | * Description: dynamic programming algorithm simple implementation examples 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | #include "dynamic_program.hpp" 12 | #include 13 | #include 14 | using namespace std; 15 | 16 | //! \brief 简单测试动态规划算法(0-1 背包问题(最大重量、最大价值) 17 | //! \run 18 | //! g++ backtracking.test.cc -std=c++11 && ./a.out 19 | 20 | //! \note 下面测试时,都会输出所有背包情况! 21 | int main(int argc, char const *argv[]) { 22 | cout << "测试 0-1 背包最大重量问题" << endl; 23 | vector backpack{2, 2, 4, 6, 3}; 24 | int max_capacity = 9; 25 | cout << "直接利用状态矩阵方法" << endl; 26 | cout << glib::BackpackMaxWeight(backpack, max_capacity) << endl; // 9 27 | cout << "直接利用状态向量方法" << endl; 28 | cout << glib::BackpackMaxWeight2(backpack, max_capacity) << endl; // 9 29 | 30 | cout << "测试 0-1 背包最大价值问题" << endl; 31 | vector> backpack1{ // 30 组数据运行时间就在 10 s 左右了 32 | {71, 26}, {34, 59}, {82, 30}, {23, 19}, {1, 66}, {88, 85}, {12, 94}, 33 | {57, 8 }, {10, 3 }, {68, 44}, {5, 5 }, {33, 1 }, {37, 41}, {69, 82}, 34 | {98, 76}, {24, 1 }, {26, 12}, {83, 81}, {16, 73}, {26, 32}, {18, 74}, 35 | {43, 54}, {52, 62}, {71, 41}, {22, 19}, {65, 10}, {68, 65}, {8, 53}, 36 | {40, 56}, {40, 53}, {24, 70}, {72, 66}, {16, 58}, {34, 22}, {10, 72}, 37 | {19, 33}, {28, 96}, {13, 88}, {34, 68}, {98, 45}, {29, 44}, {31, 61}, 38 | {79, 78}, {33, 78}, {60, 6 }, {74, 66}, {44, 11}, {56, 59}, {54, 83}, 39 | {17, 48}}; // 这 50 组数据最大价值量为 1063,但是自己测试没有运行出来 40 | 41 | vector> backpack2{ 42 | {77, 92}, {22, 22}, {29, 87}, {50, 46}, {99, 90} 43 | }; 44 | 45 | vector> backpack3{ 46 | {95, 89}, {75, 59}, {23, 19}, {73, 43}, {50, 100}, 47 | {22, 72}, {6 , 44}, {57, 16}, {89, 7 }, {98, 64} 48 | }; 49 | 50 | vector> backpack4{ 51 | {79, 83}, {58, 14}, {86, 54}, {11, 79}, {28, 72}, 52 | {62, 52}, {15 , 48}, {68, 62} 53 | }; 54 | 55 | int max_capacity1 = 300; 56 | int max_capacity2 = 100; 57 | int max_capacity3 = 300; 58 | int max_capacity4 = 200; 59 | 60 | cout << glib::BackpackMaxValue(backpack1, max_capacity1) << endl; // 1063 61 | cout << glib::BackpackMaxValue(backpack2, max_capacity2) << endl; // 133 62 | cout << glib::BackpackMaxValue(backpack3, max_capacity3) << endl; // 388 63 | cout << glib::BackpackMaxValue2(backpack4, max_capacity4) << endl; // 334 64 | 65 | // 测试双 11 问题 66 | cout << "测试双 11 问题" << endl; 67 | vector goods{11, 22, 15, 33, 9, 20, 48, 30, 66, 88}; 68 | cout << glib::DoubleOneOne(goods, 200) << endl; // 200 69 | 70 | // 测试网格最短权重问题 71 | cout << "测试网格最短权重为" << endl; 72 | std::vector> grid = std::vector>(4, std::vector(4, 0)); 73 | grid[0][0] = 1; 74 | grid[0][1] = 3; 75 | grid[0][2] = 5; 76 | grid[0][3] = 9; 77 | grid[1][0] = 2; 78 | grid[1][1] = 1; 79 | grid[1][2] = 3; 80 | grid[1][3] = 4; 81 | grid[2][0] = 5; 82 | grid[2][1] = 2; 83 | grid[2][2] = 6; 84 | grid[2][3] = 7; 85 | grid[3][0] = 6; 86 | grid[3][1] = 8; 87 | grid[3][2] = 4; 88 | grid[3][3] = 3; 89 | std::deque> pos; 90 | pos.push_back(std::make_pair(0, 0)); 91 | glib::MinPathBT(grid, 0, 0, grid[0][0], pos); 92 | for (auto ele : glib::Path) 93 | cout << ele.first << "," << ele.second << endl; 94 | cout << "状态转移表法" << endl; 95 | glib::StateTable(grid); 96 | cout << endl << "状态转移方程法" << endl; 97 | vector> mem = std::vector>(4, std::vector(4, 0)); 98 | cout << glib::StateTransitionEquation(grid, mem, 3, 3) << endl; 99 | 100 | return 0; 101 | } 102 | -------------------------------------------------------------------------------- /src/graph/graph.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: graph.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/8/24 7 | * Description: graph and BFS DFS algorithm simple implementation 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #ifndef GLIB_GRAPH_H_ 13 | #define GLIB_GRAPH_H_ 14 | 15 | #include "../internal/macros.h" 16 | #include "assert.h" 17 | #include 18 | #include 19 | #include // 广度优先用 20 | #include // 深度优先用 21 | #include // 图底层临界表用 22 | 23 | //! \brief 简单实现图以及图的深度优先搜索和广度优先搜索 24 | //! 外部调用核心函数: 25 | //! 1)向图中添加边:AddEdge() 26 | //! 2)广度优先搜索:Bfs() 27 | //! 3)深度优先搜索(递归):Dfs() 28 | //! 4)深度优先搜索(非递归):DfsCycle() 29 | //! 外部调用状态函数: 30 | //! 内部辅助核心函数: 31 | //! 1)保存搜索路径:ReservePath() 32 | //! 2)深度优先搜索递归:RecursiveDfs() 33 | //! \Note 34 | //! 1)目前仅仅支持无向图,邻接表存储图结构,且内部顶点抽象成了编号 1~n 35 | //! 36 | //! \TODO 37 | //! 1)支持有向图、带权重的有向图、带权重的无向图 38 | //! 2)调整使得邻接矩阵也适用 39 | //! 3)key 自动利用 hash 抽象到内部的顶点 0~n,进而支持任意的顶点表达 40 | //! 41 | //! \platform 42 | //! ubuntu16.04 g++ version 5.4.0 43 | 44 | namespace glib { 45 | namespace internal { 46 | enum GraphOption { 47 | // DIRECTED_GRAPH, // 有向图(不带权重) 48 | UNDIRECTED_GRAPH, // 无向图 49 | // WEIGHTED_DIRECTED_GRAPH, // 带权重的有向图 50 | // WEIGHTED_UNDIRECTED_GRAPH // 带权重的无向图 51 | }; // enum GraphOption 52 | 53 | } // namespace internal 54 | 55 | using namespace internal; 56 | 57 | template 58 | class Graph { 59 | public: // type or struct define and declaration 60 | using ValueType = _Scalar; 61 | using Key = _Scalar; 62 | using Value = _Scalar; 63 | using KeyMap = std::map; 64 | using KeyMapValueType = typename KeyMap::value_type; 65 | 66 | public: // construct function 67 | explicit 68 | Graph(size_t vertex_count) : vertex_count_(vertex_count) { 69 | adjacency_table_ = new KeyMap[vertex_count_]; 70 | } 71 | GLIB_DISALLOW_IMPLICIT_CONSTRUCTORS_PUBLIC(Graph); 72 | 73 | public: // external call function 74 | // 添加图中顶点 i 和 j 之间的边(只能从 0 开始编号!) 75 | void AddEdge(int i, int j); 76 | 77 | // 广度优先搜索,是一种最短路径(当然最短路径不只有一条) 78 | // \note 适用于图的边和边之间无权重,同样适合网格搜索! 79 | std::vector<_Scalar> Bfs(int source, int target); 80 | 81 | // 深度优先算法(递归版) 82 | std::vector<_Scalar> Dfs(int source, int target); 83 | 84 | // 深度优先算法(非递归版) 85 | std::vector<_Scalar> DfsCycle(int source, int target); 86 | 87 | private: // internal helper function 88 | // 保存路径信息 89 | void PrintPaths(int* path, int source, int target) { 90 | if (source != target) { 91 | PrintPaths(path, source, path[target]); 92 | } 93 | std::cout << target << " "; // 先打印其他的路径,最后在打印 target 94 | } 95 | void ReservePath(int* path, int source, int target) { 96 | if (source != target) { 97 | ReservePath(path, source, path[target]); 98 | } 99 | bfs_dfs_paths_.push_back(target); 100 | } 101 | 102 | // 深度优先算法递归函数 103 | void RecursiveDfs(int source, int target, 104 | std::vector& has_visited, 105 | std::vector& reserve_paths); 106 | private: // internal member variable 107 | size_t vertex_count_ = 0; 108 | KeyMap* adjacency_table_ = nullptr; // 适合有权重的!可以有序如何实现? 109 | bool dfs_found_ = false; // 广度优先算法使用的变量 110 | std::vector bfs_dfs_paths_; // 保存 dfs 或者 bfs 实现的路径结果! 111 | // 每次获取要从新调用 Dfs 和 Bfs 的代码 112 | }; // class Graph 113 | 114 | //----------------------- external call function------------------------------// 115 | // 添加图中顶点链接的边 116 | // \complexity O(1) 117 | template 118 | void Graph<_Scalar, _Option>::AddEdge(int i, int j) { 119 | assert(i >= 0 && j < vertex_count_ && "index over graph range!"); 120 | adjacency_table_[i].insert(KeyMapValueType(ValueType(j), ValueType(j))); 121 | adjacency_table_[j].insert(KeyMapValueType(ValueType(i), ValueType(i))); 122 | } 123 | 124 | // 广度优先搜索,返回搜索的路径信息 125 | //! \complexity 时间复杂度为 O(边),空间复杂度 O(顶点) 126 | template 127 | std::vector<_Scalar> 128 | Graph<_Scalar, _Option>::Bfs(int source, int target) { 129 | assert(source >= 0 && target < vertex_count_ && "index over graph range!"); 130 | bfs_dfs_paths_.clear(); // 保存路径信息的,在 PrintPaths 生成的,这里进行清空! 131 | if (source == target) return std::vector<_Scalar>(source); 132 | bool has_visited[vertex_count_] = {0}; 133 | int reserve_paths[vertex_count_]; // 保留过往的路径信息,方便后期恢复路径 134 | for (int i = 0; i < vertex_count_; ++i) reserve_paths[i] = -1; 135 | std::queue access_vertex; // 保存将要访问的顶点 136 | 137 | // 初始化 138 | access_vertex.push(source); 139 | has_visited[source] = true; 140 | 141 | // 正式 BFS 全局范围搜索 142 | while (!access_vertex.empty()) { 143 | int vertex_index = access_vertex.front(); 144 | access_vertex.pop(); 145 | for (const auto& adjacency: adjacency_table_[vertex_index]) { 146 | int matched_vertex_index = adjacency.first; 147 | if (!has_visited[matched_vertex_index]) { 148 | reserve_paths[matched_vertex_index] = vertex_index; 149 | if (matched_vertex_index == target) { 150 | PrintPaths(reserve_paths, source, target); 151 | ReservePath(reserve_paths, source, target); 152 | return bfs_dfs_paths_; 153 | } 154 | access_vertex.push(matched_vertex_index); 155 | has_visited[matched_vertex_index] = true; 156 | } 157 | } 158 | } 159 | } 160 | 161 | // 深度优先算法,找到的不是最优路径 162 | //! \complexity 时间复杂度为 O(边),空间复杂度 O(顶点) 163 | template 164 | std::vector<_Scalar> 165 | Graph<_Scalar, _Option>::Dfs(int source, int target) { 166 | assert(source >= 0 && target < vertex_count_ && "index over graph range!"); 167 | bfs_dfs_paths_.clear(); // 保存路径信息的,在 PrintPaths 生成的,这里进行清空! 168 | if (source == target) return {source}; 169 | std::vector has_visited(vertex_count_, 0); 170 | std::vector reserve_paths(vertex_count_, -1); 171 | 172 | dfs_found_ = false; 173 | RecursiveDfs(source, target, has_visited, reserve_paths); 174 | // 需要保存路径信息 175 | int temp_paths[vertex_count_]; 176 | for (int i = 0; i < vertex_count_; ++i) 177 | temp_paths[i] = reserve_paths[i]; 178 | if (dfs_found_) { 179 | PrintPaths(temp_paths, source, target); 180 | ReservePath(temp_paths, source, target); 181 | return bfs_dfs_paths_; 182 | } 183 | } 184 | 185 | 186 | // 深度优先算法(非递归版) 187 | //! \complexity 时间复杂度为 O(边),空间复杂度 O(顶点) 188 | template 189 | std::vector<_Scalar> Graph<_Scalar, _Option>::DfsCycle(int source, int target) { 190 | assert(source >= 0 && target < vertex_count_ && "index over graph range!"); 191 | bfs_dfs_paths_.clear(); // 每次使用不同的方法,这里都要清除! 192 | if (source == target) return {source}; 193 | 194 | // 初始化 1 195 | // 一些记录变量,比如访问过的顶点,保留的路径信息 196 | int has_visited[vertex_count_] = {0}; 197 | int reserve_paths[vertex_count_]; 198 | for (int i = 0; i < vertex_count_; ++i) reserve_paths[i] = -1; 199 | std::stack> vertex; // 第一个数字代表顶点序号, 200 | // 第二个代表该序号对应的上次的 source 201 | 202 | // 初始化 2 203 | // 将当前节点的邻接节点保存起来,按照递归(回溯思想)的思路, 204 | // 显示用栈来模仿函数栈 205 | vertex.push(std::make_pair(adjacency_table_[source].begin()->first, source)); 206 | has_visited[source] = true; 207 | for (const auto& adjacency: adjacency_table_[source]) { 208 | int matched_index = adjacency.first; 209 | vertex.push(std::make_pair(matched_index, source)); 210 | } 211 | 212 | while (!vertex.empty()) { 213 | // 先从栈顶弹出一个顶点,然后判断当前顶点是不是符合要求, 214 | // 符合要求,我们就直接返回,不符合要求,我们要把它的邻 215 | // 接节点压入栈中,方便回溯时来访问其他邻接节点。 216 | // 这里就是模仿上面的递归函数栈的方法 217 | auto index = vertex.top(); 218 | vertex.pop(); 219 | if (has_visited[index.first]) 220 | continue; 221 | reserve_paths[index.first] = index.second; 222 | if (index.first == target) { 223 | // PrintPaths(reserve_paths, source, target); 224 | ReservePath(reserve_paths, source, target); 225 | return bfs_dfs_paths_; 226 | } 227 | 228 | has_visited[index.first] = true; 229 | 230 | // 将当前节点的邻接节点全部压入栈 231 | for (const auto& adjacency: adjacency_table_[index.first]) { 232 | int matched_index = adjacency.first; 233 | vertex.push(std::make_pair(matched_index, index.first)); 234 | } 235 | } 236 | } 237 | 238 | //-------------------------internal helper function---------------------------// 239 | // 深度优先算法递归函数 240 | template 241 | void Graph<_Scalar, _Option>::RecursiveDfs(int source, int target, 242 | std::vector& has_visited, 243 | std::vector& reserve_paths) { 244 | has_visited[source] = true; 245 | if (dfs_found_ == true) return; 246 | if (source == target) {dfs_found_ = true; return;} 247 | 248 | // 遍历以当前 source 为起点的所有链接的路径 249 | // 已经假定前面的路径已经确定 250 | for (const auto& adjacency: adjacency_table_[source]) { 251 | int matched_index = adjacency.first; 252 | if (!has_visited[matched_index]) { 253 | reserve_paths[matched_index] = source; 254 | RecursiveDfs(matched_index, target, has_visited, reserve_paths); 255 | } 256 | } 257 | } 258 | 259 | } // namespace glib 260 | 261 | #endif // GLIB_HEAP_H_ 262 | -------------------------------------------------------------------------------- /src/graph/graph.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: graph.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/8/24 7 | * Description: test graph and BFS DFS algorithm 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "graph.hpp" 13 | #include 14 | #include 15 | 16 | //! \brief 简单测试堆及应用 17 | //! \run 18 | //! g++ graph.test.cc -std=c++11 && ./a.out 19 | 20 | using namespace std; 21 | int main(int argc, char const *argv[]) { 22 | glib::Graph graph(8); 23 | graph.AddEdge(0, 1); 24 | graph.AddEdge(0, 3); 25 | 26 | graph.AddEdge(1, 0); 27 | graph.AddEdge(1, 2); 28 | graph.AddEdge(1, 4); 29 | 30 | graph.AddEdge(2, 1); 31 | graph.AddEdge(2, 5); 32 | 33 | graph.AddEdge(3, 0); 34 | graph.AddEdge(3, 4); 35 | 36 | graph.AddEdge(4, 1); 37 | graph.AddEdge(4, 3); 38 | graph.AddEdge(4, 5); 39 | graph.AddEdge(4, 6); 40 | 41 | graph.AddEdge(5, 2); 42 | graph.AddEdge(5, 4); 43 | graph.AddEdge(5, 7); 44 | 45 | graph.AddEdge(6, 4); 46 | graph.AddEdge(6, 7); 47 | 48 | graph.AddEdge(7, 5); 49 | graph.AddEdge(7, 6); 50 | cout << "添加边完毕!" << endl; 51 | 52 | cout << "广度优先搜索" << endl; 53 | vector path_bfs = graph.Bfs(0, 6); 54 | cout << " "; 55 | for (size_t i = 0; i < path_bfs.size(); ++i) 56 | cout << path_bfs[i] << " "; 57 | cout << endl; 58 | 59 | cout << "深度优先搜索(递归)" << endl; 60 | vector path_dfs = graph.Dfs(0, 6); 61 | cout << " "; 62 | for (size_t i = 0; i < path_dfs.size(); ++i) 63 | cout << path_dfs[i] << " "; 64 | cout << endl; 65 | 66 | cout << "深度优先搜索(非递归)" << endl; 67 | vector path_dfs_circle = graph.DfsCycle(0, 6); 68 | for (size_t i = 0; i < path_dfs_circle.size(); ++i) 69 | cout << path_dfs_circle[i] << " "; 70 | cout << endl; 71 | } 72 | -------------------------------------------------------------------------------- /src/hash_table/hash_table.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: hash_table.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/5/30 7 | * Description: hash_table simple test 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "./hash_table.hpp" 13 | #include 14 | #include 15 | using namespace std; 16 | 17 | //! \brief LRU 哈希表 + 双链表实现,简单测试 18 | //! \run 19 | //! g++ hash_table.test.cc -std=c++11 && ./a.out 20 | int main(int argc, char const *argv[]) { 21 | glib::HashTable hash_table; 22 | 23 | // 验证插入是否正确 24 | cout << "验证 Insert 插入函数" << endl; 25 | // hash_table.Insert(std::make_pair("1", "n")); 26 | hash_table.Insert("1", "n"); 27 | hash_table.Insert(std::make_pair("2", "c")); 28 | hash_table.Insert(std::make_pair("3", "u")); 29 | hash_table.Insert(std::make_pair("4", "g")); 30 | hash_table.Insert(std::make_pair("5", "c")); 31 | hash_table.Insert(std::make_pair("6", "j")); 32 | hash_table.print_value(); 33 | hash_table.Insert(std::make_pair("5", "G")); 34 | 35 | // 验证扩容 36 | cout << "验证扩容" << endl; 37 | cout << "扩容前,总容量:" << hash_table.capacity() << endl; // 8 38 | cout << "扩容前,当前容量:" << hash_table.size() << endl; // 6 39 | hash_table.Insert(std::make_pair("7", "o")); // 此时会扩容 40 | cout << "扩容后,总容量:" << hash_table.capacity() << endl; // 16 41 | cout << "扩容后,当前容量:" << hash_table.size() << endl; // 7 42 | hash_table.print_value(); 43 | cout << endl; 44 | 45 | // 验证查找函数 46 | cout << "验证查找函数" << endl; 47 | auto return_result = hash_table.Find("5"); 48 | if (return_result.first) { 49 | cout << "找到目标: " << return_result.second << endl; 50 | } 51 | cout << endl; 52 | 53 | // 验证删除函数 54 | cout << "验证删除函数" << endl; 55 | cout << "删除前,总容量:" << hash_table.capacity() << endl; 56 | cout << "删除前,当前容量:" << hash_table.size() << endl; 57 | 58 | hash_table.Delete("5"); 59 | cout << "删除一个元素后,当前容量:" << hash_table.size() << endl; 60 | hash_table.print_value(); 61 | cout << endl; 62 | 63 | // 验证缩容 64 | cout << "验证缩容" << endl; 65 | cout << "缩容前,总容量:" << hash_table.capacity() << endl; // 16 66 | cout << "扩容前,当前容量:" << hash_table.size() << endl; // 6 67 | hash_table.Delete("7"); 68 | hash_table.Delete("6"); 69 | hash_table.Delete("5"); 70 | hash_table.Delete("4"); 71 | hash_table.Delete("3"); 72 | cout << "缩容后,总容量: " << hash_table.capacity() << endl; // 8 73 | cout << "缩容后,当前容量:" << hash_table.size() << endl; // 2 74 | hash_table.print_value(); 75 | 76 | // 验证修改数据内容 77 | cout << "例子:如何修改 key 对应数据" << endl; 78 | auto result = hash_table.Find("1"); 79 | if (result.first) { 80 | hash_table.Insert("1", "N"); 81 | } 82 | hash_table.print_value(); 83 | 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /src/hash_table/lru_hash.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: lru_hash.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/5/30 7 | * Description: simple test lru by hash talbe 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | #include "./lru_hash.hpp" 12 | #include 13 | #include 14 | #include // sprintf 15 | 16 | using namespace std; 17 | 18 | //! \brief LRU 哈希表 + 双链表实现,简单测试 19 | //! \run 20 | //! g++ lru_hash.test.cc -std=c++11 && ./a.out 21 | int main(int argc, char const *argv[]) { 22 | // 对于 int 类型已经测试过了,正常,所以这里保留了 string 类型的测试结果 23 | glib::LruHash lru(4); 24 | cout << "哈希容量:" << lru.hash_capacity() << endl; 25 | 26 | // 测试插入 27 | cout << "测试插入" << endl; 28 | lru.Insert("1"); 29 | lru.Insert("1"); 30 | lru.print_value(); // 1 31 | cout << lru.hash_capacity() << endl; // 8 32 | cout << lru.capacity() < 18 | #include 19 | #include 20 | #include // swap 21 | 22 | //! \brief 简单实现了一个大(小)顶堆以及堆排序 23 | //! 外部调用核心函数: 24 | //! 1)堆中插入数据:Insert() 25 | //! 2)删除堆顶元素:RemoveTop() 26 | //! 3)堆排序:Sort() 27 | //! 外部调用状态函数: 28 | //! 1)查看堆内元素:print_heap() 29 | //! 2)查看堆内有效元素大小:size() 30 | //! 3)堆内元素是否有序:ordered() 31 | //! 内部辅助核心函数: 32 | //! 1)大(小)顶堆比较函数:Compare() 33 | //! 2)堆化(低->上):HeapifyUp() 34 | //! 3)堆化(上->下):HeapifyDown() 35 | //! 4)建堆:BuildHeap() 36 | //! 5)扩容堆容量:Extend() 37 | //! 6)缩减堆容量:Reduce() 38 | //! \Note 39 | //! 1)暂且不支持自定义类对象 40 | //! 41 | //! \TODO 42 | //! 1)支持自定义类 43 | //! 44 | //! \platform 45 | //! ubuntu16.04 g++ version 5.4.0 46 | 47 | namespace glib { 48 | namespace internal { 49 | enum HeapOption { BIG_HEAP, SMALL_HEAP }; 50 | } // namespace internal 51 | 52 | using namespace internal; 53 | 54 | template 55 | class Heap { 56 | public: // type or struct declaration 57 | using ValueType = _Scalar; 58 | 59 | public: // construct function 60 | Heap(std::vector vec) { 61 | capacity_ = vec.size(); 62 | size_ = 0; 63 | heap_ = new ValueType[capacity_ + 1]; 64 | for (const auto& element: vec) { 65 | Insert(element); 66 | // print_heap(); 67 | } 68 | } 69 | Heap(std::initializer_list il) { 70 | capacity_ = il.size(); 71 | size_ = 0; 72 | heap_ = new ValueType[capacity_ + 1]; 73 | for (const auto& element: il) { 74 | Insert(element); 75 | // print_heap(); 76 | } 77 | } 78 | ~Heap() { 79 | if (heap_ != nullptr) delete heap_; 80 | } 81 | GLIB_DISALLOW_IMPLICIT_CONSTRUCTORS_PUBLIC(Heap); 82 | public: // external call function 83 | // 向堆中插入一个元素(从后向前插入,之后堆化) 84 | void Insert(const ValueType& key); 85 | 86 | // 删除堆顶元素 87 | void RemoveTop(); 88 | 89 | // 排序 90 | void Sort(); 91 | 92 | // 查看堆内元素 93 | void print_heap() { 94 | for (size_t i = 1; i < size_ + 1; i++) { 95 | std::cout << heap_[i] << " "; 96 | } 97 | std::cout << std::endl; 98 | } 99 | 100 | inline size_t size() const {return size_; } // 返回当前堆大小(有效堆) 101 | inline size_t capacity() const {return capacity_;} // 返回当前堆的容量 102 | inline bool ordered() const {return ordered_; } // 判断当前堆内元素是否是有序的 103 | private: // internal helper function 104 | // 大顶堆和小顶堆比较函数 105 | bool Compare(const ValueType& first, const ValueType& second); 106 | 107 | // 堆化操作 108 | // 从底向上堆化 109 | void HeapifyUp(size_t index); 110 | 111 | // 从上向下堆化 112 | void HeapifyDown(size_t index); 113 | 114 | // 建堆,重新把内部数据组织成一个堆 115 | void BuildHeap(); 116 | 117 | // 扩充容量 118 | inline void Extend(); 119 | 120 | // 减少容量 121 | inline void Reduce(); 122 | 123 | 124 | private: // internal member variable 125 | ValueType* heap_ = nullptr; // 不使用 0 索引! 126 | size_t capacity_ = 0; // 当前堆的容量 127 | size_t size_ = 0; // 当前堆中存储的有效容量 128 | 129 | bool ordered_ = false; // 当前堆中元素是否是有序的 130 | bool need_build_heap_ = false; // 判断是否需要建堆 131 | }; 132 | 133 | //----------------------- external call function------------------------------// 134 | // 从堆低插入一个元素,之后进行堆化 135 | // \complexity 时间复杂度为:最好: O(logn) 最坏:O(n) 平均:O(logn) 136 | // 空间复杂度为:最好:O(1) 最坏:O(n) 平均 O(1) 137 | template 138 | void Heap<_Scalar, _Option>::Insert(const ValueType& key) { 139 | // 如果当前堆经历了排序,那么内部元素就假定不符合堆的条件 140 | // 需要从新建立一个堆 141 | if (need_build_heap_) 142 | BuildHeap(); 143 | Extend(); // 如果容量不够,那么就会扩充容量 144 | ++size_; 145 | heap_[size_] = key; 146 | HeapifyUp(size_); // 从最后一个元素(堆底)向上堆化 147 | } 148 | 149 | //! \brief 删除堆顶元素 150 | //! \complexity 时间复杂度为:最好: O(logn) 最坏:O(n) 平均:O(logn) 151 | //! 空间复杂度为:最好:O(1) 最坏:O(n) 平均 O(1) 152 | template 153 | void Heap<_Scalar, _Option>::RemoveTop() { 154 | if (size_ <= 1) { 155 | size_ = 0; 156 | return; 157 | } 158 | if (need_build_heap_) 159 | BuildHeap(); 160 | heap_[1] = heap_[size_]; // 用堆底元素替换堆顶元素 161 | size_--; // 不能与下面堆化交换顺序! 162 | HeapifyDown(1); 163 | Reduce(); // 看看是不是可以缩减容量 164 | } 165 | 166 | // 排序 167 | // \note 1)在调用该函数之前,Insert 函数每次插入数据,都会调整其为堆, 168 | // 也就是说整个堆都是提前建立好了的!只要调用该函数,那么就省略 169 | // 刚开始的建堆过程 170 | // 2)默认情况下根据选择大堆和小堆的不同,这里会自动按照从小到大排 171 | // 序,和从大到小排序 172 | template 173 | void Heap<_Scalar, _Option>::Sort() { 174 | if (ordered_) return; 175 | int reserve_size = size_; 176 | while (size_ > 1) { 177 | std::swap(heap_[size_], heap_[1]); 178 | size_--; 179 | HeapifyDown(1); 180 | } 181 | size_ = reserve_size; 182 | ordered_ = true; // 标记当前堆内元素是有序的 183 | need_build_heap_ = true; // 假定排序完毕后,内部就不是堆了! 184 | } 185 | 186 | //----------------------- internal helper function----------------------------// 187 | // 大顶堆和小顶堆比较函数 188 | // \complexity O(1) 189 | template 190 | bool Heap<_Scalar, _Option>::Compare(const ValueType& first, 191 | const ValueType& second) { 192 | if (_Option == HeapOption::BIG_HEAP) { 193 | return (first > second)? true: false; 194 | } 195 | if (_Option == HeapOption::SMALL_HEAP) { 196 | return (first < second)? true: false; 197 | } 198 | } 199 | 200 | // 向上堆化 201 | // \complexity 时间复杂度为 O(logn) 空间复杂度为 O(1) 202 | template 203 | void Heap<_Scalar, _Option>::HeapifyUp(size_t index ) { 204 | assert(1 <= index && index <= size_ && 205 | "index over heap size or index < 1"); 206 | while (index > 1 && Compare(heap_[index], heap_[index/2])) { 207 | std::swap(heap_[index], heap_[index/2]); 208 | index /= 2; 209 | } 210 | return; 211 | } 212 | 213 | // 向下堆化 214 | // \complexity 时间复杂度为 O(logn) 空间复杂度为 O(1) 215 | template 216 | void Heap<_Scalar,_Option>::HeapifyDown(size_t index) { 217 | assert(1 <= index && index <= size_ && 218 | "index over heap size or index < 1"); 219 | while (true) { 220 | ValueType extremum_value_index = index; 221 | if (2 * index <= size_ && 222 | Compare(heap_[2 * index], heap_[extremum_value_index])) 223 | extremum_value_index = 2 * index; 224 | if (2 * index + 1 <= size_ && 225 | Compare(heap_[2 * index + 1], heap_[extremum_value_index])) 226 | extremum_value_index = 2 * index + 1; 227 | 228 | // 如果没有交换,那么堆化成功 229 | if (extremum_value_index == index) break; 230 | 231 | std::swap(heap_[extremum_value_index], heap_[index]); 232 | index = extremum_value_index; 233 | } 234 | return; 235 | } 236 | 237 | // 建堆,重新把内部数据组织成一个堆 238 | // \complexity 时间复杂度:O(n) 239 | template 240 | void Heap<_Scalar, _Option>::BuildHeap() { 241 | size_t index = size_/2; 242 | while (index >= 1) { 243 | HeapifyDown(index); 244 | index--; 245 | } 246 | ordered_ = false; // 此时默认没有顺序 247 | need_build_heap_ = false; // 表示已经排好序了 248 | } 249 | 250 | // 扩充容量 251 | // \complexity 时间复杂度为:平均情况为 O(1) 最好:O(1),最坏:O(n) 空间复杂度同理 252 | template 253 | inline void Heap<_Scalar, _Option>::Extend() { 254 | // 超出当前堆的容量,自动扩容 2 倍 255 | if (size_ + 1 > capacity_) { 256 | ValueType* temp_array = new ValueType[2 * capacity_ + 1]; 257 | for (int i = 1; i <= size_; ++i) 258 | temp_array[i] = heap_[i]; 259 | delete heap_; 260 | heap_ = temp_array; 261 | capacity_ = 2 * capacity_; 262 | } 263 | return; 264 | } 265 | 266 | // 减少容量 267 | // \complexity 时间复杂度为:平均情况为 O(1) 最好:O(1),最坏:O(n) 空间复杂度同理 268 | template 269 | inline void Heap<_Scalar, _Option>::Reduce() { 270 | // 看看是否需要缩容 271 | if (size_ <= 0.25 * capacity_ && 0.5 * capacity_ >= 4) { 272 | ValueType* temp_heap = new ValueType[int(0.5 * capacity_ + 1)]; 273 | for (int i = 1; i <= size_; ++i) 274 | temp_heap[i] = heap_[i]; 275 | delete heap_; 276 | capacity_ = 0.5 * capacity_; 277 | heap_ = temp_heap; 278 | } 279 | return; 280 | } 281 | 282 | } // namespace glib 283 | 284 | #endif // GLIB_HEAP_H_ 285 | -------------------------------------------------------------------------------- /src/heap/heap.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: heap.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/8/22 7 | * Description: test heap and app 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "heap.hpp" 13 | #include 14 | #include 15 | 16 | //! \brief 简单测试堆及应用 17 | //! \run 18 | //! g++ heap.test.cc -std=c++11 && ./a.out 19 | 20 | using namespace std; 21 | int main(int argc, char const *argv[]) { 22 | // 大堆测试 23 | cout << "大顶堆测试" << endl; 24 | // 两种不同的构造方式:初始化列表和用 vector 来构造! 25 | // vector vec = {33, 27, 21, 16, 13, 15, 19, 5, 6, 7, 8, 1, 2, 12}; 26 | // glib::Heap heap_big(vec); 27 | glib::Heap heap_big = {33, 27, 21, 16, 13, 15, 19, 5, 6, 7, 8, 1, 2, 12}; // 默认大堆模式 28 | heap_big.print_heap(); // 33 27 21 16 13 15 19 5 6 7 8 1 2 12 29 | heap_big.RemoveTop(); 30 | heap_big.print_heap(); // 27 16 21 12 13 15 19 5 6 7 8 1 2 31 | heap_big.Sort(); // 排序 32 | cout << "排序后的值" << endl; 33 | heap_big.print_heap(); 34 | 35 | cout << "小顶堆测试" << endl; 36 | glib::Heap 37 | heap_small = {33, 27, 21, 16, 13, 15, 19, 5, 6, 7, 8, 1, 2, 12}; // 指定小堆 38 | heap_small.print_heap(); // 1 6 2 13 7 5 12 33 16 21 8 27 15 19 39 | heap_small.RemoveTop(); 40 | heap_small.print_heap(); // 2 6 5 13 7 15 12 33 16 21 8 27 19 41 | heap_small.Sort(); // 排序 42 | cout << "排序后的值" << endl; 43 | heap_small.print_heap(); 44 | 45 | // 测试排序后,从新建立堆 46 | cout << "测试排序后,从新建立堆,然后在排序" << endl; 47 | heap_small.Insert(1); 48 | heap_small.print_heap(); // 1 6 2 8 7 15 5 12 19 27 16 21 33 13 49 | heap_small.Sort(); 50 | heap_small.print_heap(); // 33 27 21 19 16 15 13 12 8 7 6 5 2 1 51 | 52 | // 边界条件测试 53 | // cout << "测试边界条件" << endl; 54 | // glib::Heap condition_heap = {1,2}; 55 | // condition_heap.RemoveTop(); 56 | // condition_heap.RemoveTop(); 57 | // condition_heap.Sort(); 58 | // condition_heap.print_heap(); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /src/internal/macros.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: consistent_hash.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/8/19 7 | * Description: glib public internal macros 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | #ifndef GLIB_PUBLIC_INTERNAL_MACROS_H_ 12 | #define GLIB_PUBLIC_INTERNAL_MACROS_H_ 13 | 14 | #include // for size_t 15 | 16 | //! \brief 简单指定一下类的内部自用一些宏定义 17 | 18 | //! \brief 在类中不允许拷贝和赋值 19 | //! \use 用在类的 private 属性中 20 | #define GLIB_DISALLOW_COPY_AND_ASSIGN_PRIVATE(TypeName) \ 21 | TypeName(const TypeName& others); \ 22 | TypeName& operator=(const TypeName& others) 23 | 24 | //! \brief 在类中不允许拷贝和赋值 25 | //! \use 可以使用在 pubic 下,这里用了 delete 方式 26 | #define GLIB_DISALLOW_COPY_AND_ASSIGN_PUBLIC(TypeName) \ 27 | TypeName(const TypeName& others) = delete; \ 28 | TypeName& operator=(const TypeName& others) = delete 29 | 30 | //! \brief 不允许默认构造函数(构造、拷贝、赋值) 31 | //! \use 建议用在类中只有静态方法时。且只能放在 private 范围内 32 | #define GLIB_DISALLOW_IMPLICIT_CONSTRUCTORS_PRIVATE(TypeName) \ 33 | TypeName(); \ 34 | GLIB_DISALLOW_COPY_AND_ASSIGN_PRIVATE(TypeName) 35 | 36 | //! \brief 不允许默认构造函数(构造、拷贝、赋值) 37 | //! \use 建议用在类中只有静态方法时,且可以放在 public or private 中 38 | #define GLIB_DISALLOW_IMPLICIT_CONSTRUCTORS_PUBLIC(TypeName) \ 39 | TypeName(); \ 40 | GLIB_DISALLOW_COPY_AND_ASSIGN_PUBLIC(TypeName) 41 | 42 | 43 | //! \brief 隐式默认构造函数 44 | #define GLIB_IMPLICIT_CONSTRUCTORS_PUBLIC(TypeName) \ 45 | TypeName() = default; \ 46 | TypeName(const TypeName& others) = default; \ 47 | TypeName& operator=(const TypeName& other) = default 48 | 49 | #endif // GLIB_PUBLIC_INTERNAL_MACROS_H_ 50 | -------------------------------------------------------------------------------- /src/internal/temp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: hash_table.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/5/30 7 | * Description: binary search tree simple test 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | // Ceres Solver - A fast non-linear least squares minimizer 12 | // Copyright 2015 Google Inc. All rights reserved. 13 | // http://ceres-solver.org/ 14 | // 15 | // Redistribution and use in source and binary forms, with or without 16 | // modification, are permitted provided that the following conditions are met: 17 | // 18 | // * Redistributions of source code must retain the above copyright notice, 19 | // this list of conditions and the following disclaimer. 20 | // * Redistributions in binary form must reproduce the above copyright notice, 21 | // this list of conditions and the following disclaimer in the documentation 22 | // and/or other materials provided with the distribution. 23 | // * Neither the name of Google Inc. nor the names of its contributors may be 24 | // used to endorse or promote products derived from this software without 25 | // specific prior written permission. 26 | // 27 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 31 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 | // POSSIBILITY OF SUCH DAMAGE. 38 | // 39 | // 40 | // Various Google-specific macros. 41 | // 42 | // This code is compiled directly on many platforms, including client 43 | // platforms like Windows, Mac, and embedded systems. Before making 44 | // any changes here, make sure that you're not breaking any platforms. 45 | 46 | #ifndef CERES_PUBLIC_INTERNAL_MACROS_H_ 47 | #define CERES_PUBLIC_INTERNAL_MACROS_H_ 48 | 49 | #include // For size_t. 50 | 51 | // A macro to disallow the copy constructor and operator= functions 52 | // This should be used in the private: declarations for a class 53 | // 54 | // For disallowing only assign or copy, write the code directly, but declare 55 | // the intend in a comment, for example: 56 | // 57 | // void operator=(const TypeName&); // _DISALLOW_ASSIGN 58 | 59 | // Note, that most uses of CERES_DISALLOW_ASSIGN and CERES_DISALLOW_COPY 60 | // are broken semantically, one should either use disallow both or 61 | // neither. Try to avoid these in new code. 62 | #define CERES_DISALLOW_COPY_AND_ASSIGN(TypeName) \ 63 | TypeName(const TypeName&); \ 64 | void operator=(const TypeName&) 65 | 66 | // A macro to disallow all the implicit constructors, namely the 67 | // default constructor, copy constructor and operator= functions. 68 | // 69 | // This should be used in the private: declarations for a class 70 | // that wants to prevent anyone from instantiating it. This is 71 | // especially useful for classes containing only static methods. 72 | #define CERES_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ 73 | TypeName(); \ 74 | CERES_DISALLOW_COPY_AND_ASSIGN(TypeName) 75 | 76 | // The arraysize(arr) macro returns the # of elements in an array arr. 77 | // The expression is a compile-time constant, and therefore can be 78 | // used in defining new arrays, for example. If you use arraysize on 79 | // a pointer by mistake, you will get a compile-time error. 80 | // 81 | // One caveat is that arraysize() doesn't accept any array of an 82 | // anonymous type or a type defined inside a function. In these rare 83 | // cases, you have to use the unsafe ARRAYSIZE() macro below. This is 84 | // due to a limitation in C++'s template system. The limitation might 85 | // eventually be removed, but it hasn't happened yet. 86 | 87 | // This template function declaration is used in defining arraysize. 88 | // Note that the function doesn't need an implementation, as we only 89 | // use its type. 90 | template 91 | char (&ArraySizeHelper(T (&array)[N]))[N]; 92 | 93 | // That gcc wants both of these prototypes seems mysterious. VC, for 94 | // its part, can't decide which to use (another mystery). Matching of 95 | // template overloads: the final frontier. 96 | #ifndef _WIN32 97 | template 98 | char (&ArraySizeHelper(const T (&array)[N]))[N]; 99 | #endif 100 | 101 | #define arraysize(array) (sizeof(ArraySizeHelper(array))) 102 | 103 | // ARRAYSIZE performs essentially the same calculation as arraysize, 104 | // but can be used on anonymous types or types defined inside 105 | // functions. It's less safe than arraysize as it accepts some 106 | // (although not all) pointers. Therefore, you should use arraysize 107 | // whenever possible. 108 | // 109 | // The expression ARRAYSIZE(a) is a compile-time constant of type 110 | // size_t. 111 | // 112 | // ARRAYSIZE catches a few type errors. If you see a compiler error 113 | // 114 | // "warning: division by zero in ..." 115 | // 116 | // when using ARRAYSIZE, you are (wrongfully) giving it a pointer. 117 | // You should only use ARRAYSIZE on statically allocated arrays. 118 | // 119 | // The following comments are on the implementation details, and can 120 | // be ignored by the users. 121 | // 122 | // ARRAYSIZE(arr) works by inspecting sizeof(arr) (the # of bytes in 123 | // the array) and sizeof(*(arr)) (the # of bytes in one array 124 | // element). If the former is divisible by the latter, perhaps arr is 125 | // indeed an array, in which case the division result is the # of 126 | // elements in the array. Otherwise, arr cannot possibly be an array, 127 | // and we generate a compiler error to prevent the code from 128 | // compiling. 129 | // 130 | // Since the size of bool is implementation-defined, we need to cast 131 | // !(sizeof(a) & sizeof(*(a))) to size_t in order to ensure the final 132 | // result has type size_t. 133 | // 134 | // This macro is not perfect as it wrongfully accepts certain 135 | // pointers, namely where the pointer size is divisible by the pointee 136 | // size. Since all our code has to go through a 32-bit compiler, 137 | // where a pointer is 4 bytes, this means all pointers to a type whose 138 | // size is 3 or greater than 4 will be (righteously) rejected. 139 | // 140 | // Kudos to Jorg Brown for this simple and elegant implementation. 141 | // 142 | // - wan 2005-11-16 143 | // 144 | // Starting with Visual C++ 2005, WinNT.h includes ARRAYSIZE. However, 145 | // the definition comes from the over-broad windows.h header that 146 | // introduces a macro, ERROR, that conflicts with the logging framework 147 | // that Ceres uses. Instead, rename ARRAYSIZE to CERES_ARRAYSIZE. 148 | #define CERES_ARRAYSIZE(a) \ 149 | ((sizeof(a) / sizeof(*(a))) / \ 150 | static_cast(!(sizeof(a) % sizeof(*(a))))) 151 | 152 | // Tell the compiler to warn about unused return values for functions 153 | // declared with this macro. The macro should be used on function 154 | // declarations following the argument list: 155 | // 156 | // Sprocket* AllocateSprocket() MUST_USE_RESULT; 157 | // 158 | #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) \ 159 | && !defined(COMPILER_ICC) 160 | #define CERES_MUST_USE_RESULT __attribute__ ((warn_unused_result)) 161 | #else 162 | #define CERES_MUST_USE_RESULT 163 | #endif 164 | 165 | // Platform independent macros to get aligned memory allocations. 166 | // For example 167 | // 168 | // MyFoo my_foo CERES_ALIGN_ATTRIBUTE(16); 169 | // 170 | // Gives us an instance of MyFoo which is aligned at a 16 byte 171 | // boundary. 172 | #if defined(_MSC_VER) 173 | #define CERES_ALIGN_ATTRIBUTE(n) __declspec(align(n)) 174 | #define CERES_ALIGN_OF(T) __alignof(T) 175 | #elif defined(__GNUC__) 176 | #define CERES_ALIGN_ATTRIBUTE(n) __attribute__((aligned(n))) 177 | #define CERES_ALIGN_OF(T) __alignof(T) 178 | #endif 179 | 180 | #endif // CERES_PUBLIC_INTERNAL_MACROS_H_ 181 | -------------------------------------------------------------------------------- /src/list/single_list.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: single_list.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/8 7 | * Description: test single linked list 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "single_list.hpp" 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | 19 | //! \brief 简单测试单链表结构的所有功能 20 | //! \run 21 | //! g++ single_list.test.cc -std=c++11 && ./a.out 22 | //! \TODO 23 | //! 24 | int main(int argc, char **argv) { 25 | // 简单测试 string 类是否能够通过案例 26 | cout << "简单测试 string 类是否能够通过案例" << endl; 27 | glib::SingleList string_list; 28 | string_list.InsertHead(string(1,'a')); 29 | string_list.InsertHead(string(1,'b')); 30 | string_list.print_value(); // b a 31 | string_list.DeleteTail(); 32 | string_list.print_value(); // b 33 | 34 | vector values; 35 | for (int i = 0; i < 5; i++) { 36 | values.emplace_back(i); 37 | } 38 | cout << "before values.size(): " << values.size() << endl; // 5 39 | glib::SingleList single_list(values.size(), values); 40 | cout << endl; 41 | 42 | // 打印初始链表数据测试 43 | cout << "打印初始链表数据测试" << endl; 44 | cout << "print single list" << endl; 45 | single_list.print_value(); // 0 1 2 3 4 46 | cout << endl; 47 | 48 | // 删除单个数据测试 49 | cout << "删除单个数据测试" << endl; 50 | single_list.Delete(4); 51 | cout << "after delete" << endl; 52 | single_list.print_value(); // 0 1 2 3 53 | cout << endl; 54 | 55 | // 头部插入数据测试 56 | cout << "头部插入数据测试" << endl; 57 | single_list.InsertHead(6); 58 | cout << "after InsertHead()" << endl; 59 | single_list.print_value(); // 6 0 1 2 3 60 | cout << endl; 61 | 62 | // 尾部插入数据测试 63 | cout << "尾部插入数据测试" << endl; 64 | single_list.InsertTail(8); 65 | single_list.InsertTail(1); 66 | cout << "after InsertTail()" << endl; 67 | single_list.print_value(); // 6 0 1 2 3 8 1 68 | cout << endl; 69 | 70 | // 交换数据测试、查找数据测试 71 | cout << "交换数据测试、查找数据测试!" << endl; 72 | if (!single_list.Find(nullptr) && !single_list.Find(single_list.head_ptr())) 73 | cout << "false" << endl; 74 | if (single_list.Find(single_list.first_node_ptr())) 75 | cout << "yes" << endl; 76 | const auto vec_nodes = single_list.Find(1); 77 | for (size_t i = 0; i < vec_nodes.size(); i++) { 78 | cout << "vec_nodes[i]: " << vec_nodes[i]->data << endl; 79 | if (vec_nodes[i] == single_list.tail_ptr()) 80 | cout << "尾部指针success!" << endl; 81 | } 82 | cout << "after exchange!" << endl; 83 | single_list.Exchange(single_list.first_node_ptr(), single_list.first_node_ptr()->next); 84 | single_list.print_value(); // 0 6 1 2 3 8 1 85 | cout << "after exchange,node_count: " << single_list.node_count() << "tail_->data: " 86 | << single_list.tail_ptr()->data << endl; // 7 1 87 | single_list.Exchange(single_list.tail_ptr(), single_list.first_node_ptr()->next->next->next); 88 | single_list.print_value(); // 0 6 1 1 3 8 2 89 | cout << "after exchange,node_count: " << single_list.node_count() << "tail_->data: " 90 | << single_list.tail_ptr()->data << endl; // 7 2 91 | cout << endl; 92 | 93 | // 移位数据测试 94 | cout << "移位数据测试!" << endl; 95 | single_list.MoveHead(single_list.tail_ptr()); 96 | single_list.MoveHead(single_list.first_node_ptr()); 97 | single_list.MoveHead(single_list.first_node_ptr()->next->next); 98 | cout << "after MoveHead" << endl; 99 | single_list.print_value(); // 6 2 0 1 1 3 8 100 | cout << "tail_ptr->data: " << single_list.tail_ptr()->data << endl; // 8 101 | cout << endl; 102 | 103 | // 测试尾部删除、头部删除、普通删除 104 | cout << "测试尾部删除、头部删除、普通删除" << endl << "before dele" << endl; 105 | single_list.DeleteHead(); 106 | single_list.print_value(); // 2 0 1 1 3 8 107 | single_list.DeleteTail(); 108 | single_list.print_value(); // 2 0 1 1 3 109 | cout << "tail data: " << single_list.tail_ptr()->data << endl; // 3 110 | single_list.Delete(single_list.head_ptr()->next->next->next->next->next); 111 | cout << "tail data: " << single_list.tail_ptr()->data << endl; // 1 112 | single_list.print_value(); // 2 0 1 1 113 | cout << endl; 114 | 115 | 116 | // 测试删除重复数据,一些边界条件!!! 117 | // 普通插入数据 118 | cout << "测试删除重复数据,一些边界条件" << endl; 119 | cout << "common insert test!" << endl; 120 | single_list.Insert(100); 121 | single_list.print_value(); // 100 2 0 11 122 | single_list.Insert(101,10); 123 | single_list.print_value(); // 101 100 2 0 1 1 124 | cout << endl; 125 | 126 | // 建立空节点链表测试打印,默认有一个头结点 127 | cout << "测试空链表打印" << endl; 128 | glib::SingleList lists; 129 | lists.print_value(); // 空值 130 | cout << endl; 131 | 132 | // 输入重复数据测试 133 | cout << "测试输入重复数据!"; 134 | cout << endl << "after init" << endl; 135 | for (size_t i = 0; i < 2; i++) 136 | lists.InsertHead(1); 137 | for (size_t i = 0; i < 2; i++) 138 | lists.InsertHead(2); 139 | lists.print_value(); // 2 2 1 1 140 | 141 | lists.Delete(2); 142 | cout << "after default delete " << endl; 143 | lists.print_value(); // 1 1 144 | lists.Delete(1, true); 145 | cout << "after delete by repeat" << endl; 146 | lists.print_value(); // 1 1 147 | cout << endl; 148 | 149 | // 测试拷贝以及赋值函数,引用资源相关! 150 | cout << "测试拷贝赋值函数--引用!" << endl; 151 | single_list.print_value(); // 第一个链表节点打印 101 100 2 0 1 1 152 | lists.print_value(); // 第二个链表节点打印 1 153 | cout << "1 2 引用计数: "<< single_list.reference_count() << " " << lists.reference_count() << endl; // 1 1 154 | 155 | glib::SingleListsingle_list_copy = single_list; 156 | auto lists_copy = lists; 157 | single_list_copy.print_value(); // 101 100 2 0 1 1 158 | cout << "after copy ,引用计数: " << single_list_copy.reference_count() << endl; // 2 159 | 160 | lists = single_list_copy; 161 | lists.print_value(); // 101 100 2 0 1 1 162 | cout << "operator引用计数:" << lists.reference_count() << endl; // 3 163 | cout << "lists_copy 引用计数: " << lists_copy.reference_count() << endl; // 1 164 | cout << endl; 165 | 166 | // 测试单链表实现 lru ,默认类型 int 167 | cout << "测试单链表实现 lru" << endl; 168 | glib::SingleList<> lru_list; 169 | int data = 0; 170 | for (size_t i = 0; i < 15; i++) { 171 | if (i > 9 && i <= 12) 172 | data = 15 - i; 173 | else if (i > 12) 174 | data = 0; 175 | else 176 | data = i; 177 | glib::single_list_app::LRUBySingleList(lru_list, int(data)); 178 | lru_list.print_value(); 179 | cout << "tail data->: " << lru_list.tail_ptr()->data << " "; 180 | } 181 | cout << endl; 182 | cout << endl; 183 | 184 | // 单链表翻转测试,以及求取中间节点测试 185 | cout << "测试翻转单链表以及求取中间节点测试" << endl; 186 | cout << "Reserve single list test!" << endl; 187 | vector valuess; 188 | for (int i = 0; i < 5; i++) { 189 | valuess.emplace_back(i); 190 | } 191 | glib::SingleList single_lists(valuess.size(), valuess); 192 | single_lists.print_value(); // 0 1 2 3 4 193 | cout << "after reserve!" << endl; 194 | single_lists.Reserve(); 195 | single_lists.print_value(); // 4 3 2 1 0 196 | if (single_lists.node_count()) { 197 | cout << "tail->data: " << single_lists.tail_ptr()->data << endl; // 0 198 | cout << "中间节点数据: " << single_lists.MiddleNode()->data << endl; // 2 199 | } 200 | cout << endl; 201 | 202 | // 测试单链表存储的回文字符串判断---递归方法! 203 | cout << "单链表检测回文测试---递归方法" << endl; 204 | glib::SingleList palindrome_string; 205 | for (size_t i = 0; i < 3; i++) { 206 | palindrome_string.InsertHead('a' + i); 207 | } 208 | for (size_t i = 0; i < 2; i++) { 209 | palindrome_string.InsertTail('b' + i); 210 | } 211 | palindrome_string.print_value(); 212 | if (glib::single_list_app::PalingromeString(palindrome_string)) 213 | cout << "yes" << endl; 214 | else 215 | cout << "no" << endl; 216 | 217 | return 0; 218 | } 219 | -------------------------------------------------------------------------------- /src/queue/queue.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: queue.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/15 7 | * Description: use queue 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | * reference 网上 11 | */ 12 | 13 | //! \brief 一个网上下载的测试队列函数,把自己实现的队列对应替换为标准库中的队列。经过测试能够满足 14 | //! 网上提供的代码 15 | //! \Note 该测试文件已经被 use_queue.cc 测试文件替换 16 | 17 | #include "./queue.hpp" 18 | #include 19 | // #include 20 | // #include 21 | #include 22 | using namespace std; 23 | using namespace glib; 24 | void test_empty() 25 | { 26 | Queue myQueue; 27 | int sum (0); 28 | 29 | for (int i=1;i<=12;i++) { 30 | myQueue.Push(i); 31 | } 32 | while (!myQueue.empty()) 33 | { 34 | sum += myQueue.front(); 35 | myQueue.Pop(); 36 | } 37 | cout << "total: " << sum << endl; 38 | // cout << "空: " << myQueue.front() << endl; 39 | }//运行结果: total: 55 40 | void test_Pop() 41 | { 42 | Queue myQueue; 43 | int myint; 44 | 45 | cout << "\nPlease enter some integers (enter 0 to end):\n"; 46 | 47 | do 48 | { 49 | cin >> myint; 50 | myQueue.Push (myint); 51 | } while (myint); 52 | 53 | cout << "myQueue contains: " << myQueue.size(); 54 | while (!myQueue.empty()) 55 | { 56 | cout << " " << myQueue.front(); 57 | myQueue.Pop(); 58 | } 59 | } 60 | /******** 61 | 运行结果: 62 | Please enter some integers (enter 0 to end): 63 | 512 64 | 605 65 | 420 66 | 517 67 | 532 68 | 0 69 | myQueue contains: 512 605 420 517 532 0 70 | ********/ 71 | void test_size() 72 | { 73 | Queue myints; 74 | cout << "0. size: " << (int) myints.size() << endl; 75 | 76 | for (int i=0; i<5; i++) myints.Push(i); 77 | cout << "1. size: " << (int) myints.size() << endl; 78 | 79 | myints.Pop(); 80 | cout << "2. size: " << (int) myints.size() << endl; 81 | } 82 | /**** 83 | 运行结果: 84 | 0. size: 0 85 | 1. size: 5 86 | 2. size: 4 87 | ****/ 88 | int main() 89 | { 90 | test_empty(); 91 | cout<<"\n***********************************************\n"; 92 | test_size(); 93 | cout<<"\n***********************************************\n"; 94 | test_Pop(); 95 | cout<<"\n***********************************************\n"; 96 | Queue q; 97 | 98 | // insert three elements into the Queue 99 | q.Push("These "); 100 | q.Push("are "); 101 | q.Push("more than "); 102 | //cout << "number of elements in the Queue: " << q.size()<< endl; 103 | 104 | // read and print two elements from the Queue 105 | cout << q.front(); 106 | q.Pop(); 107 | cout << q.front(); 108 | q.Pop(); 109 | //cout << "number of elements in the Queue: " << q.size()<< endl; 110 | 111 | // insert two new elements 112 | q.Push("four "); 113 | q.Push("words!"); 114 | //cout << "\nnumber of elements in the Queue: " << q.size()<< endl; 115 | // skip one element 116 | q.Pop(); 117 | 118 | // read and print two elements 119 | cout << q.front(); 120 | q.Pop(); 121 | cout << q.front() << endl; 122 | q.Pop(); 123 | 124 | // print number of elements in the Queue 125 | cout << "number of elements in the Queue: " << q.size()<< endl; 126 | } 127 | /******* 128 | *运行结果: 129 | total: 55 130 | *********************************************** 131 | 0. size: 0 132 | 1. size: 5 133 | 2. size: 4 134 | *********************************************** 135 | Please enter some integers (enter 0 to end): 136 | 512 137 | 605 138 | 420 139 | 517 140 | 532 141 | 0 142 | myQueue contains: 512 605 420 517 532 0 143 | *********************************************** 144 | These are four words! 145 | number of elements in the Queue: 0 146 | Process returned 0 (0x0) execution time : 33.512 s 147 | Press any key to continue. 148 | ********/ 149 | -------------------------------------------------------------------------------- /src/queue/queue.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: queue.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/15 7 | * Description: verify correctness implement of queue 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "./queue.hpp" 13 | #include 14 | #include // 捕获特定类型异常 15 | using namespace std; 16 | 17 | //! \brief 测试实现的队列,包含了循环队列、非循环队列的分别测试 18 | //! 分别测试了入队、出队、移动赋值、移动构造、赋值、构造、取出第一个元素、取出末尾元素、改变第一个元素、改变末尾元素 19 | //! 以及队列改变前后时的状态:有效数据个数、队尾索引及其值、队头索引及其值、队列是否为空、如果为空,取值时会报异常。 20 | //! \run 21 | //! g++ queue.test.cc -std=c++11 && ./a.out 22 | int main(int argc, char const *argv[]) { 23 | 24 | // 测试 针对循环队列 25 | // Push Pop front back 26 | cout << "---------------------------测试循环队列!----------------------------------" << endl; 27 | cout << endl; 28 | glib::QueueCycle queue_cycle; // 默认初始化为 10 个空间 29 | for (size_t i = 0; i < 10; i++) { 30 | queue_cycle.Push(i); 31 | } 32 | queue_cycle.print_value(); // 0 1 2 3 4 5 6 7 8 9 tail_index: 10 back_value: 9 head_index: 0 front_value: 0 33 | queue_cycle.Push(11); 34 | cout << "after push 11 number: " << endl; 35 | queue_cycle.print_value(); // 0 1 2 3 4 5 6 7 8 9 tail_index: 10 back_value: 9 head_index: 0 front_value: 0 36 | cout << "size: " << queue_cycle.size() << endl; // size = 10 37 | cout << "front: " << queue_cycle.front() << endl; // front = 0 38 | cout << "back: " << queue_cycle.back() << endl; // back = 9 39 | queue_cycle.Pop(); 40 | queue_cycle.Push(11); 41 | cout << "after Pop and Push : " << endl; 42 | queue_cycle.print_value(); // 1 2 3 4 5 6 7 8 9 11 tail_index: 0 back_value: 11 head_index: 1 front_value: 1 43 | cout << "size: " << queue_cycle.size() << endl; // size = 10 44 | cout << "front: " << queue_cycle.front() << endl; // front = 1 45 | cout << "back: " << queue_cycle.back() << endl; // back = 11 46 | queue_cycle.Pop(); 47 | queue_cycle.Pop(); 48 | cout << "after two pop: " << endl; 49 | queue_cycle.print_value(); // 3 4 5 6 7 8 9 11 tail_index: 0 back_value: 11 head_index: 3 front_value: 3 50 | cout << "size: " << queue_cycle.size() << endl; // size = 8 51 | cout << "front: " << queue_cycle.front() << endl; // front = 3 52 | cout << "back: " << queue_cycle.back() << endl; // back = 11 53 | queue_cycle.back() = 12; 54 | queue_cycle.front() = 0; 55 | cout << "after change back(): " << endl; 56 | queue_cycle.print_value(); // 0 4 5 6 7 8 9 12 tail_index: 0 back_value: 12 head_index: 3 front_value: 0 57 | cout << "size: " << queue_cycle.size() << endl; // size = 8 58 | cout << "front: " << queue_cycle.front() << endl; // front = 0 59 | cout << "back: " << queue_cycle.back() << endl; // back = 12 60 | cout << "capacity: " << queue_cycle.capacity() << endl; // capacity = 10 61 | 62 | // 测试循环队列赋值拷贝函数 63 | cout << "测试循环队列拷贝函数,移动拷贝函数" << endl; 64 | glib::QueueCycle queue2_cycle = std::move(queue_cycle); // or queue_cycle 65 | cout << "size: " << queue2_cycle.size() << endl; // size = 8 66 | cout << "front: " << queue2_cycle.front() << endl; // front = 0 67 | cout << "back: " << queue2_cycle.back() << endl; // back = 12 68 | cout << "capacity: " << queue2_cycle.capacity() << endl; // capacity = 10 69 | // 测试队列为空时,队列的状态 70 | cout << "测试异常" << endl; 71 | if (queue_cycle.empty()) { 72 | cout << "queue_cycle is empty" << endl; 73 | cout << "capacity: " << queue_cycle.capacity() << endl; // capacity = 0 74 | try { 75 | queue_cycle.front(); // 测试异常: terminate called after throwing an instance of 'char const*' 76 | } catch (const char * except) { // 捕获自己 throw 的异常 77 | std::cerr << except << '\n'; 78 | } catch (const std::out_of_range& except) { // 捕获标准异常 79 | std::cerr << except.what() << '\n'; 80 | } 81 | } 82 | queue_cycle.print_value(); // 不打印,此时队列为空。 83 | queue2_cycle.print_value(); // 0 4 5 6 7 8 9 12 tail_index: 0 back_value: 12 head_index: 3 front_value: 0 84 | cout << "测试循环队列赋值,移动赋值函数" << endl; 85 | glib::QueueCycle queue3_cycle(5); 86 | queue3_cycle.print_value(); // 不打印,此时队列为空 87 | queue3_cycle = std::move(queue2_cycle); // or queue_cycle 88 | queue3_cycle.print_value(); // 0 4 5 6 7 8 9 12 tail_index: 0 back_value: 12 head_index: 3 front_value: 0 89 | 90 | cout << endl; 91 | cout << "---------------------------测试非循环队列!----------------------------------" << endl; 92 | // 测试 针对非循环队列 93 | cout << endl; 94 | glib::Queue queue; // 默认初始化为 10 个空间 95 | for (size_t i = 0; i < 10; i++) { 96 | queue.Push(i); 97 | } 98 | queue.print_value(); // 0 1 2 3 4 5 6 7 8 9 tail_index: 10 back_value: 9 head_index: 0 front_value: 0 99 | queue.Push(11); 100 | cout << "after push 11 number: " << endl; 101 | queue.print_value(); // 0 1 2 3 4 5 6 7 8 9 tail_index: 10 back_value: 9 head_index: 0 front_value: 0 102 | cout << "size: " << queue.size() << endl; // size = 10 103 | cout << "front: " << queue.front() << endl; // front = 0 104 | cout << "back: " << queue.back() << endl; // back = 9 105 | queue.Pop(); 106 | queue.Push(11); 107 | cout << "after Pop and Push : " << endl; 108 | queue.print_value(); // 1 2 3 4 5 6 7 8 9 11 tail_index: 10 back_value: 11 head_index: 0 front_value: 1 109 | cout << "size: " << queue.size() << endl; // size = 10 110 | cout << "front: " << queue.front() << endl; // front = 1 111 | cout << "back: " << queue.back() << endl; // back = 11 112 | queue.Pop(); 113 | queue.Pop(); 114 | cout << "after two pop: " << endl; 115 | queue.print_value(); // 3 4 5 6 7 8 9 11 tail_index: 10 back_value: 11 head_index: 2 front_value: 3 116 | cout << "size: " << queue.size() << endl; // size = 8 117 | cout << "front: " << queue.front() << endl; // front = 3 118 | cout << "back: " << queue.back() << endl; // back = 11 119 | queue.back() = 12; 120 | queue.front() = 0; 121 | cout << "after change back(): " << endl; 122 | queue.print_value(); // 0 4 5 6 7 8 9 12 tail_index: 10 back_value: 12 head_index: 2 front_value: 0 123 | // 验证数据搬移操作 124 | queue.Pop(); 125 | queue.Pop(); 126 | queue.print_value(); // 5 6 7 8 9 12 tail_index: 10 back_value: 12 head_index: 4 front_value: 5 127 | queue.Push(1); 128 | queue.print_value(); // 5 6 7 8 9 12 1 tail_index: 7 back_value: 1 head_index: 0 front_value: 5 129 | cout << "size: " << queue.size() << endl; // size = 7 130 | cout << "front: " << queue.front() << endl; // front = 5 131 | cout << "back: " << queue.back() << endl; // back = 1 132 | cout << "capacity: " << queue.capacity() << endl; // capacity = 10 133 | 134 | // 验证拷贝移动赋值等函数 135 | cout << "测试非循环队列拷贝函数,移动拷贝函数" << endl; 136 | glib::Queue queue2 = std::move(queue); // or queue_cycle 137 | cout << "size: " << queue2.size() << endl; // size = 7 138 | cout << "front: " << queue2.front() << endl; // front = 5 139 | cout << "back: " << queue2.back() << endl; // back = 1 140 | cout << "capacity: " << queue2.capacity() << endl; // capacity = 10 141 | 142 | // 测试队列为空时,队列的状态 143 | if (queue.empty()) { 144 | cout << "queue_cycle is empty" << endl; 145 | cout << "capacity: " << queue.capacity() << endl; // capacity = 0 146 | // queue.front(); // 测试异常:terminate called after throwing an instance of 'char const*' 147 | // 使用标准异常封装时:terminate called after throwing an instance of 'std::out_of_range' 148 | // what(): Fetch data from an empty queue! 149 | 150 | } 151 | queue.print_value(); // 不打印,此时队列为空。 152 | queue2.print_value(); // 5 6 7 8 9 12 1 tail_index: 7 back_value: 1 head_index: 0 front_value: 5 153 | cout << "测试非循环队列赋值,移动赋值函数" << endl; 154 | glib::Queue queue3(5); 155 | queue3.print_value(); // 不打印,此时队列为空 156 | queue3 = std::move(queue2); // or queue_cycle 157 | queue3.print_value(); // 5 6 7 8 9 12 1 tail_index: 7 back_value: 1 head_index: 0 front_value: 5 158 | 159 | return 0; 160 | } 161 | -------------------------------------------------------------------------------- /src/recursion/recursion.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: recursion.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/15 7 | * Description: some recursion examples 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #ifndef GLIB_RECURSION_HPP_ 13 | #define GLIB_RECURSION_HPP_ 14 | #include 15 | #include "../utils/types.h" // 包含了基本类型的别名 16 | 17 | //! \brief 简单完成了 3 个递归实例。电影院问题、爬楼梯问题、最终推荐人问题 18 | //! 对应函数:WhichRow、ClimbStairs、FinalRecommender 19 | //! 20 | //! \Note 21 | //! 1)下面 2 个递归实例:电影院问题和爬楼梯问题,都有相应的循环版本, 22 | //! 可通过定义或者取消 RECURSIVE 宏,实现运行相应版本。 23 | //! 2)代码中考虑了深度问题,以及重复元素问题(哈希表存储值) 24 | //! 25 | //! TODO 26 | //! 1)调试递归代码 27 | //! 2)编程实现一组数据集合的全排列 28 | //! 3)Leetcode 题目查找 29 | //! 4)最终推荐人非递归实现 30 | 31 | namespace glib { 32 | 33 | #define RECURSIVE // 表示后面的算法是否使用递归方法,否则将递归改为循环! 34 | 35 | #ifdef RECURSIVE 36 | #define MAX_DEPTH 50 // 限制递归深度 37 | // constexpr size_t kMAX_DEPTH = 50; // 或者使用这种方式 38 | #endif 39 | 40 | //! \description 电影院第几排问题。假设在第 n 排。 41 | //! \note 需要解决递归深度问题。两种方式其一:定义一个深度变量。如果深度大于某个值直接退出 42 | //! 方法二:转换为 for 循环 43 | //! \complexity 递归实现:O(n) 非递归实现:O(n) 44 | //! \method 当前排数 = 前一排数 + 1,协成递推公式: f(n) = f(n-1) + 1,确定终止条件为 f(1) = 1 45 | static size_t CurrentDepth = 0; 46 | int WhichRow(int row) { 47 | #ifdef RECURSIVE 48 | if (1 == row) 49 | return 1; 50 | if (CurrentDepth++ > MAX_DEPTH) { // 可以用 assert() 来替代 51 | std::cerr << "achive max depth!" << '\n'; 52 | exit(-1); 53 | } 54 | return 1 + WhichRow(row - 1); 55 | #else 56 | int which_row = 1; 57 | for (size_t i = 1; i < row; i++) { 58 | which_row = which_row + 1; 59 | } 60 | return which_row; 61 | #endif 62 | } 63 | 64 | //! \description n 个台阶问题: 共有 n 个台阶,每步要么走 1 个台阶,要么走 2 个台阶。问走到第 n 个台阶时有多少种情况? 65 | //! \note 需要用一个全局的散列表记录某个值是否计算成功。减少重复计算。并且对于递归方法限制一下递归深度 66 | //! \complexity 没有哈希表存储重复元素时,复杂度为指数阶:近似 O(2^(o.5n)),有哈希表存储时,复杂度量级也近似指数。 67 | //! \method 分析如下:最后一步时可以分解为两个部分。一个部分是还剩一个台阶,另一个部分是还剩两个台阶。 68 | //! 则可以假设 f(n) = f(n-1) + f(n-2) 其中 f() 函数表示对应有几种情况 69 | //! 终止条件: f(1) = 1, f(2) = 2 70 | static glib::HashMap StairAndValue; // 保存楼梯以及 f(楼梯) 71 | static size_t ClimbStairsRecursiveDepth = 0; 72 | int ClimbStairs(int stairs_num) { 73 | assert(stairs_num >= 1); 74 | #ifdef RECURSIVE 75 | if (1 == stairs_num) 76 | return 1; 77 | else if (2 == stairs_num) 78 | return 2; 79 | 80 | if ( StairAndValue.end() != StairAndValue.find(stairs_num)) 81 | return StairAndValue[stairs_num]; 82 | if (ClimbStairsRecursiveDepth++ > MAX_DEPTH) { // 可以用 assert 来替代 83 | std::cerr << "achive max recursion depth!" << '\n'; 84 | exit(-1); 85 | } 86 | int kinds = ClimbStairs(stairs_num - 1) + ClimbStairs(stairs_num - 2); 87 | // StairAndValue.insert(glib::HashMap::value_type(stairs_num, kinds)); 88 | StairAndValue.insert({stairs_num, kinds}); 89 | return kinds; 90 | #else 91 | if (1 == stairs_num) 92 | return 1; 93 | else if (2 == stairs_num) 94 | return 2; 95 | 96 | int preprekinds = 1; 97 | int prekinds = 2; 98 | int kinds = 0; 99 | for (size_t i = 3; i <= stairs_num; i++) { 100 | kinds = prekinds + preprekinds; 101 | preprekinds = prekinds; 102 | prekinds = kinds; 103 | } 104 | return kinds; 105 | #endif 106 | } 107 | 108 | //! \description 最终推荐人问题。首先要有一个推荐人表,第一栏是当前要查询的 id。第二栏是对应的推荐人 109 | //! \method 直接用递归方式,递推公式: f(id) = f(f(id)); f(id) == null 表示终止条件 110 | //! \param _Type 表示最终推荐人的表达方式,可以是 char、int 等类型 111 | //! \note 需要解决递归深度问题。两种方式其一:定义一个深度变量。如果深度大于某个值直接退出 112 | //! 方法二:转换为 for 循环 113 | //! \complexity 递归:复杂度为 O(n) 114 | static size_t FinalRecommenderRecursiveDepth = 0; 115 | static glib::HashSet CircleTable; 116 | template 117 | _Value FinalRecommender(const glib::KeyMap<_Key, _Value> &record_table, const _Key inquiry) { 118 | assert(inquiry != _Key()); 119 | // assert(FinalRecommenderRecursiveDepth++ < MAX_DEPTH); 120 | if (FinalRecommenderRecursiveDepth++ > MAX_DEPTH) { 121 | std::cerr << "achive max recursion depth!" << '\n'; 122 | exit(-1); 123 | } 124 | const auto &inquiry_record = record_table.find(inquiry); 125 | if (record_table.end() == inquiry_record) 126 | return inquiry; 127 | if (CircleTable.find(inquiry) != CircleTable.end()) { // 说明找到了环,直接退出 128 | std::cerr << "This recommender table is circled!" << '\n'; 129 | exit(-1); 130 | } 131 | 132 | CircleTable.insert(inquiry); 133 | return FinalRecommender(record_table, inquiry_record->second); // 如何这里用 first 就会出现段错误,也就是一直在这里循环 134 | } 135 | 136 | } // namespace glib 137 | 138 | #endif // GLIB_RECURSION_HPP_ 139 | -------------------------------------------------------------------------------- /src/recursion/recursion.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: recursion.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/15 7 | * Description: test some recursion examples 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include 13 | #include "../utils/types.h" // 包含了基本类型的别名 14 | #include "./recursion.hpp" 15 | 16 | using namespace std; 17 | 18 | //! \brief 对递归用例简单测试 19 | //! \run 20 | //! g++ recursion.test.cc -std=c++11 && ./a.out 21 | int main(int argc, char const *argv[]) { 22 | cout << "台阶问题求解:" << glib::ClimbStairs(7) << endl; 23 | cout << "电影院排求解:" << glib::WhichRow(5) << endl; 24 | 25 | // 有环情况测试 26 | cout << "最终推荐人有环测试" << endl; 27 | glib::KeyMap record_table = { {'A', 'B'}, 28 | {'B', 'C'}, 29 | {'C', 'D'}, 30 | {'D', 'A'} }; 31 | for (const auto& m: record_table) 32 | cout << "record_table: " << m.first << " " << m.second << endl; 33 | cout << "最终推荐人: " << glib::FinalRecommender(record_table, 'A') << endl; 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /src/skip_list/skip_list.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: skip_list.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/5/15 7 | * Description: skip list simple implementation 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #ifndef GLIB_SKIP_LIST_HPP_ 13 | #define GLIB_SKIP_LIST_HPP_ 14 | #include 15 | #include 16 | #include 17 | 18 | //! \brief 实现了跳表 19 | //! 基本功能: 20 | //! 1)插入:Insert 21 | //! 2)删除:Delete 22 | //! 3)查找;Find 23 | //! 4)打印跳表内的值:print_value 24 | //! 25 | //! \Note 26 | //! 1)目前只支持内置数值类型的数据 27 | //! 2)不支持拷贝赋值 28 | //! 3)该实现非理论上的跳表实现,每个节点对应有几层索引,在数据插入时就已经确定, 29 | //! 不会随着数据增多而变大或缩小 30 | //! \TODO 31 | //! 1)支持其他数据类型 32 | //! 2)能否变为理论上的跳表实现 33 | //! 3)动态修改索引层,根据数据插入和删除的次数,动态更新跳表索引层,实现理论跳表。 34 | //! 35 | //! \platform 36 | //! ubuntu16.04 g++ version 5.4.0 37 | 38 | namespace glib { 39 | using namespace std; 40 | 41 | template 42 | class SkipList { 43 | public: 44 | struct Node { 45 | _Scalar data; 46 | Node * forwards[MAX_LEVEL] = {nullptr}; // 每个索引对应的值,代表跳表相应层指向的下一个节点 47 | // 这个 16 个数组值,本质上就是指向不同的底层跳表节点 48 | int max_level; // 表示当前节点所在最大层数 49 | Node() : data(-1), max_level(0) {} 50 | }; 51 | 52 | SkipList() : current_max_level_(1) { 53 | head_ = new Node; 54 | } 55 | 56 | SkipList(std::initializer_list<_Scalar> il) : current_max_level_(1) { 57 | head_ = new Node; 58 | for (const auto &iter: il) 59 | Insert(iter); 60 | } 61 | 62 | ~SkipList() { 63 | if (nullptr != head_) { 64 | if (head_->forwards[0] != nullptr) { 65 | Node *p = head_->forwards[0]; 66 | while (p->forwards[0] != nullptr) { 67 | Node *next = p->forwards[0]; 68 | delete p; 69 | p = next; 70 | } 71 | delete p; 72 | } 73 | delete head_; 74 | } 75 | } 76 | SkipList(const SkipList& other) = delete; 77 | SkipList& operator=(const SkipList& other) = delete; 78 | 79 | // 给定值找到在链表中的索引 80 | //! \complexity 时间复杂度 O(log(n)) 空间复杂度 O(1) 81 | Node* Find(int value) { 82 | Node *p = head_; 83 | // 对每一层节点进行遍历 84 | for (int i = current_max_level_ - 1; i >= 0; --i) { 85 | // 这里 i 对应该跳表的遍历层数 86 | while (p->forwards[i] != nullptr && p->forwards[i]->data < value) { 87 | p = p->forwards[i]; 88 | } 89 | } 90 | if (p->forwards[0] != nullptr && p->forwards[0]->data == value) { 91 | return p->forwards[0]; 92 | } else { 93 | return nullptr; 94 | } 95 | } 96 | 97 | // 一边插入(按照数据值从小到大)数据,一边建立索引层 98 | //! \complexity 时间复杂度 O(log(n)) 空间复杂度 O(1) 99 | void Insert(int value) { 100 | int level = Random(); // 每次插入一个数据 都会得到一个当前数据能够达到的跳表层 101 | Node *new_node = new Node; 102 | new_node->data = value; 103 | new_node->max_level = level; 104 | Node *temp[level] = {nullptr}; // 保留将要插入的数据对应每层的位置,方便后面把数据插入到相应的位置,然后构建 level 层索引 105 | 106 | // 遍历整个跳表,找到该值对应的每一层的位置 107 | Node *p = head_; 108 | for (int i = level - 1; i >= 0; --i) { 109 | while (p->forwards[i] != nullptr && p->forwards[i]->data < value) { 110 | p = p->forwards[i]; 111 | } 112 | temp[i] = p; // 保留每一层的前驱节点(值小于 value) 113 | } 114 | 115 | // 上面找到了每一层的位置后,把将要插入的值 value 放置到对应位置 116 | for (int i = level - 1; i >= 0; --i) { 117 | new_node->forwards[i] = temp[i]->forwards[i]; 118 | temp[i]->forwards[i] = new_node; // 每一层都指向跳表底层新建立的节点 119 | } 120 | 121 | // 更新跳表中最大层数 122 | if (current_max_level_ < level) current_max_level_ = level; 123 | } 124 | 125 | // 根据给定值,删除跳表中对应的节点。本质上仅仅删除了底层链表上节点。 126 | // 如果该值含有重复数据,这里仅仅删除其中一个数据 127 | //! \complexity 时间复杂度 O(logn) 空间复杂度 O(1) 128 | void Delete(int value) { 129 | Node *temp[current_max_level_] = {nullptr}; 130 | Node *p = head_; 131 | 132 | // 找到给定值对应链表节点的前驱节点 133 | for (int i = current_max_level_ - 1; i >=0; --i) { 134 | while (p->forwards[i] != nullptr && p->forwards[i]->data < value) { 135 | p = p->forwards[i]; 136 | } 137 | temp[i] = p; 138 | } 139 | 140 | Node *find_value = nullptr; 141 | if (p->forwards[0] != nullptr && p->forwards[0]->data == value) { 142 | find_value = p->forwards[0]; 143 | } 144 | 145 | // 跳表中确实含有该值,那么删除其值等于给定值 value 的底层根节点,且改变前驱节点的指向 146 | if (nullptr != find_value) { // 找到了要删除的节点值 147 | for (int i = current_max_level_ - 1; i >= 0; --i) { 148 | // 改变每层索引的指向 149 | if (temp[i]->forwards[i] != nullptr && temp[i]->forwards[i]->data == value) 150 | temp[i]->forwards[i] = find_value->forwards[i]; 151 | } 152 | // 最后删除底层的根节点 153 | delete find_value; 154 | } 155 | } 156 | 157 | // 产生一个随机跳表高度 158 | int Random() { 159 | int level = 1; 160 | std::random_device random_device_; // 随机数生成器 161 | for (int i = 0; i < max_level_ - 1; ++i) { 162 | if (random_device_() % 2 == 1) 163 | level++; 164 | } 165 | return level; 166 | } 167 | 168 | void print_value() { 169 | Node *p = head_; 170 | while (p->forwards[0] != nullptr) { 171 | std::cout << " " << p->forwards[0]->data; 172 | p = p->forwards[0]; 173 | } 174 | cout << endl; 175 | } 176 | 177 | private: 178 | Node *head_; // 底层链表的头结点 179 | const int max_level_ = MAX_LEVEL; // 即 [0 - 16) 180 | int current_max_level_; // 当前跳表内最大索引层数,有效层数是其 - 1 181 | }; // class SkipList 182 | 183 | } // namespace glib 184 | 185 | #endif 186 | -------------------------------------------------------------------------------- /src/skip_list/skip_list.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: skip_list.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/5/15 7 | * Description: skip list simple test 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | #include "skip_list.hpp" 12 | #include 13 | using namespace std; 14 | 15 | //! \brief 跳表功能的简单测试 16 | //! \run 17 | //! g++ skip_list.test.cc -std=c++11 && ./a.out 18 | int main(int argc, char const *argv[]) { 19 | { 20 | glib::SkipList skip_list{5, 4, 3, 2, 1}; 21 | 22 | // 测试插入 23 | cout << "测试插入" << endl; 24 | skip_list.Insert(9); 25 | skip_list.print_value(); 26 | cout << endl; 27 | 28 | // 测试查找 29 | cout << "测试查找" << endl; 30 | cout << skip_list.Find(3)->data << endl; 31 | cout << endl; 32 | 33 | // 测试删除 34 | cout << "测试删除" << endl; 35 | skip_list.Delete(6); 36 | skip_list.print_value(); 37 | skip_list.Delete(5); 38 | skip_list.print_value(); 39 | cout << endl; 40 | } 41 | 42 | // 测试跳表超出作用域后,释放内部节点 43 | cout << "退出" << endl; 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /src/sort/sort.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: sort.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/15 7 | * Description: test and use sort algorithms 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "sort.hpp" 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | 18 | size_t Random(int x) { 19 | return rand()%x; 20 | } 21 | 22 | //! \brief 排序算法简单测试,通过随机生成 10000 个数据,分别测试不同排序算法的执行时间。 23 | //! \run 24 | //! g++ sort.test.cc -std=c++11 && ./a.out 25 | 26 | int main(int argc, char const *argv[]) { 27 | // 随机生成一个大的数列,测试下面排序算法效率 28 | srand((int)time(0)); 29 | size_t n = 10000; 30 | vector vec_bubble(n); 31 | vector vec_insert(n); 32 | vector vec_selection(n); 33 | vector vec_merge(n); 34 | vector vec_quick(n); 35 | vector vec_counting(n); 36 | for (size_t i = 0; i < n; i++) { 37 | size_t re = Random(n); 38 | vec_bubble[i] = re; 39 | vec_insert[i] = re; 40 | vec_selection[i] = re; 41 | vec_merge[i] = re; 42 | vec_quick[i] = re; 43 | vec_counting[i] = re; 44 | } 45 | 46 | 47 | // 冒泡排序测试 48 | // std::vector vec_bubble = {6, 5, 4, 3, 2, 1}; 49 | auto start_bubble = chrono::system_clock::now(); 50 | glib::Sort(vec_bubble, glib::SortOption::BUBBLE); 51 | auto end_bubble = chrono::system_clock::now(); 52 | chrono::duration elaspsed_seconds_bubble = end_bubble - start_bubble; 53 | cout << " bubble elaspsed_seconds: " << elaspsed_seconds_bubble.count() << endl; 54 | // for (auto &p: vec_bubble) 55 | // cout << " " << p; 56 | cout << endl; 57 | 58 | 59 | // 插入排序测试 60 | // std::vector vec_insert = {6, 6, 6, 6, 6, 5}; // 5,5,7,8,5 61 | // std::vector vec_insert = {5, 5, 7, 8, 5, 1}; // 5,5,7,8,5 62 | auto start_insert = chrono::system_clock::now(); 63 | glib::Sort(vec_insert, glib::SortOption::INSERTION); 64 | auto end_insert = chrono::system_clock::now(); 65 | chrono::duration elaspsed_seconds = end_insert - start_insert; 66 | cout << " insert elaspsed_seconds: " << elaspsed_seconds.count() << endl; 67 | // for (auto &p: vec_insert) 68 | // cout << " " << p; 69 | cout << endl; 70 | 71 | 72 | // 选择排序测试 73 | // vector vec_selection = {6, 5, 4, 3, 2, 1}; // 简单测试 74 | // vector vec_selection = {5, 5, 7, 8, 5, 1}; 75 | auto start_selection = chrono::system_clock::now(); 76 | glib::Sort(vec_selection, glib::SortOption::SELECTION); 77 | auto end_selection = chrono::system_clock::now(); 78 | chrono::duration elaspsed_seconds_selection = end_selection - start_selection; 79 | cout << " selection elaspsed_seconds: " << elaspsed_seconds_selection.count() << endl; 80 | // for (auto &p: vec_selection) 81 | // cout << " " << p; 82 | cout << endl; 83 | 84 | 85 | // 归并排序测试 86 | // vector vec_merge = {4, 4, 5, 2, 4}; 87 | // vector vec2 = {1, 6, 10, 15}; 88 | // glib::Sort u; 89 | // auto vv = u.Merge(vec_merge, vec2); // 测试两个有序数组融合没问题 90 | auto start_merge = chrono::system_clock::now(); 91 | glib::Sort(vec_merge, glib::SortOption::MERGE); 92 | auto end_merge = chrono::system_clock::now(); 93 | chrono::duration elaspsed_seconds_merge = end_merge - start_merge; 94 | cout << " merge elaspsed_seconds: " << elaspsed_seconds_merge.count() << endl; 95 | // for (auto &p: vec_merge) 96 | // cout << " " << p; 97 | cout << endl; 98 | 99 | 100 | // 快速排序测试 101 | // vector vec_quick = {6, 5, 4, 3, 3, 2, 1, 5}; 102 | auto start_quick = chrono::system_clock::now(); 103 | glib::Sort(vec_quick, glib::SortOption::QUICK); 104 | auto end_quick = chrono::system_clock::now(); 105 | chrono::duration elaspsed_seconds_quick = end_quick - start_quick; 106 | cout << " quick elaspsed_seconds: " << elaspsed_seconds_quick.count() << endl; 107 | // for (auto &p: vec_quick) 108 | // cout << " " << p; 109 | cout << endl; 110 | 111 | 112 | // 计数排序测试 113 | // vector vec_counting = {6, 5, 4, 3, 2, 1, 6, 2, 0, 3, -1, -1, -2, -2, -3, -3}; 114 | // vector vec_counting = {1}; 115 | auto start_counting = chrono::system_clock::now(); 116 | glib::Sort(vec_counting, glib::SortOption::COUNTING); 117 | auto end_counting = chrono::system_clock::now(); 118 | chrono::duration elaspsed_seconds_counting = end_counting - start_counting; 119 | cout << " counting elaspsed_seconds: " << elaspsed_seconds_counting.count() << endl; 120 | // for (auto &p: vec_counting) 121 | // cout << " " << p; 122 | cout << endl; 123 | 124 | // 验证后面的的排序结果是否与冒泡排序一致!进而检验后面排序算法的实现是否正确 125 | for (size_t i = 0; i < n; i++) { 126 | // cout << vec_bubble[i] << " "; 127 | if (vec_bubble[i] != vec_insert[i]) { 128 | cout << "insert sort error!" << endl; 129 | break; 130 | } 131 | if (vec_bubble[i] != vec_selection[i]) { 132 | cout << "selection sort error!" << endl; 133 | break; 134 | } 135 | if (vec_bubble[i] != vec_merge[i]) { 136 | cout << "merge sort error!" << endl; 137 | break; 138 | } 139 | if (vec_bubble[i] != vec_quick[i]) { 140 | cout << "quick sort error!" << endl; 141 | break; 142 | } 143 | if (vec_bubble[i] != vec_counting[i]) { 144 | cout << "counting sort error!" << endl; 145 | break; 146 | } 147 | } 148 | 149 | // 利用快速排序思路查找第 k 大元素 150 | vector vev{1, 2, 3, 4, 5, 6, 6, 6}; 151 | cout << "kth ---> value " << glib::FindKthBigElement(vev, 3) << endl; 152 | 153 | // 验证,提示其他类型数据不能使用计数排序! 154 | vector vec_char_counting = {'c', 'a'}; 155 | glib::Sort(vec_char_counting, glib::SortOption::COUNTING); 156 | 157 | return 0; 158 | } 159 | -------------------------------------------------------------------------------- /src/stack/stack.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: stack.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/8 7 | * Description: using stack test 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "./stack.hpp" 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | 18 | //! \brief 简单测试栈的基本功能 19 | //! \run 20 | //! g++ stack.test.cc -std=c++11 && ./a.out 21 | int main(int argc, char const *argv[]) { 22 | // 测试堆栈! 23 | cout << "测试压栈,出栈" << endl; 24 | glib::Stack stac(5); 25 | for (size_t i = 0; i < 10; i++) { 26 | stac.Push("abc"); 27 | } 28 | for (size_t i = 0; i < 20; i++) { 29 | cout << stac.Pop().second << " "; 30 | } // 10 个 abc 31 | cout << "sizeof(string): " << sizeof(string) << endl; 32 | cout << endl; 33 | 34 | // 测试 glib::EliminateAdjacent() 35 | cout << "测试消消乐" << endl; 36 | string ss("10"); 37 | cout << glib::stack_app::EliminateAdjacent(ss) << endl; // 0 38 | cout << endl; 39 | 40 | // 测试 Element 元素 使用 EliminateAdjacent() 41 | cout << "测试消消乐通用框架,测试 Element 元素 使用 EliminateAdjacent()" << endl; 42 | vector> str; 43 | for (size_t i = 0; i < 20; i++) { 44 | str.push_back(glib::stack_app::Element('1')); 45 | str.push_back(glib::stack_app::Element('1')); 46 | } 47 | for (size_t i = 0; i < 2; i++) { 48 | str.push_back(glib::stack_app::Element('0')); 49 | } 50 | cout << glib::stack_app::EliminateAdjacent(str) << endl; 51 | cout << endl; 52 | 53 | // 测试 vector 使用 glib::EliminateAdjacent() 54 | cout << "测试 vector 直接进行消消乐 " << endl; 55 | vector str1= {'1','1','1','1','1','1','1','1','1','1'}; 56 | cout << glib::stack_app::EliminateAdjacent({'1','1','1','1','1','1','1','1','1','1'}) << endl; 57 | cout << endl; 58 | 59 | // 测试运算符表达式 34+13*9+44-12/3。结果为 191 60 | // 运行时输入上面表达式 61 | cout << "测试运算符表达式" << endl; 62 | string input; 63 | cin >> input; 64 | // std::map priority_table; 65 | // priority_table.insert({"+",0}); 66 | // priority_table.insert({"-",0}); 67 | // priority_table.insert({"*",1}); 68 | // priority_table.insert({"/",1}); 69 | //or // 优先级表,对应数字越大表示优先级越高! 70 | std::map priority_table = { {"+", 0}, {"-", 0}, 71 | {"*", 1}, {"/", 1} }; 72 | cout << glib::stack_app::StackForExpression(input, {'+','-','*','/'}, priority_table) << endl; 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /src/stl_examples/use_hashmap.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: use_hashmap.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/15 7 | * Description: use hash_map 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | // #include // 这种方式已经不用了! 13 | #include // 哈希表 14 | #include 15 | #include 16 | #include 17 | using namespace std; 18 | template 19 | using KeyMap = std::unordered_map<_Key, _Value>; 20 | 21 | //! \brief 简单学习使用 std::unordered_map 22 | //! \run 23 | //! g++ use_hashmap.cc -std=c++11 && ./a.out 24 | //! \reference 25 | //! 1)C++ STL中哈希表 hash_map介绍--->https://blog.csdn.net/ddkxddkx/article/details/6555754#t4 26 | //! 2)C++ 中标准库 map 和 hash_map 的使用方法--->https://blog.csdn.net/qq_26399665/article/details/52295380 27 | //! 3)C++ unordered_map--->https://blog.csdn.net/charles1e/article/details/52042066 28 | //! 4)hashCode 方法及 equals 方法的规范--->https://www.sczyh30.com/posts/Java/java-hashcode-equal/ 29 | //! 5)C++ STL 之哈希表 | unordered_map--->https://www.sczyh30.com/posts/C-C/cpp-stl-hashmap/ 30 | //! 6) 31 | int main(int argc, char const *argv[]) { 32 | KeyMap hashs; 33 | hashs[0] = "唐伯虎"; 34 | hashs[1] = "sans"; 35 | if (hashs.find(1) != hashs.end()) { 36 | cout << "hashs: " << hashs[1] << endl; 37 | } 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/stl_examples/use_map.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: use_map.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/15 7 | * Description: use map 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | using namespace std; 16 | 17 | // 通用类型声明 18 | template 19 | using KeyMap = std::map<_Key, _Value>; 20 | 21 | //! \brief 简单学习使用标准库 std::map 22 | //! map 的功能 23 | //! 1)自动建立Key - value的对应。key 和 value可以是任意你需要的类型。 24 | //! 2)快速插入Key - Value 记录。--- inset() 25 | //! 3) 根据key值快速查找记录,查找的复杂度基本是Log(N),如果有1000个记录,最多查找10次, 26 | //! 1,000,000个记录,最多查找20次。 --- find() 27 | //! 4)遍历所有记录。--- for(auto &value: xxx) xxx; 28 | //! 5)根据Key 修改value记录。TODO 29 | //! 6)快速删除记录 --- erase() TODO 30 | //! 31 | //! \run 32 | //! g++ use_hashmap.cc -std=c++11 && ./a.out 33 | //! \TODO 34 | //! 1)完成上面第 5)点 35 | //! 2)完成上面第 6)点 36 | //! \TODO Reference 37 | //! 1)C++ Map常见用法说明 38 | int main(int argc, char const *argv[]) { 39 | KeyMap key_map; 40 | // 1)插入元素 41 | // 低效插入方式 42 | key_map[0] = "One"; 43 | key_map[1] = "Two"; // 先查找有没有键值 1,没有的话先构造一个,然后把 "Two" 拷贝进去 44 | 45 | // 2)高效插入,通过移动构造函数 46 | key_map.insert(KeyMap::value_type(2, "three")); 47 | 48 | // 3)查询元素 auto iter = find(_Key); 返回一个迭代器。 可以使用 iter.first iter.second 49 | if (key_map.find(1) != key_map.end()) { // 表示查找到,且 find() 返回一个迭代器,类型是 std::pair 50 | cout << "查找到的元素: " << key_map[1]; 51 | } else { 52 | // 处理没有查找到的情况! 53 | } 54 | 55 | // 4)遍历 56 | for (auto &value: key_map) { 57 | cout << "key: " << value.first << " value: " << value.second << endl; 58 | } 59 | 60 | // 5)修改记录 61 | 62 | 63 | // 4)删除元素: erase(iterator it); erase(iterator first, iterator last); 64 | // erase(const _Key& key); 通过关键字删除 clear(); 相当于删除所有元素! 65 | 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /src/stl_examples/use_sort.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: use_sort.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/15 7 | * Description: std sort algo example 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include 13 | #include 14 | 15 | //! \brief 简单学习使用 std::sort 16 | //! \run 17 | //! g++ use_sort.cc -std=c++11 && ./a.out 18 | //! \TODO 19 | //! 1)增加学习代码 20 | int main(int argc, char const *argv[]) { 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /src/tree/binary_search_tree.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: binary_search_tree.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/8 7 | * Description: using stack test 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "binary_search_tree.hpp" 13 | #include 14 | using namespace std; 15 | 16 | //! \brief 测试二叉搜索树核心算法 17 | //! \run 18 | //! g++ binary_search_tree.test.cc -std=c++11 && ./a.out 19 | 20 | int main(int argc, char const *argv[]) { 21 | // 二叉查找树的构建及插入测试 22 | cout << "二叉查找树插入测试" << endl; 23 | glib::BinarySearchTree tree{33, 16, 50, 13, 18, 34, 58, 15, 17, 25, 51, 66, 19, 27, 55, 56}; 24 | 25 | // glib::BinarySearchTree tree{1, 2}; 26 | // glib::BinarySearchTree tree{13, 10, 16, 9, 11, 14}; 27 | cout << endl; 28 | 29 | // 测试二叉查找树三种遍历方式 30 | cout << "二叉查找树四种遍历方式" << endl; 31 | cout << "前序遍历:"; 32 | tree.print_by_front_order(); 33 | cout << endl; 34 | cout << "中序遍历:"; 35 | tree.print_by_middle_order(); 36 | cout << endl; 37 | cout << "后序遍历:"; 38 | tree.print_by_back_order(); 39 | cout << endl; 40 | cout << "层序遍历:"; 41 | tree.print_by_level_order(); 42 | cout << endl; 43 | 44 | // 测试二叉查找树查询 45 | cout << "二叉查找树查询测试" << endl; 46 | cout << "当前已有数据中序顺序为(从小到大输出):"; 47 | tree.print_by_middle_order(); 48 | cout << endl; 49 | cout << "请输入要查找的数据:"; 50 | int element_inquired; 51 | cin >> element_inquired; 52 | if (tree.Find(element_inquired) != nullptr) 53 | cout << "内部含有被查询的数据!" << endl; 54 | else 55 | cout << "内部不含被查询的数据" << endl; 56 | 57 | // 测试树的高度 58 | cout << "测试树的高度" << endl; 59 | cout << "树的高度(递归方法):" << tree.TreeHeight(glib::internal::TreeHeightOption::RECURSIVE) << endl; 60 | cout << "树的高度(非递归方法):" << tree.TreeHeight() << endl; 61 | 62 | // 测试树的删除操作 63 | // while (1) { 64 | cout << "测试树的删除操作" << endl; 65 | cout << "请输入要删除的值:"; 66 | int element_deleted; 67 | cin >> element_deleted; 68 | tree.Delete(element_deleted); 69 | cout << "删除后,中序遍历结果:"; 70 | tree.print_by_level_order(); 71 | cout << endl; 72 | // } 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /src/utils/glib_exception.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: glib_exception.h 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/5/2 7 | * Description: some exception implementation 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #ifndef GLIB_GLIB_EXCEPTION_H_ 13 | #define GLIB_GLIB_EXCEPTION_H_ 14 | #include // 标准库异常 15 | 16 | //! \brief 一些自定义异常 17 | //! 18 | //! \Note 19 | //! 1)还没有实现一个通用的异常框架,当前该文件还不能使用 20 | //! 21 | //! \TODO 22 | //! 1)准备实现一个通用的异常框架,可以用一个类 or 可调用对象,比如 Lambda 表达式 23 | //! 24 | //! \platform 25 | //! ubuntu16.04 g++ version 5.4.0 26 | 27 | 28 | namespace glib { 29 | namespace utils { 30 | 31 | //! \from 队列为空时操作就会报异常 32 | //! \Note 在使用该宏时,需要内部函数 empty() 函数,想办法如何把该函数变通用 33 | #define REQUIRES_NONEMPTY ( \ 34 | [&]() { if (empty()) throw std::out_of_range("Fetch data from an empty queue!"); } \ 35 | ) 36 | 37 | } // namespace utils 38 | } // namespace glib 39 | 40 | #endif // GLIB_GLIB_EXCEPTION_H_ 41 | -------------------------------------------------------------------------------- /src/utils/string_common.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: string_common.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/8 7 | * Description: manipulate string method 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #ifndef GLIB_STRING_COMMON_HPP_ 13 | #define GLIB_STRING_COMMON_HPP_ 14 | #include 15 | #include 16 | #include 17 | 18 | //! \brief 一些自定义处理字符串函数库,将字符串和 vector 进行转换 19 | //! 基本功能 20 | //! 1)字符串按照符号分割、将由纯数字构成的字符串容器转换成数值类型, 21 | //! 可用符号集:',' '.' ' ', '-' 等等各种符号 22 | //! 对应函数:Split、StringsToInt、StringsToFloat 23 | //! 24 | //! \platform 25 | //! ubuntu16.04 g++ version 5.4.0 26 | //! 27 | //! \example 28 | // 假定使用了 using namespace std; 29 | //string str("1,2,3_44-55"); 30 | //string symbol = {',','_',' ', '-'}; 31 | // vector substrs = glib::utils::Split(str, symbol); // 产生 1 2 3 44 55 总共 5 个字符串 32 | // vector substrs = glib::utils::Split(str, {',','.','!'}); 33 | // for (auto iter = substrs.begin(); iter != substrs.end(); ) 34 | // cout << *iter++ << " "; 35 | // cout << endl; 36 | // vector ints = glib::utils::StringsToInt(substrs); 37 | // for (size_t i = 0; i < ints.size(); i++) { 38 | // cout << ints[i] << " "; 39 | // } 40 | 41 | namespace glib { 42 | namespace utils { 43 | 44 | //! \brief 将 string 保存的字符串按照符号分割成子串。 45 | //! \complexity 时间复杂度:O(n) 空间复杂度:O(n) 46 | //! \param str 被分割的字符串 47 | //! \param symbol 分割的字符集。可用初始化列表作为输入 48 | //! \param reserve_symbol 是否保留符号,默认情况下,分割字符串时不保留符号。 49 | //! \return 分割好的字符串 50 | std::vector Split(const std::string &str, 51 | const std::string symbol, 52 | const bool reserve_symbol = false) { 53 | std::vector substrs; 54 | std::string sub; 55 | for (const auto &ch : str) { 56 | if (symbol.find(ch) != std::string::npos) { // 表示在给定的分隔符号表中 57 | if (!sub.empty()) // 防止连续的符号在一块 58 | substrs.push_back(sub); 59 | if (reserve_symbol) { substrs.push_back(std::string(1, ch)); } // 保存符号 60 | sub.clear(); // 清空 string 61 | } else 62 | sub += ch; 63 | } 64 | if (!sub.empty()) 65 | substrs.push_back(sub); 66 | return substrs; 67 | } 68 | 69 | //! \brief 将由数字组成的多个子串转换为内置数值类型 比如 vector{"12", "23"}---> 12 23 70 | //! \note 所有字符串包含的数字,要转换成的类型必须相同!! 默认 int 类型 71 | std::vector StringsToInt(const std::vector &str) { 72 | std::vector ints; 73 | for (auto iter = str.begin(); iter != str.end(); ) 74 | ints.emplace_back(stoi(*iter++)); 75 | return ints; 76 | } 77 | 78 | //! \brief 字符串转换为 float 类型集合 79 | //! \note 与上个 StringsToInt 函数一致 80 | std::vector StringsToFloat(const std::vector &str) { 81 | std::vector floats; 82 | for (auto iter = str.begin(); iter != str.end(); ) 83 | floats.emplace_back(stof(*iter++)); 84 | return floats; 85 | } 86 | 87 | } // namespace utils 88 | } // namespace glib 89 | 90 | #endif // GLIB_STRING_COMMON_HPP_ 91 | -------------------------------------------------------------------------------- /src/utils/tic_toc.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: tic_toc.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/5/30 7 | * Description: static time consumption 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #ifndef GLIB_TIC_TOC_H_ 13 | #define GLIB_TIC_TOC_H_ 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | // 记录每个程序段使用的时间 22 | class TicToc { 23 | public: 24 | 25 | TicToc() { 26 | tic(); 27 | } 28 | 29 | void tic() { 30 | start_ = std::chrono::system_clock::now(); 31 | } 32 | 33 | double toc() { 34 | end_ = std::chrono::system_clock::now(); 35 | std::chrono::duration elapsed_seconds = end_ - start_; 36 | return elapsed_seconds.count() * 1000; 37 | } 38 | 39 | private: 40 | std::chrono::time_point start_, end_; 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/utils/types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: types.hpp 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/15 7 | * Description: some basic type alias 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #ifndef GLIB_TYPES_H_ 13 | #define GLIB_TYPES_H_ 14 | #include // 无序地图,内部实现是哈希表 15 | #include 16 | #include // map 内部实现是红黑树 17 | #include 18 | #include // 定义了 size_t 类型 19 | #include 20 | #include // std::shared_ptr 21 | 22 | //! \brief 一些自定义标准库别名 23 | //! 包含内容:HashMap、HashSet、KeyMap、Set、SharePtr 24 | //! 25 | //! \TODO 26 | //! 1)加入更多的别名 27 | //! 28 | //! \platform 29 | //! ubuntu16.04 g++ version 5.4.0 30 | 31 | namespace glib { 32 | 33 | // 哈希表---无序地图 输出是无序的 34 | template 35 | using HashMap = std::unordered_map<_Key, _Value>; 36 | 37 | // map 38 | template 39 | using KeyMap = std::map<_Key, _Value>; 40 | 41 | // 哈希 set 42 | template 43 | using HashSet = std::unordered_set<_Key>; 44 | 45 | // set 46 | template 47 | using Set = std::set<_Key>; 48 | 49 | // 共享智能指针 50 | template 51 | using SharePtr = std::shared_ptr<_T>; 52 | 53 | } // namespace glib 54 | 55 | #endif // GLIB_TYPES_H_ 56 | -------------------------------------------------------------------------------- /template/README.md: -------------------------------------------------------------------------------- 1 | ## :pencil2: 内容说明 2 | 3 | 一份简单的代码格式文件。模板化了当前项目源码代码风格。`template.hpp` 是算法或者数据结构的实现文件,`template.test.cc` 是算法对应的测试文件。`other_type.h` 是一些类型或者其他的声明。`template.md` 是该算法 or 数据结构的笔记。笔记具体格式可以直接参考 `notes` 文件夹下的笔记。 4 | 5 | **持续更新中 ......** -------------------------------------------------------------------------------- /template/other_type.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef GLIB_TEMPLATE_H_ 3 | // 这个文件与 template.hpp 是一样的,只不过这里变为 _H_ 结尾, 4 | // 这里主要写一些需要用的类型声明,函数声明等等。 5 | // 当然最好不需要写这个类型声明,直接在 template.hpp 中全部写好 6 | -------------------------------------------------------------------------------- /template/template.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj // 在后面添加上你的署名 3 | * File: template.hpp // 该算法对应的名字,以小写+下划线形式命名 4 | * Project: algorithm 5 | * Author: gcj // 在后面添加你自己的署名 6 | * Date: 2019/4/8 // 修改成当前文件创作完成时的日期 7 | * Description: a template // 简单描述文件包含的算法 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #ifndef GLIB_TEMPLATE_HPP_ // 将 TEMPLATE 替换为当前文件的名字 13 | #define GLIB_TEMPLATE_HPP_ 14 | 15 | #include // 一些标准库 16 | #include 17 | #include 18 | #include "ohter_type.h" // 你自己定义的 19 | 20 | // 下面对应内容需要根据贡献指导文档 CONTRIBUTION.md 的介绍自行替换。 21 | // 以及参照代码相应的规范文档 CODING_GUIDELINES.md 调整下面的示例。 22 | 23 | //! \brief 描述该文件对应的功能 24 | //! 基本功能: 25 | //! 1)支持动态增加内存从后插入元素、下标访问功能[]、合并两个有序数组(仅支持小到大内置类型) 26 | //! 对应函数:PushBack、operator[]、Merge 27 | //! 28 | //! 2)其他普通函数:返回数组有效字节数、数组总容量、排序函数 29 | //! 对应函数:size、capacity、Sort 30 | //! 31 | //! 3)内部函数:比较两个元素大小 32 | //! 对应函数:Compare 33 | //! 34 | //! \Note 35 | //! 1)该数组不支持,赋值、移动赋值、移动拷贝功能,仅仅做了一个动态数组的简单实现 36 | //! 37 | //! \TODO 38 | //! 1)支持移动赋值、移动构造功能 39 | //! 2)实现 LRU 缓存淘汰策略——在单链表中已经实现了 40 | //! 3)实现一个大小固定的有序数组,支持动态增删改操作——实际上考核插入时的数据移动、删除时的垃圾回收策略 41 | //! 4)leetCode 题目待查找 42 | //! 43 | //! \platform 44 | //! ubuntu16.04 g++ version 5.4.0 45 | //! 46 | //! \TODO Reference 47 | //! 48 | //! \conclusion 49 | //! 50 | 51 | namespace glib { // 为了保证整个项目的一致,这个命名空间不变,仍然使用 glib 52 | using namespace std; 53 | ...... 其他一些声明 54 | 55 | #define MY_MACRO_THAT_SCARES; // 你自己定义宏处,要全大写加下划线(但是最好不要使用宏) 56 | const int GlibGlobalVariable; // 如果你想定义命名空间的全局变量,此时该变量作为 glib 命名空间的一个接口, 57 | // 此时按照函数命名规则,首字母大写 58 | static int GlibGlobalVariable; // 当然,最好不要定义一个命名空间内的静态存储期属性变量/常量 59 | 60 | template // 如果有必要,可以做一些模板类的前置声明 61 | class Template; 62 | 63 | // 简单函数直接 「// + 注释」,或者 「\brief+注释」, 64 | // 复杂函数可以按照下面 CoreFunction 上面的注释说明 65 | 66 | // 该函数的功能 67 | //! \brief 该函数的功能 68 | void FuncTo2() { // 一个全局非内联函数的写法,供外部调用 69 | static int temp_variable; // 函数内部静态变量,命名规则是小写加下划线,与普通变量命名一致 70 | } 71 | 72 | inline void FuncTo() { // 一个全局的内联函数的写法 73 | 74 | } 75 | 76 | namespace template_internal { // 如果想定义一些当前文件内部使用的函数 77 | // 可以使用 internal_文件名,来定义命名空间 78 | //此时默认规定,当前函数只能用在当前文件中使用 79 | const static int InternalVariable; 80 | static int InternalVariable; 81 | 82 | // 定义一些当前文件内部需要使用的函数 83 | // 改造后的内联函数 84 | inline void FuncTodo() { 85 | 86 | } 87 | 88 | } // namespace template_internal 89 | 90 | enum class TemplateEnum { // 如果使用了枚举,那么按照这个格式,内部元素是大写,下划线连接 91 | PRE_ORDER, 92 | IN_ORDER, 93 | POST_ORDER 94 | }; 95 | 96 | 97 | struct UrlTableProperties { // 如果使用了结构体,最后没有下划线,全部都是小写,下划线连接单词 98 | const int table_name = 5; 99 | int table_name; 100 | static int table_name; 101 | }; 102 | 103 | // 可以在这里做一下当前类的简单介绍 104 | template > 105 | class Template { // 核心算法的开发,需要替换为当前实现算法的名字 106 | public: // 类型声明 107 | // 一系列的类型的声明处 108 | using KeyType = _Key; 109 | using MappedType = _Value; 110 | using Hasher = _Hash; 111 | template <_Scalar> 112 | using VectorTemplate = std::vector<_Scalar>; 113 | typedef std::vector VectorInt; 114 | 115 | public: // 构造函数相关 116 | Template() : template_const_member_(0), template_member_(2) { cout << "默认构造函数" << endl; } 117 | explicit Template(int template_member) : template_member_(template_member) {} 118 | Template(int template_const_member, int template_member) 119 | : template_member_(template_member), template_const_member_(template_const_member) { 120 | // 内部其他代码 121 | type_ptr_ = new T[10](); 122 | } 123 | Template(const Template &other) 124 | : template_member_(other.template_member_), 125 | template_const_member_(other.template_const_member_) { 126 | // 内部其他代码 127 | } 128 | // 可选的移动拷贝 129 | Template(Template &&other) : template_member_(other.template_member_) 130 | template_const_member_(other.template_const_member_) { 131 | other.template_member_ = 0; 132 | other.template_const_member_ = 0; 133 | } 134 | Template& operator=(const Template &other) { 135 | if (&other != this) { 136 | template_member_ = other.template_member_; 137 | type_ptr_ = new T[10](); 138 | copy(type_ptr_, other.type_ptr_); // 做变量的拷贝 139 | } 140 | return *this; 141 | } 142 | 143 | Template& operator=(Template &&other) { // 可选的移动赋值 144 | if (&other != this) { 145 | template_member_ = other.template_member_; 146 | type_ptr_ = other.type_ptr_; 147 | other.template_member_ = 0; 148 | other.type_ptr_ = nullptr; 149 | // 一些其他操作 150 | } 151 | return *this; 152 | } 153 | 154 | // 有默认拷贝、没有拷贝、有赋值、没有默认赋值 155 | Template& operator=(const Template &other) = default; 156 | Template& operator=(const Template &other) = delete; 157 | Template(const Template &other) = default; 158 | Template(const Template &other) = delete; 159 | 160 | 161 | ~Template() { // 析构函数,如果内部有申请内存操作,那么可以按照这种方式释放内存 162 | // 做一些释放资源的事 163 | if (nullptr != type_ptr_) { 164 | delete type_ptr_; 165 | // .... 166 | } 167 | } 168 | 169 | public: // 外部调用核心函数 170 | 171 | // 正式的成员函数 172 | 173 | // 取值、设值、状态函数,比较短小的,可以按照如下方式进行书写 174 | void set_template_member(int template_member) { template_member_ = template_member; } 175 | int get_template_member() const { return template_member_; } 176 | bool is_empty() const { return is_true; } 177 | 178 | // 形式参数的命名规则,以及形参变量是引用还是拷贝。需要看是否是内置类型或者根据实际需求来定 179 | // 形式参数太长要分行 180 | void FindTemplate(const T &input_member, 181 | const string &strings, 182 | int temp_variable) { 183 | // 二元操作符号,可以是下面其中一种 184 | v = w * x + y / z; 185 | v = w*x + y/z; 186 | v = w * (x + z); 187 | } 188 | 189 | 190 | // 假设下面的函数是算法的核心函数,那么需要按照下面格式书写注释。前三个必选,后面可选 191 | 192 | //! \brief 简单描述当前函数的功能 193 | //! \note 当前函数使用过程中需要的注意事项 194 | //! \complexity 时间复杂度(如果有最好、最坏、平均复杂度要分别说明),空间复杂度(如果是 O(1),可以默认不写) 195 | //! \param example_one 该变量有什么功能 196 | //! \return 返回值有什么含义 197 | //! \method 用了什么技巧方法 198 | //! \description 解决的实际问题的简单描述 199 | //! \reference 参考的资料代码 200 | //! \TODO 还有那些不足 201 | //! 1)xx 202 | //! 2)xxx 203 | //! \example 如果当前函数使用复杂,可以举例子 204 | //! int example_one = 2; 205 | //! CoreFunction(example_one); 206 | int CoreFunction(int example_one) { 207 | bool flag = false; 208 | // .... 209 | // if (xxx) flag = ture; 210 | return flag; 211 | } 212 | private: // 内部辅助函数 helper function 213 | 214 | public: // 外部调用成员变量 215 | int TemplateMember; // 如果定义了外部可获取的共有成员变量,那么需要与函数命名规则一致, 216 | // 首字母大写且无下划线连接 217 | const int TableName = 5; 218 | 219 | private: // 内部成员变量 220 | // 私有变量直接是小写单词,单词之间用下划线连接,最后是下划线结尾, 221 | // 当然为了美观,可以进行必要的对齐。也可以不对齐,但是自己的风格要一致。建议对齐! 222 | //constexpr int template_const_member_ = 0; 223 | const int template_const_member_ = 0; 224 | bool is_ture_ = false; 225 | int template_member_; 226 | T* type_ptr_; 227 | }; // class Template 228 | 229 | // 如果你有一些当前算法的应用,可以定义一个「文件名_app」的命名空间 230 | // 表示外部可以实际来访问。比如单链表可以有 LRU 算法,那么下面就可以写应用算法 231 | namespace template_app { 232 | 233 | //! \brief 单链表应用之 LRU 缓存淘汰算法实现 234 | ......其他一些描述 235 | template 236 | void LRUBySingleList() { 237 | 238 | } 239 | 240 | } // namespace template_app 241 | } // namespace glib 242 | 243 | #endif // GLIB_TEMPLATE_HPP_ 244 | -------------------------------------------------------------------------------- /template/template.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saber/algorithm/86373a2ecda8424b4a31a9a35ff4efef7401b898/template/template.md -------------------------------------------------------------------------------- /template/template.test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * CopyRight (c) 2019 gcj 3 | * File: template.test.cc 4 | * Project: algorithm 5 | * Author: gcj 6 | * Date: 2019/4/8 7 | * Description: test template algorithm 8 | * License: see the LICENSE.txt file 9 | * github: https://github.com/saber/algorithm 10 | */ 11 | 12 | #include "template.hpp" 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | const int kGlobalConst = 5; // 如果定义了一个全局常量,那么需要前缀 k 字母 19 | // constexpr int kGlobalConst = 6; 20 | 21 | //! \brief 测试 template 中的核心算法 22 | //! \run 23 | //! g++ template.test.cc -std=c++11 && ./a.out 24 | //! \TODO 25 | //! 1)xxx 26 | //! 27 | //! \conclusion 28 | //! 1)xxx 29 | int main(int argc, char **argv) { 30 | 31 | // 测试核心算法功能1 32 | cout << "测试核心算法功能1" << endl; 33 | 其他测试代码...... 34 | cout << endl; 35 | 36 | // 测试核心算法功能2 37 | cout << "测试核心算法功能2" << endl; 38 | 其他测试代码...... 39 | cout << endl; 40 | 41 | // 测试其他 ...... 42 | cout << "测试核心算法功能n" << endl; 43 | 其他测试代码...... 44 | cout << endl; 45 | 46 | return 0; 47 | } 48 | --------------------------------------------------------------------------------