字符串的查找:朴素查找算法和KMP算法

Q:一个字符串A”abcabbcabcd”,要想知道里面是否包含另一个字符串a”abcd”?

按照我们以前所学,无非就是设置i和j记录两个字符串的下标,将两个字符串同时遍历,如果A串的i和a串的j下标所对应的字符一样,那么我们就让i,j同时向后遍历++,然而,绝大多数情况是i和j匹配失败,按照我们以前所学,匹配失败后A退回到i原来位置的下一个位置即:i=i-j+1,j=0。这种算法就叫朴素查找算法,也叫简单查找算法。

int BF(const char *str,const char *sub,int pos)//O(n*m)
{
    if(pos < 0)
    {
        return -1;
    }
    int lenstr = strlen(str);
    int lensub = strlen(sub);
    int i = pos;
    int j = 0;

    while(i<lenstr && j<lensub)
    {
        if(str[i] == sub[j]) {
            i++;
            j++;
        }
        else
        {
            i = i-j+1;//失配,退到i原来的位置的下一个
            j = 0;
        }
    }

    if(j >= lensub)//找到
    {
        return i-j;
    }
    else
    {
        return -1;
    }
}

然而这种算法虽然在编写上比较简单,但是查找速度太慢,时间复杂度太高,有很多不必要的遍历。《字符串的查找:朴素查找算法和KMP算法》
1.首先,字符串”bbcabcabbcabcd”的第一个字符与搜索词”abcd”的第一个字符,进行比较。因为b与a不匹配,所以搜索词后移一位。
2.因为b与a不匹配,搜索词再往后移。
3.因为c与a不匹配,搜索词再往后移。
4.就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。
5、6.接着比较字符串和搜索词的下一个字符,还是相同。
7.直到字符串有一个字符,与搜索词对应的字符不相同为止。
8.这时,按照朴素算法的做法是:将搜索词整个后移一位,再从头比较。这样做虽然可行,但是效率太差,因为你要把搜索位置移到已经比较过的位置,再重复比较一遍。

然而事实是,当a与d不匹配时,你其实知道前面3个字符是”abcd”。这就引出了KMP算法的思想,利用这个已知信息,不把搜索位置移回之前已经比较过的位置,而是继续向后移,这样就减少了重复比较。要做到这一点可以针对搜索词算出一个next数组。

随便举个例子:求“ababdabc”的next[]
“a b a b d a b c”
-1
a失配,它前面没有任何串了,记为-1;

 “a b a b d a b c”
 -1 0
第二位b失配,它前面只有一个"a",记为0

  “a b a b d a b c”
  -1 0 0
第三位a失配,它前面是"ab"   很显然,它只有"a"和"b"两个真子串。按规则来,也是没有相同的真子串的,记为0。

  "a b a b d a b c"
  -1 0 0 1
第四位b失配,它前面的串是"aba","a"和"a"两个真子串符合规则,记为1。

  "a b a b d a b c"
  -1 0 0 1 2
第五位d失配,它前面的串是"abab","ab"和"ab",记为2。

   "a b a b d a b c"
   -1 0 0 1 2 0
第6位a失配,它前面的串是"ababd",记为0。

    "a b a b d a b c"
    -1 0 0 1 2 0 1
第7位b失配,它前面的串是"ababda",它前面的串是"a"和"a",记为1。

   "a b a b d a b c"
   -1 0 0 1 2 0 1 0
第8位c失配,它前面的串是"ababda",它前面的串是"a"和"a",记为1。

 只要找到了这些数组,我们就知道每次匹配失败后,j该回到哪个位置:***移动位数 = 已匹配的字符数 - next数组匹配值***。逐位比较,直到搜索词的最后一位,搜索完成。
next[0]和next[1]固定为-1和0,我们可以发现next数组最多增长,降低不确定。不难发现,每次新增的失配位的前一位 如果和上次记录的第一个最大真子串的下一个字符  一样的话,就可以增长。因为每次最大比上次多出一位,所以next元素最多只能加1。如果每次新增的失配位的前一位和上次记录的第一个最大真子串的下一个字符不一样,那就继续退,直到-1。
next数组求出之后,只要把朴素算法的匹配成功代码块保持不变,把失配时的 i=i-j+1; j=0;修改为j=next[j];把判断匹配成功的判断条件改为 if(j==-1 || (str1[i]==str2[j]))   
static void GetNext(const char*sub,int *next)
{
    int lensub = strlen(sub);
    next[0] = -1;
    next[1] = 0;
    int j = 1;//通过j求j+1的值
    int k = 0;
    while(j+1<lensub)
    {
        if(k==-1 || sub[j]==sub[k]) {
            next[++j] = ++k;
        }
        else
        {
            k = next[k];
        }
    }
}

int KMP(const char *str,const char *sub,int pos)
{
    if(pos < 0)
    {
        return -1;
    }
    int lenstr = strlen(str);
    int lensub = strlen(sub);
    int i = pos;
    int j = 0;
    int *next = (int *)malloc(lensub*sizeof(int));
    GetNext(sub,next);

    while(i<lenstr && j<lensub)
    {
        if(j==-1 || (str[i]==sub[j])) {
            i++;
            j++;
        }
        else//i不回退,j退到next[j]
        {   
            j = next[j];
        }
    }

    free(next);

    if(j >= lensub)//找到
    {
        return i-j;
    }
    else
    {
        return -1;
    }
}

如此一来,时间复杂度就从0(n*m)变成了0(m+n)。

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/ZzZzZeEnNgG/article/details/78268720
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