请选择 进入手机版 | 继续访问电脑版

[C 语言] C语言字符串的模式匹配之BF与KMP

[复制链接]
查看130 | 回复20 | 2021-9-14 08:08:25 | 显示全部楼层 |阅读模式
目次

确定一个子串(模式串)在主串中第一次出现的位置。

BF算法(Brute-Force算法)

BF算法即质朴 的简单匹配法,采用的是穷举的思绪 。从主串的每一个字符开始依次与模式串的字符举行 比较。

在这里插入图片形貌

在这里插入图片形貌

  1. int index_BF(SeqString S, SeqString T, int begin)//从S的第begin位(下标)开始进行匹配判断
  2. {
  3. int i = begin, j = 0;
  4. while (i < S.length && j < T.length)
  5. {
  6. if (S.ch[i] == T.ch[j])
  7. {
  8. i ++;
  9. j ++;//比较下一个字符
  10. }
  11. else
  12. {
  13. i = i - j + 1;
  14. j = 0;//模式串回溯到起点
  15. }
  16. }
  17. if (j == T.length) return i - T.length; //匹配成功,则返回该模式串在主串中第一次出现的位置下标
  18. else return -1;
  19. }
复制代码
  1. int index_BF(char S[], char T[], int beg)
  2. {
  3. int i = beg, j = 0;
  4. while (i < strlen(S) && j < strlen(T))
  5. {
  6. if (S[i] == T[j])
  7. {
  8. i ++;
  9. j ++;
  10. }
  11. else
  12. {
  13. i = i - j + 1;
  14. j = 0;
  15. }
  16. }
  17. if (i == strlen(S)) return i - strlen(T);
  18. else return -1;
  19. }
  20. int main()
  21. {
  22. char str1[10] = "abcde";
  23. char str2[10] = "cde";
  24. printf("%d", index_BF(str1, str2, 0));
  25. return 0;
  26. }
复制代码

KMP算法(快速的)

基本头脑 为:主串的指针 i i i不必回溯,利用 已经得到前面“部分匹配”的效果 ,将模式串向右滑动多少 个字符,继续与主串中的当前字符举行 比较,减少了一些不必要的比较。

时间复杂度为 O ( n + m )

KMP算法的核心,是一个被称为部分匹配表(Partial Match Table)的数组。

  1. 首先要明白什么是字符串的前缀和后缀。
  2. 如果字符串A和B,存在A=BS,其中S是任意的<strong>非空字符串</strong>,那就称B为A的前缀。例如,”Harry”的前缀包括{”H”, ”Ha”,”Har”, ”Harr”},我们把所有前缀组成的集合,称为字符串的前缀集合。
  3. 同样可以定义后缀A=SB,其中S是任意的<strong>非空字符串</strong>,那就称B为A的后缀,例如,”Potter”的后缀包括{”otter”, ”tter”, ”ter”, ”er”, ”r”},然后把所有后缀组成的集合,称为字符串的后缀集合。
  4. 要注意的是,<strong>字符串本身并不是自己的前缀或后缀</strong>。
复制代码

PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。

比如,对于字符串”ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},此中 最长的元素为”aba”,长度为3,即该字符串在PMT表中的值为3。性子 为:该字符串前3个字符与后三个字符雷同 。

假如 模式串有 j个字符,则PMT表中就有 j 个数值。此中 第一个数值总为0。

在这里插入图片形貌

在这里插入图片形貌

在这里插入图片形貌

在这里插入图片形貌

  1. int index_KMP(SeqString S, SeqString T, int begin)//从S的第begin位(下标)开始进行匹配判断
  2. {
  3. int i = begin, j = 0;
  4. while (i < S.length && j < T.length)
  5. {
  6. if (j == -1 || S.ch[i] == T.ch[j])
  7. {
  8. i ++;
  9. j ++;
  10. }
  11. else j = next[j];//即PMT[j-1]
  12. }
  13. if (j == T.length) return i - T.length; //匹配成功,则返回该模式串在主串中第一次出现的位置下标
  14. else return -1;
  15. }
复制代码

那么该怎样 求出next数组呢?

在这里插入图片形貌

实在 ,求next数组的过程完全可以当作 字符串匹配的过程,即以模式字符串为主字符串,以模式字符串的前缀为目标 字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。
具体 来说,就是从模式字符串的第一位(留意 ,不包括第0位)开始对自身举行 匹配运算。 在任一位置,能匹配的最长长度就是当前i位置的next值。如下图所示。

在这里插入图片形貌

在这里插入图片形貌

