字符串匹配(string match)是在实际工程中经常会碰到的问题,通常其输入是原字符串(String)和子串(又称模式,Pattern)组成,输出为子串在原字符串中的首次出现的位置。通常精确的字符串搜索算法包括暴力搜索(Brute force),KMP, BM(Boyer Moore), sunday, robin-karp 以及 bitap。下面分析这几种方法并给出其实现。假设原字符串长度M,字串长度为N。
1. Brute force.
该方法又称暴力搜索,也是最容易想到的方法。也叫朴素字符串匹配算法,NAIVE-STRING-MATCHER
预处理时间 O(0)
匹配时间复杂度O(N*M)
主要过程:从原字符串开始搜索,若出现不能匹配,则从原搜索位置+1继续。
//无视我的库文件
#include<iostream>
#include<string>
#include <math.h>
#include <algorithm>
#include <vector>
#include <functional>
using namespace std;
int bf(string text, string find)
{
auto m = text.size(), n = find.size();
if (0 == m*n || m < n) return -1;
string::iterator p, q, s;
s = text.begin();
p = text.begin();
q = find.begin();
while (p != text.end() && (text.end() - s) >= n)
{
if (*p == *q)
{
p++;
q++;
}
else
{
s++;
p = s;
q = find.begin();
}
if (q == find.end()) return s - text.begin();
}
return -1;
}
int main()
{
const string text{ "introduction to algorithms" };
const string find{ "algorithm" };
cout << bf(text, find) << endl;
system("pause");
}
假设模式P中的所有字符都是不同的。试说明如何对一段n个字符的文本T加速朴素字符串匹配的执行速度,使其运行时间达O(n).——算法导论32.1-2
主要过程:这里所有字符不同说明对T遍历的时候不需要回过来,只需要把模式P中的下标记为起始就可以了。
int bf_unique(string text, string find)
{
auto m = text.size(), n = find.size();
if (0 == m*n || m < n) return -1;
for (auto p = text.begin(), q = find.begin(); p != text.end(); ++p)
{
if (*p == *q&&q != find.end()) ++q;
else
{
if (q!=find.begin())
{
q = find.begin();
--p;
continue;
}
}
if (find.end() == q) return p - text.begin() - find.size() + 1;//从0开始
}
return -1;
}
RabinKarp算法
算法思想是将字符串映射到一个数字,然后比较字串的数字是否相等,如果相等则继续进一步判断确认正确性,如果不相等,则可以判断字符串不匹配。
预处理时间O(0)
最坏匹配时间复杂度O(N*M)
int RabinKarpMatcher(string st, string sp, int d, int q)
{
int n = st.length(), m = sp.length(), h = ((int)pow(d, m - 1)) % q;
int p = 0, t = 0;
//计算p和t0
for (int i = 0; i < m; ++i)
{
p = (p*d + sp[i]) % q;
t = (t*d + st[i]) % q;
}
for (int s = 0; s < n - m + 1; ++s)
{
if (p == t&&sp == st.substr(s, m))
{
return s;//如果找多个应该先存起来,或者这边输出但是别return
}
t = (d*(t - st[s] * h) + st[s + m]);
t = ((t < 0) ? q : 0) + t%q;//如果上式直接取余数的话会遇到负号的问题,这里判断下。
}
return -1;
}
KMP
KMP是经典的字符串匹配算法。
预处理时间:O(M)
匹配时间复杂度:O(N)
主要过程:通过对字串进行预处理,当发现不能匹配时,可以不进行回溯。
void computePrefixFunction(string sp, int* a)
{
int m = sp.length(), k = 0;
a[0] = 0;
for (int q = 1; q < m; ++q)
{
while (k>0 && sp[k] != sp[q])//加到一定程度和不匹对的情况;
k = a[k-1];//这边也是从0开始的,所以还是要减1。。
if (sp[k] == sp[q])
++k;
a[q] = k;
}
// return;
}
void kmpMatcher(string st, string sp)
{
int n = st.length(), m = sp.length(), q = 0;
int* p = new int[m];
computePrefixFunction(sp, p);
for (int i = 0; i < n; ++i)
{
while (q>0 && sp[q] != st[i])
q = p[q - 1];
if (sp[q] == st[i])
q++;
if (q == m)//因为上面提前+1了;
{
cout << i - m + 1 << endl;//从0开始计算。
q = p[q - 1];//注意数组不要越界。
}
}
delete []p;
// return -1;//如果以返回值的方式那就只能输出第一个了。要输出多个要么提前保存,要么直接输出。
}