C++ 子串匹配主串
暴力匹配
#include <iostream>
#include <string>
using namespace std;
int strTsr(string haystack, string needle)
{
if (needle.empty())
return 0;
int n = haystack.size();
int m = needle.size();
for (int i = 0; i < n; ++i)
{
int pos = i;
for (int j = 0; j < m; ++j)
{
if (needle[j] != haystack[pos++])
{
break;
}
else
{
if (j == (m - 1))
{
return i;
}
}
}
}
return -1;
}
int main()
{
int asn = strStr("ABCDABCDE", "ABCDE");
if (asn == 0)
{
cout << "匹配的子串为空!" << endl;
}
else if (asn == -1)
{
cout << "子串不匹配主串!" << endl;
}
else
{
cout << "子串匹配了主串,开始位置:" << asn << endl;
}
return 0;
}
字符串的KMP算法
1. 原由
紧接上文,我们知道了暴力匹配的算法在时间运行上的缺陷,假设字符串T的长度为n,字符串P的长度为m,则整个算法的时间复杂度为O( n * m ),而对于一个复杂的现实情况而言 n >> m >> 2 (即n远远大于m,m远远大于常数),这样的计算计算机的负担很重。
请思考一个暴力匹配的情况:
给定一个主字符串
T = “AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB”(47位)
同时给定模式串 P = “AAAAAB”(6位)
试问搜索的情况,很显然,暴力搜索对于每一次搜索,都要搜索到最后一个字符才能进行下一轮的搜索,因此进行的计算近似可以理解为:O(47 * 6) ,对于这样很少的数据已经有很高的计算量了。
KMP算法一种改进的模式匹配算法,是D.E.Knuth、V.R.Pratt、J.H.Morris于1977年联合发表,KMP算法又称克努特-莫里斯-普拉特操作, KMP算法与前文的暴力匹配算法,核心的区别就是没有不匹配的回溯,而是根据整个字符串的情况进行一次位移,这样大大减少了回溯产生的缺陷,KMP算法的时间复杂度可以优化到 O( n + m)级别,是二次优化到线性的程度。
2.构造next表(以-1开头)
对于模式串P而言,我们需要知道模式串中P的每一位的前一位是否存在相等的完全相等的前后缀,并且求这个最大的完全相等的前后缀,如一个模式串”ABCABDE”对于第倒数第二位字符而言,其符合情况的前后缀就是”AB”,而最后一位则没有完全相等的前后缀。
PS:何为前后缀:如一个字符串”ABCD”,其前缀有可能为”A”“AB”“ABC”(即除去本身的全部字符),同理,则后缀可能为:”D””CD””BCD”
我们需要求的就是每一个字符其相对应的最大前后缀数,这样与模式串P一一对应的表称之为next表。
因此”ABCABDE”的next表为:-1 0 0 0 1 2 0 (字符用空格隔开)
A | B | C | A | B | D | E |
---|---|---|---|---|---|---|
-1 | 0 | 0 | 0 | 1 | 2 | 0 |
那么我们该如何实现代码呢?
对于每一个当前需要判断的字符而言,在构造next表时,应该向前进行比对,以上一个已经判断的情况为基础(初始值赋-1,部分教程中初始值赋0,两者没有实质区别),后缀如果+1位置的字符与前缀+1位置的字符相等,则next[i]就是next[i-1]+1,而如果不相等,则说明无法匹配,则next[i]=0。
3. KMP实现
与暴力匹配极其相似,利用while循环的条件控制, 进行匹配失败时,只需要将失败的模式串P的索引指向next表中对应的数值即可,其余匹配照旧线性执行即可。
KMP算法
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
class Solution
{
public:
int * next;
public:
void buildNext(string P)
{
// 记录字符串的长度
int m = P.size(), j = 0;
// 给next数组分配空间
next = new int[m];
// 因为第一个字母没有前缀,所以next[0] = -1
int t = next[0] = -1;
while (j < m - 1)
{
// t < 0 也就是 t = -1, 模式串回溯到第一位,主串向前移动1步
if (0 > t || P[j] == P[t])
{
next[++j] = ++t;
}
else
{
t = next[t];
}
}
}
int KMP(string T, string P)
{
// 更新next数组
buildNext(P);
// 指向主串
int n = T.size(), i = 0;
// 指向模式串
int m = P.size(), j = 0;
while (j < m && i < n)
{
// 如果 主串的第i个字符 和 模式串的第j个匹配,同时加加,匹配下一个
// 如果 j < 0, 主串的i++ 和 模式串从0开始匹配
if (j < 0 || T[i] == P[j])
{
i++;
j++;
}
else
{
// 在next数组中找到j下一次开始的位置
j = next[j];
}
}
if (j == m) // 匹配
{
return i - j;
}
return -1; // 不匹配
}
};
int strStr(string haystack, string needle)
{
Solution s;
int ans = s.KMP(haystack, needle);
return ans;
}
int main()
{
int ans = strStr("ABCDABCDEF", "ABCDE");
cout << ans << endl;
return 0;
}