目录
一.暴力匹配
当我们需要在长串S中,找到我们需要的模式串L。使用暴力匹配的话,需要将整个串不断的回溯,时间复杂度是(m*n)
#include<stdio.h>
int violentmatch(char* ske, char* str) {
int len1 = strlen(ske);
int len2 = strlen(str);
int i = 0, j = 0;
while (i < len1 && j < len2) {
if (ske[i] == str[j]) {
i++;
j++;
} else {
i = i - j + 1;//i需要回溯到主串的第n+1个 n=0;
j = 0;
}
}
if (j == len2) {
return i - j;
} else {
return -1;
}
}
二.KMP算法
介绍:KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。
图文与代码:
- 假设:现在主串S为ABEABDABCE,字串L为ABC
图文流程:
ABEABDABCE | A |
ABEABDABCE | B |
ABEABDABCE | C(匹配失败) |
ABEABDABCE | 此时如果是暴力算法,将会回溯标红处,重新匹配。但是如果是KMP算法,将会从标蓝处开始。 |
ABEABDABCE | A(匹配失败) |
ABEABDABCE | A |
ABEABDABCE | B |
ABEABDABCE | C(匹配失败) |
ABEABDABCE | A(匹配失败) |
ABEABDABCE | A |
ABEABDABCE | B |
ABEABDABCE | ABC(匹配成功) |
- 我们不难发现,每次匹配的位置并非是暴力算法中的,将主串固定的向右位移一位,而是向右位移n位。而这里的n,就是KMP算法的核心。
代码:
-
int KMPalgorithm(char* ske, char* str) { int len1 = strlen(ske); int len2 = strlen(str); int i = 0, j = 0;//i用来记录主串,j用来记录子串 while (i < len1 && j < len2) { if (j==-1&&ske[i] == str[j]) //j==-1做条件是为什么?会在后面说到{ i++; j++; } else { j = next[j];//前文所述的n就是j-next[j]; } } if (j == len2) { return i - j; } else { return -1; } }
最大公共元素与next数组:
最大公共元素的求法是理解KMP算法和求出next数组的关键。
假定:主串S为ABABCDEABABD,字串L为ABABD
求最大公共元素长度:
字串 | 前缀 | 后缀 | 最大公共元素长度 |
A | NULL | NULL | 0 |
AB | A | B | 0 |
ABA | A,AB | A,BA | 1 |
ABAB | A,AB,ABA | B,AB,BAB | 2 |
ABABD | A,AB,ABA,ABAB | D,BD,ABD,BABD | 0 |
这时候可以得出:
字串 | A | B | A | B | D |
最大公共元素长度 | 0 | 0 | 1 | 2 | 0 |
求next数组:next 数组考虑的是除当前字符外的最长相同前缀后缀,将最大公共元素中求得的值整体右移一位,然后初值赋为-1即可。
字串 | A | B | A | B | D |
最大公共元素长度 | -1 | 0 | 0 | 1 | 2 |
根据next匹配:
主串:ABABCDEABABD | 字串: ABABD |
ABABCDEABABD | A |
ABABCDEABABD | AB |
ABABCDEABABD | ABA |
ABABCDEABABD | ABAB |
ABABCDEABABD ABABCDEABABD | ABABD(第五个不匹配时,根据next表可以知道,next[j]=2,j=4,j-next[j]=2,所以将子串右移两位,也就是绿色标记处) |
ABABCDEABABD | A |
...... | ....... |
ABABCDEABABD | ABA(第三个不匹配时,next[j]=0,j=3,j-next[j]=3,所以子串右移三位) |
...... | ...... |
ABABCDEABABD | A |
ABABCDEABABD | AB |
ABABCDEABABD | ABA |
ABABCDEABABD | ABAB |
ABABCDEABABD | ABABD(匹配成功) |
next数组的求法:
这段内容是最难以理解的,即使已经弄懂了,本人也无法组织好语言,只好借鉴他人的语言(原文链接https://blog.youkuaiyun.com/dark_cy/article/details/88698736),来方便理解并补充内容。
首先是代码:
void Getnext(int next[], String t) {
int j = 0, k = -1;
next[0] = -1;
while (j < t.length - 1) {
if (k == -1 || t[j] == t[k]) {
j++;
k++;
next[j] = k;
} else k =
next[k];
}
}
next求解的三个情况:
- 当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。
- 当 t[j] == t[k] 的情况
观察上图可知,当 t[j] == t[k] 时,必然有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",此时的 k 即是相同子串的长度。因为有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",且 t[j] == t[k],则有"t[0]…t[k]" == " t[j-k]…t[j]",这样也就得出了next[j+1]=k+1。
-
当t[j] != t[k] 的情况
t[j+1] 的最大子串的长度为k,(我认为这个k应该就是最大的公共长度)
所以就有 next[j+1] < k,那么求 next[j+1] 就等同于求 t[j] 往前小于 k 个的字符(包括t[j],看上图蓝色框框)与 t[k] 前面的字符(绿色框框)的最长重合串,即 t[j-k+1] ~ t[j] 与 t[0] ~ t[k-1] 的最长重合串(这里所说“最长重合串”实不严谨,但你知道是符合 k 的子串就行…),那么就相当于求 next[k](只不过 t[k] 变成了 t[j],但是 next[k] 的值与 t[k] 无关)所以才有了这句 k =next[k],如果新的一轮循环(这时 k = next[k] ,j 不变)中 t[j] 依然不等于 t[k] ,则说明倒数第二大 t[0~next[k]-1] 也不行,那么 k 会继续被 next[k] 赋值(这就是所谓的 k 回退…),直到找到符合重合的子串或者 k == -1。