题目:有一个主串S ={a,b,c,a,c,a,b,d,c},模式串T = {a,b,d};请找到模式串在主串中第一次出现的位置
提示:不需要考虑字符串大小写问题,字符串均为小写字母
BF算法 (暴利解法)
思路:
1. 分别利用计数指针i和j指示主串S和模式T中当前正待比较的字符位置,i初值为pos,j的初值为1;
2. 如果2个串均为比较到串尾,即i和j均小于等于S和T的长度时, 则循环执行以下的操作:
* S[i]和T[j]比较,若相等,则i 和 j分别指示串中下一个位置,继续比较后续的字符;
* 若不相等,指针后退重新开始匹配. 从主串的下一个字符串(i = i - j + 2)起再重新和模式第一个字符(j = 1)比较;
3. 如果j > T.length, 说明模式T中的每个字符串依次和主串S找中的一个连续字符序列相等,则匹配成功,返回和模式T中第一个字符的字符在主串S中的序号(i-T.length);否则匹配失败,返回0;
执行过程
int Index_BF(String S, String T,int pos){
//i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配
int i = pos;
//j用于子串T中当前位置下标值
int j = 1;
//若i小于S的长度并且j小于T的长度时,循环继续
while (i <= S[0] && j <= T[0]) {
//比较的2个字母相等,则继续比较
if (S[i] == T[j]) {
i++;
j++;
}else
{
//不相等,则指针后退重新匹配
//i 退回到上次匹配的首位的下一位;
//加1,因为是子串的首位是1开始计算;
//再加1的元素,从上次匹配的首位的下一位;
i = i-j+2;
//j 退回到子串T的首位
j = 1;
}}
//如果j>T[0],则找到了匹配模式
if (j > T[0]) {
//i母串遍历的位置 - 模式字符串长度 = index 位置
return i - T[0];
}else{
return -1;
}
}
KMP算法
D.E.Knuth, J.H.Morris 和 V.R.Pratt 共同发表模式匹配算法, 称之克鲁特-莫里斯-普拉特算法. 简称 KMP 算法
思考:
思考问题1:主串的索引 i 是否需要退回到上次匹配的首位的下一位 也就是 i-j+2
在上述列子中,第一次执行时
当 i = 3 , j = 3 时,我们发现 c != d,同时我们也可以得到一些有用的信息
比如 S[1] = T[1], S[2] = T[2], 要不然 i 和 j 是不会到3的啊
我们直接看一下子串的前两个字符 T[1] 和 T[2],发现 T[1] = a, T[2] = b, a != b , 所以 T[1] != T[2],又因为T[2] = S[2], 所以 T[1] != S[2]
此时我们回顾一下第二次比较的什么
第二次比较的不就是T[1] 和 S[2]么?那第一次的结果其实已经证明了的东西,我还需要在遍历一次么?答案是不需要的
所以 i 不需要回到 2,i 直接来到3,也就是说当发现不匹配时,i 是没有必要回退到上次匹配的首页的下一位的!
换句话说当发现与子串不相同时,i不需要回退,但是如果第一位就不相同,i是需要移动到下一位的
思考问题2:子串的索引 j 是否一定要需要回退到首位
在上面这个例子中如果是第二位或者第三位不相同 j 都要回到 1
再举个例子
主串为:aaab
模式串为: aab
肉眼可见 当i = 2时,模式串第一次出现在主串中
如果i不回退,i = 3, 而正确答案i = 2 就完美错过了啊,如果避免这个问题呢,答案是让 j 回退 2
S[3] = J[2],i++,j++,S[4] = b,J[3] = b,完全匹配,i = 5-3 = 2 5因为只要匹配成功i就会自加,3是子串的长度
为什么j 要回退到2呢?
因为 当i =3发生不匹配时,证明主串的前两位和子串的前两位肯定是相同的,有因为子串的前两位是相同的都是a
如果得到j应该回退的位置是MVP算法的核心,我们用一个名字叫next数组来存储
next数组
通过计算返回子串T的next数组
eg: next[i] 翻译成中文就是,主串的第i个字符与模式串不一样时,模式串的索引应该回到next[i]这个位置
特殊情况:第一个位置就不一样时,i = 1,i 是需要移动到下一位的也就是i++, j 需要回到第一位,而i移动的时候,j也会移动,所以设置 next[1] = 0,j++ 后,j = 1
(注意字符串T[0]中是存储的字符串长度; 真正的字符内容从T[1]开始;)
void get_next(String T,int *next){
int i,j;
j = 1;
i = 0;
next[1] = 0;
//abcdex
//遍历T模式串, 此时T[0]为模式串T的长度;
//printf("length = %d\n",T[0]);
while (j < T[0]) {
//printf("i = %d j = %d\n",i,j);
if(i ==0 || T[i] == T[j]){
//T[i] 表示后缀的单个字符;
//T[j] 表示前缀的单个字符;
++i;
++j;
next[j] = i;
//printf("next[%d]=%d\n",j,next[j]);
}else
{
//如果字符不相同,则i值回溯;
i = next[i];
}
}
}
上面的例子中 模式串aab next数组为012
算法实现 1
int count = 0;
//KMP 匹配算法(1)
//返回子串T在主串S中第pos个字符之后的位置, 如不存在则返回0;
int Index_KMP(String S,String T,int pos){
//i 是主串当前位置的下标准,j是模式串当前位置的下标准
int i = pos;
int j = 1;
//定义一个空的next数组;
int next[MAXSIZE];
//对T串进行分析,得到next数组;
get_next(T, next);
count = 0;
//注意: T[0] 和 S[0] 存储的是字符串T与字符串S的长度;
//若i小于S长度并且j小于T的长度是循环继续;
while (i <= S[0] && j <= T[0]) {
//如果两字母相等则继续,并且j++,i++
if(j == 0 || S[i] == T[j]){
i++;
j++;
}else{
//如果不匹配时,j回退到合适的位置,i值不变;
j = next[j];
}
}
if (j > T[0]) {
return i-T[0];
}else{
return -1;
}
}
算法实现2
next数组优化
//KMP 匹配算法(2)
//求模式串T的next函数值修正值并存入nextval数组中;
void get_nextVal(String T,int *nextVal){
int i,j;
j = 1;
i = 0;
nextVal[1] = 0;
while (j < T[0]) {
if (i == 0 || T[i] == T[j]) {
++j;
++i;
//如果当前字符与前缀不同,则当前的j为nextVal 在i的位置的值
if(T[i] != T[j])
nextVal[j] = i;
else
//如果当前字符与前缀相同,则将前缀的nextVal 值赋值给nextVal 在i的位置
nextVal[j] = nextVal[i];
}else{
i = nextVal[i];
}
}
}
int Index_KMP2(String S,String T,int pos){
//i 是主串当前位置的下标准,j是模式串当前位置的下标准
int i = pos;
int j = 1;
//定义一个空的next数组;
int next[MAXSIZE];
//对T串进行分析,得到next数组;
get_nextVal(T, next);
count = 0;
//注意: T[0] 和 S[0] 存储的是字符串T与字符串S的长度;
//若i小于S长度并且j小于T的长度是循环继续;
while (i <= S[0] && j <= T[0]) {
//如果两字母相等则继续,并且j++,i++
if(j == 0 || S[i] == T[j]){
i++;
j++;
}else{
//如果不匹配时,j回退到合适的位置,i值不变;
j = next[j];
}
}
if (j > T[0]) {
return i-T[0];
}else{
return -1;
}
}