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;
}