字符串匹配:
在匹配串中寻找模式串是否出现,并返回模式串的位置。注意和最长公共子序列相区别(LCS: Longest Common Substring)。
常用的字符串匹配算法:
假设匹配串长度为m,模式串长度为n。
暴力匹配Brute Force算法:
首先将匹配串和模式串左对齐,然后从左向右一个一个进行比较,如果不成功则模式串向右移动一个单位重新比较。速度最慢。最不理想的情况下,时间复杂度为O(m*n)。
KMP算法:
KMP 的思想是这样的:
利用不匹配字符的前面那一段(匹配)字符的最长前后缀来尽可能地跳过最大的距离。
比如,
匹配串为xbaxbaxbay
模式串为baxbay
匹配到如下状态:
匹配串:xbaxbaxbay
模式串: baxbay
发现在y之前的字符串都匹配了,找到其最大相等前后缀为ba。x和y虽然不匹配,但是对于匹配串,x之前的ba和模式串的前缀ba一定匹配上了,所以可以移动到如下状态:
匹配串:xbaxbaxbay
模式串: baxbay
此时从模式串前缀ba之后的x开始和匹配串进行比较。这个操作利用最大相等前后缀跳过了一段距离,减小了复杂度,这就是KMP。
B站上小哥讲的很好:https://www.bilibili.com/video/av3246487?from=search&seid=5460317095667529595
那么如何寻找最大相等前后缀?
我们不知道在哪里会匹配失败,所以我们希望结果是一个数组:在模式串x处失败时,访问这个数组的x处元素,能够得到最大相等前后缀长度。
首先初始化一个长度为n的数组,两个指针i和j,i指向数组首部,j指向数组第二个元素,数组首部元素初始化为0。
一开始,如果i和j不相等,数组计入0,同时后移j。重复这个步骤直到i和j相等。
如果i和j相等,数组存入i+1的值,i和j一起右移;如果i和j从相等变为不相等,i等于它指向数组元素之前的数组元素(前驱);如果i和j从不相等变为不相等,,数组计入0,同时后移j。
构建这个数组时间复杂度是O(n)。
Sunday算法
Sunday算法从前往后匹配,在匹配失败时关注的是主串中参加匹配的最末位字符的下一位字符。
- 如果该字符没有在模式串中出现则直接跳过,即移动位数 = 模式串长度 + 1;
- 否则,其移动位数 = 模式串长度 - 该字符在子串最右出现的位置(以0开始) = 模式串中该字符最右出现的位置到尾部的距离 + 1。
详细的可以看https://blog.csdn.net/q547550831/article/details/51860017
int Sunday(string const &str, string const & substr) {
int sz = str.size(), subsz = substr.size();
int i = 0, j = 0;
while(i<sz&&j<subsz) {
while (j < subsz) {
if (str[i + j] != substr[j]) { //字符串匹配失败,进行移动
int k = 0;
if (i + subsz >= sz) break;
while (k<subsz && str[i + subsz] != substr[k])
k++;
if (k != subsz) { //子串包含参加匹配最末字符的下一节点
//向右移动 subsz-k
i += subsz - k;
j = 0;
}
else {
i += subsz + 1;
j = 0;
}
if (i >= sz) break;
}
else j++;
}
if (j == subsz) return i;
}
return -1;
};