串:
串(String)是由零个或多个字符组成的有序序列,又叫字符串。
字符串的匹配:
串S和T存在,T时非空串,若主串中存在和串T值相同的字串,则返回它在主串s中第pos个字符之后第一次出现的位置,否则返回-1。
这种子串的定位操作通常称作串的模式匹配。一般把S称为主串,T称为模式串。
一般字符串的匹配算法主要有:
(1)朴素的字符串匹配算法(Naive String Matching Algorithm)
(2)Knuth-Morris-Pratt字符串匹配算法(KMP算法)
假设我们要从s="goodgoogle"寻找T="google"。
朴素的字符串匹配算法
(1)将主串中的第一个和子串中的第一个比较,如果匹配成功,则将主串和子串中的第二个比较。如果两个串中出现不相同的情况,就将主串的第二个和子串的第一个比较。这样一直到匹配完成或者,主串完结。
(1) (2) (3)(4) (5)
思想比较简单,程序也比较好实现。
int simpleString(string mainStr, string modeStr, int pos)
{
int mainStrLen = mainStr.length();
int modeStrLen = modeStr.length();
//记录主串中模式串的起始位置
int p = -1;
int i = pos, j = 0;
while (pos > -1 && i < mainStrLen)
{
if (mainStr[i] == modeStr[j])
{
++j;
++i;
}
else
{
i = i - j + 1;
j = 0;
}
if (j == modeStrLen)
{
p = i - j;
break;
}
}
return p;
}
KMP算法
观察上面的朴素的字符串匹配算法我们就会发现,在第一次匹配失败后,其实在从主串的第二位置继续寻找是大可不必的,因为“google”中不相同的元素,所以我们可以在主串中下标为4的位置开始重新开始匹配,这样就可以减少比较的次数。这就是KMP的基本思路。那如何计算匹配失败后重新开始匹配的位置,就是KMP算法的关键(next数组)。
KMP的算法流程:
假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置
(1)如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
(2) 如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。
这是问题的关键就转化成如何求next数组(KMP的核心问题):
next数组中存储的是除当前字符外最长公共前缀后缀。那么什么是最长公共前缀后缀那?简单来说就是一段字符前面和后面都相同的部分的长度,例如:aba,前缀有(a,ab)后缀有(a,ba),那aba的最长公共前缀后缀就是1,同理abab的最长公共前缀后缀就是2。但next中存储的是除当前字符外最长公共前缀后缀,需要将第一步中求出的数组,整体后移一位,然后将第0位的元素置成-1。next数组虽然描述起来很简单,在给定一个特定的串下用笔计算也不难,但问题在是模式串是不确定的,所以算法的理解上还是有一定的困难的,尤其是匹配失败后的回溯过程。下面是求解next数组的代码:
/*
(1)先生成最长公共前缀后缀表,不做转化。
(2)转化为next数组。
*/
void makeNext(string modeStr, vector<int>& pNext)
{
//只有一位时为0
int modeStrLen = modeStr.length();
//i记录当前位置
//j记录当前位置之前的字符串最大公共前缀后缀。
int i = 1, j = 0;
//存储的是从0位置到当前下标位置之间字符串的最大公共前缀后缀
pNext.push_back(0);
while (i < modeStrLen)
{
//当前的modeStr[j]和modeStr[i]不匹配
while (j>0 && modeStr[i] != modeStr[j])
{
j = pNext[j-1];
}
/*
退出循环:
(1)j==0,没有在前面寻找到和mode[i]相同的元素
(2)modeStr[i]==modeStr[j],在前面寻找到了和mode[i]相同的元素。
*/
if (modeStr[i] == modeStr[j])
{
++j;
}
pNext.push_back(j);
++i;
}
//转化为next数组
pNext.insert(pNext.begin(), -1);
pNext.pop_back();
}
next数组一旦求出,那么KMP问题也就完成了99%了,下面是KMP算法:
int KMP(string mainStr, string modeStr)
{
int mainStrLen = mainStr.length();
int modeStrLen = modeStr.length();
vector<int> next;
makeNext(modeStr, next);
int i = 0, j = 0, pos = -1;
while (i < mainStrLen)
{
if (j==-1||mainStr[i] == modeStr[j])
{
++i;
++j;
if (j == modeStrLen)
{
pos = i - j;
break;
}
}
else
{
j = next[j];
}
}
return pos;
}
如果理解可以看看这篇文章:教你从头到尾彻底理解KMP算法