diff --git "a/docs/algorithm/01-\347\256\227\346\263\225\345\237\272\347\241\200.md" "b/docs/algorithm/01-\347\256\227\346\263\225\345\237\272\347\241\200.md" deleted file mode 100644 index 564dcbedb..000000000 --- "a/docs/algorithm/01-\347\256\227\346\263\225\345\237\272\347\241\200.md" +++ /dev/null @@ -1,179 +0,0 @@ -# 算法基础 - - - - - - - - - - -## 介绍 - -**定义** - -> 算法是解决特定问题求解步骤的描述 , 在计算机中表现为指令的有限序列 , 并且每条指令表示一个或多个操作 - -**算法的特性** - -1. **输入输出** - - 算法具有零个或多个输入 , 算法至少有一个或多个输出 - -2. **有穷性** - - 算法在执行有限的步骤之后 , 自动结束而不会出现无限循环 , 并且每一个步骤在可接受的时间内完成 - -3. **确定性** - - 算法的每一步骤都具有确定的含义 - -4. **可行性** - - 算法的每一步都必须是可行的 , 也就是说 , 每一步都能够通过执行有限次数完成 - -## 设计要求 - -解决一个问题的途径可以有非常多种 , 掌握好的算法 , 对我们解决问题很有帮助 , 而一个好的算法应该具备以下要求 - -**正确性** - -算法的正确性是指算法至少应该具有输入 , 输出和加工处理无歧义性 , 能正确反应问题的需求 , 能够得到问题的正确答案 - -正确性应该符合以下四点 : - -1. 算法程序没有语法错误 -2. 算法程序对于合法的输入数据能够产生满足要求的输出结果 -3. 算法程序对于非法的输入数据能够得出满足规格说明的结果 -4. 算法程序对于精心选择的 , 甚至刁难的测试数据都有满足要求的输出结果 - -**健壮性** - -一个好的算法还应该能对输入数据不合法的情况做合适的处理 , 比如输入的时间或者距离不应该是负数等 - -健壮性就是当输入数据不合法时 , 算法也能作出相关处理 , 而不是产生异常或者莫名奇妙的结果 - -**时间效率高和存储量低** - -时间效率是指算法的执行时间 , 对于同一个问题 , 如果有多个算法能够解决 , 执行时间短的算法效率高 , 执行时间长的效率低 - -存储量需求值的是在执行过程中需要的最大存储空间 , 主要指算法程序运行时所占用的内存或外部硬盘空间 - -**设计算法应该尽量满足时间效率和存储量低的要求** - -## 效率的度量方法 - -**事后统计方法** - -这种方法主要是通过设计好的测试程序和数据 , 利用计算机计时器对不同算法编程的程序的运行时间进行比较 , 从而确定算法效率的高低 - -但是这种方法是有很大的缺陷的 : - -- 必须依据算法事先编制好程序 , 这通常需要花费大量的时间和精力 , 并且一旦编制出来发现它根本是很糟糕的算法 , 那就白忙活了 -- 时间的比较依赖计算机硬件和软件等环境因素 , 有时会掩盖算法本身的优劣 -- 算法的测试数据设计困难 , 并且程序的运行时间往往还与测试数据的规模有很大的关系 , 效率高的算法在小的测试数据面前往往得不到体现 ; 比如10个数据的排序 , 不管用什么算法 , 差异几乎是零 , 而如果有一百万个随机数据排序 , 那不同的算法的差异就非常大了 , 所以用多少数据来测试我们的算法 , 这是一个很难判断的问题 - -**事前分析估算方法** - -事前分析估算方法就是在计算机程序编制前 , 依据统计方法对算法进行估算 - -一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素 : - -1. 算法采用的策略 , 方法 -2. 编译产生的代码质量 -3. 问题的输入规模 -4. 机器执行指令的速度 - -抛开与计算机硬件 , 软件有关的因素 , 一个程序的运行时间 , 依赖于算法的好坏和问题的输入规模 - -## 时间复杂度 - -在进行算法分析时 , 语句总的执行次数`T(n)`是关于问题规模`n`的函数 , 进而分析`T(n)`随`n`的变化情况并确定`T(n)`的数量级 - -算法的时间复杂度 , 也就是算法的时间量度 , 记作 : `T(n)=O(f(n))` ; 它表示岁问题规模`n` 的增大 , 算法执行时间的增长率和`f(n)`的增长率相同 , 称作算法的渐进时间复杂度 , 简称为时间复杂度 , 其中`f(n)`是问题规模n的某个函数 - -我们用大写`O()` 来体现算法时间复杂度的记法 , 称之为`大O记法` - -### 推导大O阶 - -分析一个算法的时间复杂度 , 推导大O阶时有以下方法 : - -1. 用常数1取代运行时间的所有加法常数 -2. 在修改后的运行次数函数中 , 只保留最高阶项 -3. 如果最高阶项存在且不是1 , 则去除与这个项相乘的常数 - -由此得到的结果就是大O阶 - -### 常数阶 - -首先介绍顺序结构的时间复杂度 , 现有如下算法 - -```python -n = 100 # 执行一次 -sum = (1+n)*n/2 # 执行一次 -print(sum) # 执行一次 -``` - -这个算法的运行次数函数是`f(n)=3` , 根据推导大O阶的方法 , 直接把常数项改为1 , 在保留高阶项时发现 , 它根本没有最高阶项 , 所以这个算法的时间复杂度为`O(1)` - -注意 : 不管这个常数是多少 , 我们都记作`O(1)` , 而不是`O(3)`等其他任意数字 - -对于分支结构而言 , 无论是真还是假 , 执行的次数都是恒定的 , 不会随着n的变大而发生变化 , 所以单纯的分支结构 (不包含在循环结构中) , 其时间复杂度也是`O(1)` - -### 线性阶 - -线性阶的循环结构会复杂很多 , 要确定某个算法的阶次 , 我们常常需要确定某个特定语句或某个语句集运行的次数 ; 因此 , 我们要分析算法的复杂度 , 关键就是要分析循环结构的运行情况 , 如下 : - -```python -for i in range(n): - print(i) # 时间复杂度为O(1) -``` - -上面代码中 , `print`语句会执行`n`次 , 所以它的算法复杂度为`O(n)` - -### 对数阶 - -```python -count = 1 -while count < n: - count *= 2 -``` - -上面代码中 , 每次`count`乘以2之后 , 就距离`n`更近了一分 , 也就是说 , 有多少个2想乘后大于`n` , 则会退出循环 , 由`2^x = n` 可以得到`x = log2n` , 所以这个循环的时间复杂度为`O(logn)` - -### 平方阶 - -下面例子是一个循环嵌套 - -```python -for i in range(n): - for j in range(n): - print(i,j) -``` - -对于外层的循环 , 时间复杂度为`O(n)` , 而内部循环时间复杂度也为`O(n)` , 所以这段代码的时间复杂度为`O(n²)` , 如果将外层循环改成`m` , 那么时间复杂度就会变成`O(m×n)` - -**常见时间复杂度** - -| 执行次数函数 | 阶 | 非正式术语 | -| ----------------- | -------- | ------ | -| 12 | O(1) | 常数阶 | -| 2n+3 | O(n) | 线性阶 | -| 3n²+2n+1 | O(n²) | 平方阶 | -| 5log2n + 20 | O(logn) | 对数阶 | -| 2n + 3nlog2n + 19 | O(nlogn) | nlogn阶 | -| 6n³ + 2n² + 3n +4 | O(n³) | 立方阶 | -| 2" | O(2") | 指数阶 | - -常用时间复杂度所耗费的时间从小到大一次是 : - -`O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2") < O(n!) < O(n")` - -## 空间复杂度 - -算法的空间复杂度通过计算算法所需的存储空间实现 , 算法空间复杂度的计算公式记作 : `S(n) = O(f(n))` , 其中`n`为问题的规模 , `f(n)`为语句关于`n`所占存储空间的函数 - -一般情况下 , 一个程序在机器上执行时 , 除了需要存储程序本身的指令 , 常数 , 变量和输入数据外 , 还需要存储对数据操作的存储单元 , 若输入数据所占空间值取决于问题本身 , 和算法无关 , 这样只需要分析该算法在实现时所需的辅助单元即可 - -若算法执行时所需的辅助空间相对于输入数据量而言是个常数 , 则称此算法为原地工作 , 空间复杂度为`O(1)` \ No newline at end of file diff --git "a/docs/algorithm/02-\346\225\260\347\273\204\343\200\201\346\240\210\343\200\201\351\230\237\345\210\227.md.bak" "b/docs/algorithm/02-\346\225\260\347\273\204\343\200\201\346\240\210\343\200\201\351\230\237\345\210\227.md.bak" deleted file mode 100644 index bc9fdcab2..000000000 --- "a/docs/algorithm/02-\346\225\260\347\273\204\343\200\201\346\240\210\343\200\201\351\230\237\345\210\227.md.bak" +++ /dev/null @@ -1,165 +0,0 @@ -本文为 `《91 天学算法》` 中的讲义, 因为是非开源项目, 所以这里无法给出链接, 作者 [lucifer](https://lucifer.ren/blog/2020/05/23/91-algo/) - -后期本人会重写, 暂时引用, 特此声明 - -# 数组 - -数组存储一系列同一数据类型的值(虽然在JavaScript数组可以保存不同类型的值) - -![https://www.runoob.com/wp-content/uploads/2015/06/goarray.png](https://www.runoob.com/wp-content/uploads/2015/06/goarray.png) - -(图来自 [https://www.runoob.com/go/go-arrays.html](https://www.runoob.com/go/go-arrays.html)) - -那么为什么数组要存储相同类型的值呢?为什么有的语言就可以存储不同类型的值呢? - -实际上存储相同的类型有两个原因: - -1. 相同的类型大小可以是固定的,这样数组就可以随机访问了。试想数组第一项是4字节,第二项是8字节,第三项是6字节,我怎么随机访问? -2. 强类型语言要求指定数组的类型。 - -数组的一个特点就是**支持随机访问**,请务必记住这一点。当你需要支持随机访问的数据结构的话, 自然而然应该想到数组。 - -本质上数组是一段连续的地址空间,这个是和我们之后要讲的链表的本质差别。 虽然二者从逻辑上来看都是线性的数据结构。 - -需要注意的是虽然数据结构有很多,比如树,图,哈希表,但是这只是逻辑数据结构。而真正实现它们的还需要落实到具体的物理数据结构,即**数组和链表**。 - -- 一个数组表示的是一系列的元素 -- 数组(static array)的长度是固定的,一旦创建就不能改变(但是可以有dynamic array) -- 所有的元素需要是同一类型(个别的语言除外) -- 可以通过下标索引获取到所储存的元素(随机访问)。 比如 array[index] -- 下标可以是是 0 到 array.length - 1 的任意整数 - -## 数组的常见操作 - -1. 随机访问 -```py -arr = [1,2,33] -arr[0] # 1 -arr[2] # 33 -``` -2. 遍历 -```py -for num in nums: - print(num) -``` -3. 任意位置插入元素、删除元素 -```py -arr = [1,2,33] -# 在索引2前插入一个5 -arr.insert(2, 5) -print(arr) # [1,2,5,33] -``` - -我们不难发现, 插入2之后,新插入的元素之后的元素(最后一个元素)的索引发生了变化,从2变成了3,而其前面的元素没有影响。从平均上来看,数组插入元素和删除元素的时间复杂度为$O{N}$。 - -> 删除也是类似 - -基本上数组都支持这些方法。 虽然命名各有不同,但是都是上面四种操作的实现 - -- each() -- push(item) -- pop() -- insert(item, index) - -**时间复杂度分析** - -- 随机访问 -> O(1) -- 根据索引修改 -> O(1) -- 遍历数组 -> O(N) -- 插入数值到数组 -> O(N) -- 插入数值到数组最后 -> O(1) -- 从数组最后删除数值 -> O(1) - -## 多维数组 - -当数组里的元素也是一个数组的时候,就可以形成多维数组 -例子: - -1. 用一个多维数组表示坐标 `array[y]` -2. 用一个多维数组来记录照片上每一个pixel的数值 - -力扣中有很多二维数组的题目,我一般称其为board。 - -## 题目推荐 - -### [380. 常数时间插入、删除和获取随机元素](https://leetcode-cn.com/problems/insert-delete-getrandom-o1/) - -### [75. 颜色分类](https://leetcode-cn.com/problems/sort-colors/) - -做题的时候经常会用到数组,常常会遍历数组,排序数组以及将元素插入或删除 - - -# Stack - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfbikq9ipmj30cd0a73yp.jpg) - -栈只允许新的内容从一个方向插入或删除,这个方向我们叫栈顶,而从其他位置获取内容是不被允许的 - -栈最显著的特征就是LIFO(Last In, First Out - 后进先出) - -举个例子: -栈就像是一个放书本的抽屉,进栈的操作就好比是想抽屉里放一本书,新进去的书永远在最上层,而退栈则相当于从里往外拿书本,永远是从最上层开始拿,所以拿出来的永远是最后进去的哪一个 - -## 栈的常用操作 - -1. 进栈 - push - 将元素放置到栈顶 -2. 退栈 - pop - 将栈顶元素弹出 -3. 栈顶 - top - 得到栈顶元素的值 -4. 是否空栈 - isEmpty - 判断栈内是否有元素 - -## 栈的常用操作时间复杂度 - -由于栈只在尾部操作就行了,我们用数组进行模拟的话,可以很容易达到O(1)的时间复杂度,很完美! - -1. 进栈 - O(1) -2. 出栈 - O(1) - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfbil9jqqej30sd0fhdgz.jpg) - -## 题目推荐 - -- [1381. 设计一个支持增量操作的栈](https://leetcode-cn.com/problems/design-a-stack-with-increment-operation/) -- [394. 字符串解码](https://leetcode-cn.com/problems/decode-string/) - - -# 队列 - -- 正如前面所说,队列是逻辑结构,底层可以用数组实现,也可以用链表实现,不同实现有不同的取舍。 -- 队列也是数据结构的其中一种,和栈相反的是。队列是只允许在一端进行插入,在另一端进行删除的线性表。与栈一样,队列也是一种**受限的数据结构** -- 队列(Queue)是一种先进先出(FIFO - First In First Out)的数据结构 - -- 队列中,插入元素会发生在尾部而删除元素会发生在头部 - 先进先出原则 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfbilukn6tj30ng0a30t6.jpg) - -## 队列的操作 - -- 插入 - 在队列的尾部添加元素 -- 删除 - 在队列的头部删除元素 -- 查看首个元素 - 返回队列头部的元素的值 - -## 队列操作的时间复杂度 - -跟栈一样,队列的操作都是有效率的 -> O(1) - -- 插入 - O(1) -- 删除 - O(1) - -我们知道直接用数组模拟队列的话, 在队头删除元素是无法达到O(1) 的复杂度的, 上面提到了由于存在调整数组的原因,时间复杂度为$O(N)$。因此我们需要一种别的方式,这种防方式就是下面要讲的Linked List - -## 队列的实现(Linked List) - -我们知道链表的删除操作,尤其是删除头节点的情况下,是很容易做到O(1)。那么我们是否可利用这一点来弥补上面说的删除无法达到O(1)? - -> 删除非头节点可以做到O(1)么?什么情况下可以? - -但是在链表末尾插入就不是O(1),而是O(n)了。别急, 我们只要维护一个遍历tail 存放当前链表的尾节点即可在O(1)的时间完成插入操作。 - -具体来说有两个指针 - 一个在头部 一个在尾部。 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfbjwbk20wj30jr0mtq5q.jpg) - - -## 更多 - -- [基础数据结构 by lucifer](https://github.com/azl397985856/leetcode/blob/master/thinkings/basic-data-structure.md) diff --git "a/docs/algorithm/03-\346\240\210.md" "b/docs/algorithm/03-\346\240\210.md" deleted file mode 100644 index 8304b4094..000000000 --- "a/docs/algorithm/03-\346\240\210.md" +++ /dev/null @@ -1,2 +0,0 @@ -# 栈 - diff --git "a/docs/algorithm/03-\351\223\276\350\241\250.md" "b/docs/algorithm/03-\351\223\276\350\241\250.md" deleted file mode 100644 index f66cd7c8a..000000000 --- "a/docs/algorithm/03-\351\223\276\350\241\250.md" +++ /dev/null @@ -1,603 +0,0 @@ -本文为 `《91 天学算法》` 中的讲义, 因为是非开源项目, 所以这里无法给出链接, 作者 [lucifer](https://lucifer.ren/blog/2020/05/23/91-algo/) - -后期本人会重写, 暂时引用, 特此声明 - -# 链表 - - - - - - - - - - -## 链表定义 - -各种数据结构,不管是队列,栈等线性数据结构还是树,图的等非线性数据结构,从根本上底层都是数组和链表。两者在物理上存储是非常不一样的,如图: - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfqtbpbwmrj31gu0qmtej.jpg) - -(图1. 数组和链表的物理存储图) - - -物理内存是一个个大小相同的内存单元构成的,如图: - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfqt71jt4cj30kg0b4wfl.jpg) - -(图2. 物理内存) - -不难看出,数组和链表只是使用物理内存的两种方式。 - -数组是连续的内存空间,通常每一个单位的大小也是固定的,因此可以按下标随机访问。而链表则不一定连续,因此其查找只能依靠别的方式,一般我们是通过一个叫 next 指针来遍历查找。 - -链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfigmeqc3xj316o094jt6.jpg) - -(图3. 一个典型的链表逻辑表示图) - -> 后面所有的图都是基于逻辑结构,而不是物理结构 - - -## 基本概念 - -- 头节点 -- 尾节点 -- 静态链表 - -## 链表分类 - -以下分类是两种分类标准,也就是一个链表可以既属于循环链表,也属于单链表,这是毋庸置疑的。 - -### 按照是否循环分为:循环链表和非循环链表 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfqtoi9hodj313m0nswhz.jpg) - -当我们需要在遍历到尾部之后重新开始遍历的时候,可以考虑使用循环链表。 需要注意的是,如果链表长度始终不变,那么使用循环链表很容易造成死循环,因此循环链表经常会伴随着节点的删除操作,比如**约瑟夫环问题**。 - -### 按照指针个数分为:单链表和双链表 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfqtscvwdfj31fs0dkwhk.jpg) - -- 单链表。 每个节点包括两部分:一个是存储数据的数据域,另一个是存储下一个节点指针的指针域。 - -- 双向链表。 每个节点包括三部分:一个是存储数据的数据域,一个是存储下一个节点指针的指针域,一个是存储上一个节点指针的指针域。 - -Java 中的 LinkedHashMap 以及 Python 中的 OrderedDict 底层都是双向链表。 其好处在于删除和插入的时候,可以更快地找到前驱指针。如果用单链表的话, 那么时间复杂度最坏的情况是 $O(N)$。双向链表的本质就是**空间换时间**,因此如果题目对时间有要求,可以考虑使用双向链表,比如力扣的 145. LRU缓存机制 。 - - -## 链表的基本操作 - -### 插入 - -插入只需要考虑要插入位置前驱节点和后继节点(双向链表的情况下需要更新后继节点)即可,其他节点不受影响,因此在给定指针的情况下插入的操作时间复杂度为$O(1)$。这里给定指针中的指针指的是插入位置的前驱节点。 - -伪代码: - -``` - -temp = 待插入位置的前驱节点.next -待插入位置的前驱节点.next = 待插入指针 -待插入指针.next = temp - -``` - -如果没有给定指针,我们需要先遍历找到节点,因此最坏情况下时间复杂度为 $O(N)$。 - -> 提示1: 考虑头尾指针的情况。 - -> 提示2: 新手推荐先画图,再写代码。等熟练之后,自然就不需要画图了。 - - -## 删除 - -只需要将需要删除的节点的前驱指针的next指针修正为其下下个节点即可,注意考虑边界条件。 - -伪代码: - -``` -待删除位置的前驱节点.next = 待删除位置的前驱节点.next.next -``` - -> 提示1: 考虑头尾指针的情况。 - -> 提示2: 新手推荐先画图,再写代码。等熟练之后,自然就不需要画图了。 - - -## 遍历 - -遍历比较简单,直接上伪代码。 - -伪代码: - -``` -当前指针 = 头指针 -while 当前指针不为空 { - print(当前节点) - 当前指针 = 当前指针.next -} - -``` - - -## 常见题型 - -1. 反转链表 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfih5wm9vzj31em080abx.jpg) -(图2) - -2. 合并链表 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfih6tlsicj310e0bwwh1.jpg) -(图3) - -3. 相交或环形链表 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfihaxlmqej317q0lg41w.jpg) -(图4) - - -4. 设计题 - -## 常见套路 -1. 反转链表 - - 1.1 将某个链表进行反转 - - 1.2 在 O(n) 时间, O(1) 空间复杂度下逆序读取链表的某个值 - - 1.3 将某个链表按 K 个一组进行反转 - -2. 合并链表 - - 2.1. 将两条有序或无序的链表合并成一条 有序链表 - - 2.2. 将 k 条有序链表合并成一条有序链表 - -3. 相交或环形链表 - - 3.1. 判断某条链表是否存在环 - - 3.2. 获取某条链表环的大小 - - 3.3. 获取某两条链表的相交节点 - -4. 设计题。需要你充分掌握链表特点才能写出来。 - -## 模板 -1. 反转链表 - -伪代码: - -```jsx -let cur = head; -let pre = null; - -while(cur) { - const next = cur.next; - cur.next = pre; - pre = cur; - cur = next; -} - -return pre; -``` - -2. 合并链表 - -伪代码: - -```jsx - -ans = new Node(-1) // ans 为需要返回的头节点 -cur = ans -// l1和l2分别为需要合并的两个链表的头节点 -while l1 和 l2 都不为空 - cur.next = min(l1.val, l2.val) - 更新较小的指针,往后移动一位 -if l1 == null - cur.next = l2 -if l2 == null - cur.next = l1 -return ans.next - -``` - -JS代码参考: - - ```jsx - ans = now = new ListNode(0); - while(l1 !== null && l2 !== null){ - if(l1.val < l2.val){ - now.next = l1; - l1 = l1.next; - }else{ - now.next = l2; - l2 = l2.next; - } - now = now.next; - } - - if(l1 === null){ - now.next = l2; - }else{ - now.next = l1; - } - return ans.next; -``` - -3. 相交或环形链表 - -3.1 链表相交求交点 - -哈希法: - -- 有A, B这两条链表, 先遍历其中一个,比如A链表, 并将A中的所有节点存入哈希表。 -- 遍历B链表,检查节点是否在哈希表中, 第一个存在的就是相交节点 - -> 时间复杂度O(N), 空间复杂度O(N) - -伪代码: - -```jsx -data = new Set() // 存放A链表的所有节点的地址 - -while A不为空{ - 哈希表中添加A链表当前节点 - A指针向后移动 -} - -while B不为空{ - if 如果哈希表中含有B链表当前节点 - return B - B指针向后移动 -} - -return null // 两条链表没有相交点 -``` - -JS代码参考: - -```jsx -let data = new Set() -while(A !== null){ - data.add(A) - A = A.next -} -while(B !== null){ - if(data.has(B)) return B - B = B.next -} -return null -``` - -双指针: - -- 例如使用a, b两个指针分别指向A, B这两条链表, 两个指针相同的速度向后移动, -- 当 a 到达链表的尾部时,重定位到链表 B 的头结点 -- 当 b到达链表的尾部时,重定位到链表 A 的头结点。 -- a, b 指针相遇的点为相交的起始节点,否则没有相交点 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfig7vsvwhj30bs05z3yl.jpg) -(图5) - -PS: 为什么a, b指针相遇的点一定是相交的起始节点? 我们证明一下: - -如果我们将两条链表按相交的起始节点继续截断,A链表为: a + c,B链表为: b + c, 当 a 指针将 A 链表遍历完后,重定位到链表 B 的头结点,然后继续遍历至相交点,a指针遍历的距离为 a + c + b,同理b指针遍历的距离为 b + c + a。 - -> 时间复杂度O(N), 空间复杂度O(1) - -伪代码: - -```jsx -a = headA -b = headB -while a,b指针不相等时 { - a, b指针都向后移动 - if a, b指针都为空 - return null //没有相交点 - if a指针为空时 - a指针重定位到链表 B的头结点 - if b指针为空时 - b指针重定位到链表 A的头结点 -} -return a -``` - -JS代码参考: - -```jsx -let a = headA, b = headB -while(a != b){ - a = a ? a.next : null - b = b ? b.next : null - if(a == null && b == null) return null - if(a == null) a = headB - if(b == null) b = headA -} -return a -``` - -3.2 环形链表求环的起点 - -哈希法: - -- 遍历整个链表,同时将每个节点都插入哈希表, - -- 如果当前节点在哈希表中不存在,继续遍历, - -- 如果存在,那么当前节点就是环的入口节点 - -> 时间复杂度O(n),空间复杂度O(n) - -伪代码: - -```jsx -data = new Set() // 声明哈希表 -while head不为空{ - if 当前节点在哈希表中存在{ - return head // 当前节点就是环的入口节点 - } else { - 将当前节点插入哈希表 - } - head指针后移 -} -return null // 环不存在 -``` -JS代码参考: - -```jsx -let data = new Set() -while(head){ - if(data.has(head)){ - return head - } else { - data.add(head) - } - head = head.next -} -return null -``` - -快慢指针法: - -1. 定义一个fast指针,每次**前进两步**,一个slow指针,每次**前进一步** - -2. 当两个指针相遇时 - - 2.1. 将fast指针指向链表头部,同时fast指针每次只**前进一步** - - 2.2. slow指针继续前进,每次**前进一步** - - 3. 当两个指针再次相遇时,当前节点就是环的入口 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfigbvzje1j30ky0bhq3x.jpg) -(图6) - -为什么第二次相遇的点为环的入口? 原因如下: - -- **第一次相遇时** -- 慢指针移动的距离为 s1 = A + B + n1 * L -- 快指针移动的距离为 s2 = A + B + n2 * L -- 快指针是慢指针速度的两倍,所以 s2 = 2* s1 -- A + B + n2 * L = 2A + 2B + n1 * L -- A = -B + (n2 - n1) * L -- 因为圆的性质 -- A = -B + (n2 - n1) * L ===> A = -B -- 即在第一次相遇点, 向前走A步 ===> 向后走B步 -- **第一次相遇后** -- 快指针从头节点走A步会到达环的入口 -- 慢指针从第一次相遇点走A步,相当于向后走B步,也会到达环的入口 - -> 时间复杂度O(n),空间复杂度O(1) - -参考:[【每日一题】- 2020-01-14 - 142. 环形链表 II](https://github.com/azl397985856/leetcode/issues/274#issuecomment-573985706) - -伪代码: - -```jsx -fast = head -slow = head //快慢指针都指向头部 -do { - 快指针向后两步 - 慢指针向后一步 -} while 快慢指针不相等时 -if 指针都为空时{ - return null // 没有环 -} -while 快慢指针不相等时{ - 快指针向后一步 - 慢指针向后一步 -} -return fast -``` - -JS代码参考: - -```jsx -if(head == null || head.next == null) return null -let fast = slow = head -do{ - if(fast != null && fast.next != null){ - fast = fast.next.next - } else { - fast = null - } - slow = slow.next -} while(fast != slow) -if(fast == null) return null -fast = head -while(fast != slow){ - fast = fast.next - slow = slow.next -} -return fast -``` - -4. 设计题 - -这个直接直接结合一个例子来给大家讲解一下。 - -题目描述: - -``` -设计一个算法支持以下操作: - -获取数据 get 和 写入数据 put 。 - -获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。 - -写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 - -在 O(1) 时间复杂度内完成这两种操作 -``` - - -思路: - -1.确定需要使用的数据结构 - -根据题目要求,存储的数据需要保证顺序关系(逻辑层面) ==⇒ 可以使用数组,链表等 - -同时需要对数据进行频繁的增删, 时间复杂度O(1) == >只能使用链表存储数据 - -对数据进行读取时, 时间复杂度O(1) =⇒ 使用哈希表 - -最终采取双向链表 + 哈希表 - -双向链表按最后一次访问的时间的顺序进行排列, 链表头部为最近访问的节点 - -哈希表,以关键字为键,以链表节点的地址为值 - -2. put操作 - -通过哈希表, 查看put操作传入的关键字对应的链表节点, 是否已经存在 - -如果已经存在,将该节点的值进行覆盖,同时将该节点位置调整至链表头部 - -如果不存在,查看当前链表容量是否已满 - -如果链表容量未满, 新生成节点, 同时将该节点位置调整至链表头部 - -如果链表容量已满, 删除尾部节点,新生成节点, 同时将该节点位置调整至链表头部 - -3. get操作 - -通过哈希表, 查看get操作传入的关键字对应的链表节点, 是否已经存在 - -存在, 返回该节点的值, 同时将该节点位置调整至链表头部 - -不存在, 返回null - - -伪代码: - -```jsx -var LRUCache = function(capacity) { - 保存一个该数据结构的最大容量 - 生成一个双向链表,同时保存该链表的头结点与尾节点 - 生成一个哈希表 -}; - -function get (key) { - if 哈希表中存在该关键字 { - 根据哈希表获取该链表节点 - 将该节点放置于链表头部 - return 链表节点的值 - } else { - return -1 - } -}; - -function put (key, value) { - if 哈希表中存在该关键字 { - 根据哈希表获取该链表节点 - 将该链表节点的值更新 - 将该节点放置于链表头部 - } else { - if 容量已满 { - 删除链表尾部的节点 - 新生成一个节点 - 将该节点放置于链表头部 - } else { - 新生成一个节点 - 将该节点放置于链表头部 - } - } -}; -``` - -JS代码参考: - -```jsx - function ListNode(key, val) { - this.key = key - this.val = val; - this.pre = this.next = null; - } - - var LRUCache = function(capacity) { - this.capacity = capacity - this.size = 0 - this.data = {} - this.head = new ListNode() - this.tail = new ListNode() - this.head.next = this.tail - this.tail.pre = this.head - }; - - function get (key) { - if(this.data[key] !== undefined){ - let node = this.data[key] - this.removeNode(node) - this.appendHead(node) - return node.val - } else { - return -1 - } - }; - - function put (key, value) { - let node - if(this.data[key] !== undefined){ - node = this.data[key] - this.removeNode(node) - node.val = value - } else { - node = new ListNode(key, value) - this.data[key] = node - if(this.size < this.capacity){ - this.size++ - } else { - key = this.removeTail() - delete this.data[key] - } - } - this.appendHead(node) - }; - - function removeNode (node) { - let preNode = node.pre, - nextNode = node.next - preNode.next = nextNode - nextNode.pre = preNode - }; - - function appendHead (node) { - let firstNode = this.head.next - this.head.next = node - node.pre = this.head - node.next = firstNode - firstNode.pre = node - }; - - function removeTail () { - let key = this.tail.pre.key - this.removeNode(this.tail.pre) - return key - }; - ``` - diff --git "a/docs/algorithm/04-\346\240\221.md" "b/docs/algorithm/04-\346\240\221.md" deleted file mode 100644 index 9d5ec0339..000000000 --- "a/docs/algorithm/04-\346\240\221.md" +++ /dev/null @@ -1,247 +0,0 @@ -本文为 `《91 天学算法》` 中的讲义, 因为是非开源项目, 所以这里无法给出链接, 作者 [lucifer](https://lucifer.ren/blog/2020/05/23/91-algo/) - -后期本人会重写, 暂时引用, 特此声明 - -# 树 - -计算机的数据结构是现实世界物体间关系的一种抽象。家族的族谱,公司架构中的人员组织关系,电脑中的文件夹结构,html渲染的dom结构等等,这些有层次关系的结构在计算机领域都叫做树。 - -树是一种非线性数据结构。 - -树结构的基本单位是节点。节点之间的链接,称为分支(branch)。节点与分支形成树状,结构的开端,称为根(root),或根结点。根节点之外的节点,称为子节点(child)。没有链接到其他子节点的节点,称为叶节点(Leaf)。 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfjv3xmkknj30jb0cymxw.jpg) - -每个节点用以下结构表示 - -```c -Node { - value: any; // 当前节点的值 - children: Array; // 指向其儿子 -} -``` - -### 基本概念 - -- 树的高度 -- 深度 -- 层 -- 二叉树,三叉树,。。。 N 叉树 - -### 二叉树 - -二叉树是树结构的一种,两个叉就是说每个节点最多只有两个子节点,我们习惯称之为左节点和右节点。 - -> 注意这个只是名字而已,并不是实际位置上的左右 - -二叉树也是我们做算法题最常见的一种树(最简单 🤓)。可以用以下结构表示 - -```c -Node { - value: any; // 当前节点的值 - left: Node | null; // 左儿子 - right: Node | null; // 右儿子 -} -``` - -二叉树分类: - -- 完全二叉树 -- 满二叉树 -- 二叉搜索树 -- 平横二叉树 -- 红黑树 - - -二叉树的表示: - -- 链表存储 -- 数组存储。非常适合完全二叉树 - -### 二叉树遍历 - -二叉树的大部分题都围绕二叉树遍历展开,二叉树以下遍历方式 - -1. 前序遍历 -2. 中序遍历 -3. 后序遍历 -4. 层序遍历(BFS) - -- 前序遍历 - 1. 访问当前节点 - 2. 遍历左子树 - 3. 遍历右子树 - -![](https://cdn.jsdelivr.net/gh/wylu/cdn/post/Algorithm/Tree/%E4%BA%8C%E5%8F%89%E6%A0%91/preorder-traversal.gif) - -```jsx -preorder(root) { - if root - doSomething(root) - preOrder(root.left) - preOrder(root.right) -} -``` - -- 中序遍历 - 1. 遍历左子树 - 2. 访问当前节点 - 3. 遍历右子树 - -```jsx -inorder(root) { - if root - inorder(root.left) - doSomething(root) - inorder(root.right) -} - -``` - -![](https://cdn.jsdelivr.net/gh/wylu/cdn/post/Algorithm/Tree/%E4%BA%8C%E5%8F%89%E6%A0%91/inorder-traversal.gif) - -- 后序遍历 - 1. 遍历左子树 - 2. 遍历右子树 - 3. 访问当前节点 - -```jsx -postorder(root) { - if root - postorder(root.left) - postorder(root.right) - dosomething(root) -``` - -![](https://cdn.jsdelivr.net/gh/wylu/cdn/post/Algorithm/Tree/%E4%BA%8C%E5%8F%89%E6%A0%91/postorder-traversal.gif) - -- 层序遍历(BFS) - -```jsx -bfs(root) { - queue = [] - queue.push(root) - while queue.length { - curLevel = queue - queue = [] - for i = 0 to curLevel.length { - doSomething(curLevel[i]) - if (curLevel[i].left) { - queue.push(curLevel[i].left) - } - if (curLevel[i].right) { - queue.push(curLevel[i].right) - } - } - } -} -``` - -![https://cdn.jsdelivr.net/gh/wylu/cdn/post/Algorithm/Tree/%E4%BA%8C%E5%8F%89%E6%A0%91/level-order-traversal.gif](https://cdn.jsdelivr.net/gh/wylu/cdn/post/Algorithm/Tree/%E4%BA%8C%E5%8F%89%E6%A0%91/level-order-traversal.gif) - -二叉树是最能体现递归美感的结构,看到二叉树的题第一反应应该是递归。 - -### 二叉树构建 - -中序序列和前、后,层次序列任一组合唯一确定一颗二叉树。前、中,层次序列都是提供根结点的信息,中序序列用来区分左右子树; - -注意单前/中/后序遍历是无法确定一棵树,比如以下所有二叉树的前序遍历都为123 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfjv6f92aqj316k0u0tcy.jpg) - -构造一棵树的本质是 - -1. 确定根节点 -2. 确定其左子树 -3. 确定其右子树 - -比如拿到前序遍历结果preorder和中序遍历inorder,在preorder我们可以能确定树根root,拿到root可以将中序遍历切割中左右子树。这样就可以确定并构造一棵树,整个过程我们可以用递归完成。详情见 [105. 从前序与中序遍历序列构造二叉树]([https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)) - -### 二叉搜索树 - -二叉搜索树是二叉树的一种,具有以下性质 - -1. 左子树的所有节点值小于根的节点值 -2. 右子树的所有节点值大于根的节点值 - -二叉搜索树的中序遍历结果是一个有序列表,这个性质很有用。比如 [leetcode 1008]([https://leetcode-cn.com/problems/construct-binary-search-tree-from-preorder-traversal/description/](https://leetcode-cn.com/problems/construct-binary-search-tree-from-preorder-traversal/description/)), 根据先序遍历构建对应的二叉搜索树。由于二叉树的中序遍历是一个有序列表,我们可以有以下思路 - -1. 对先序遍历结果排序,排序结果是中序遍历结果 -2. 根据先序遍历和中序遍历确定一棵树 - -### 堆 - -在这里讲堆是因为堆可以被看作近似的完全二叉树。堆通常以数组形式的存储,而非上述的链式存储。 - -表示堆的数组A中,如果A[1]为根节点,那么给定任意节点i,其父子节点分别为 - -- 父亲节点:Math.floor(i / 2) -- 左子节点:2 * i -- 右子节点: 2 * i + 1 - -如果 A[parent(i)] ≥ A[i],则称该堆为最大堆,如果A[parent(i)] ≤ A[i],称该堆为最小堆。 - -堆这个数据结构有很多应用,比如堆排序,TopK问题,共享计算机系统的作业调度(优先队列)等。下面看下给定一个数据如何构建一个最大堆。 - -```jsx -// 自底向上建堆 -BUILD-MAX-HEAP(A) - A.heap-size = A.length - for i = Math.floor(A.length / 2) downto 1 - MAX-HEAPIFY(A, i) - -// 维护最大堆的性质 -MAX-HEAPIFY(A, i) - l = LEFT(i) - r = RIGHT(i) - // 找到当前节点和左右儿子节点中最大的一个,并交换 - if l <= A.heap-size and A[l] > A[i] - largest = l - else largest = i - if r <= A.heap-size and A[r] > A[largest] - largest = r - if largest != i - exchange A[i] with A[largest] - // 递归维护交换后的节点堆性质 - MAX-HEAPIFY(A, largest) -``` - -*ps: 伪代码参考自算法导论* - -### 递归 - -方法或者函数调用自身的方式成为递归调用。在这个过程中,调用称之为**递**,返回成为**归**。 - -#### 推荐题目 - -- 汉诺塔问题 -- fibonacci 数列 -- 二叉树的前中后序遍历 -- 归并排序 -- 求阶乘 -- 递归求和 - - - - - - - - - - -## 相关题目 - -- 二叉树的最大路径和 -- 给出所有路径和等于给定值的路径 -- 最近公共祖先 -- 各种遍历。前中后,层次,拉链式等。 - -### 参考 - -1. 图片参考自 [https://wylu.me/posts/e85d694a/](https://wylu.me/posts/e85d694a/) -2. 算法导论 -### 更多内容 - -- [二叉树的遍历](https://github.com/azl397985856/leetcode/blob/master/thinkings/binary-tree-traversal.md) -- [前缀树专题](https://github.com/azl397985856/leetcode/blob/master/thinkings/trie.md) diff --git "a/docs/algorithm/04-\351\230\237\345\210\227.md" "b/docs/algorithm/04-\351\230\237\345\210\227.md" deleted file mode 100644 index 58ff6d9bc..000000000 --- "a/docs/algorithm/04-\351\230\237\345\210\227.md" +++ /dev/null @@ -1,2 +0,0 @@ -# 队列 - diff --git "a/docs/algorithm/05-\345\223\210\345\270\214\350\241\250.md" "b/docs/algorithm/05-\345\223\210\345\270\214\350\241\250.md" deleted file mode 100644 index 7c63092da..000000000 --- "a/docs/algorithm/05-\345\223\210\345\270\214\350\241\250.md" +++ /dev/null @@ -1,139 +0,0 @@ -本文为 `《91 天学算法》` 中的讲义, 因为是非开源项目, 所以这里无法给出链接, 作者 [lucifer](https://lucifer.ren/blog/2020/05/23/91-algo/) - -后期本人会重写, 暂时引用, 特此声明 - -# 哈希表 - -### 介绍 - -- 定义:散列表(Hash table,也叫哈希表),是根据关键码值(Key)而直接进行访问的数据结构。散列表可以使用数组 + 链表的方式来实现。 -- 哈希函数:哈希表查找过程是根据Key来算出hashcode(通常是一个数字),根据这个数字来随机访问数组,而理论上两个不同的key是可以算出同样的hashcode的。这个就叫做哈希冲突。 -- 哈希冲突:hash函数不是万能的,这样不可避免的可能会造成两个不同的Key算出来的hash值相同,比如hash函数是x % 3,这样key=2和key=5算出的hash值都为2,这是要怎么办呢?一般我们有两种方法来处理,开放地址法和拉链法,具体大家可以查阅相关资料。这里简单的画了个图给大家直观看一下大概意思: - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfatsgoiutj30n60hzjtr.jpg) - -> 最后偷偷说一下,用Java的同学有兴趣可以看看HashSet的源码,底层也是用的HashMap😄 -- 哈希表的性能:构造一个冲突小,稳定性高的hash函数是很重要的,我们在刷题的时候大部分时间都不会去考虑这个问题,但是实际工程中有时不可避免需要我们自己构造hash函数,这时就要根据实际情况进行分析测试啦。 - - - - - - - - - - - - - - -## 刷题技巧 - -### 常用操作的时间复杂度 - -- 插入:O(1) -- 删除:O(1) -- 查找:O(1) - -是的,常用的操作在非极其特殊情况下,平均的时间复杂度都为**O(1)** - -### 常见的题类型如下: - -- **统计xx出现次数/频率/** (见下方多人运动) - - 该种题比较直观,若已知数据范围较小且比较连续,可以考虑用数组来实现 - -- **需要查找/增加/删除操作为O(1)时间复杂度** (一些设计题) - - 见到这种要求的题可以考虑一下是否需要hash表来做,比如LRU,LFU之类的题,题目中要求了时间复杂度,就是用hash表+双向链表解决的。 - -- **题目类型为图数据结构相关** (比如并查集) - - 这样可能需要构建有向图/无向图,这时可以用hash表来表示图并进行后续操作。 - -- **需要存储之前的状态以减少计算开销**(比如经典的两数和) - - 相信大家做过dp的一些题目就知道,记忆化搜索,该方法就利用hash表来存储历史状态,这样可以大大减少重复计算。 - -- **等等,大家多做类似的题目,相信可以总结出一套自己的思路。** - -### 模板(伪代码) - -- 判断目标值是否出现过(例题如:两数之和、是否存在重复元素、合法数独等等) - -```python -for num in nums: - if num(该处为目标值target) in hashtable: - return true -return false -``` - -- 统计频率 - -数据比较离散 - -```python -for num in nums: - if num in hashtable: - hashtable[num] += 1 - else - hashtable[num] = 1 -# 后续操作 -------------------- -``` - -数据范围较小且连续则可以用数组代替 - -```java -// 假设数据范围是0-n且n较小 -int[] hashtable = new int[n + 1]; - -for num in nums: - hashtable[num] += 1; - -// 后续操作 ------------------- -``` - -### 题目推荐 - 👥多人运动 - -#### 题目描述 -已知小猪每晚都要约好几个女生到酒店房间。每个女生 i 与小猪约好的时间由 [si , ei]表示,其中 si 表示女生进入房间的时间, ei 表示女生离开房间的时间。由于小猪心胸开阔,思想开明,不同女生可以同时存在于小猪的房间。请计算出小猪最多同时在做几人的「多人运动」。 - - -例子: - -Input : [[ 0 , 30] ,[ 5 , 10 ] , [15 , 20 ] ] - -OutPut :最多同时有两个女生的「三人运动」 - -#### 思路 - -这个题 解法不止一种,但是我们这里因为在讲hash表,统计频率。下面我只写一下大致思路的伪代码,具体细节大家不妨可以尝试自己实现一下。 - -```java -// 上面刚刚说了关于频率统计的方法,这里读完题,是不是就立刻想到了: -// 用hash表来统计每个时刻房间内的人数并维护一个最大值就是我们所求的结果啦 - -res = -1 - -for everyGirl in girls: - for curTime in [everyGirl.start, everyGirl.end]: - // 套上面板子 - if curTime in hashtable: - hashtable[curTime] += 1 - else - hashtable[curTime] = 1 - - // 维护最大值 - res = max(res, hashtable[curTime]) - ----------------------------- -``` - -线下验证通过可以贴到这里哦 - -**[【每日一题】- 2020-04-27 - 多人运动](https://github.com/azl397985856/leetcode/issues/347)** - -这里还有各种解题方法,大家都可以学习下思路并试着自己做一做! diff --git "a/docs/algorithm/06-\345\217\214\346\214\207\351\222\210.md" "b/docs/algorithm/06-\345\217\214\346\214\207\351\222\210.md" deleted file mode 100644 index a547fa3a3..000000000 --- "a/docs/algorithm/06-\345\217\214\346\214\207\351\222\210.md" +++ /dev/null @@ -1,203 +0,0 @@ -本文为 `《91 天学算法》` 中的讲义, 因为是非开源项目, 所以这里无法给出链接, 作者 [lucifer](https://lucifer.ren/blog/2020/05/23/91-algo/) - -后期本人会重写, 暂时引用, 特此声明 - -# 双指针 - - - - - - - - - - -## 什么是双指针 - -顾名思议,双指针就是**两个指针**,但是不同于 C,C++中的指针, 其是一种**算法思想**。 - -如果说,我们迭代一个数组,并输出数组每一项,我们需要一个指针来记录当前遍历的项,这个过程我们叫单指针(index)的话。 - -```java -for(int i = 0;i < nums.size(); i++) { - 输出(nums[i]); -} -``` - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gf5w79tciyj30aa0hl77b.jpg) - -(图 1) - -那么双指针实际上就是有两个这样的指针,最为经典的就是二分法中的左右双指针啦。 - -```java -int l = 0; -int r = nums.size() - 1; - -while (l < r) { - if(一定条件) return 合适的值,一般是 l 和 r 的中点 - if(一定条件) l++ - if(一定条件) r-- -} -// 因为 l == r,因此返回 l 和 r 都是一样的 -return l -``` - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gf5yfe9da7j307504ut8r.jpg) - -(图 2) - -读到这里,你发现双指针是一个很宽泛的概念,就好像数组,链表一样,其类型会有很多很多, 比如二分法经常用到`左右端点双指针`。滑动窗口会用到`快慢指针和固定间距指针`。 因此双指针其实是一种综合性很强的类型,类似于数组,栈等。 但是我们这里所讲述的双指针,往往指的是某几种类型的双指针,而不是“只要有两个指针就是双指针了”。 - -> 有了这样一个**算法框架**,或者算法思维,有很大的好处。它能帮助你理清思路,当你碰到新的问题,在脑海里进行搜索的时候,**双指针**这个词就会在你脑海里闪过,闪过的同时你可以根据**双指针**的所有套路和这道题进行**穷举匹配**,这个思考解题过程本来就像是算法,我会在进阶篇《搜索算法》中详细阐述。 - -那么究竟我们算法中提到的双指针指的是什么呢?我们一起来看下算法中双指针的常见题型吧。 - -## 常见题型有哪些? - -这里我将其分为三种类类型,分别是: - -1. 快慢指针(两个指针步长不同) -2. 左右端点指针(两个指针分别指向头尾,并往中间移动,步长不确定) -3. 固定间距指针(两个指针间距相同,步长相同) - -> 上面是我自己的分类,没有参考别人。可以发现我的分类标准已经覆盖了几乎所有常见的情况。 大家在平时做题的时候一定要养成这样的习惯,将题目类型进行总结,当然这个总结可以是别人总结好的,也可以是自己独立总结的。不管是哪一种,都要进行一定的消化吸收,把它们变成真正属于自己的知识。 - -不管是哪一种双指针,只考虑双指针部分的话 ,由于最多还是会遍历整个数组一次,因此时间复杂度取决于步长,如果步长是 1,2 这种常数的话,那么时间复杂度就是 O(N),如果步长是和数据规模有关(比如二分法),其时间复杂度就是 O(logN)。并且由于不管规模多大,我们都只需要最多两个指针,因此空间复杂度是 O(1)。下面我们就来看看双指针的常见套路有哪些。 - -## 常见套路 - -### 快慢指针 - -1. 判断链表是否有环 - -这里给大家推荐两个非常经典的题目,一个是力扣 287 题,一个是 142 题。其中 142 题我在我的 LeetCode 题解仓库中的每日一题板块出过,并且给了很详细的证明和解答。而 287 题相对不直观,比较难以想到,这道题曾被官方选定为每日一题,也是相当经典的。 - -- [287. 寻找重复数](https://leetcode-cn.com/problems/find-the-duplicate-number/) - -- [【每日一题】- 2020-01-14 - 142. 环形链表 II · Issue #274 · azl397985856/leetcode](https://github.com/azl397985856/leetcode/issues/274) - -2. 读写指针。典型的是`删除重复元素` - -这里推荐我仓库中的一道题, 我这里写了一个题解,横向对比了几个相似题目,并剖析了这种题目的本质是什么,让你看透题目本质,推荐阅读。 - -- [80.删除排序数组中的重复项 II](https://github.com/azl397985856/leetcode/blob/master/problems/80.remove-duplicates-from-sorted-array-ii.md) - -## 左右端点指针 - -1. 二分查找。 - -二分查找会在专题篇展开,这里不多说,大家先知道就行了。 - -2. 暴力枚举中“从大到小枚举”(剪枝) - -一个典型的题目是我之前参加官方每日一题的时候给的一个解法,大家可以看下。这种解法是可以 AC 的。同样地,这道题我也给出了三种方法,帮助大家从多个纬度看清这个题目。强烈推荐大家做到一题多解。这对于你做题很多帮助。除了一题多解,还有一个大招是多题同解,这部分我们放在专题篇介绍。 - -[find-the-longest-substring-containing-vowels-in-even](https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/solution/qian-zhui-he-zhuang-tai-ya-suo-pythonjava-by-fe-lu/) - -3. 有序数组。 - -区别于上面的二分查找,这种算法指针移动是连续的,而不是跳跃性的,典型的是 LeetCode 的`两数和`,以及`N数和`系列问题。 - -## 固定间距指针 - -1. 一次遍历(One Pass)求链表的中点 - -2. 一次遍历(One Pass)求链表的倒数第 k 个元素 - -3. 固定窗口大小的滑动窗口 - -## 模板(伪代码) - -我们来看下上面三种题目的算法框架是什么样的。这个时候我们没必要纠结具体的语言,这里我直接使用了伪代码,就是防止你掉进细节。 - -当你掌握了这种算法的细节,就应该找几个题目试试。一方面是检测自己是否真的掌握了,另一方面是“细节”,”细节“是人类,尤其是软件工程师最大的敌人,毕竟我们都是`差不多先生`。 - -1. 快慢指针 - -```jsx -l = 0 -r = 0 -while 没有遍历完 - if 一定条件 - l += 1 - r += 1 -return 合适的值 -``` - -2. 左右端点指针 - -```jsx -l = 0 -r = n - 1 -while l < r - if 找到了 - return 找到的值 - if 一定条件1 - l += 1 - else if 一定条件2 - r -= 1 -return 没找到 - -``` - -3. 固定间距指针 - -```jsx -l = 0 -r = k -while 没有遍历完 - 自定义逻辑 - l += 1 - r += 1 -return 合适的值 -``` - -## 题目推荐 - -如果你`差不多`理解了上面的东西,那么可以拿下面的题练练手。Let's Go! - -### 左右端点指针 - -- 16.3Sum Closest (Medium) -- 713.Subarray Product Less Than K (Medium) -- 977.Squares of a Sorted Array (Easy) -- Dutch National Flag Problem - -> 下面是二分类型 - -- 33.Search in Rotated Sorted Array (Medium) -- 875.Koko Eating Bananas(Medium) -- 881.Boats to Save People(Medium) - -更多二分推荐: - -- [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) -- [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) -- [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) -- [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/) -- [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) -- [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) -- [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) -- [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) - -### 快慢指针 - -- 26.Remove Duplicates from Sorted Array(Easy) -- 141.Linked List Cycle (Easy) -- 142.Linked List Cycle II(Medium) -- 287.Find the Duplicate Number(Medium) -- 202.Happy Number (Easy) - -### 固定间距指针 - -- 1456.Maximum Number of Vowels in a Substring of Given Length(Medium) - -> 固定窗口大小的滑动窗口见专题篇的滑动窗口专题(暂未发布) - -## 其他 - -有时候也不能太思维定式,比如 https://leetcode-cn.com/problems/consecutive-characters/ 这道题根本就没必要双指针什么的。 - -再比如:https://lucifer.ren/blog/2020/05/31/101.symmetric-tree/ diff --git "a/docs/algorithm/07-\345\271\266\346\237\245\351\233\206.md" "b/docs/algorithm/07-\345\271\266\346\237\245\351\233\206.md" deleted file mode 100644 index 8258727bb..000000000 --- "a/docs/algorithm/07-\345\271\266\346\237\245\351\233\206.md" +++ /dev/null @@ -1,217 +0,0 @@ -本文为 `《91 天学算法》` 中的讲义, 因为是非开源项目, 所以这里无法给出链接, 作者 [lucifer](https://lucifer.ren/blog/2020/05/23/91-algo/) - -后期本人会重写, 暂时引用, 特此声明 - -# 并查集 - -> 并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。 - -并查集维护了一个不相交动态集合$S = {S1, S2, ..., Sn}$。我们用集合中的某个元素来代表这个集合,该元素称为集合的代表元。 - -### 并查集的操作 - -- MAKE-SET(x): 构建并查集 -- UNION(x, y): 合并两个子集 -- FIND(x): 确定 x 属于哪个子集 - -### 并查集的数据表示 - -并查集元素一般用树来表示,树中的每个节点代表一个成员,每棵树表表示一个集合,多棵树构成一个并查集森林。 -每个集合中,树根即其代表元。 - -```ts -interface Node { - parent: Node; -} -``` - -![](./assets/find-union-set/express.png) - -### 并查集实现 - -#### MAKE-SET(x) - -MAKE-SET, 初始化时,节点的 parent 指向其自身,每个元素都是一棵数,并以自己为代表元。 - -``` -function MAKE-SET(x) - x.parent = x -``` - -#### FIND-SET(x) - -查找 x 元素的代表元,以下是递归查询 - -``` -FIND-SET(x) - if x != FIND-SET(x) - return FIND-SET(x.parent) - return x.parent -``` - -#### UNION(x, y) - -集合合并,分别找到 x, y 的代表元,将其合并,具体表现形式为将 FIND(x).parent 指向 FIND(y).parent - -``` -UNION(x, y) - xRoot = FIND-SET(x) - yRoot = FIND-SET(y) - xRoot.parent = yRoot -``` - -上述并集是最基础的一种表示,n-1 次的 UNION 操作,可能会构造初一棵恰好含有 n 个节点的线性链的树,考虑以下情景 -![](./assets/find-union-set/find-union-bad.png) - -以下两种启发式的时间优化 - -1. 按秩合并,这里的秩是指节点的高度,具体操作为将较小秩序的根指向较大秩的根。 - ![](./assets/find-union-set/rank.png) -2. 路径压缩,在 FIND-SET 的时候将节点的 parent 指向代表元也就是树根。 - ![](./assets/find-union-set/path.png) - -#### 带秩的 UNION 实现 - -带秩的数据节点表示为 - -```ts -Node { - rank: 0, - parent: Node -} -``` - -``` -UNION(x, y) - if x.rank > y.rank - y.parent = x - else x.parent = y // 将低秩树根指向高秩树根 - if (x.rank === y.rank) - y.rank = y.rank + 1 // 如果两个树秩相同 -``` - -#### 路径压缩的 FIND-SET 实现 - -``` -FIND-SET(x) - if x !== x.parent - x.parent = FIND-SET(x.parent) // 递归时找到树根代表元,回溯时将当前节点的 parent 指向树根 - return x.parent -``` - -### 完整代码 - -#### python - -##### 不带优化的实现 - -```python -class UF: - parent = {} - cnt = 0 - def __init__(self, M): - # 初始化 parent 和 cnt - - def find(self, x): - while x != self.parent[x]: - x = self.parent[x] - return x - def union(self, p, q): - if self.connected(p, q): return - self.parent[self.find(p)] = self.find(q) - self.cnt -= 1 - def connected(self, p, q): - return self.find(p) == self.find(q) -``` - -##### 带路径压缩的代码模板 - -```python -class UF: - parent = {} - size = {} - cnt = 0 - def __init__(self, M): - # 初始化 parent,size 和 cnt - - def find(self, x): - while x != self.parent[x]: - x = self.parent[x] - # 路径压缩 - self.parent[x] = self.parent[self.parent[x]]; - return x - def union(self, p, q): - if self.connected(p, q): return - # 小的树挂到大的树上, 使树尽量平衡 - leader_p = self.find(p) - leader_q = self.find(q) - if self.size[leader_p] < self.size[leader_q]: - self.parent[leader_p] = leader_q - else: - self.parent[leader_q] = leader_p - self.cnt -= 1 - def connected(self, p, q): - return self.find(p) == self.find(q) -``` - -#### typescript 实现 - -```ts -class UF { - private parents: Map = new Map(); - private ranks: Map = new Map(); - - constructor(values: T[]) { - this.makeSet(values); - } - - makeSet(values: T[]) { - values.forEach((value) => { - this.parents.set(value, value); - this.ranks.set(value, 0); - }); - } - - find(value: T) { - const parent = this.parents.get(value); - if (parent === value) { - return parent; - } - this.parents.set(value, this.find(parent)); - return this.parents.get(value); - } - - union(x: T, y: T) { - const xRank = this.ranks.get(x); - const yRank = this.ranks.get(y); - const xRoot = this.parents.get(x); - const yRoot = this.parents.get(y); - if (xRank > yRank) { - this.parents.set(yRoot, xRoot); - } else { - this.parents.set(xRoot, yRoot); - if (xRank === yRank) { - this.ranks.set(y, yRank + 1); - } - } - } -} -``` - -### 并查集的应用 - -1. 确定无向图的连通分量 -2. 亲戚问题,是否同个祖先 - -### 推荐下面的题练练手 - -- [547. 朋友圈](https://leetcode-cn.com/problems/friend-circles/) -- [1319. 连通网络的操作次数](https://leetcode-cn.com/problems/number-of-operations-to-make-network-connected/) -- [924. 尽量减少恶意软件的传播](https://leetcode-cn.com/problems/minimize-malware-spread/) -- [928. 尽量减少恶意软件的传播 II](https://leetcode-cn.com/problems/minimize-malware-spread-ii/) - -### 参考 - -1. 算法导论 -2. [维基百科](https://zh.wikipedia.org/wiki/%E5%B9%B6%E6%9F%A5%E9%9B%86) -3. [并查集详解 ——图文解说,简单易懂(转)](https://blog.csdn.net/liujian20150808/article/details/50848646) diff --git "a/docs/algorithm/08-\345\211\215\347\274\200\346\240\221.md" "b/docs/algorithm/08-\345\211\215\347\274\200\346\240\221.md" deleted file mode 100644 index 3cc8a4d70..000000000 --- "a/docs/algorithm/08-\345\211\215\347\274\200\346\240\221.md" +++ /dev/null @@ -1,52 +0,0 @@ -本文为 `《91 天学算法》` 中的讲义, 因为是非开源项目, 所以这里无法给出链接, 作者 [lucifer](https://lucifer.ren/blog/2020/05/23/91-algo/) - -后期本人会重写, 暂时引用, 特此声明 - -# Trie - -### 简介 - -字典树也叫前缀树、Trie。它本身就是一个树型结构,也就是一颗多叉树,学过树的朋友应该非常容易理解,它的核心操作是插入,查找。删除很少使用,因此这个讲义不包含删除操作。 - -### 应用场景及分析(个人理解): - -它的核心思想是用空间换时间,利用字符串的公共前缀来降低查询的时间开销。 - -- 比如给你一个字符串query,问你这个字符串是否在字符串集合中出现过,这样我们就可以将字符串集合建树,建好之后来匹配query是否出现,那有的朋友肯定会问,之前讲过的hashmap岂不是更好? -- 我们想一下用百度搜索时候,打个“一语”,搜索栏中会给出“一语道破”,“一语成谶(四声的chen)”等推荐文本,这种叫模糊匹配,也就是给出一个模糊的query,希望给出一个相关推荐列表,很明显,hashmap并不能做到模糊匹配,而Trie可以完美实现。 - - 因此,这里我的理解是:上述精确查找只是模糊查找一个特例,模糊查找hashmap显然做不到,并且如果在精确查找问题中,hashmap出现过多冲突,效率还不一定比Trie高,有兴趣的朋友可以做一下测试,看看哪个快。 - -- 给你一个长句和一堆敏感词,找出长句中所有敏感词出现的所有位置(想下,有时候我们口吐芬芳,结果发送出去却变成了****,懂了吧) -- 还有些其他场景,这里不过多讨论,有兴趣的可以google一下。 - -### Trie的节点: - -- 根结点无实际意义 -- 每一个节点代表一个字符 -- 每个节点中的数据结构可以自定义,如isWord(是否是单词),count(该前缀出现的次数)等,需实际问题实际分析需要什么。 - -### Trie的插入 - -- 假定给出几个单词如[she,he,her,good,god]构造出一个Trie如下图: - -![前缀树](assets/trie/Untitled.png) - -- 也就是说从根结点出发到某一粉色节点所经过的字符组成的单词,在单词列表中出现过,当然我们也可以给树的每个节点加个count属性,代表根结点到该节点所构成的字符串前缀出现的次数 - -![前缀树](assets/trie/Untitled%201.png) - -可以看出树的构造非常简单,插入新单词的时候就从根结点出发一个字符一个字符插入,有对应的字符节点就更新对应的属性,没有就创建一个! - -### Trie的查询 - -查询更简单了,给定一个Trie和一个单词,和插入的过程类似,一个字符一个字符找 - -- 若中途有个字符没有对应节点→Trie不含该单词 -- 若字符串遍历完了,都有对应节点,但最后一个字符对应的节点并不是粉色的,也就不是一个单词→Trie不含该单词 - -### Trie的复杂度 - -插入和查询的时间复杂度自然是$O(len(key))$,key是待插入(查找)的字串。 - -建树的最坏空间复杂度是$O(m^{n})$, m是字符集中字符个数,n是字符串长度。 diff --git a/docs/algorithm/09-KMP&RK.md b/docs/algorithm/09-KMP&RK.md deleted file mode 100644 index 640632409..000000000 --- a/docs/algorithm/09-KMP&RK.md +++ /dev/null @@ -1,149 +0,0 @@ -本文为 `《91 天学算法》` 中的讲义, 因为是非开源项目, 所以这里无法给出链接, 作者 [lucifer](https://lucifer.ren/blog/2020/05/23/91-algo/) - -后期本人会重写, 暂时引用, 特此声明 - -# 字符串匹配 - -字符串搜索算法(String searching algorithms)又称字符串比对算法(string matching algorithms)是一种搜索算法,是字符串算法中的一类,用以试图在一长字符串或文章中,找出其是否包含某一个或多个字符串,以及其位置。 - -用数学语言描述如下: - -假设文本串 T 是一个长度为 n 的数组 $T[1..n]$, P 是长度为 m 的数组 $P[1..m]$, 其中 T 被称为文本串,P 被称为模式串。如果有 $0 <= s < m-n$, 使得 T[s, s + 1, .., s + m] 等于 P[1..m], 则称 P 在 T 中出现且位移为 s - -字符串匹配问题就是找到模式串在文本串中的位移 s - - - - - - - - - - - - -## 暴力 - -遍历文本串,对遍历到的每个位置 i, 判断 $T[i..i+m]$ 是否和模式串$P[1..m]$相等 -伪代码 - -``` -n = length[T] -m = length[P] - -for s = 0 to n - m - do if T[s...s+m] == P[1...m] - find s -``` - -时间复杂度 $O((n - m + 1) * n)$, 空间复杂度$O(1)$ - -这种暴力算法效率并不高,因为对于 s 的每个值,我们获得文本串和模式串的信息在考虑 s 的其它值时被丢弃了。比如 t = 'aaaabbbb', p = 'aabb' -当 s = 1 时,我们判断了 t[1] == p[1], t[2] == p[2], t[3] != p[3], s 递增判断 s=2 的情况,注意到在判断 s = 1 的时候,我们已经知道了 t[2] == p[2], 但是这个时候我们还要计算 t[2] 和 p[2]的比对情况,导致了计算重复。如果我们充分利用之前计算过的匹配信息,算法效率会有什么样的提升呢,接下来介绍另外几种算法。 - -## RK - -RK 算法主要思想主要是对 T 中每个长度为 m 的子字符串 T[s..s+m] 做哈希,生成哈希值 h1, 对 P 做哈希,生成哈希值 h2, 比对 h1 和 h2,如果两个哈希值(不考虑冲突)相等,则判断 P 在 T 中出现,且位移为 s - -伪代码 - -``` -hp = hash(P[0..m]) -for s = 0 to n - m - hs = hash(T[s..s+m]) - if hp == hs - then print 'find s' -``` - -![示意图](./assets/kmp/rk.svg) - -我们这里选取的哈希函数为 - -$f(P[1..m]) = P[1..m]表示的10进制值$ - -假设 P 和 T 全由 d 个字符组成的,则我们可以选择 d 进制表示 P 和 T,再将 d 进制转为 10 进制便于计算。 -为了简化说明,我们更特殊的假设 P 和 T 全由[0-9]10 个数字组成, -那么 P 的 10 进制为 - -``` -f(P) = P[0] * 10 ^ (m - 1) + P[1] * 10 ^ (m - 2) ... + P[1] -``` - -T[s...s + m]的 10 进制为 - -``` -f(T[s...s + m]) = T[s] * 10 ^ (m - 1) + T[s + 1] * 10 ^ (m - 2) ... + T[s + m] -``` - -T[s+1...s + m + 1] 可以根据 T[s...s + m]推导 - -``` -f(T[s+1...s + m + 1]) = T[s+1] * 10 ^ (m - 1) + T[s + 2] * 10 ^ (m - 2) ... + T[s + m + 1] -= (f(T[s...s + m]) - T[s] * 10 ^ (m - 1)) * 10 + T[s + m + 1] -``` - -这样就把以上 `hp === hs` 的哈希比较转化为正常的 10 进制比较。 - -到目前为止,以上假设我们回避的一个问题是如果 f(P) 或者 f(T)计算的 10 进制过大,导致运算溢出怎么办? - -这里我们通过选择一个比较大的素数 q, 计算后的 10 进制数对 q 取模后再进行比较。但是这种方案并不完美,f(P) % q === f(T[s]) % q 并不能代表 f(P) === f(T[s])。任何的 f(P) % q === f(T[s]) % q 都需要额外的测试来保证 P === T[s], 这里我们通过检测 P[1...m] === T[s...s+m]来完成。 - -## KMP - -下面介绍下 KMP 算法,KMP 算法由 Knuth Morris Pratt 三个大佬联合发明,KMP 算法名字由三个大佬名字首位字符组成。 - -首先我们定义模式串的前缀函数 f(i) 为 模式串 P 中 P[1...i]相同前缀后缀的最大长度。对 P[1...m]中的每个 i,(i > 0 && i <= m), 用一个数组 next 记录。 - -之前提到暴力算法的时间复杂度很高是因为每次字符失配后,我们只是简单的将 s 位移加一,将之前的匹配信息舍去,造成计算重复。 - -KMP 算法在每次失配后,会根据上一次的比对信息跳转到相应的 s 处,借助的就是上述的 next 数组。 - -推导过程可以参考 [从头到尾彻底理解KMP](https://blog.csdn.net/v_JULY_v/article/details/7041827),个人觉得这篇讲的非常透彻,这里就不班门弄斧了。 - - -以下是计算 next 数组的伪代码 - -``` -get_next(P): - m = P.length - 使得 next 为长度为m的数组 - next[1] = 0 - k = 0 - for i = 2 to m - while(k > 0 并且 P[k+1] != P[i]) - k = next[k] - if P[k+1] == P[i] - k = k + 1 - next[i] = k - return next -``` - -以下是 KMP 的伪代码 - -``` -KMP(T, P) - n = T.length - m = P.length - next = getNext(P) - q = 0 - for let i = 1 to n: - while(q > 0 并且 P[q + 1] !== T[i]) - q = next[q] - if P[q + 1] == T[i] - q = q + 1 - if (q == m) - 找到匹配位移 s = i -``` - -## 基于有限自动机的字符串匹配 - -这个算法仅限了解即可,这里不做展开,感兴趣可以参考 [Finite Automata algorithm for Pattern Searching](https://www.geeksforgeeks.org/finite-automata-algorithm-for-pattern-searching/) - -## 参考 - -+ [维基百科](https://zh.wikipedia.org/wiki/%E5%85%8B%E5%8A%AA%E6%96%AF-%E8%8E%AB%E9%87%8C%E6%96%AF-%E6%99%AE%E6%8B%89%E7%89%B9%E7%AE%97%E6%B3%95) -+ [从头到尾彻底理解KMP](https://blog.csdn.net/v_JULY_v/article/details/7041827) -+ [Finite Automata algorithm for Pattern Searching](https://www.geeksforgeeks.org/finite-automata-algorithm-for-pattern-searching/) - - diff --git "a/docs/algorithm/10-\350\267\263\350\241\250.md" "b/docs/algorithm/10-\350\267\263\350\241\250.md" deleted file mode 100644 index bf89abd92..000000000 --- "a/docs/algorithm/10-\350\267\263\350\241\250.md" +++ /dev/null @@ -1,73 +0,0 @@ -本文为 `《91 天学算法》` 中的讲义, 因为是非开源项目, 所以这里无法给出链接, 作者 [lucifer](https://lucifer.ren/blog/2020/05/23/91-algo/) - -后期本人会重写, 暂时引用, 特此声明 - -# 跳表 - -虽然在面试中出现的频率不大,但是在工业中,跳表会经常被用到。力扣中关于跳表的题目只有一个。但是跳表的设计思路值得我们去学习和思考。 其中有很多算法和数据结构技巧值得我们学习。比如空间环时间的思想,比如效率的取舍问题等。 - - - - - - - - - - -## 解决的问题 - -只有知道跳表试图解决的问题, 后面学习才会有针对性。实际上,跳表解决的问题非常简单,一句话就可以说清楚,那就是**为了减少链表长度增加,查找链表节点时带来的额外比较次数**。 - -不借助额外空间的情况下,在链表中查找一个值,需要按照顺序一个个查找,时间复杂度为 $O(N)$,其中 N 为链表长度。 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gg6ynn9eknj31ts05wgn5.jpg) - -(单链表) - -当链表长度很大的时候, 这种时间是很难接受的。 一种常见的的优化方式是**建立哈希表,将所有节点都放到哈希表中,以空间换时间的方式减少时间复杂度**,这种做法时间复杂度为 $O(1)$,但是空间复杂度为 $O(N)$。 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gg6ysd1g34j317o0lun0d.jpg) - -(单链表 + 哈希表) - -为了防止链表中出现重复节点带来的问题,我们需要序列化节点,再建立哈希表,这种空间占用会更高,虽然只是系数级别的增加,但是这种开销也是不小的 。 - -为了解决上面的问题,跳表应运而生。 - -如下图所示,我们从链表中每两个元素抽出来,加一级索引,一级索引指向了原始链表,即:通过一级索引 7 的 down 指针可以找到原始链表的 7 。那怎么查找 10 呢? - -> 注意这个算法要求链表是有序的。 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gg6yzbgxdcj32340kun2t.jpg) - -(建立一级索引) - -我们可以: - -- 通过现在一级跳表中搜索到 7,发现下一个 18 大于 10 ,也就是说我们要找的 10 在这两者之间。 -- 通过 down 指针回到原始链表,通过原始链表的 next 指针我们找到了 10。 - -这个例子看不出性能提升。但是如果元素继续增大, 继续增加索引的层数,建立二级,三级。。。索引,使得链表能够实现二分查找,从而获得更好的效率。但是相应地,我们需要付出额外空间的代价。 - -![](https://tva1.sinaimg.cn/large/007S8ZIlly1gg6z4oovv2j31u90u0n50.jpg) - -(增加索引层数) - -理解了上面的点,你可以形象地将跳表想象为玩游戏的**存档**。 - -一个游戏有 10 关。如果我想要玩第 5 关的某一个地方,那么我可以直接从第五关开始,这样要比从第一关开始快。我们甚至可以在每一关同时设置很多的存档。这样我如果想玩第 5 关的某一个地方,也可以不用从第 5 关的开头开始,而是直接选择**离你想玩的地方更近的存档**,这就相当于跳表的二级索引。 - -跳表的时间复杂度和空间复杂度不是很好分析。由于时间复杂度 = 索引的高度 \* 平均每层索引遍历元素的个数,而高度大概为 $logn$,并且每层遍历的元素是常数,因此时间复杂度为 $logn$,和二分查找的空间复杂度是一样的。 - -空间复杂度就等同于索引节点的个数,以每两个节点建立一个索引为例,大概是 n/2 + n/4 + n/8 + … + 8 + 4 + 2 ,因此空间复杂度是 $O(n)$。当然你如果没三个建立一个索引节点的话,空间会更省,但是复杂度不变。 - -## 总结 - -- 跳表是可以实现二分查找的有序链表; - -- 跳表由多层构成,最底层是包含所有的元素原始链表,往上是索引链表; - -- 实际的设计中,需要做好取舍,设定合理数量的索引。 - -- 跳表查询、插入、删除的时间复杂度为 $O(log N)$,空间复杂度为 $O(N)$; diff --git "a/docs/algorithm/11-\345\211\252\346\236\235.md" "b/docs/algorithm/11-\345\211\252\346\236\235.md" deleted file mode 100644 index ed79d48fd..000000000 --- "a/docs/algorithm/11-\345\211\252\346\236\235.md" +++ /dev/null @@ -1,33 +0,0 @@ -本文为 `《91 天学算法》` 中的讲义, 因为是非开源项目, 所以这里无法给出链接, 作者 [lucifer](https://lucifer.ren/blog/2020/05/23/91-algo/) - -后期本人会重写, 暂时引用, 特此声明 - -# 剪枝 - -### 简介 - -关于剪枝这个概念,有的同学对机器学习有一定了解的肯定能脱口而出: - -- 剪枝是为了解决决策树过拟合,为了降低模型复杂度的一种手段。 - -而我们这次要介绍的剪枝又何尝不是为了降低我们所写程序的时空复杂度呢?我们在日常编程中或多或少都用到了剪枝,只不过大家没有系统去了解过这块的概念而已,相信大家都会用,所以这次的讲义希望将大家对剪枝中模糊不清对概念有一个比较清晰的认识。 - -### 剪枝的目的: - -上面也说了,我们剪枝就是为了降低我们算法的复杂度,剪枝最常出现在搜索相关的问题上,我们常用的搜索算法,其实描绘出的搜索空间就是一个树形结构,日常生活中剪枝剪的就是树的枝桠,在搜索空间中剪枝剪掉的就是必得不到解的部分来减小搜索空间。 - -### 剪枝遵循的三原则: - -- 正确性:这个很好理解,我们把这个树杈剪掉的前提是剪掉的这块一定不存在我们所要搜寻的解,不然我们把正确结果都剪没了,那还搜索个啥呢。 -- 准确性:我们在保证正确性的前提下,尽可能多的剪掉不包含所搜寻解的枝叶,也就是咱们剪,就要努力剪到最好。 -- 高效性:这个就是一个衡量我们剪枝是否必要的一个标准了,比如我们设计出了一个非常优秀的剪枝策略,可以把搜索规模控制在非常小范围,很棒!但是我们去实现这个剪枝策略的时候,又耗费了大量的时间和空间,是不是有点得不偿失呢?也就是我们需要在算法的整体效率和剪枝策略之间trade-off。 - -### 常用的剪枝策略: - -- 可行性剪枝:如果我们当前的状态已经不合法了,我们也没有必要继续搜索了,直接把这块搜索空间剪掉,也就是return。 -- 记忆化:常做dp题的同学应该也知道,我们把已经计算出来的问题答案保存下来,下次遇到该问题就可以直接取答案而不用重复计算。 -- 搜索顺序剪枝:在我们已知一些有用的先验信息的前提下,定义我们的搜索顺序。举个最简单例子,有时候我们正序遍历数组遇到答案返回,这种解法会TLE,但是,我们倒着遍历却过了,这就是对搜索顺序进行剪枝。 -- 最优性剪枝:也叫上下边界剪枝,Alpha-Beta剪枝,常用于对抗类游戏。当算法评估出某策略的后续走法比之前策略的还差时,就会剪掉该策略的后续发展。 -- 等等。 - -用好剪枝,会让我们的算法事半功倍,所以大家一定要掌握剪枝这一强力的思想。 diff --git "a/docs/algorithm/Python\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" "b/docs/algorithm/Python\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" deleted file mode 100644 index 07bda2743..000000000 --- "a/docs/algorithm/Python\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" +++ /dev/null @@ -1,71 +0,0 @@ -# Python内置操作时间复杂度 - - - - - - - - - - -## 列表 - list - -| **操作** | **平均情况** | **最坏情况** | -| ------------------------------------------------------------ | ------------ | ------------ | -| copy | O(n) | O(n) | -| append() | O(1) | O(1) | -| pop() | O(1) | O(1) | -| pop(k) | O(k) | O(k) | -| insert(n, x) | O(n) | O(n) | -| get `(即: list[index])` | O(1) | O(1) | -| set `(即: list[index] = x)` | O(1) | O(1) | -| delete `(即: list.remove(x))` | O(n) | O(n) | -| 遍历 | O(n) | O(n) | -| 获取切片 | O(k) | O(k) | -| 删除切片 | O(n) | O(n) | -| 设置切片 | O(k+n) | O(k+n) | -| extend(list) | O(k) | O(k) | -| [sort](http://svn.python.org/projects/python/trunk/Objects/listsort.txt) | O(n log n) | O(n log n) | -| [n] * k | O(nk) | O(nk) | -| x in s | O(n) | | -| min(s), max(s) | O(n) | | -| len() | O(1) | O(1) | - -## 双向队列 - collections.deque - -| **操作** | **平均情况** | **最坏情况** | -| ---------- | ------------ | ------------ | -| copy | O(n) | O(n) | -| append | O(1) | O(1) | -| appendleft | O(1) | O(1) | -| pop | O(1) | O(1) | -| popleft | O(1) | O(1) | -| extend | O(k) | O(k) | -| extendleft | O(k) | O(k) | -| rotate | O(k) | O(k) | -| remove | O(n) | O(n) | - -## 集合 - set - -| **操作** | **平均情况** | **最坏情况** | **备注** | -| --------------------------------- | ------------------------------------------------------------ | --------------------------------------------- | ------------------------------------------ | -| x in s | O(1) | O(n) | | -| Union s\|t | [O(len(s)+len(t))](https://wiki.python.org/moin/TimeComplexity_%28SetCode%29) | | | -| Intersection s&t | O(min(len(s), len(t)) | O(len(s) * len(t)) | replace "min" with "max" if t is not a set | -| Multiple intersection s1&s2&..&sn | | (n-1)*O(l) where l is max(len(s1),..,len(sn)) | | -| Difference s-t | O(len(s)) | | | -| s.difference_update(t) | O(len(t)) | | | -| Symmetric Difference s^t | O(len(s)) | O(len(s) * len(t)) | | -| s.symmetric_difference_update(t) | O(len(t)) | O(len(t) * len(s)) | | - -## 字典 - dict - -| **操作** | **平均情况** | **最坏情况** | -| -------------------- | ------------ | ------------ | -| k in d | O(1) | O(n) | -| copy | O(n) | O(n) | -| dict[key] | O(1) | O(n) | -| dict[key] = value | O(1) | O(n) | -| dict.pop() popitem() | O(1) | O(n) | -| 遍历 | O(n) | O(n) | \ No newline at end of file diff --git a/docs/algorithm/README.md b/docs/algorithm/README.md deleted file mode 100644 index 65f79b5a8..000000000 --- a/docs/algorithm/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Algorithms - - - - - - - - - - -## 更多内容待后期推送... \ No newline at end of file diff --git "a/docs/algorithm/\344\274\230\345\205\210\351\230\237\345\210\227.md" "b/docs/algorithm/\344\274\230\345\205\210\351\230\237\345\210\227.md" deleted file mode 100644 index 5354b68b6..000000000 --- "a/docs/algorithm/\344\274\230\345\205\210\351\230\237\345\210\227.md" +++ /dev/null @@ -1,2 +0,0 @@ -# 优先队列 - diff --git "a/docs/algorithm/\345\212\233\346\211\243\351\242\230\350\247\243/README.md" "b/docs/algorithm/\345\212\233\346\211\243\351\242\230\350\247\243/README.md" deleted file mode 100644 index 9f296c682..000000000 --- "a/docs/algorithm/\345\212\233\346\211\243\351\242\230\350\247\243/README.md" +++ /dev/null @@ -1,3 +0,0 @@ -# 力扣题解 - -记录 `Python` 力扣题解 \ No newline at end of file diff --git "a/docs/algorithm/\345\217\214\347\253\257\351\230\237\345\210\227.md" "b/docs/algorithm/\345\217\214\347\253\257\351\230\237\345\210\227.md" deleted file mode 100644 index bb9155dee..000000000 --- "a/docs/algorithm/\345\217\214\347\253\257\351\230\237\345\210\227.md" +++ /dev/null @@ -1,2 +0,0 @@ -# 双端队列 - diff --git "a/docs/algorithm/\346\216\222\345\272\217\345\220\210\351\233\206.md" "b/docs/algorithm/\346\216\222\345\272\217\345\220\210\351\233\206.md" deleted file mode 100644 index 7fa55859f..000000000 --- "a/docs/algorithm/\346\216\222\345\272\217\345\220\210\351\233\206.md" +++ /dev/null @@ -1,512 +0,0 @@ -# 排序算法 - - - - - - - - - - -## 前言 - -排序问题是我们学习编程过程中最常见的 , 以Python中的列表为例 , 进行排序算法分析 , - -在进行分析之前 , 我们先自己写一个时间测试装饰器 - -timewrap.py - -```python -import time -def cal_time(func): - def wrapper(*args, **kwargs): - t1 = time.time() - result = func(*args, **kwargs) - t2 = time.time() - print("%s running time: %s secs." % (func.__name__, t2-t1)) - return result - return wrapper -``` - -这个装饰器只是为了进行简单的时间测试 , 因为影响一个算法的执行时间实在是太多 , 但对于我们学习算法确是够了 - -## 冒泡排序 - -工作流程 : - -1. 比较相邻的元素 , 如果第一个比第二个大 , 就交换它们 -2. 对每一对相邻元素作同样的工作 , 从列表的开始到结尾依次进行 , 如果列表长度为n , 那么总共走(n-1)躺 - -图解 : - -![bubble_sort](http://oux34p43l.bkt.clouddn.com/bubble_sort.gif) - - -代码实现 : - -```python -import random -from timewrap import * -# 第一版 -@cal_time -def bubble_sort_1(li): - # 总共走n-1躺 - for n in range(len(li) - 1): - # 每一躺比较(总长度-n-1次) - for j in range(0, len(li) - n - 1): - if li[j] > li[j+1]: - li[j], li[j+1] = li[j+1], li[j] - -# 在第一版的基础进行优化 -@cal_time -def bubble_sort_2(li): - # 总共走(n-1)躺 - for n in range(len(li) - 1): - # 没有改变说明元素位置正确,为了防止更多不必要的比较 - change = False - # 每一躺比较(总长度-n-1次) - for j in range(0, len(li) - n - 1): - if li[j] > li[j+1]: - li[j], li[j+1] = li[j+1], li[j] - change = True - if not change: - return - -li = list(range(10000)) -random.shuffle(li) -bubble_sort_1(li) -bubble_sort_2(li) -''' -执行结果: -bubble_sort_1 running time: 29.570897817611694 secs. -bubble_sort_2 running time: 0.002001523971557617 secs. -''' -``` - -## 选择排序 - -工作流程 : - -每次从待排序的数据元素中选出最小 (或最大) 的一个元素 , 存放在序列的起始位置 , 直到全部待排序的数据元素排完 - -代码实现 : - -```python -import random -from timewrap import * -@cal_time -def select_sort(li): - # 总共走(n-1)躺 - for n in range(len(li) - 1): - # 最小数的位置 - min_loc = n - for j in range(n + 1, len(li) - 1): - if li[j] < li[min_loc]: - min_loc = j - li[n], li[min_loc] = li[min_loc], li[n] - -li = list(range(10000)) -random.shuffle(li) -select_sort(li) -''' -执行结果: -select_sort running time: 18.08176565170288 secs. -''' -``` - -## 插入排序 - -工作流程 : - -1. 存在一个已经有序的数据序列 -2. 将后续数据按照要求依次一个个插入有序序列中 - -图解 : - -![insert_sort](http://oux34p43l.bkt.clouddn.com/insert_sort.gif) - -代码实现 : - -```python -import random -from timewrap import * -@cal_time -def insert_sort(li): - # 循环无序区进行排序 - for n in range(1, len(li)): - tmp = li[n] - # 指向有序区最后位置 - j = n - 1 - while li[j] > tmp and j >= 0: - li[j+1] = li[j] - j -= 1 - li[j+1] = tmp - -li = list(range(10000)) -random.shuffle(li) -insert_sort(li) -''' -执行结果: -insert_sort running time: 18.230905055999756 secs. -''' -``` - -## 快速排序 - -工作流程 : - -1. 取一个元素P(第一个元素) , 使元素P归位 -2. 列表被P分成两部分 , 左边都小于P , 右边都大于P -3. 递归完成排序 - -图解 : - -![quick_sort](http://oux34p43l.bkt.clouddn.com/quick_sort.gif) - -代码实现 : - -```python -import random -from timewrap import * -import copy -import sys -# 修改递归最大层数 -sys.setrecursionlimit(100000) - -def partition(li, left, right): - """ - 进行分区 - """ - # 防止出现最坏情况 - # ri = random.randint(left, right) - # li[left], li[ri] = li[ri], li[left] - tmp = li[left] - while left < right: - while left < right and li[right] >= tmp: - right -= 1 - li[left] = li[right] - while left < right and li[left] <= tmp: - left += 1 - li[right] = li[left] - li[left] = tmp - return left - -def _quick_sort(li, left, right): - """ - 进行排序 - """ - if left < right: # 至少有两个元素 - mid = partition(li, left, right) - _quick_sort(li, left, mid-1) - _quick_sort(li, mid+1, right) - -@cal_time -def quick_sort(li): - return _quick_sort(li, 0, len(li)-1) - -li = list(range(10000)) -random.shuffle(li) -quick_sort(li) -''' -执行结果: -quick_sort running time: 0.10507440567016602 secs. -''' -``` - -## 堆排序 - -**堆分类** - -1. 大根堆 : 一颗完全二叉树 , 满足任一节点都比其孩子节点大 -2. 小根堆 : 一颗完全二叉树 , 满足任一节点都比其孩子节点小 - -工作流程 : - -1. 建立堆 , 已完成调整 (以构建大根堆为例) -2. 得到堆顶元素 , 为最大元素 -3. 去掉堆顶 , 将堆最后一个元素放到堆顶 , 随后重新调整 (挨个出数) -4. 一直重复3 , 直到堆为空 - -挨个出数规则 : 找最后一个数作为棋子 , 然后取堆顶的值 , 存放最后 , 依次执行取出 - -图解 : - -1.构建大根堆 , 索引按照从上倒下 , 从左到右依次递增 - -![构造大根堆](http://oux34p43l.bkt.clouddn.com/构造大根堆.gif) - -2.挨个出数 - -![挨个出数](http://oux34p43l.bkt.clouddn.com/挨个出数.gif) - -代码实现 : - -```python -from timewrap import * -import random - -def sift(li, low, high): - """ - :param li: - :param low: 堆根节点的位置 - :param high: 堆最有一个节点的位置 - :return: - """ - # 父亲的位置 - i = low - # 孩子的位置 - j = 2 * i + 1 - # 原父亲 - tmp = li[low] - while j <= high: - # 如果右孩子存在并且右孩子更大 - if j + 1 <= high and li[j+1] > li[j]: - j += 1 - # 如果原父亲比孩子小 - if tmp < li[j]: - # 把孩子向上移动一层 - li[i] = li[j] - i = j - j = 2 * i + 1 - else: - break - li[i] = tmp - -@cal_time -def heap_sort(li): - n = len(li) - # 建堆 - for i in range(n//2-1, -1, -1): - sift(li, i, n-1) - # 挨个出数 - for j in range(n-1, -1, -1): # j表示堆最后一个元素的位置 - li[0], li[j] = li[j], li[0] - # 堆的大小少了一个元素(j-1) - sift(li, 0, j-1) - -li = list(range(10000)) -random.shuffle(li) -heap_sort(li) -''' -执行结果: -heap_sort running time: 0.3037147521972656 secs. -''' -``` - -Python中有一个内置模块`heapq`可以帮助我们快速实现对排序 - -```python -import heapq -import random -from timewrap import * - -@cal_time -def heap_sort(li): - heapq.heapify(li) - n = len(li) - new_li = [] - for i in range(n): - new_li.append(heapq.heappop(li)) - return new_li - -li = list(range(10000)) -random.shuffle(li) -# 小根堆 -heap_sort(li) -# 大根堆,直接利用方法nlargest -heapq.nlargest(100, li) -``` - -## 归并排序 - -假设现在的列表分成两段有序 , 如何将其合成为一个有序的列表 - -工作流程 : - -1. 分解列表 , 直至分解为一个个只有一个元素的列表 -2. 比较两段序列中索引相同的值的大小 , 符合条件就进行交换 , 如小的在左 , 大的在右 -3. 进行合并 , 重复2操作 , 直至合并为一个列表得出结果 - -图解 : - -![merge_sort](http://oux34p43l.bkt.clouddn.com/merge_sort.png?imageMogr2/blur/1x0/quality/75|watermark/2/text/bHlvbi55YW5nQHFxLmNvbQ==/font/YXBhcmFqaXRh/fontsize/560/fill/Izk0ODI4Mg==/dissolve/100/gravity/SouthEast/dx/10/dy/10) - -代码实现 : - -```python -import random -def merge(li, low, mid, high): - i = low - j = mid + 1 - ltmp = [] - while i <= mid and j <= high: - if li[i] < li[j]: - ltmp.append(li[i]) - i += 1 - else: - ltmp.append(li[j]) - j += 1 - while i <= mid: - ltmp.append(li[i]) - i += 1 - while j <= high: - ltmp.append(li[j]) - j += 1 - li[low:high+1] = ltmp - -def _merge_sort(li, low, high): - # 至少两个元素 - if low < high: - mid = (low + high) // 2 - _merge_sort(li, low, mid) - _merge_sort(li, mid+1, high) - merge(li, low, mid, high) - -def merge_sort(li): - return _merge_sort(li, 0, len(li)-1) - -li = list(range(1,9)) -random.shuffle(li) -merge_sort(li) -``` - -## 希尔排序 - -希尔排序是一种分组插入排序算法 - -工作流程 : - -1. 首先取一个整数`d1=n/2` , 将元素分为d1个组 , 每组相邻两元素距离为d1 , 在各组内进行直接插入排序 -2. 取第二个整数`d2=d1/2` , 重复上述分组排序过程 , 直到`d1=1` , 即所有元素在同一组内进行直接插入排序 - -希尔排序每躺并不使某些元素有序 , 而是使整体数据越来越进阶有序 ; 最后一趟排序使得所有数据有序 - -代码实现 : - -```python -import random -def shell_sort(li): - gap = int(len(li) // 2) - while gap > 0: - for i in range(gap, len(li)): - tmp = li[i] - print(i-gap) - j = i - gap - while j >= 0 and tmp < li[j]: - li[j + gap] = li[j] - j -= gap - li[j + gap] = tmp - gap = int(gap / 2) - -li = list(range(10000)) -random.shuffle(li) -shell_sort(li) -print(li) -``` - -## 计数排序 - -现有一个列表 , 列表中的数范围都在0到100之间 , 列表长度大约为100万 , 设计算法在O(n)时间复杂度内将列表进行排序 - -工作流程 : - -1. 查找列表中最大和最小的元素 -2. 开辟一个新的空间存放统计的每个元素出现次数 -3. 反向填充目标列表 - -代码实现 : - -```python -import random -import copy -from timewrap import * - -@cal_time -def count_sort(li, max_num = 100): - count = [0 for i in range(max_num+1)] - for num in li: - count[num]+=1 - li.clear() - for i, val in enumerate(count): - for _ in range(val): - li.append(i) - -@cal_time -def sys_sort(li): - li.sort() - -li = [random.randint(0,100) for i in range(100000)] -li1 = copy.deepcopy(li) -count_sort(li) -''' -执行结果: -count_sort running time: 0.024517059326171875 secs. -''' -``` - -## 桶排序 - -桶排序的基本思想是将一个数据表分割成许多桶 , 然后每个桶各自排序 , 有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序 - -工作流程 : - -1. 建立一堆buckets -2. 遍历原始数组 , 并将数据放入到各自的buckets当中 -3. 对非空的buckets进行排序 -4. 按照顺序遍历这些buckets并放回到原始数组中即可构成排序后的数组 - -代码实现 : - -```python -import random -from timewrap import * - -def list_to_buckets(li, iteration): - """ - :param li: 列表 - :param iteration: 装桶是第几次迭代 - :return: - """ - buckets = [[] for _ in range(10)] - for num in li: - digit = (num // (10 ** iteration)) % 10 - buckets[digit].append(num) - return buckets - -def buckets_to_list(buckets): - return [num for bucket in buckets for num in bucket] - -@cal_time -def radix_sort(li): - maxval = max(li) - it = 0 - while 10 ** it <= maxval: - li = buckets_to_list(list_to_buckets(li, it)) - it += 1 - return li - -li = [random.randint(0,1000) for _ in range(100000)] -radix_sort(li) -''' -执行结果: -radix_sort running time: 0.3001980781555176 secs. -''' -``` - -## 小结 - -| 排序方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 | 复杂性 | -| ---- | --------- | --------- | --------- | -------------------------- | ---- | ---- | -| 冒泡排序 | O(n²) | O(n²) | O(n) | O(1) | 稳定 | 简单 | -| 选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 | 简单 | -| 插入排序 | O(n²) | O(n²) | O(n²) | O(1) | 稳定 | 简单 | -| 快速排序 | O(n²) | O(nlogn) | O(nlogn) | 平均情况O(nlogn)
最坏情况O(n) | 不稳定 | 较复杂 | -| 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 | 复杂 | -| 归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | 较复杂 | -| 希尔排序 | O(nlogn) | O(n²) | O(n) | O(1) | 不稳定 | 较复杂 | -| 计数排序 | O(n+k) | O(n+k) | O(n+k) | O(n+k) | 稳定 | 简单 | -| 桶排序 | O(n+k) | O(n²) | O(n+k) | O(n*k) | 不稳定 | 简单 | - - - diff --git "a/docs/algorithm/\346\240\221.md" "b/docs/algorithm/\346\240\221.md" deleted file mode 100644 index 6c9304c5a..000000000 --- "a/docs/algorithm/\346\240\221.md" +++ /dev/null @@ -1,2 +0,0 @@ -# 树 - diff --git "a/docs/algorithm/\351\223\276\350\241\250.md" "b/docs/algorithm/\351\223\276\350\241\250.md" deleted file mode 100644 index aa8ed453d..000000000 --- "a/docs/algorithm/\351\223\276\350\241\250.md" +++ /dev/null @@ -1,2 +0,0 @@ -# 链表 - diff --git "a/docs/algorithm/02-\346\225\260\347\273\204.md" b/docs/design-pattern/dry.md similarity index 100% rename from "docs/algorithm/02-\346\225\260\347\273\204.md" rename to docs/design-pattern/dry.md diff --git a/docs/design-pattern/foreword.md b/docs/design-pattern/foreword.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-pattern/ioc.md b/docs/design-pattern/ioc.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-pattern/isp.md b/docs/design-pattern/isp.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-pattern/kill&yagni.md b/docs/design-pattern/kill&yagni.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-pattern/lod.md b/docs/design-pattern/lod.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-pattern/lsp.md b/docs/design-pattern/lsp.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-pattern/ocp.md b/docs/design-pattern/ocp.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-pattern/srp.md b/docs/design-pattern/srp.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/linux/centos-deploy-elasticsearch.md b/docs/linux/centos-deploy-elasticsearch.md new file mode 100644 index 000000000..974570f95 --- /dev/null +++ b/docs/linux/centos-deploy-elasticsearch.md @@ -0,0 +1,57 @@ +# CentOS安装ElasticSearch + +## 安装JDK + +```bash +$ yum list java-11* +yum install java-11-openjdk* -y +java -version +``` + +## 安装ElasticSearch + +```bash +# 因为ElasticSearch无法使用root用户启动, 所以我们可以放到opt下 +$ cd /opt/ +$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.0-linux-x86_64.tar.gz +$ tar -zxvf elasticsearch-7.17.0-linux-x86_64.tar.gz +``` + +## 修改配置 + +```bash +# /opt/elasticsearch-7.17.0/config/elasticsearch.yml + +network.host: 0.0.0.0 +http.port: 9200 +node.name: node-1 +cluster.initial_master_nodes: ["node-1"] +``` + +## 创建用户和用户组 + + `ElasticSearch` 无法使用 `root` 用户启动 + +创建elsearch用户组及elsearch用户 + +```bash +$ groupadd elsearch +$ useradd user -g elsearch -p password +``` + +更改elasticsearch文件夹及内部文件的所属用户及组为elsearch:elsearch + +```bash +$ chown -R elsearch:elsearch /opt/elasticsearch +``` + +## 启动ElasticSearch + +```bash +$ su elsearch +$ cd /opt/elasticsearch-7.17.0/bin/ +$ ./elasticsearch +# 后台启动 +$ ./elasticsearch -d +``` + diff --git a/docs/linux/centos-deploy-gitlab.md b/docs/linux/centos-deploy-gitlab.md new file mode 100644 index 000000000..99c8a2cee --- /dev/null +++ b/docs/linux/centos-deploy-gitlab.md @@ -0,0 +1,78 @@ +# CentOS搭建Gitlab + +## 下载镜像 + +```shell +$ wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-14.4.2-ce.0.el7.x86_64.rpm --no-check-certificate +``` + +## 安装 + +```shell +$ yum install -y gitlab-ce-14.4.2-ce.0.el7.x86_64.rpm +``` + +安装成功之后会在 `/opt/gitlab` 目录下 + +安装依赖服务 + +```shell +$ yum -y install policycoreutils openssh-server openssh-clients postfix + +# 设置开机启动 +$ systemctl enable postfix && systemctl start postfix +``` + +## 修改配置文件 + +获取相应 `SSL` 证书 , 并将证书存放到 `/etc/gitlab/ssl/` 下 + +配置文件地址 : `/etc/gitlab/gitlab.rb` + +```shell +external_url 'https://gitlab.domain.com' +nginx['enable'] = true +nginx['redirect_http_to_https'] = true #http重定向到https +nginx['ssl_certificate'] = "/etc/gitlab/ssl/gitlab.com.pem" #放置对应的证书密钥 +nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/gitlab.com.key" #放置对应的证书密钥 +``` + +## 启动 + +```shell +# 加载配置 +$ gitlab-ctl reconfigure + +# 启动 +$ gitlab-ctl restart|start +``` + +## 汉化包 + +Gitlab 12 版本以上不需要汉化 + +```shell +wget https://gitlab.com/xhang/gitlab/-/archive/12-3-stable-zh/gitlab-12-3-stable-zh.tar.gz +cp -r gitlab-12-3-stable-zh/* /opt/gitlab/embedded/service/gitlab-rails/ +``` + +重新启动 + +## 备份 + +```shell +$ gitlab-rake gitlab:backup:create +``` + +## 恢复 + +```shell +# 停止相关数据连接服务 +$ gitlab-ctl stop unicorn +$ gitlab-ctl stop sidekiq +``` + +```shell +$ gitlab-rake gitlab:backup:restore BACKUP=1636682101_2021_11_12_12.3.9 +``` + diff --git a/docs/linux/centos-deploy-grafana.md b/docs/linux/centos-deploy-grafana.md new file mode 100644 index 000000000..4ef19a676 --- /dev/null +++ b/docs/linux/centos-deploy-grafana.md @@ -0,0 +1,22 @@ +# CentOS安装Grafana + +## 安装Grafana + +```bash +# 官网下载地址: https://grafana.com/grafana/download +$ wget https://dl.grafana.com/enterprise/release/grafana-enterprise-8.4.0.linux-amd64.tar.gz +$ tar -zxvf grafana-enterprise-8.4.0.linux-amd64.tar.gz +``` + +## 启动Grafana + +```bash +$ ./grafana-server +# 后台启动 +$ nohup ./grafana-server & +``` + +`Grafana` 默认端口为 3000 , 初始账号密码 `admin/admin` , 第一次登录会要求修改密码 + + + diff --git a/docs/linux/centos-deploy-influxdb.md b/docs/linux/centos-deploy-influxdb.md new file mode 100644 index 000000000..d90cc9a8b --- /dev/null +++ b/docs/linux/centos-deploy-influxdb.md @@ -0,0 +1,38 @@ +# CentOS安装InfluxDB + +## 安装 + +```bash +$ wget https://dl.influxdata.com/influxdb/releases/influxdb-1.8.2.x86_64.rpm +$ yum localinstall influxdb-1.8.2.x86_64.rpm +``` + +## 启动 + +```bash +$ systemctl start influxdb #启动 +$ systemctl restart influxdb #重启 +$ systemctl status influxdb #查看状态 +``` + +## 创建用户 + +```bash +$ influx +> CREATE USER root WITH PASSWORD '123456' WITH ALL PRIVILEGES +``` + +## 配置权限 + +```bash +$ vim /etc/influxdb/influxdb.conf +auth-enabled = false # 把这个配置改成 true +$ systemctl restart influxdb #重启 +``` + +## 登录 + +```bash +$ influx -username root -password 123456 +``` + diff --git a/docs/linux/centos-deploy-kafka.md b/docs/linux/centos-deploy-kafka.md new file mode 100644 index 000000000..1dd6d0784 --- /dev/null +++ b/docs/linux/centos-deploy-kafka.md @@ -0,0 +1,137 @@ +# CentOS安装Kafka + +## 安装JDK + +```bash +$ yum list java-1.8* +yum install java-1.8.0-openjdk* -y +java -version +``` + +## 安装Zookeeper + +```bash +$ wget https://mirrors.bfsu.edu.cn/apache/zookeeper/zookeeper-3.7.0/apache-zookeeper-3.7.0-bin.tar.gz +$ tar -zxvf apache-zookeeper-3.7.0-bin.tar.gz +``` + +修改 `zookeeper` 配置 + +```bash +$ cp apache-zookeeper-3.7.0-bin/conf/zoo_sample.cfg apache-zookeeper-3.7.0-bin/conf/zoo.cfg +$ vim apache-zookeeper-3.7.0-bin/conf/zoo.cfg + +dataDir=apache-zookeeper-3.7.0-bin/data #修改数据存放目录 +dataLogDir=apache-zookeeper-3.7.0-bin/log +``` + +启动 `zookeeper` + +```bash +$ cd apache-zookeeper-3.7.0-bin/bin +# 后台启动 +$ nohup apache-zookeeper-3.7.0-bin/bin/zkServer.sh start & +``` + +## 安装Kafka + +```bash +$ wget https://archive.apache.org/dist/kafka/2.8.0/kafka_2.12-2.8.0.tgz +$ tar -zxvf kafka_2.12-2.8.0.tgz +``` + +修改 `Kafka` 配置 + +```bash +$ vim kafka_2.12-2.8.0/config/server.properties + +# broker 的编号,如果集群中有多个 broker,则每个 broker 的编号需要设置的不同 +broker.id=0 +# 31 行 +listeners=PLAINTEXT://your.host.name:9092 +# 123 行,修改 zookeeper.connect 为自己的 IP:PORT +zookeeper.connect=localhost:2181/kafka +# 存放消息日志文件的地址 +log.dirs=/data/kafka-logs +``` + +`server.properties` 配置详解 + +```bash +//当前机器在集群中的唯一标识,和zookeeper的myid性质一样(broker.id和host.name每个节点都不相同) +broker.id=0 + +//当前kafka对外提供服务的端口默认是9092 +listeners=PLAINTEXT://hostname:9092 + +//这个参数默认是关闭的,在0.8.1有个bug,DNS解析问题,失败率的问题。 +host.name=hadoop1 + +//这个是borker进行网络处理的线程数 +num.network.threads=3 + +//这个是borker进行I/O处理的线程数 +num.io.threads=8 + +//发送缓冲区buffer大小,数据不是一下子就发送的,先回存储到缓冲区了到达一定的大小后在发送,能提高性能 +socket.send.buffer.bytes=102400 + +//kafka接收缓冲区大小,当数据到达一定大小后在序列化到磁盘 +socket.receive.buffer.bytes=102400 + +//这个参数是向kafka请求消息或者向kafka发送消息的请请求的最大数,这个值不能超过java的堆栈大小 +socket.request.max.bytes=104857600 + +//消息存放的目录,这个目录可以配置为“,”逗号分割的表达式,上面的num.io.threads要大于这个目录的个数这个目录, +//如果配置多个目录,新创建的topic他把消息持久化的地方是,当前以逗号分割的目录中,那个分区数最少就放那一个 +log.dirs=/home/hadoop/log/kafka-logs + +//默认的分区数,一个topic默认1个分区数 +num.partitions=1 + +//每个数据目录用来日志恢复的线程数目 +num.recovery.threads.per.data.dir=1 + +//默认消息的最大持久化时间,168小时,7天 +log.retention.hours=168 + +//轮转时间,当需要删除指定小时之前的数据时,该设置项很重要 +log.roll.hours=12 + +//这个参数是:因为kafka的消息是以追加的形式落地到文件,当超过这个值的时候,kafka会新起一个文件 +log.segment.bytes=1073741824 + +//每隔300000毫秒去检查上面配置的log失效时间 +log.retention.check.interval.ms=300000 + +//是否启用log压缩,一般不用启用,启用的话可以提高性能 +log.cleaner.enable=false + +//设置zookeeper的连接端口 +zookeeper.connect=192.168.123.102:2181,192.168.123.103:2181,192.168.123.104:2181 + +//设置zookeeper的连接超时时间 +zookeeper.connection.timeout.ms=6000 +``` + +启动 `Kafka` + +```bash +$ nohup kafka_2.12-2.8.0/bin/kafka-server-start.sh kafka_2.12-2.8.0/config/server.properties +``` + +## 测试 + +```bash +# topic 是发布消息的 category,以单节点的配置创建了一个叫 dblab01 的 topic.可以用 list 列出所有创建的 topics,来查看刚才创建的主题是否存在 +kafka_2.12-2.8.0/bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test + +kafka_2.12-2.8.0/bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test + +kafka_2.12-2.8.0/bin/kafka-topics.sh --list --zookeeper localhost:2181 + +kafka_2.12-2.8.0/bin/kafka-console-producer.sh --broker-list PLAINTEXT://localhost:9092 --topic test + +kafka_2.12-2.8.0/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning +``` + diff --git a/docs/linux/centos-deploy-nginx.md b/docs/linux/centos-deploy-nginx.md new file mode 100644 index 000000000..dac8f108f --- /dev/null +++ b/docs/linux/centos-deploy-nginx.md @@ -0,0 +1,109 @@ +# CentOS安装Nginx + +## 安装依赖 + +```shell +$ sudo yum install gcc-c++ + +$ sudo yum install -y openssl openssl-devel pcre pcre-devel zlib zlib-devel +``` + +## 安装Nginx + +```shell +$ wget https://nginx.org/download/nginx-1.19.9.tar.gz + +# 解压 +$ tar -zxvf nginx-1.19.9.tar.gz + +$ cd nginx-1.19.9 + +# 编译安装, 指定目录 /usr/local/nginx +$ sudo ./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module + +$ sudo make + +$ sudo make install +``` + +## 默认Nginx配置 + +**创建日志目录** + +```shell +$ sudo mkdir -p /data/logs/nginx/error /data/logs/nginx/logs /data/logs/nginx/info /data/logs/nginx/access +``` + +**配置文件** + +```yaml +user root; +worker_processes auto; +error_log /data/logs/nginx/error/log; +pid /data/logs/nginx/logs/nginx.pid; +worker_rlimit_nofile 51200; + +events { + use epoll; + worker_connections 51200; + multi_accept on; +} + +http { + include mime.types; + default_type application/octet-stream; + uwsgi_connect_timeout 600s; + server_names_hash_bucket_size 512; + client_header_buffer_size 32k; + large_client_header_buffers 4 32k; + client_max_body_size 1000m; + client_header_timeout 600s; + client_body_timeout 600; + sendfile on; + tcp_nopush on; + + keepalive_timeout 1800; + tcp_nodelay on; + + send_timeout 600; + + fastcgi_connect_timeout 600; + fastcgi_send_timeout 600; + fastcgi_read_timeout 600; + fastcgi_buffer_size 256k; + fastcgi_buffers 16 256k; + fastcgi_busy_buffers_size 512k; + fastcgi_temp_file_write_size 512k; + fastcgi_intercept_errors on; + + gzip on; + gzip_min_length 1k; + gzip_buffers 4 16k; + gzip_http_version 1.1; + gzip_comp_level 2; + gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml; + gzip_vary on; + gzip_proxied expired no-cache no-store private auth; + gzip_disable "MSIE [1-6]\."; + + limit_conn_zone $binary_remote_addr zone=perip:10m; + limit_conn_zone $server_name zone=perserver:10m; + + server_tokens off; + log_format _main '$remote_addr $http_host "$time_iso8601" $request_method $http_cookie "$http_token" "$uri" "$args" "$request_body" $status $body_bytes_sent $request_time "$http_user_agent"'; + + access_log /data/logs/nginx/access/log.pipe _main; +} +``` + +## 启动Nginx + +```bash +# 启动nginx +$ /usr/local/nginx -c /usr/local/nginx/conf/nginx.conf +# 重启nginx +$ /usr/local/nginx -s reload +# 检查nginx配置 +$ /usr/local/nginx -t +``` + diff --git a/docs/linux/centos-deploy-prometheus.md b/docs/linux/centos-deploy-prometheus.md new file mode 100644 index 000000000..ef5518555 --- /dev/null +++ b/docs/linux/centos-deploy-prometheus.md @@ -0,0 +1,114 @@ +# CentOS安装Prometheus + +## 安装Prometheus + +```bash +$ wget https://mirrors.bfsu.edu.cn/github-release/prometheus/prometheus/2.33.3%20_%202022-02-11/prometheus-2.33.3.linux-amd64.tar.gz +$ tar -zxvf prometheus-2.33.3.linux-amd64.tar.gz +``` + +## 安装node-exporter + +```bash +$ wget https://github.com/prometheus/node_exporter/releases/download/v1.3.1/node_exporter-1.3.1.linux-amd64.tar.gz +$ tar -zxvf node_exporter-1.3.1.linux-amd64.tar.gz +``` + +## 启动node-exporter + +```bash +$ node_exporter-1.3.1.linux-amd64/node_exporter --web.listen-address 0.0.0.0:9100 +$ nohup node_exporter-1.3.1.linux-amd64/node_exporter --web.listen-address 0.0.0.0:9100 & +``` + +## Promehteus.yml说明 + + `prometheus-2.33.3.linux-amd64/prometheus.yml` + +```bash +global # 此片段指定的是prometheus的全局配置, 比如采集间隔,抓取超时时间等。 +rule_files # 此片段指定报警规则文件, prometheus根据这些规则信息,会推送报警信息到alertmanager中。 +scrape_configs # 此片段指定抓取配置,prometheus的数据采集通过此片段配置。 +alerting # 此片段指定报警配置, 这里主要是指定prometheus将报警规则推送到指定的alertmanager实例地址。 +remote_write # 指定后端的存储的写入api地址。 +remote_read # 指定后端的存储的读取api地址 +scrape_interval # 抓取间隔,默认继承global值。 +scrape_timeout # 抓取超时时间,默认继承global值。 +evaluation_interval # 评估规则间隔 +external_labels # 外部一些标签设置 +metric_path # 抓取路径, 默认是/metrics +scheme # 指定采集使用的协议,http或者https。 +params # 指定url参数。 +basic_auth # 指定认证信息。 +*_sd_configs # 指定服务发现配置 +static_configs # 静态指定服务job。 +relabel_config # relabel设置。 +``` + +## 配置node-exporter + +```bash +vim prometheus.yml + +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: "prometheus" + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + + static_configs: + - targets: ["ip:9090"] + # 添加node-exporter + - job_name: 'node' + + scrape_interval: 5s + + static_configs: + # 其它主机上的exporter只用将localhost改为主机IP,不同的主机之间 , 隔开 + - targets: ["ip:9100"] +``` + +## 启动Prometheus + +```bash +# --config.file 指定配置文件 --storage.tsdb.path 指定数据存放目录 +$ cd prometheus-2.33.3.linux-amd64 +$ ./prometheus --config.file=prometheus.yml --storage.tsdb.path=/data/prometheus + +# 后台启动 +$ nohup ./prometheus --config.file=prometheus.yml --storage.tsdb.path=/data/prometheus & +``` + +访问 `http://ip:9090/targets` 就可以看到 `node` 的相关信息了 + +不过 `Prometheus` 自带的监控面板内容不太直观 . 我们需要通过 `Grafana` 来进行数据可视化 + +## 安装Grafana + +```bash +# 官网下载地址: https://grafana.com/grafana/download +$ wget https://dl.grafana.com/enterprise/release/grafana-enterprise-8.4.0.linux-amd64.tar.gz +$ tar -zxvf grafana-enterprise-8.4.0.linux-amd64.tar.gz +``` + +## 启动Grafana + +```bash +$ ./grafana-server +# 后台启动 +$ nohup ./grafana-server & +``` + +## 配置Grafana + +`Grafana` 默认端口为 3000 , 初始账号密码 `admin/admin` , 第一次登录会要求修改密码 + +点击 `Add your first data source` 添加数据源 + +选择 `Prometheus` , 填写好相关配置保存之后 + +再导入模板 (点击加号后再点击 `import` , 即可进入模板配置页面) , 我们可以使用 `9276` 或者到 `https://grafana.com/grafana/dashboards/` 自己进行选择 + + + diff --git a/docs/linux/centos-deploy-python.md b/docs/linux/centos-deploy-python.md new file mode 100644 index 000000000..00127ea49 --- /dev/null +++ b/docs/linux/centos-deploy-python.md @@ -0,0 +1,36 @@ +# CentOS安装Python + +## .configure 失败 + +缺少C++编译器, 解决方法 + +```shell +$ yum install -y gcc-c++ +``` + +## Python + +### 下载源码 + +Python源码地址 : `https://www.python.org/ftp/python/` + +以 `Python 3.6.3` 为例 : + +```shell +$ wget https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tgz +``` + +下载完成后解压 + +```shell +$ tar -zxvf Python-3.6.3.tgz +``` + +编译安装 + +```shell +$ ./configure --prefix=/usr/local/python3 +$ make +$ make install +``` + diff --git a/docs/linux/centos-deploy-rabbitmq.md b/docs/linux/centos-deploy-rabbitmq.md new file mode 100644 index 000000000..6b7240ab3 --- /dev/null +++ b/docs/linux/centos-deploy-rabbitmq.md @@ -0,0 +1,57 @@ +# CentOS安装RabbitMQ + +## 安装RabbitMQ + +安装 `Erlang` + +```bash +$ yum install epel-release +$ wget https://packages.erlang-solutions.com/erlang/rpm/centos/7/x86_64/esl-erlang_22.1-1~centos~7_amd64.rpm +$ yum install esl-erlang_22.1-1~centos~7_amd64.rpm +``` + +安装 `RabbitMQ` + +```bash +$ wget https://mirrors.huaweicloud.com/rabbitmq-server/v3.7.21/rabbitmq-server-3.7.21-1.el7.noarch.rpm +``` + +导入GPG密钥 + +```bash +$ rpm –import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc +``` + +安装 + +```bash +$ rpm -Uvh rabbitmq-server-3.7.21-1.el7.noarch.rpm +``` + +## 启动RabbitMQ + +```bash +# 后台启动 +$ rabbitmq-server -detached +# 启动管理插件 +$ rabbitmq-plugins enable rabbitmq_management +# 关闭管理插件 +$ rabbitmq-plugins disable rabbitmq_management +``` + +## 常用命令 + +```bash +# 查看用户 +$ rabbitmqctl list_users +# 添加管理用户 +$ rabbitmqctl add_user admin yourpassword # 增加普通用户 +$ rabbitmqctl set_user_tags admin administrator # 给普通用户分配管理员角色 +# 创建vhost +$ rabbitmqctl add_vhost test +# 赋予用户vhost操作权限 +$ rabbitmqctl set_permissions -p test admin ".*" " +# 查看用户的权限 +$ rabbitmqctl list_user_permissions username +``` + diff --git a/docs/linux/centos-deploy-supervisor.md b/docs/linux/centos-deploy-supervisor.md new file mode 100644 index 000000000..af3365bdc --- /dev/null +++ b/docs/linux/centos-deploy-supervisor.md @@ -0,0 +1,67 @@ +# CentOS安装Supervisor + +## 安装 + +```bash +$ pip install supervisor +``` + +## 创建配置文件 + +```bash +$ mkdir /opt/supervisor +$ echo_supervisord_conf > /opt/supervisor/supervisord.conf +``` + +修改配置文件 + +```conf +[unix_http_server] +file=/opt/supervisor/supervisor.sock ; the path to the socket file +;chmod=0700 ; socket file mode (default 0700) +;chown=nobody:nogroup ; socket file uid:gid owner +;username=user ; default is no username (open server) +;password=123 ; default is no password (open server) + +;[inet_http_server] ; inet (TCP) server disabled by default +;port=127.0.0.1:9001 ; ip_address:port specifier, *:port for all iface +;username=user ; default is no username (open server) +;password=123 ; default is no password (open server) + +[supervisord] +logfile=/opt/supervisor/supervisord.log ; main log file; default $CWD/supervisord.log +logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB +logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 +loglevel=info ; log level; default info; others: debug,warn,trace +pidfile=/opt/supervisor/supervisord.pid ; supervisord pidfile; default supervisord.pid +nodaemon=false ; start in foreground if true; default false +minfds=16384 ; min. avail startup file descriptors; default 1024 +minprocs=600 ; min. avail process descriptors;default 200 + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=unix:///opt/supervisor/supervisor.sock ; use a unix:// URL for a unix socket + +[include] +files = /opt/supervisor/*.ini +``` + +## 启动 + +先启动 `supervisord` + +```bash +$ supervisord -c /opt/supervisor/supervisord.conf +``` + +再启动 `supervisorctl` + +```bash +$ supervisorctl -c /opt/supervisor/supervisord.conf +$ supervisorctl -c /usr/lib/supervisor/supervisord.conf reload # 重启supervisor +$ supervisorctl -c /usr/lib/supervisor/supervisord.conf restart # 重启某个conf,加 all 指重启全部 +$ supervisorctl -c /usr/lib/supervisor/supervisord.conf update # 更新全部conf文件 +``` + diff --git a/docs/recommended-books.md b/docs/recommended-books.md deleted file mode 100644 index 828ef41a4..000000000 --- a/docs/recommended-books.md +++ /dev/null @@ -1,110 +0,0 @@ -# 推荐书单 - -电子书 - -链接: https://pan.baidu.com/s/1-EjPZ6xbb6DG9i5OoCSRlQ 提取码: wtnd - - -## 1. 《Python源码剖析:深度探索动态语言核心技术》 - -推荐指数 : ★★★★★★ - -本书文件位置 : `电子书/Python/Python源码剖析:深度探索动态语言核心技术.pdf` - -这本书讲述的是 `Python` 底层的实现 , 比如对象机制 , 为什么 `Python` 中一切皆对象 , 各种底层数据结构的缓存池设计 , `Python` 代码运行设计以及 `Python` 多线程实现等等 - -虽然这本书更偏向于动态语言的设计 , 但是其中的很多思想都是对我们软件开发有帮助的 , 当然也可以更底层的了解 `Python` - -## 2. 《Python核心技术与实战》 - -推荐指数 : ★★★★★ - -本书文件位置 : `电子书/Python/Python核心技术与实战.pdf` - -这本书实际上是极客时间的一个课程 , 内容主要为一些 `Python` 技巧 , 以及一些对性能的考究 , 可以作为 `Python` 进阶书籍 , 值得一看 - -## 3. 《Python Cookbook(第3版)中文版》 - -推荐指数 : ★★★★★ - -本书文件位置 : `电子书/Python/Python Cookbook(第3版)中文版.epub` - -这本书包含了 `Python` 中处理问题的一些奇妙的技巧 , 涵盖了整个 `Python` 各个版块 , 是一本值得反复阅读的工具书 - -## 4. 《流畅的Python》 - -推荐指数 : ★★★★★ - -本书文件位置 : `电子书/Python/流畅的Python.epub` - -这本书和 《Python Cookbook》的内容版块差不多 , 将这两本书的内容笔记整理到一起一定会是一个非常不错的笔记的 - -## 5. 《Python数据结构与算法分析(第2版) - [美] 布拉德利 • 米勒 & 戴维 • 拉努姆(2019)》 - -推荐指数 : ★★★★ - -本书文件位置 : `电子书/Python/Python数据结构与算法分析(第2版) - [美] 布拉德利 • 米勒 & 戴维 • 拉努姆(2019).epub` - -这本书其实是数据结构和算法的一个入门吧 , 可以看看 , 不过个人觉得数据结构和算法最好的方式是刷题 - -## 6. 《MySQL实战45讲》 - -推荐指数 : ★★★★★★ - -本书文件位置 : `电子书/MySQL/MySQL实战45讲.pdf` - -这本书也是极客时间的一门课程 , 也是业内比较出名的的课 , 这门课程是作者的一些经验之谈以及 `MySQL` 的一些底层核心原理的分析讲解 - -## 7. 《高性能MySQL》 - -推荐指数 : ★★★★★★ - -本书文件位置 : `电子书/MySQL/高性能MySQL:第3版.epub` - -这本书作为 `MySQL` 系列书籍中最经典的一本也不为过 , 涵盖的内容比较丰富 , 是一套完整的方法论 - -## 8. 《深入剖析Kubernetes》 - -推荐指数 : ★★★★★★ - -本书文件位置 : `电子书/Docker&Kubernetes/深入剖析Kubernetes.pdf` - -这本书同样是极客时间的一门课程 , 作者张磊是 `Kubernetes` 社区的资深成员和项目维护者 , 如果你想要了解容器技术或者 `Docker` 的实现原理 , 我建议你可以看这本书的前面几章 , 只是短短的几章 , 容器技术的前世今身一览无余 , 并且作者的文笔非常好 , 这本书就像看小说一样 , 强烈推荐 - -Docker & Kubernetes , 这本书足以 - -## 9. 《Redis设计与实现》 - -推荐指数 : ★★★★★★ - -本书文件位置 : `电子书/Redis/Redis设计与实现.epub` - -`Redis` 系列必看书籍 , 看书名你就知道 , 就不过多说明了 - -## 10. 《mongodb权威指南(中文第二版)》 - -推荐指数 : ★★★★★ - -本书文件位置 : `电子书/MongoDB/mongodb权威指南(中文第二版).epub` - -在文件夹 `MongoDB` 中关于 `MongoDB` 的书籍其实不少 , 但是其他的书相对于这本 , 都太偏向于使用过于基础了 , 有的可能会有一些原理性的 , 但是也只是点到为止 , 所以强烈推荐这本 - -## 11. 《elasticsearch源码解析与优化实战》 - -推荐指数 : ★★★★★ - -本书文件位置 : `电子书/ELFK/elasticsearch源码解析与优化实战.epub` - -## 12. 《RPC实战与核心原理》 - -推荐指数 : ★★★★★ - -本书文件位置 : `电子书/微服务/RPC实战与核心原理.pdf` - -## 13. 《设计模式之美》 - -推荐指数 : ★★★★★ - -本书文件位置 : `电子书/后端/设计模式之美.pdf` - -这本书同样是极客时间的一个课程 , 虽然说文笔上可能让人有一些不舒服 , 但是毕竟还是有很多干货 , 相比于单纯为了写设计模式的书 , 这本书更多的是教你如何提高代码质量 \ No newline at end of file