DSA - 串 + 习题集
DSA 的字符串算法。最近看了好多漫画。
KMP 算法
i 将永远不必回退。比对成功就一起进一格,否则 j 更新为某个更小的值并继续在原位比较。
为此需要构造 next 表,使得一旦在 P [j] 处失配就将 j 替换为 next [j] 继续与 T [i] 比对。
1 | 0001 int match( char* T, char* P ) { //KMP算法 |
抽象出来的匹配算法。
因为 i 从来不后退,所以 i 的位置就是当前已经成功匹配的次数,于是 O (n);朴素 KMP 的话如果匹配串是重复的同一个字母,每个元素的 next 都是前一个元素,那么 T [i] 需要匹配 m 次。
对匹配串构造一个类似这样的 next 表。要求是 next 所指位置的前缀,和当前位置的前缀能匹配。在匹配串前面补哨兵。第一个元素一定指向哨兵
$N (P,j)={0<=t < j|P [0,t)=P [j-t,j)}$,所有自匹配的长度取最大值 maxN (p,j) 为最大长度。
$j->next [j]->next^2 [j]->…->0->-1$,next 构成传递链
为求 P [j + 1] 的 next 数组,如果 P [j]==P [next [j]] 说明 j 的前缀和 next [j] 的前缀相等,算上 j 和 next [j] 本身就是 j + 1 和 next [j]+1 的前缀相等, 那么 next [j + 1]=next [j]+1;如果不相等就找下家,也就是其他能让 j 的前缀与之匹配的位置,也就是 next 链的下一个,这是 j + 1 能与之匹配的必要条件。
1 | 0001 int* buildNext( char* P ) { //构造模式串P的next表 |
摊还分析
令 k = 2*i-j
k<=2n-1, 所以是线性的。
k = i+(i-j)= 主串移动 + 相对移动偏移量(也就是匹配串相对于主串的位置),匹配上了主串和匹配串一起移动 1 格,没匹配上匹配串向前移动至少 1 格。想想两个滑块的移动。
因为 i 只会向前动;因为失败一次对齐位置就移动了,也就是匹配串向前滑动,而且不会回头。什么叫通配符出现失败比对啊没看懂。
改进
如果出现跳到 next 的位置不改变当前匹配字符,那一定会发生失配,这种情况可以优化掉。
代码容易看懂不过多赘述。
1 | 0001 int* buildNext( char* P ) { //构造模式串P的next表(改进版本) |
不可能。在考虑 next [j] 处时已经避免了相等的出现,一旦出现就覆盖掉了。

不会。
BM 算法
BC 策略:
构造 bc 表,匹配串从后往前匹配直到第一个失配的 X - Y,此时在匹配串里找 X 出现的最靠右的位置 bc [‘X’],匹配串右移 j-bc [‘X’]。为了避免陷入死循环,shift 必须至少 = 1。这样匹配串就只会向前不会后退。
1 | 0001 int match( char* T, char* P ) { //Boyer-Morre算法(简化版,只考虑Bad Character Shift) |
最好 O (n / m),因为只要匹配串不含主串元素就可以一次跳 m 格;最坏就是蛮力复杂度。
bc 表做成二维的记录在 j 左侧的最靠右的 X,但是空间开销太大了。
gs 策略:这部分好像不考?
失配时找能和已经匹配了的后缀再匹配的中间部分跳过去;如果不能完全匹配就匹配一部分。
头上数字表示最长能匹配的后缀。
KP 算法和字典树
就是给字符串做哈希,产生哈希使用滑动窗口即可实现 O (n) 哈希。对比窗口内的哈希是否和匹配串的相同,不相同就下一个,相同就再看看是不是真的一模一样
字典树 Trie:一图胜千言