先上代码:
#include <string>
using namespace std;
/****************************KMP模式匹配算法****************************/
void setIndexOfNext(const string &AnalyzeThis, int *Next){//AnalyzeThis, Next从0开始存储有效数据..
const int Num = AnalyzeThis.size() - 1;
int preChar = -1, posChar = 0;
Next[0] = -1;
while(posChar < Num){
if(preChar == -1 || AnalyzeThis[preChar] == AnalyzeThis[posChar]){
++preChar;
++posChar;
Next[posChar] = preChar;
}
else
preChar = Next[preChar];
}
}
int First_Index_of(const string &Text, const string &Key, int i){//i用来定位Text的字符
int Next[300];
int temIndexOfKey = 0;
setIndexOfNext(Key, Next);
const int Num_Text = Text.size();
const int Num_Key = Key.size();
while(i < Num_Text && temIndexOfKey < Num_Key){
if(temIndexOfKey == -1 || Text[i] == Key[temIndexOfKey]){
++i;
++temIndexOfKey;
}
else
temIndexOfKey = Next[temIndexOfKey];
}
if(temIndexOfKey == Key.size())//结束时i指向符合单词的最后一个字母所在的下标的后一位
return i - Key.size();
else
return -1;
}
/****************************KMP模式匹配算法优化版****************************/
void better_setIndexOfNext(const string &AnalyzeThis, int *Next){//AnalyzeThis, Next从0开始存储有效数据..
const int Num = AnalyzeThis.size() - 1;
int preChar = -1, posChar = 0;
Next[0] = -1;
while(posChar < Num){
if(preChar == -1 || AnalyzeThis[preChar] == AnalyzeThis[posChar]){
++preChar;
++posChar;
if(AnalyzeThis[preChar] != AnalyzeThis[posChar])
Next[posChar] = preChar;
else
Next[posChar] = Next[preChar];//再次去掉多余的比较..
}
else
preChar = Next[preChar];
}
}
int better_First_Index_of(const string &Text, const string &Key, int i){//i用来定位Text的字符
int Next[300];
int temIndexOfKey = 0;
better_setIndexOfNext(Key, Next);
const int Num_Text = Text.size();
const int Num_Key = Key.size();
while(i < Num_Text && temIndexOfKey < Num_Key){
if(temIndexOfKey == -1 || Text[i] == Key[temIndexOfKey]){
++i;
++temIndexOfKey;
}
else
temIndexOfKey = Next[temIndexOfKey];
}
if(temIndexOfKey == Key.size())//结束时i指向符合单词的最后一个字母所在的下标的后一位
return i - Key.size();
else
return -1;
}
KMP算法的精髓在于:建立匹配串的模式数组(Next[])
1. 什么是Next数组?
传统的字符串匹配采用的是最简单最二逼的迭代,这必然会造成大量不必要的操作,Next数组的存在就是为了去除这些不必要的操作。
每一个Next数组的下标都映射了一个匹配串(AnalyzeThis)中具有相同下标的元素,当这个元素与主串(Text)的某个元素(设为a)不匹配的时候,
Next立功的时候就到了:
它会告诉这个傻逼的电脑,应该继续从匹配串(AnalyzeThis)的哪个元素开始继续与a进行匹配工作。
2. Next数组的建立原理与数值意义
如前文所述,Next充当的是一个指挥着的角色。
这个是Next数组的定义:
(这是第一个元素) || ======================|| =(这货就是传说中的第K个元素) ===============....(1)
===============================|| ========================|| =(这个是第J个元素) ============....(2)
如上面图所示,这两行表示的是同一串字符,为编译理解我将它表述为两行。其中第一行红色部分指代 字符串P1 ...Pk-1
,第二行红色部分指代 字符串Pj-k+1......Pj-1, 他俩相等。
相信看到这里大家都清楚了。因为 Next[J]被使用的前提是,匹配串(Key)的J元素的前面所有元素都与主串某区域匹配,但主串的a元素与匹配串中K元素不匹配。
即等价于(2)的红色部分( 字符串Pj-k+1......Pj-1)与主串相同区域匹配,即等价于(1)中的红色部分( 字符串P1 ...Pk-1)
与主串的相同区域匹配,又因为这是一个最大的字串(参照上面的MAX定义),所以我只要继续比较匹配串中的K元素与主串中的a元素即可。
Next[J] = K 的原理如上所示~
再来看实现的代码:
while(posChar < Num){
if(preChar == -1 || AnalyzeThis[preChar] == AnalyzeThis[posChar]){
++preChar;
++posChar;
Next[posChar] = preChar;
}
else
preChar = Next[preChar];
}
这货的主要思想是,1. 将 AnalyzeThis[preChar] == AnalyzeThis[posChar] 的关系表达在第 posChar + 1个元素上,而这种关系又与 posChar + 1上的元素无关!这是一个不断重复的过程..2. 通过 preChar = Next[preChar] 回溯,找到先前一个较短的串,再进行比较..
PS: KMP已经OUT了,但这种算法设计思路相当经典..