31 | 深度和广度优先搜索
1. 基于数据结构“图”的搜索算法,比较简单的有 深度优先 和 广度优先 搜索算法。适用于图不大的情况。
2. 广度优先用 队列 来实现。逐层遍历,每遍历一个结点,就放入队列。
3. 深度优先用 栈 来实现。通过堆栈,一层一层递归下去。
4. 深度优先和广度优先搜索的时间复杂度都是 O(E),空间复杂度是 O(V)。
E: 图结构里的边的数目。
V: 图结构里的结点的数目。
32 | 字符串匹配基础(上)
1. 定义: 在字符串 A 中查找字符串 B,那字符串 A 就是主串,字符串B 就是模式串。
2. 字符串匹配算法,比较简单粗暴的方法是 BF 算法。 方式是:逐个逐个字母地比较主串和模式串。
理论上的最坏情况,时间复杂度是 O(n*m)。 n: 主串长度;m: 模式串长度。
3. RK 算法效率要高不少。方式是:
(1) 求主串中每个子串的hash值,和模式串的hash值做比较。数字之间比较是非常快速的,所以模式串和子串比较的
效率就提高了。
(2) 计算hash值时,可以把字符串视为一个26进制数(26个英文字母),然后转成十进制数。这就能快速计算出hash值。
而且,下一个子串的是可以根据上一子串的数值来快速计算出结果(有一个公式),不需要每个子串都完整算一次hash值。
通过查表法,还能进一步提升计算hash值的效率。
hash值相同的话,子串和匹配串就相等了。
(3) 这个方式要注意,这个26进制数转成10进制数时,容易出现溢出情况(匹配串很长的时候)。 这时可以修改计算hash值的算法: 将子串的各位字母视为数字然后相加,得出一个整数。
(4) 上面(3) 的方法会有hash冲突的情况。 冲突处理时,再做一次BF 算法匹配就好了。效率依然很高。
4. RK 算法整体的时间复杂度就是 O(n)。
33 | 字符串匹配基础(中) -- BM算法
1. BF 算法和 RK 算法在极端情况下(数据量很大)退化得比较严重,RK 算法需要用到哈希算法,而设计一个可以应对各种类型字符的哈希算法并不简单。
为了支持大容量文本,文字多样的文本的搜索,需要BM算法。
2. BM算法方式是:
(1) 回归主串和模式串逐个字母比较的普通方式。
(2) 在模式串中某个字符与主串不能匹配的时候,将模式串往后多滑动几位,减少不必要的比较,提高效率。
具体有两个规则:坏字符规则和好后缀规则
(3) 坏字符规则: 从模式串的末端往前做匹配,如果和主串不一致,那在不一致的字符就是坏字符(位于主串里的那个)。
如果坏字符在模式串里不存在,就可以模式串向后滑动N位。(细节不讲了)
如果坏字符存在,就交给好后缀规则来做。(其实不管是否存在,都要走好后缀规则)
(4) 好后缀规则:
模式串和主串有M位是一致的,这M位子串就是好后缀。然后在模式串的前端查找第一个同样的好后缀。然后根据各种判断, 向后滑动N位。(细节不讲了)
(5) 坏字符规则和好后缀规则一起使用,然后取滑动位数较大的那个。
(6) 坏字符规则要建立hash表来记录字符位置,消耗内存比较大,可以考虑不用它,只用好后缀规则。效率降低一点。
附加:
1. 需要查找,需要减少时间复杂度,应该想到什么?散列表。
2. 如果某个表达式计算开销比较大,又需要频繁的使用怎么办?预处理,并缓存。
34 | 字符串匹配基础(下)-- KPM算法
1. kpm算法的意义: 虽然效率不如bm算法,但是当前应用广泛,并且很著名
2. kpm算法方式是:
(1) 也是主串和模式串逐个字母比较的普通方式。
(2) 在模式串中某个字符与主串不能匹配的时候,将模式串往后多滑动几位,减少不必要的比较,提高效率。
具体是: 根据模式串的最长可匹配后缀子串(与前缀子串做比较),来计算后滑几位。
(3) 可针对模式串,预先计算好这个后滑几位的参数,叫做next数组。
(4) next数组可以简单粗暴地计算,但效率很低。所以采用优化的方式计算。
具体是:模式字符串对自身进行匹配, 在任一位置,能匹配的最长长度就是当前位置的next值。
3. 参考文章:
kmp算法理解参考 https://mp.weixin.qq.com/s/g9oD-Sd5PFOEucDS2_F0bA
35 | Trie树
1. Trie 树是一种解决字符串快速匹配问题的数据结构。
2. 时间复杂度是 O(k),k 表示要查找的字符串的长度。
Trie 树比较耗内存,是一种空间换时间的思路。(每个结点都要存储一个从a到z的数组)
3. Trie 树不适合精确匹配查找(耗费空间),适合的是查找前缀匹配的字符串的需求。(做动态集合数据的查找)
36 | AC自动机
1. AC自动机是基于 Trie 树的多模式串匹配算法,为了快速在主串中查找多个模式串。
2. 它跟 Trie 树的关系,就像单模式串中,KMP 算法与 BF 算法的关系一样。
KMP 算法中有一个非常关键的 next 数组,类比到 AC 自动机中就是失败指针。
AC 自动机失败指针的构建过程,跟 KMP 算法中计算 next 数组极其相似。
3. AC 自动机其实就是 KMP 算法在多个模式串上的改造。
附加:
1. BF: 简单场景,主串和模式串都不太长, O(m*n)
2. KP:字符集范围不要太大且模式串不要太长, 否则hash值可能冲突,O(n)
3. BM:模式串最好不要太长(因为预处理较重),比如IDE编辑器里的查找场景; 预处理O(m*m), 匹配O(n), 实现较复杂,需要较多额外空间.
4. KMP:适合所有场景,整体实现起来也比BM简单,O(n+m),仅需一个next数组的O(n)额外空间;但统计意义下似乎BM更快,原因不明.
37 | 贪心算法
1. 贪心算法的核心将要解决的问题抽象成贪心算法模型。
第一步:定义限制值和期望值。
第二部:找限制值相同请款下,贡献期望值最大的数据。
第三部:找几个例子测试一下。不需要严格的数学推导证明。
2. 例子:
(1) 分糖果:每个孩子的贡献期望值相同,优先满足限制值最小的那个。
(2) 钱币找零:每种钱币带来的限制值一样,优先选取贡献期望值最大的纸币。
(3) 区间覆盖:每个区间的贡献期望值相同,优先满足限制值最小的那个。
(4) 用贪心算法实现Huffman压缩编码: 最常用的字母选用最小的编码。每个字母贡献期望值相同,优先选取限制值最小的字母。
38 | 分治算法
1. 分治算法分三步:
(1) 分解:将原问题分解成一系列子问题;
(2) 解决:递归地求解各个子问题,若子问题足够小,则直接求解;
(3) 合并:将子问题的结果合并成原问题。
2. 需要满足下面几个条件:
(1) 原问题与分解成的小问题具有相同的模式;
(2) 原问题分解成的子问题可以独立求解,子问题之间没有相关性,
(3) 具有分解终止条件
(4) 可以将子问题合并成原问题,而这个合并操作的复杂度不能太高