数据结构与算法之BF算法和RK算法

·  阅读 483
题目:有一个主串S = {a, b, c, a, c, a, b, d, c}, 模式串T = { a, b, d } ; 请找到模式串在主串中第一次出现的位置;
提示: 不需要考虑字符串大小写问题, 字符均为小写字母

要找到模式串第一次在主串中出现的次数,我们可以提供BF与RK两种算法,现在我们先看BF算法

BF算法

  • 定义:即暴力(Brute Force)算法,是普通的模式匹配算法
  • 算法思想:将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
  • 算法逻辑图示:
代码实现:

typedef char String[100+1]; /*  0号单元存放串的长度 *//**
 思路:主串S与模式串T的首字符位置是字符串的长度,所以遍历字符串从1开始
    1、利用计数指针i与j分别指向主串S与模式串T中正在比较的字符,i从1开始,j从1开始,startIndex用来记录i开始的位置
    2、当S[i] == T[j] 时i++,j++,继续遍历
    3、当 S[i]  != T[j] 时,计数指针回退到主串的下一个字符开始,i = i - j +  2(因为从1开始计算,再加1就是下一个开始的字符),j回退到 j = 1;
    4、当j > strlen(T)时,表示匹配完成,匹配到了,此时startIndex就是开始的位置
 */
int BFAlgorithm(String S, String T){
    if (strlen(T)>strlen(S)) {
        return -1;
    }
    int startIndex = 1;
    int i = 1,j = 1,sLength = (int)S[0],tLength = (int)T[0];
    while (i <= sLength && j <= tLength) {
        if (S[i] != T[j]) {
            i = i - j + 2;
            startIndex = i;
            j = 1;
        }else{
            j++;
            i++;
        }
    }
    if (j <= tLength) {//没有找到
        startIndex = -1;
    }
    return startIndex;
}
int StrAssign(String T,char *chars)
{
    int i;
    if(strlen(chars)>100)
        return -1;
    else
    {
        T[0]=strlen(chars);
        for(i=1;i<=T[0];i++)
            T[i]=*(chars+i-1);
        return 0;
    }
}
int main(int argc, const char * argv[]) {
    // insert code here...
    printf("算法匹配之BF算法\n");
    String S,T;
    StrAssign(S, "jkdjkfhds");
    StrAssign(T, "kfh");
    int index = BFAlgorithm(S, T);
    if (index == -1) {
        printf("主串是:%s,\n模式串是:%s\n没有找到\n",S,T);
    }else{
        printf("主串是:%s,\n模式串是:%s\n找到了位置:%d\n",S,T,index);
    }
    
    return 0;
}
复制代码
  • 时间复杂度:O(n*m),其中n是主串的长度,m是模式串的长度

RK算法

  • 算法思想:利用两个字符串的哈希值来比较字符串是否相同
  • 算法逻辑图:



  • 哈希算法:又叫散列,比如大家常见的MD5加密算法就是哈希,主要应用于文件校验、数字签名以及鉴权协议。
  • 如何将字符串转成哈希值
  1. 将 ‘当前字母’  - ‘a’  = 数字,比如: a - a = 0,b - a = 1,c - a = 2等
  2. 字符串每个字母转成数字后再组合,比如:

        'cab’ = 'c' * 26 * 26 + 'a' * 26 + 'b' * 1

                = 2 * 26^2 + 0 * 26 + 1 * 1

                = 1353

       此时字符串'cab'就转成1353数值了(26是26个字母,这个值越大,计算出来的不同字符串         哈希值相等的概率越小)

  • 主串拆解子串哈希值计算图解
  • 子串哈希值计算规律:相邻的2个子串 s[i] 与 s[i+1] (i表示子串从主串中的起始位置,子串的长度都为m). 对应的哈希值计算公式有交集. 也就说我们可以使用s[i-1]计算出s[i]的哈希值; 
  • 代码实现

    //d用质数,值越大,哈希值冲突的可能性越小
    static int d = 26;
    //获得d的(m - 1)次方的值
    long getMaxValue(int m){
        long maxValue = 1;
        for (int i = 1; i<=m - 1; i++) {
            maxValue = maxValue * d;
        }
        return maxValue;
    }
    //比对字符串是否相同
    int isMatch(char *S, char *T, int start, int lenght){
        for (int i = 0; i<lenght; i++) {
            if (S[start + i] != T[i]) {
                return 0;
            }
        }
        return 1;
    }
    /**
     思路:
        1、先计算出模式串以及主串中模式串长度的子串哈希值
        2、计算出d进制下的最高位的d^(m - 1)数值,m为模式串的长度
        3、拆解主串并计算出子串的哈希值
        4、比对子串的哈希值跟模式串的哈希值大小
         ① 如果两个哈希值不同,则继续步骤3、4
         ② 如果两个哈希值相同,为了解决哈希冲突,再比较两个字符串是否相同,如果相同则找到了,不同则继续步骤3、4
     */
    int RKAlgorithm(char *S, char *T){
        int n = (int)strlen(S);
        int m = (int)strlen(T);
        long long  sHashValue = 0,tHashValue = 0;
        /**
         以S = "cbdfaiurnd",T = "dfa"为例,
        第一次循环:
                sHashValue = 0 * 26 + 2 = 2;
                tHashValue = 0 * 26 + 3 = 3;
        第二次循环:
                sHashValue = 2 * 26 + 1 = 53;
                tHashValue = 3 * 26 + 5 = 83;
        第三次循环:
                sHashValue = 53 * 26 + 3 = 1381;
                tHashValue = 83 * 26 + 0 = 2158;
        */
        for (int i = 0; i<m; i++) {
            sHashValue = sHashValue * d + (S[i] - 'a');
            tHashValue = tHashValue * d + (T[i] - 'a');
        }
        //计算出子串最高位d^(m - 1)的值
        long maxValue = getMaxValue(m);
        for (int i = 0; i<=n-m; i++) {
            if (tHashValue == sHashValue) {
                if (isMatch(S, T, i, m) == 1) {
                    //从1开始计算
                    return i + 1;
                }
            }else{
                //公式:St[i] =(st[i-1] - d^2 ✖ (s[i] - ‘a’)) ✖ d + (s[i+m]-‘a’)
                sHashValue = (sHashValue - maxValue * (S[i] - 'a')) * d + (S[i + m] - 'a');
                printf("子串哈希值为:%lld\n",sHashValue);
            }
        }
        return -1;
    }
    
    int main(int argc, const char * argv[]) {
        // insert code here...
        printf("字符串匹配之RK算法\n");
        char *S = "cbdfaiurnd";
        char *T = "dfa";
        printf("主串为:%s,\n模式串为:%s\n",S,T);
        printf("匹配位置:%d (注意:-1为没有匹配到)\n",RKAlgorithm(S, T));
        return 0;}复制代码
  • 时间复杂度:如果不考虑哈希值冲突的判断字符串是否相同,则时间复杂度O(n),否则是O(n*m)
  • 哈希值冲突解决 :    由于计算字符串哈希值,总会有一定的几率导致不同字符串的哈希值是相同的,这个时候就产生了哈希值冲突。解决方法有两种:

       第一种:进制数d尽可能的使用质数比较大的值,这样可以减少哈希值出现冲突的概率

       第二种:当哈希值相同的时候,比对子串跟模式串是否一样(本文使用的就是这个)


分类:
iOS
标签:
分类:
iOS
标签: