专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

字符串匹配问题(BF&RK算法)

1. 题目

有一个主串S={a,b,c,a,c,a,b,d,c},模式串T={a,b,d},请找出模式串在主串中第一次出现的位置

提示:不需要考虑字符串大小写问题,字符均为小写字母

2. BF算法

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;

51_1.png

  • 辅助部分
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define MAXSIZE 40    /* 存储空间初始分配量 */
typedef int Status;   /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType; /* ElemType类型根据实际情况而定,这里假设为int */
typedef char String[MAXSIZE+1]; /*  0号单元存放串的长度 */

// chars 转换为String[]
Status CreateString(String T, char *chars){

    int i;
    if (strlen(chars) > MAXSIZE) {
        return ERROR;
    }else{
        T[0] = strlen(chars); // 串0位置存放字符长度
        for (i = 1; i<=T[0]; i++) {
            T[i] = *(chars+i-1);
        }
        return OK;
    }
}

  • 核心部分
int Question_BF(String S, String T, int pos){

    int i = pos; // 指定主串开始匹配的位置
    int j = 1;

    // 判断i和j 都小于对应的主串模式串的长度 才能进行匹配
    while (i<=S[0] && j<=T[0]) {
        // 相等时,i,j自加,进行下一个字符的匹配
        if (S[i] == T[j]) {
            i++;
            j++;
        }else{
            //不相等时,j的的位置要从头开始进行匹配,i应当退回到当次匹配的首位的下一位
            // +1 是因为子串的首位从1开始
            // 再 +1 是因为要往后移一位进行匹配
            j = 1;
            i = i-j+2;
        }
    }

    // 如果最终的j大于模式串的长度,就说明while中模式串对比完了所有的字符,即找到了位置
    if (j>T[0]) {
        return i-T[0];
    }else{
        return -1;
    }
}

3. RK算法

相对来说,比较字母总是不如比较数字大小来的简单,那么我们是否可以将字符通过什么方式来计算出对应的数字结果呢?

我们的前辈,Rabin和Karp就很有想法的,通过设计一个哈希公式,利用字符串对应的哈希值来进行比较。即我们已知的模式串可以计算出它的哈希值,同时将主串拆分出与模式串相同长度的子串来计算哈希值,比较两个哈希值是否相等。

那么问题来了:如何将一个‘bcd’换算成一个哈希值?

  • 通过ASCII表可以直到字母对应的ASCII值,‘a’ = 97。
  • 字母一共有26位,我们可以设计一个26进制

bcb = ('b'-'a')*26^2 + ('c'-'a')*26^1 + ('b'-'a')*26^0;

其中的2次方,也可以认为是模式串长度m减1得来的。

上边计算字符串的哈希值的逻辑假如理解的话,那我们可以再来想一个问题:主串拆分出来的,相邻两个子串,对应的哈希值计算公式是否有交集?是否可以通过前一个子串的哈希值,来求得下一个子串呢?

我们再来看看:

假如相邻的两个字符为’bcb’和’cba’,他们的哈希计算分别为

bcb = (‘b’-‘a’)*26^2 + ('c'-'a')*26^1 + ('b'-'a')*26^0

cba = ('c'-'a')*26^2 + ('b'-'a')*26^1 + (‘a’-‘a’)*26^0;

可以看到,‘bcb‘减去了最高位的‘b’,同时后两位的多乘了一次26,然后再加上下一个字符串的最低位’a’,得到的结果就是‘cba’的哈希值。

最终的公式

St[i] = (St[i-1] - 26^(m-1)*(S[i]-'a')) * 26 + (S[i+m]-'a')

ps:St[i]为主串中当前子串的哈希值,St[i-1]为前一个子串的哈希值,m为模式串的长度

那么,两个字符串的哈希值相等,就表示两个字符串就一定相同吗?

答案是不一定的,所以我们还需要进一步进行验证,哈希值相等时,两个字符串是否真的匹配?

  • 辅助部分
//d 表示进制 字母是26个 这里可以用26进制来设计
#define d 26
//为了杜绝哈希冲突. 当前发现模式串和子串的HashValue 是一样的时候.还是需要二次确认2个字符串是否相等.
int isMatch(char *S, int i, char *P, int m)
{
    int is, ip;
    for(is=i, ip=0; is != m && ip != m; is++, ip++)
        if(S[is] != P[ip])
            return 0;
    return 1;
}

//算出在d进制下的最高位
//d^(m-1)位的值;
int getMaxValue(int m){
    int h = 1;
    for(int i = 0;i < m - 1;i++){
        h = (h*d);
    }
    return h;
}

  • 核心部分
int Question_RK(char *S, char *P){
    // n:主串长度 m:子串长度
    int n = (int)strlen(S);
    int m = (int)strlen(P);

    // 初始化A为模式串的哈希值 St为主串的哈希值

    unsigned int A = 0;
    unsigned int St = 0;

     //2.求得子串与主串中0~m字符串的哈希值[计算子串与主串0-m的哈希值]    //循环[0,m)获取模式串A的HashValue以及主串第一个[0,m)的HashValue
    for (int i = 0; i<m; i++) {
         //假设模式串为 bcb
         // 循环第一次:A = 26*0 + 1;
         // 第二次:A = 26*1 + 2;
         // 第三次:A = 26^2 + 2*26 + 1;       
         A = (d * A + (P[i]-'a'));        
         St = (d * St + (S[i]-'a'));    
    }
    //3. 获取d^m-1值(因为经常要用d^m-1进制值)    int hValue = getMaxValue(m);

        //4.遍历[0,n-m], 判断模式串HashValue A是否和其他子串的HashValue 一致.
    //不一致则继续求得下一个HashValue
    //如果一致则进行二次确认判断,2个字符串是否真正相等.反正哈希值冲突导致错误
    //注意细节:
    //① 在进入循环时,就已经得到子串的哈希值以及主串的[0,m)的哈希值,可以直接进行第一轮比较;
    //② 哈希值相等后,再次用字符串进行比较.防止哈希值冲突;
    //③ 如果不相等,利用在循环之前已经计算好的st[0] 来计算后面的st[1];
    //④ 在对比过程,并不是一次性把所有的主串子串都求解好Hash值. 而是是借助s[i]来求解s[i+1] . 简单说就是一边比较哈希值,一边计算哈希值;

    for (int i = 0; i<= n-m; i++) {

        if (A == St) {
            if (isMatch(S, i, P, m)) {
                return i+1;
            }
        }
        St = (St - hValue*(S[i]-'a')) * d + (S[i+m]-'a');
    }
    return -1;
}

文章永久链接:https://tech.souyunku.com/31412

未经允许不得转载:搜云库技术团队 » 字符串匹配问题(BF&RK算法)

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们