<数据结构>KMP算法

next数组

定义#

  • 严格定义:next[i]表示使子串s[0...k] == s[i-k...i]的最大的k(前后缀可以重叠,但不能是s[0..i]本身)
  • 含义:最长相等前后缀的下标,没有则赋-1
  • 图形化解释:s[0]开始找到一个最长子串,满足一个条件:把该子串拉到末尾时能与母串的完全重合

求解#

递归#

上述判断可以归纳为一个递归过程:
读取两行子串,一行提供前缀,一行提供后缀。
读取新字符s[i]时,后缀行不断向左滑动。
如果能匹配,则根据后缀行最后一个匹配元素下标即next值
否则,后缀行向右滑动,直到找到一个完全匹配处

举例#

如果已知next[0]~next[3],如何递归地求出next[4]和next[5]

求next[4]:
已知next[3]=1,由于s[4]==s[next[3]+1]所以最长相等前后缀拓展,next[4]=next[3]+1
如果令j=next[3]则上述两个式子变成s[4]=s[j+1],next[4]=j+1

求next[5]:
已知next[4]=2,s[5]!=s[j+1]此时最长相等前后缀无法拓展,需要将后缀串向右滑动 到某个位置,使之满足"s[5]==s[j+1]",如图12-3最右图

现在确定j:本质就是确定 ~
由于 ~ 是由"aba"向右滑动得来的,所以它是aba的前缀
由于 ~ 又是“aba”的后缀,如图12-3最右图,所以可知 ~ 是"aba"的最长相等前后缀
"aba"在后缀行的下标为0-2,所以 j = next[2] (结合next数组的定义再理解一下) = next[next[4]] = j'(计算next[4]时的j值)

所以求解next[5]时,只需令next[5]=next[2],再判断s[5] == s[j+1]是否成立
如果成立,next[5]=next[j]+1
否则,不断令j=next[j],直到j=-1或者途中s[5] == s[j+1]成立

实现#

步骤#

  1. 初始化next数组,next[0] = j = -1
  2. 令i由1-(len-1)重复 3. 4.
  3. 不断令 j = next[j], 知道 j !=-1 或者 s[i] == s[j+1],
  4. 如果 s[i] == s[j+1],next[i] = j+1

代码#

//getNext求解长度为len的字符串s的next数组
void getNext(char s[], int len){
    int j = -1;
    next[0] = -1;  //初始化 j = next[0] = -1
    for(int i = 1; i < len; i++){
        while(j != -1 && s[i] != s[j+1]){  //求解next[1] ~ next[len-1]
            j = next[j];  //反复令j = next[j]
        }  //直到j回退到-1,或是 s[i] == s[j+1]
        if(s[i] == s[j+1]){
            j++; //则next[i] = j + 1,先令j指向这个位置
        }
        next[i] = j;  //令next[i] = j
    }
}

不难发现,j是用来给next[i]赋值以及在递归求解(代码中用循环代替了递归,但本质是递归思想)过程中给记录前一个next值的中间变量

KMP算法

分析#

字符串匹配,被匹配串:文本串text,匹配串:模式串patten

初始化,令j = -1, i = 0。

如下图,遍历text,当text[i] == patten[j+1]时,i和j都不断右移

如下图,当出现 text[i] != patten[j+1]时, 需要将patten向右滑动,直到满足条件 text[i] == patten[j+1],
不难发现,这一过程和求解next数组时失配的情况非常类似,和求解next数组时一样的思路,只需要令j = next[j],就可以让patten快速移动到相应位置。可见,next[j]就是当前j失配时,j应该回退的位置。
最后如果 j == 5也匹配成功,说明patten是text的子串

实现#

步骤#

  1. 初始化j=1
  2. 让i遍历text数组,对每个i,执行3.4.来试图匹配text[i]和patten[j+1]
  3. 不断令 j = next[j],直到 j == -1或 text[i] == patten[j+1]
  4. 如果text[i] == patten[j+1], 令 j++; 当 j== m-1时说明patten是text子串

代码#

//KMP算法,判断pattern数组是否是text的子串
/*O(m+n)*/
bool KMP(char text[], char patten[]){
    int n = strlen(text), m = strlen(patten);  //字符串长度
    getNext(patten, m);  //计算patten的next数组
    int j = -1;  //初始化j为-1,表示当前还没有任意一位被匹配
    for(int i = 0; i < n; i++){  //试图匹配text[i]
        while(j != -1 && text[i] != patten[j+1]){
            j = next[j];  //不断回退,知道j回到-1 或 text[i] == patten[j+1]
        }
        if(text[i] == patten[j+1]){
            j++;  //text[i]与patten匹配成功,令j加1
        }
        if(j == m-1){
            return true;  //patten完全匹配,说明patten是text的子串
        }
    }
    return false;  //执行完text还没匹配成功,说明patten不是text的子串
}

完整代码

#include<stdio.h>
#include<string.h>
const int MaxLen = 100;
int next[MaxLen];
//getNext求解长度为len的字符串s的next数组
void getNext(char s[], int len){
    int j = -1;
    next[0] = -1;  //初始化 j = next[0] = -1
    for(int i = 1; i < len; i++){
        while(j != -1 && s[i] != s[j+1]){  //求解next[1] ~ next[len-1]
            j = next[j];  //反复令j = next[j]
        }  //直到j回退到-1,或是 s[i] == s[j+1]
        if(s[i] == s[j+1]){
            j++; //则next[i] = j + 1,先令j指向这个位置
        }
        next[i] = j;  //令next[i] = j
    }
}

//KMP算法,判断pattern数组是否是text的子串
/*O(m+n)*/
bool KMP(char text[], char patten[]){
    int n = strlen(text), m = strlen(patten);  //字符串长度
    getNext(patten, m);  //计算patten的next数组
    int j = -1;  //初始化j为-1,表示当前还没有任意一位被匹配
    for(int i = 0; i < n; i++){  //试图匹配text[i]
        while(j != -1 && text[i] != patten[j+1]){
            j = next[j];  //不断回退,知道j回到-1 或 text[i] == patten[j+1]
        }
        if(text[i] == patten[j+1]){
            j++;  //text[i]与patten匹配成功,令j加1
        }
        if(j == m-1){
            return true;  //patten完全匹配,说明patten是text的子串
        }
    }
    return false;  //执行完text还没匹配成功,说明patten不是text的子串
}

关系

求解nex数组的过程就是模式串patten自我匹配的过程

posted @   咪啪魔女  阅读(54)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示