【算法】字符串匹配
1.经典的KMP算法
-
时间复杂度
O(n+m)
:其中n为文本串s的长度,m为模式串p的长度。因为首先要遍历模式串求解部分匹配数组next,然后遍历文本串寻找匹配起始字符的下标。 -
空间复杂度为
O(m)
:其中m为模式串的长度,用来存放next数组。
// kmp参考代码
// p: a b c d a b d a
// next: -1 0 0 0 0 1 2 0
// 可以直观理解为先求出最长前缀后缀公共长度,然后右移一位得到next的结果。
void get_next(std::string p, int *next)
{
int plen = p.size();
int i = 0;
int j = -1;
next[0] = -1;
while (i<plen) {
if (j==-1 || p[i]==p[j]) {
i++;
j++;
next[i] = j;
} else {
// 失配时移动的位置
j = next[j];
}
}
}
// s: aaabcdabaaabcdabdamns
// p: abcdabda
// next: -1 0 0 0 0 1 2 0
int kmp(std::string s, std::string p)
{
if (s.empty()) return -1;
if (p.empty()) return 0;
int slen = s.size();
int plen = p.size();
std::vector<int> next(plen, 0);
get_next(p, next);
int i = 0;
int j = 0;
while (i<slen)
{
if (j==-1 || s[i]==s[j]) {
i++;
j++;
} else {
j = next[j];
}
if (j==plen) break;
}
if (j==plen)
return i-j;
return -1;
}
2.效率更高的Sunday算法
-
时间复杂度O(n):其中n为文本串s的长度。因为只需要遍历文本串一遍,跳过的间隔相比kmp更大。
-
空间复杂度O(1):只用到有限的几个指示变量。
// sunday
// 寻找next所指向的字符在模式串的最右侧出现的位置,然后更新next的值
void helper(std::string p, int plen, char ch, int *next)
{
int pos = plen-1;
for (int i=plen-1; i>=0; i--)
{
if (p[i]==ch) {
pos = i;
break;
}
}
// 模式串中不包含ch字符,则next向后再移动一位
if (pos==plen-1 && ch!=p[pos])
{
(*next)++;
} else {
// 如果找到了字符所在位置,则更新next的值
*next -= pos;
}
}
// aaabcdabaaabcdabdamns
// abcdabda
int sunday(std::string s, std::string p)
{
if (s.empty()) return -1;
if (p.empty()) return 0;
int slen = s.size();
int plen = p.size();
int i = 0;
int j = 0;
int next = 0;
while (i<slen)
{
j = 0;
next = i+plen;
if (s[i]!=s[j]) {
if (next<slen)
helper(p, plen, s[next], &next);
i = next;
} else {
// 字符匹配时继续查看下一位
while (s[i]==s[j]) {
if (s[i]!=s[j]) {
if (next<slen)
helper(p, plen, s[next], &next);
i = next;
break;
}
i++;
j++;
}
}
if (j==plen) break;
}
if (j==plen)
return i-j;
return -1;
}