来源:刘毅
链接:https://subetter.com/
1、蛮力法O (n*m)
如果每个匹配位置复杂度为常数,可以达到O(n)。
2、KMP O(n+m)
next数组
KMP改进:
P[i]和P[j]相同时,没有比的必要,去前面找。
#include <iostream>
#include <string>
using namespace std;
/* P 为模式串,下标从 0 开始 */
void GetNext(string P, int next[])
{
int p_len = P.size();
int i = 0; // P 的下标
int j = -1;
next[0] = -1;
while (i < p_len - 1)
{
if (j == -1 || P[i] == P[j])
{
i++;
j++;
//next[i] = j;
//改进的KMP
if (P[i] != P[j])
nextval[i] = j;
else
nextval[i] = nextval[j]; // 既然相同就继续往前找真前缀
}
else
j = next[j];
}
}
/* 在 S 中找到 P 第一次出现的位置 */
int KMP(string S, string P, int next[])
{
GetNext(P, next);
int i = 0; // S 的下标
int j = 0; // P 的下标
int s_len = S.size();
int p_len = P.size();
while (i < s_len && j < p_len)
{
if (j == -1 || S[i] == P[j]) // P 的第一个字符不匹配或 S[i] == P[j]
{
i++;
j++;
}
else
j = next[j]; // 当前字符匹配失败,进行跳转
}
if (j == p_len) // 匹配成功
return i - j;
return -1;
}
int main()
{
int next[100] = { 0 };
cout << KMP("bbc abcdab abcdabcdabde", "abcdabd", next) << endl; // 15
return 0;
}
3、Boyer-Moore
规则1——坏字符规则: 单个字符都不成功
坏字符总是在最后一个(因为单个字符都比较不成功),后移位数=(m-1) - 坏字符前面出现的位置。
移动完重新从末尾开始比较,直到遇到新的坏字符。
多次比较后发现坏字符不在末尾,即已经匹配了后缀,此时进入规则2。
规则2.好后缀规则: 多个字符匹配,构成好后缀
好后缀的位置仍为m-1,需要确定最长好后缀上次出现的位置。
移动结果:
从末尾开始查找,遇到坏字符,回到规则1。
坏字符表和好后缀表
与next数组一样,只与模式串有关,计算过程如下。
计算后缀数组。
void suffixes(char *pat, int m, int *suff)
{
suff[m - 1] = m;
for (i = m - 2;i >= 0;--i){
q = i;
while (q >= 0 && pat[q] == pat[m - 1 - i + q])
--q;
suff[i] = i - q;
}
}
BMH算法
BM算法的核心在于两个启发式算法,一个叫做坏字符(bad character),一个叫做好后缀(good suffix)。
BMH(Boyer-Moore-Horspool)算法是对BM算法的改进算法。经统计分析,在字符串搜索过程中,遇到坏字符的概率要远远大于好后缀的情况,所以在实际使用时,只使用坏字符表也有很好的效率。
BMH它不再像BM算法一样关注失配的字符(好后缀),它的关注的焦点在于这个坏字符上,根据这个字符在模式串P中出现的最后的位置算出偏移长度,否则偏移模式串的长度。
const int maxNum = 1005;
int shift[maxNum];
int BMH(const string& T, const string& P) {
int n = T.length();
int m = P.length();
// 默认值,主串左移m位
for(int i = 0; i < maxNum; i++) {
shift[i] = m;
}
// 模式串P中每个字母出现的最后的下标,最后一个字母除外
// 主串从不匹配最后一个字符,所需要左移的位数
for(int i = 0; i < m - 1; i++) {
shift[P[i]] = m - i - 1;
}
// 模式串开始位置在主串的哪里
int s = 0;
// 从后往前匹配的变量
int j;
while(s <= n - m) {
j = m - 1;
// 从模式串尾部开始匹配
while(T[s + j] == P[j]) {
j--;
// 匹配成功
if(j < 0) {
return s;
}
}
// 找到坏字符(当前跟模式串匹配的最后一个字符)
// 在模式串中出现最后的位置(最后一位除外)
// 所需要从模式串末尾移动到该位置的步数
s = s + shift[T[s + m - 1]];
}
return -1;
}
sunday算法
原理:模式串必定后移,必然需要后一个字符存在于模式串。所以不存在就直接后移m,存在就对齐,看看是否满足。
#include <iostream>
#include <string>
#define MAX_CHAR 256
#define MAX_LENGTH 1000
using namespace std;
void GetNext(string & p, int & m, int next[])
{
for (int i = 0; i < MAX_CHAR; i++)
next[i] = -1;
for (int i = 0; i < m; i++)
next[p[i]] = i;
}
void Sunday(string & s, int & n, string & p, int & m)
{
int next[MAX_CHAR];
GetNext(p, m, next);
int j; // s 的下标
int k; // p 的下标
int i = 0;
bool is_find = false;
while (i <= n - m)
{
j = i;
k = 0;
while (j < n && k < m && s[j] == p[k])
j++, k++;
if (k == m)
{
cout << "在主串下标 " << i << " 处找到匹配\n";
is_find = true;
}
if (i + m < n)
i += (m - next[s[i + m]]);
else
break;
}
if (!is_find)
cout << "未找到匹配\n";
}
int main()
{
string s, p;
int n, m;
while (cin >> s >> p)
{
n = s.size();
m = p.size();
Sunday(s, n, p, m);
cout << endl;
}
return 0;
}