在这里插入图片形貌

在这里插入图片形貌

在这里插入图片形貌

  1. void GetNext(SeqString T, int next[])
  2. {
  3. next[0] = -1;
  4. int j = 0, k = -1;//起始时k落后j一位
  5. while (j < T.length)//j遍历一遍模式串,对于每个字符得到该位置的next数组的值
  6. {
  7. if (k == -1 || T.ch[j] == T.ch[k])
  8. {
  9. j ++;
  10. next[j] = k + 1;//将j视为指向一个子串(后缀)结束后的下一个字符,k指向一个子串(前缀)的最后一个字符,则这两个子串的重叠部分的长度(k下标从0开始)即PMT[j-1]的值
  11. k ++;
  12. /*也可以简便地写为(易记):
  13. j ++;
  14. k ++;
  15. next[j] = k;
  16. 最简单的形式为:
  17. next[++ j] = ++ k;
  18. */
  19. }
  20. else k = next[k];//k回溯,即将第二个子串(右滑)(减小匹配的前缀长度)
  21. }
  22. }
复制代码

即:

  1. #include <stdio.h>
  2. #include <string.h>
  3. int next[10];//全局数组
  4. void GetNext(char T[])
  5. {
  6. int j = 0, k = -1;
  7. next[0] = -1;
  8. while (j < strlen(T))
  9. {
  10. if (k == -1 || T[j] == T[k])
  11. {
  12. j ++;
  13. next[j] = k + 1;
  14. k ++;
  15. }
  16. else k = next[k];
  17. }
  18. }
  19. int index_KMP(char S[], char T[], int begin)//从S的第begin位(下标)开始进行匹配判断
  20. {
  21. int i = begin, j = 0;
  22. while (i < strlen(S) && strlen(T))
  23. {
  24. if (j == -1 || S[i] == T[j])
  25. {
  26. i ++;
  27. j ++;
  28. }
  29. else j = next[j];//即PMT[j-1]
  30. }
  31. if (j == strlen(T)) return i - strlen(T); //匹配成功,则返回该模式串在主串中第一次出现的位置下标
  32. else return -1;
  33. }
  34. int main()
  35. {
  36. char str1[10] = "abcde";
  37. char str2[10] = "cde";
  38. GetNext(str2);
  39. printf("%d", index_KMP(str1, str2, 0));
  40. return 0;
  41. }
复制代码

求next数组的方法也可举行 优化:

在这里插入图片形貌

  1. void GetNextVal(SeqString T, int nextval[])
  2. {
  3. nextval[0] = -1;
  4. int j = 0, k = -1;
  5. while (j < T.length)
  6. {
  7. if (k == -1 || T.ch[j] == T.ch[k])
  8. {
  9. j ++;
  10. k ++;
  11. if (T.ch[j] != T.ch[k])
  12. nextval[j] = k;
  13. else
  14. nextval[j] = nextval[k];
  15. }
  16. else k = nextval[k];
  17. }
  18. }
复制代码

即:

  1. int nextval[10];//全局数组
  2. void GetNextVal(char T[])
  3. {
  4. int j = 0, k = -1;
  5. nextval[0] = -1;
  6. while (j < strlen(T))
  7. {
  8. if (k == -1 || T[j] == T[k])
  9. {
  10. j ++;
  11. k ++;
  12. if (T[j] != T[k]) nextval[j] = k;
  13. else nextval[j] = nextval[k];
  14. }
  15. else k = nextval[k];
  16. }
  17. }
  18. int index_KMP(char S[], char T[], int begin)//从S的第begin位(下标)开始进行匹配判断
  19. {
  20. int i = begin, j = 0;
  21. while (i < strlen(S) && strlen(T))
  22. {
  23. if (j == -1 || S[i] == T[j])
  24. {
  25. i ++;
  26. j ++;
  27. }
  28. else j = nextval[j];
  29. }
  30. if (j == strlen(T)) return i - strlen(T); //匹配成功,则返回该模式串在主串中第一次出现的位置下标
  31. else return -1;
  32. }
  33. int main()
  34. {
  35. char str1[10] = "abcde";
  36. char str2[10] = "bcde";
  37. GetNextVal(str2);
  38. printf("%d", index_KMP(str1, str2, 0));
  39. return 0;
  40. }
复制代码

KMP—yxc模板

