011-数据结构与算法 字符串匹配算法

题目:有一个主串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;
    }
    
}

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值