C 语言| 字符串匹配BF算法与RK算法

🌟前言


字符串匹配算法最经典的手段是BF算法,字符串匹配即给出一个主串S,根据模式串T中的字符串,找出在主串中第一次出现的位置,这个就是字符串匹配,简而言之即给一个规定的内容T,在大范围S中找到一个与之对应的,且第一次出现的位置。

BF算法


BF算法全称叫BruteForce,如本名一样,BF算法十分简单暴力,对主串和模式串进行逐个字符比较,中文名叫暴力匹配算法或者朴素匹配算法

而BF算法就是完成对模式串的移动,一位一位地往后移,然后从第一个字母开始比较,直到找到三个字母都相等的位置。

代码实现


首先我们先来写几个基本函数

/*生成一个其值等于chars的串T*/
Status StrAssign(String T,char *chars)
{
    int i;
    if(strlen(chars)>MAXSIZE)
        return ERROR;
    else{
        T[0]=strlen(chars);     //T[0]存字符串的长度
        for(i=1;i<=T[0];i++)
            T[i]=*(chars+i-1);
        return OK;
        }
}


/*清除字符串,直接令其长度为0*/
Status ClearString(String S)
{
    S[0]=0;
    return OK;
}


/*打印字符串*/
void StrPrint(String T)
{
    int i;
    for(i=1;i<T[0];i++)
        printf("%c",T[i]);
    printf("\n");
}

下面才是我们真正要开始写的BF算法的代码

先传进一个主串,模式串,然后再来一个pos(pos数值为多少,就代表从主串的第几个字母开始查找)

int Index_BF(String S,String T,int pos){

}

接着呢我们就写while循环,一步一步遍历我们的主串和模式串,分别用i,j变量。这里要注意的是,为了方便,我们默认把S[0]和T[0]存入他们各自的串长,也就是遍历从i=1和j=1开始,这样子也更方便我们书写代码,明确代码遍历到了哪一步

然后在while循环里添加if循环,判断S[i]和T[j]是否相等,相等就移到下一位,继续判断,移动就用i++和j++即可

如果不相等,证明遇到了不相同的字母,就让它俩回溯,让i回到初始位置,然后后移,而j回到1,又从模式串第一位开始判断,详细回溯解析看此代码后面的图解

int Index_BF(String S,String T,int pos){
    //主串索引
    int i = pos;
    //模式串索引
    int j = 1;

    while (i<= S[0] && j<=T[0]){
        //S[0]和T[0]的数值代表他们的长度
        if(S[i] == T[j]){
            i++;
            j++;
        }else{
            //回溯,索引下标更改
            i = i-j+2;
            j = 1;
        }
    }
    if(j>T[0]){
        return i-T[0];
    }
    return -1;
}

if循环实现回溯图解

以此时i=3为例,正好判断到了第三位,S[i]和T[j]不相等,此时我们发现i = i -j +1是回到原位

那我们让其再+1,即i = i - j +2,就可以使其回到原位后再进一位,接着令j = 1,回到模式串第一位进行S[i-j+2]==T[j]的判断

此时i=i-j+2=2,S[2]不等于T[1],又一次执行i = i - j +2,这时的i = 3,j = 1,通过这个循环,一步一步后移,实现了我们的BF算法,真的是简单粗暴,这个代码实现如果不能理解,就多看几遍图解,我相信你会有所收获

BF算法的时间复杂度很高,但有一个很好的优点就是:代码实现简单,在实际开发中,简单意味着,不容易出错,满足我们所说的KISS,咳咳,不是那个意思哈,是keep it simple and stupid(doge)

RK算法


RK算法是由两位发明者Rabin和Karp的名字来命名的,这个来历应该很容易理解吧(外国人都喜欢用自己名字命名一个新鲜事物哈哈)

相对于BF算法,RK算法比较的不是字母,而是数字,利用到哈希表这种算法,需要计算出哈希值

拿我们的例题来解释

模式串有三个字母,那我们就把主串分别以三个字母为一组子串,全部分隔出来,转化成我们的哈希值h1,h2,h3······,然后再和模式串比较,与哈希值相同的子串,就是我们要匹配到的,在主串中的位置

在这里边我们还可以稍作修改,例如我们没必要把所有子串都转化好,再进行与模式串的比较。为了去掉多余的步骤,我们可以边转化,边比较

假设我们的模式串为dda,此时转完第一个abd,不匹配;接着转第二个bdd,也不匹配;此时转到第三个,哎!是dda了,哈希值和模式串相同,这时我们就节省了后面dab,abc等子串的转化。

此时就有疑问了,我们该怎么把这模式串和拆出来的各个子串换算成哈希值呢?

首先我们先看一下十进制位的数字分解,以三位数为例

从上面的步骤看,abc的哈希值就是28,有了这套理论,我们就可以开始实践了

//RK算法
int getIndex_RK(String strOne, String strTwo){
    
    //1、记录两个字符串的长度
    int lengthOne = strOne[0];
    int lengthTwo = strTwo[0];
    
    //2、计算模式串的Hash值
    long long twoHashValue = 1;
    for (int i = 1; i <= lengthTwo; i++) {
        int value = strTwo[i] - 'a' + 1;
        twoHashValue *= value;
    }
    
    //3、依次计算出主串每个子串的Hash值,边计算边比较
    long long oneHashValue = 1;
    for (int i = 1; i <= lengthOne - lengthTwo + 1; i++) {
        if (i == 1) {
            for (int j = 1; j <= lengthTwo; j++) {
                int value = strOne[j] - 'a' + 1;
                oneHashValue *= value;
            }
        }
        else {
            //4、计算新子串的Hash值可以根据旧子串的Hash计算得出,减少重复计算
            int valueOld = (strOne[i - 1] - 'a' + 1);
            int valueNew = (strOne[i + lengthTwo - 1] - 'a' + 1);
            oneHashValue = oneHashValue / valueOld * valueNew;
        }
        //5、Hash值相同需对比一下子串和模式串是否匹配(防止出现哈希冲突),匹配则返回index
        if (oneHashValue == twoHashValue) {
            int isOK = isMatch(strOne, i, strTwo);
            if (isOK == 1) {
                return I;
            }
        }
    }
    //6、如果没有找到匹配的子串,则返回-1
    return -1;
}

//判断Hash值相等的字符串是否相等
int isMatch(String strOne, int index, String strTwo){
    
    for (int i = 1; i <= strTwo[0]; i++) {
        if (strOne[index + i - 1] != strTwo[i]) {
            return 0;
        }
    }
    return 1;
}
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小泥人Hyper

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值