字符串从数组下标1开始存

  1. #include <iostream>
  2. using namespace std;
  3. const int M = 1000010, N = 100010;
  4. char S[M], p[N];
  5. int ne[N]; //全局变量数组,初始化全为0
  6. int main()
  7. {
  8. int m, n;
  9. cin >> m;
  10. for (int i = 1; i <= m; i ++) cin >> S[i];
  11. cin >> n;
  12. for (int i = 1; i <= n; i ++) cin >> p[i];//主串与模式串均由数组下标1开始存储
  13. // 也可以简写为 cin >> m >> S + 1 >> n >> p + 1;
  14. for (int i = 2, j = 0; i <= n; i ++)//求模式串各字符处的next值,即求串p[1~i]的前后缀最大交集的长度
  15. { //由于字符串由下标1开始存储,next[i]+1也是模式串下次比较的起始下标
  16. while (j && p[i] != p[j + 1]) j = ne[j];//记录的最大交集的长度减小,直到为0,表示p[1~i]前后缀无交集
  17. if (p[i] == p[j + 1]) j ++;//该位匹配成功
  18. ne[i] = j;//j即该位的ne值
  19. }
  20. for (int i = 1, j = 0; i <= m; i ++)//遍历一遍主串
  21. {
  22. while (j && S[i] != p[j + 1]) j = ne[j];//不匹配且并非无路可退,则j后滑。j==0意味着当前i所指的字符与模式串的第一个字符都不一样,只能等该轮循环结束i++,之后再比较
  23. if (S[i] == p[j + 1]) j ++;//该位匹配成功
  24. if (j == n)//主串与模式串匹配成功
  25. {
  26. cout << i - n << ' ';//匹配时,输出 模式串首元素在主串中的下标
  27. j = ne[j];//j后滑,准备继续寻找下一个匹配处
  28. }
  29. }
  30. return 0;
  31. }
复制代码

字符串从数组下标为开始存

  1. const int N = 1000010;
  2. char s[N], p[N];
  3. int ne[N];
  4. int main()
  5. {
  6. int n, m;
  7. cin >> m >> p >> n >> s;
  8. ne[0] = -1;//ne[0]初始化为-1
  9. for (int i = 1, j = -1; i < m; i ++ )//从模式串的第2位2开始求next值
  10. {
  11. while (j != -1 && p[j + 1] != p[i]) j = ne[j];
  12. if (p[j + 1] == p[i]) j ++ ;
  13. ne[i] = j;
  14. }
  15. for (int i = 0, j = -1; i < n; i ++ )//遍历一遍主串
  16. {
  17. while (j != -1 && s[i] != p[j + 1]) j = ne[j];
  18. if (s[i] == p[j + 1]) j ++ ;
  19. if (j == m - 1)//扫描到模式串结尾,说明匹配完成
  20. {
  21. cout << i - j << ' ';
  22. j = ne[j];
  23. }
  24. }
  25. return 0;
  26. }
复制代码

总结

本篇文章就到这里了,盼望 可以或许 给你带来帮助,也盼望 您可以或许 多多关注脚本之家的更多内容!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

avatar 山东美家环保 | 2021-9-14 18:21:20 | 显示全部楼层
admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的网站,他卖的服务器是永久的,我们的网站用 服务器都是在这家买的,你可以去试试。访问地址:http://fwq.mxswl.com
回复

使用道具 举报

avatar 嗅觉Y不缺失 | 2021-9-27 17:14:29 | 显示全部楼层
今天怎么了,什么人都出来了!
回复

使用道具 举报

avatar 小妖花满楼满fx | 2021-9-28 02:07:19 | 显示全部楼层
admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的源码论坛他的站点都是商业源码,还是免费下载的那种!特别好用。访问地址:http://www.mxswl.com 猫先森网络
回复

使用道具 举报

avatar 我是的十八簿 | 2021-10-1 22:13:53 | 显示全部楼层
有品位!
回复

使用道具 举报

avatar 喵呜_520 | 2021-10-3 04:25:04 | 显示全部楼层
admin楼主,您忘记吃药了吧?
回复

使用道具 举报

avatar 白刃玄衣及 | 2021-10-5 06:08:29 | 显示全部楼层
支持一下,下面的保持队形!
回复

使用道具 举报

avatar 姆班他姑店 | 2021-10-14 13:06:52 | 显示全部楼层
admin楼主写的很经典!
回复

使用道具 举报

avatar 柳芽2017 | 2021-10-16 13:41:39 | 显示全部楼层
这个帖子好无聊啊!
回复

使用道具 举报

avatar 玲嘉婕嘉n | 2021-10-16 14:00:40 | 显示全部楼层
admin楼主看起来很有学问!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则