发现
-匹配失败时的右移位数与子串本身相关,与目标串无关
-移动位数=已匹配的字符数-对应的部分匹配值
-任意子串都穿在一个唯一的部位匹配表
前缀
-除了最后一个字符以外,一个字符串的全部头部组合
后缀
-出了第一个字符以外,一个字符串的全部尾部组合
部分匹配值
-前缀和后缀最长共有元素的长度
字符 | 前缀 | 后缀 | 交集 | 匹配 | |
1 | A | 空 | 空 | 空 | 0 |
2 | AB | A | B | 空 | 0 |
3 | ABC | A,AB | BC,C | 空 | 0 |
4 | ABCD | A,AB,ABC | BCD,CD,D | 空 | 0 |
5 | ABCDA | A,AB,ABC,ABCD | BCDA,CDA,DA,A | A | 1 |
6 | ABCDAB | A,AB,ABC,ABCD,ABCDA | BCDAB,CDAB,DAB,AB,B | AB | 2 |
7 | ABCDABD | A,AB,ABC,ABCD,ABCDA, ABCDAB | BCDABD,CDABD,DABD,ABD,BD,D | 空 | 0 |
实现关键
-PMT[1] = 0(下标为0的元素匹配值为0)
-从2个字符开始递推(从下标为1的字符开始递推)
-假设PMT[n] = PMT[n-1] +1(最长共有元素的长度)
-当假设不成立,PMT[n] 在PMT[n-1]的基础上减小
部分匹配表的使用(KMP算法)
#include <iostream>
int* make_pmt(const char* p)
{
int len = strlen(p);
int* ret = static_cast<int*>(malloc(sizeof(int) * len));
if( ret != NULL)
{
int ll = 0;
ret[0] = 0;
for(int i = 1;i<len;i++)
{
while((ll > 0) && p[ll] != p[i])
{
ll = ret[ll -1];
}
if(p[ll] == p[i])
{
ll++;
}
ret[i] = ll;
}
}
return ret;
}
int kmp(const char* s,const char* p)
{
int ret = -1;
int sl = strlen(s);
int pl = strlen(p);
int* pmt = make_pmt(p);
if((pmt != NULL)&&(0 < pl) &&(pl <= sl))
{
for(int i = 0,j = 0;i<sl;i++)
{
while((j > 0) && (s[i] != p[j]))
{
j = pmt[j-1];
}
if(s[i] == p[j])
{
j++;
}
if( j == pl )
{
ret = i + 1 - pl;
break;
}
}
}
free(pmt);
return ret;
}
int main()
{
cout << kmp("sfshdfuweihrfwshfuiwehfuwefiwhe","sfshdfuweihrfwshfuiwehfuwefiwhes") << endl;
return 0;
}
总结:
-部分匹配表示提高子串查找效率的关键
-部分匹配值定义为前缀和后缀最长共有元素的长度
-可以用递推的方法产生部分匹配表
-KMP利用部分匹配值与子串移动位数的关系提高查找效率