字符串-KMP的模式匹配算法

来源:互联网 发布:返利 知乎 编辑:程序博客网 时间:2024/06/01 15:08

KMP算法中,主要的是如何得到next数组;

假设:
已知模式字符串P的next[0,….,j],且next[j] = k,如何求出next[j+1]?

递归公式:
若P[k] = P[j] , 则next[j + 1] = next[j] + 1 = k + 1;
若P[k] != P[j] , 如果此时P[next[k]] = P[j], 则next[j + 1] = k + 1,否则继续递归索引 k=next[k];

void GetNext(char *P, int next[]){    int pLen = strlen(P);    next[0] = -1;    int prefix = -1;    int suffix = 0;    while (suffix < pLen - 1)    {        if (prefix == -1 || P[prefix] == P[suffix])        {            next[suffix + 1] = prefix + 1;//此处这么写,容易理解            ++prefix;            ++suffix;//只有此处,才会递增模式字符串的索引suffix        }         else        {            //若不相等,则执行递归,直到找到相等,或是找到开头            prefix = next[prefix];        }    }}

在了解next数组前,还需要知道前缀后缀的概念:

  • “前缀”指除了最后一个字符以外,一个字符串的全部头部组合;
  • “后缀”指除了第一个字符以外,一个字符串的全部尾部组合。

“ABCDABD”为例:

  • “A”的前缀和后缀都为空集,共有元素的长度为0;
  • “AB”的前缀为[A],后缀为[B],共有元素的长度为0;
  • “ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
  • “ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
  • “ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为”A”,长度为1;
  • “ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,长度为2;
  • “ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

本文参考了网上的一些帖子,以及部分算法书籍。

KMP算法的优化:

考虑模式字符串为”abab“的情况:

  1. 它的前缀后缀最大长度为0,0,1,2;
  2. 进行右移一位后,得到的next数组为-1,0,0,1;
  3. 倘若文本字符串为“abacxxxxx“ ,在比较到”c“,发现出现不相同字符,按照之前的next数组,需要移动 3 - next[3] = 3 - 1 = 2后,模式字符串中的P[1]字符b,仍然和文本字符的c不同。所以,匹配失败;即,不能使P[j] != P[next[j] ]。如果出现了该情况,需要执行递归,next[j] = next[ next[j] ]。
void GetNext(char *P, int next[]){    int pLen = strlen(P);    next[0] = -1;    int prefix = -1;    int suffix = 0;    while (suffix < pLen - 1)    {        if (prefix == -1 || P[prefix] == P[suffix])        {                       //优化后            if (P[prefix + 1] != P[suffix + 1])            {                next[suffix + 1] = prefix + 1;//优化之前,只有该行            }             else            {                next[suffix + 1] = next[prefix + 1];            }            ++prefix;            ++suffix;//只有此处,才会递增模式字符串的索引suffix        }         else        {            //若不相等,则执行递归,直到找到相等,或是找到开头            prefix = next[prefix];        }    }}

完整的kmp字符串匹配算法的代码为:

int KMPSearch(char *s, char *p){    int len1 = strlen(s);    int len2 = strlen(p);    int *next = new int[len2];    GetNext(p, next);    int i = 0;     int j = 0;    while (i < len1 && j < len2)    {        //此处的-1,是next数组值;经过优化后,模式字符串的部分子字符串的next值,有可能为-1        if (j == -1 || s[i] == p[j])        {            ++i;            ++j;        }         else        {            j = next[j];        }    }    delete[] next;    if (j == len2)    {        return (i - j);    }     else    {        return -1;    }   }
原创粉丝点击