【字符串】KMP算法

KPKPKMP

KMP算法

真のKMP算法

在线性时间复杂度内匹配字符串(判断串BBB是否是串AAA的子串,并找出串BBB在串AAA中出现的位置)。

暴力匹配方法:
BBB的第一位与AAA的每一位进行比较,若匹配则继续向后比较,否则将BBB后移一位。
时间复杂度:O(n2)O(n^2)O(n2)

当比较到BBB的某个位置与AAA不匹配时,这个位置前的部分已经被比较过了,若继续逐个比较就会导致效率极低。效率高的做法是,若BBB的某个前缀在已匹配部分的其它位置出现过(如图中的"AB"),可以直接将BBB移至这些位置;否则已匹配部分中的字符都不可能与BBB的首字符匹配,直接跳过。
而KMP算法通过针对BBB计算出一张回退表来实现这个过程。

搜索词BBBABCDABD
回退表failfailfail0000120


BBB中每个位置的回退值就是当前已匹配部分的前缀与后缀集合中最长相同元素的长度。例如"ABCDABD"中第六位的回退值为222,因为"ABCDAB"的前缀与后缀集合中最长相同元素为"AB",其长度为222
根据回退表,移动位数===已匹配的字符数−-对应的回退值

记两个串长度分别为n,mn,mn,m。分别给串A,BA,BA,B两个指针i,ji,ji,j,始终满足BBB的前jjj个字符正好匹配AAA的以A[i]A[i]A[i]结尾的长度为jjj的子串,即B[1…j]B[1\dots j]B[1j]A[i−j+1…i]A[i-j+1\dots i]A[ij+1i]匹配。

  • A[i+1]=B[j+1]A[i+1]=B[j+1]A[i+1]=B[j+1],则可以增加i,ji,ji,j的值,继续往后比较。若某个时候j=mj=mj=m,说明BBBAAA的字串。
  • A[i+1]≠B[j+1]A[i+1]\neq B[j+1]A[i+1]̸=B[j+1],则将jjj改为fail[j]fail[j]fail[j],相当于BBB向右移动了j−fail[j]j-fail[j]jfail[j]位。

如何求串BBBfailfailfail数组?显然fail[1]=0fail[1]=0fail[1]=0。然后从i=2i=2i=2开始,拿两个BBB串进行KMP,一边匹配一边记下每个位置的回退值。因为iii始终大于jjj,当jjj需要改为fail[j]fail[j]fail[j]时,fail[1…j]fail[1\dots j]fail[1j]都已经算好了。
时间复杂度:O(n)O(n)O(n)

void kmp(char *a,char *b,int *fail){
  int n=strlen(s+1),m=strlen(b+1);
  for(int i=2,j=0;i<=m;i++){
    while(j&&b[j+1]!=b[i])j=fail[j];
    if(b[j+1]==b[i])j++;fail[i]=j;
  }
  for(int i=1,j=0;i<=n;i++){
    while(j&&b[j+1]!=a[i])j=fail[j];
    if(b[j+1]==a[i])j++;
    if(j==m){cout<<i-m+1<<endl;j=fail[j];}
  }
}
求最短循环节

failfailfail数组有一个奇妙的性质:对于长度为lenlenlen的字符串,其最短循环节长度为len−fail[len]len-fail[len]lenfail[len]
但这样算出的循环节可能在原串中是不完整的。例如"ABCAB"算出的最短循环节为"ABC",长度为333。所以有些题目需要特判。

求最长公共子串

利用KMP可以在O(nL2)O(nL^2)O(nL2)的时间内求nnn个长度不超过LLL的字符串的最长公共子串的长度。原因是在匹配过程中,BBB串的长度为jjj的前缀会一直是AAA串的子串。随便取一个串,枚举这个串的后缀,把这个后缀与其它的串进行KMP,每个串记录匹配过程中出现过的最大的jjj,取被匹配的每个串的最小值,再取匹配串每个后缀的最大值。

KMP算法字符串匹配中应用广泛,能显著提升匹配效率,尤其是面对较长的模式串或文本串时,其优势相较于朴素字符串匹配算法更为明显,常用于文本编辑器搜索功能、数据压缩中的字符串查找等领域 [^1]。 ### 原理 KMP算法的核心在于利用已经匹配过的部分信息,避免在匹配过程中进行不必要的回溯。该算法通过对模式串进行预处理,生成一个 `next` 数组,`next` 数组记录了模式串中每个位置之前的子串的最长公共前后缀长度。当匹配过程中出现字符不匹配的情况时,根据 `next` 数组的值,将模式串向右移动一定的位数,从而跳过一些不必要的比较步骤。 ### 实现代码 以下是C++实现KMP算法的代码示例: ```cpp #include <iostream> #include <vector> #include <string> // 生成next数组 std::vector<int> getNextArr(const std::string& p) { int m = p.size(); std::vector<int> next(m); next[0] = -1; int i = 0, j = -1; while (i < m - 1) { if (j == -1 || p[i] == p[j]) { ++i; ++j; next[i] = j; } else { j = next[j]; } } return next; } // KMP算法实现 int kmp(const std::string& s, const std::string& p) { std::vector<int> next = getNextArr(p); int n = s.size(); int m = p.size(); int i = 0, j = 0; while (i < n && j < m) { if (j == -1 || s[i] == p[j]) { ++i; ++j; } else { j = next[j]; } } if (j == m) { return i - m; } return -1; } ``` ### 使用方法 可以使用以下方式调用上述实现的KMP算法: ```cpp int main() { std::string text = "ABABDABACDABABCABAB"; std::string pattern = "ABABCABAB"; int result = kmp(text, pattern); if (result != -1) { std::cout << "Pattern found at index: " << result << std::endl; } else { std::cout << "Pattern not found." << std::endl; } return 0; } ``` 在上述代码中,`getNextArr` 函数用于生成模式串的 `next` 数组,`kmp` 函数则是具体的KMP匹配函数。在 `main` 函数中,定义了一个文本串 `text` 和一个模式串 `pattern`,调用 `kmp` 函数进行匹配,并根据返回结果输出匹配信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值