题目:查找B串在A串中的位置。
KMP优点及原因:
A:QWPQW RTY QWPQW PO
B:QWPQW P
B串与A串一个字符一个字符的进行比较,当到达某一位置两字符不等(P、R不等),此时这两字符前面的串都是相等的(QWPQW),此时最好的形式是:直接到达如下的比较阶段。为什么能到达如下的阶段呢?因为某一位置两字符不等,此时这两字符前面的串都是相等的,因此这个位置的比较只由B串决定,那么就可以提前知道应该跳转比较到什么位置。
运用KMP:
A:QWPQW RTY QWPQW PO
: QW PQW P
一般:
A:QWPQW RTY QWPQW PO
: QW PQW P
—————————————————————————
思路:
构造next数组其实就是计算模式串s前缀表的过程。 主要有如下三步:
1:初始化
2:处理前后缀不相同的情况
3:处理前后缀相同的情况
next 数组 下标代表:后缀尾(比较失败字符的前一个字符)
next数组内的值代表:与比较失败的字符进行比较的字符的下标。所以这个值就是最长字符串前缀的长度。
next数组下标是后缀尾:next【j-1】,j-1为后缀尾。
i 是后缀尾(也是要遍历的数组的下标):要比较字符前一个字符的下标。
j 是前缀尾(j 也用来表示要与比较失败字符进行比较的字符的下标):与P要比较的字符的下标即 R的下标。
若后缀尾与前缀尾相等,则 j++;next【i】 = j。
若后缀尾与前缀尾不相等,则寻找最大前后缀中的最大前后缀,直到找到后一个值刚好等于S【i】(j = next【j-1】),或者不存在满足条件的最大前后缀。(j <= 0跳出循环)。
i,j 代表的都是前后缀尾,i,j是互相对应的,将 j = next【j-1】看成 i = next【i-1】(数组下标即后缀尾)这个next【j-1】是 j 应该比较的元素即这个next【i-1】是 i 应该比较的元素,若相等则满足条件,若不相等经历了一次j = next【j-1】
则继续此时 j 为i应该比较的元素的下标,j-1为。
比较过程如下:
P A Q P W P A Q P RTY PAQPWPAQP AO
P A Q P W P A Q P j PAQPWPAQP i 第一回
P A Q P j1/next[j-1] PAQP i 第二回
P j2/next[j1-1] P i 第三回
即寻找最大前后缀中的最大前后缀,直到满足此值相等为止。
next[j-1] 表示 j 位应该匹配的值的位置。
不管第一回还是第二回比较的都是将 j位置的值与 i位置的值进行比较。
next【j-1】:即寻找最大缀。‘
j = next【j-1】:是将缀尾后面一个值赋给 j(缀尾)
的值即与 i位置对应的值
QWPQW RTY QWPQW PO
QWPQW R //第一回P、R不相等,则j = next【j-1】
QW P //刚刚好匹配
此时的流程是查找P公共前后缀的前缀的公共前后缀即Q 如下:然后比较Q后面的是不是W,W后面的是不是P如果是则找到(即Q依次往后比较直到P全部相等则成功)
Q
—————————————————————————
怎么思考:
脑海中存在两个前后缀的串以及一个值,和 i,j 指针的指向。图如下:
第一步:
@@@@@1----------------@@@@@@4@3-----@2
要找到一个@2的最长前后缀,后缀尾为@3。
第二步:
@@@@@1----------------@@@@@@4----@3-----@2
求next【@3】看最长前缀尾后面一个元素是否与@3相等。
若相等则:j++;next【i】 = j。
若不相等:寻找最大前后缀中的最大前后缀,直到满足此值相等为止。
非常容易理解 例:
总结就是:
查找P的公共前后缀,同时比较next【P】与P的值,不满足则缩小范围找next【T】的公共前后缀中的公共前后缀,再比较next【W】与W,成功在比较next【W+1】与P。
QWPQW RTY QWPQW PO
QWPQW R //R与 T不匹配
QW P //刚刚好匹配
此时的流程是查找P公共前后缀的前缀的公共前后缀即Q 如下:然后比较Q后面的是不是W,W后面的是不是P如果是则找到(即Q依次往后比较直到P全部相等则成功)
Q
第二块案例代码:
比较过程如下:
PPQWPPQW RTY PPQWPPQW AO
PPQWPPQW ? //R与 T不匹配
PPQW ? //刚刚好匹配
即寻找最大前后缀中的最大前后缀,直到满足此值相等为止。
自己思考难以理解的原因主要是,当查找next【P】的值与P不一致后,自己知道要查找到一个更小的匹配的串,但是不知道怎么查找,在PPQWPPQW中查找即可。
代码(此时采用前缀表统一减1)
void getNext(int* next, const string& s){
int j = -1;
next[0] = j;
for(int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
j = next[j]; // 向前回退
}
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将j(前缀的长度)赋给next[i]
}
}
一般代码(前缀表不减1):
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++) {
while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下标的操作
j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
按照以下顺序书写就会出错,因为假如s[j] == s[i] 此时 j++了,但是 i 没有加1,下面while中又有比较会造成 j 的改变。
if(s[j] == s[i]) j++;
while (j > 0 && s[i] != s[j]){
j = next[j-1];
}