• KMP算法


    场景:在字符串去寻找一个特定的子串

    28. 实现 strStr() - 力扣(LeetCode)

    暴力匹配(BF)方法

    暴力匹配的方法不难想到,也很容易实现。本质上说就是双指针的方式,逐个字符进行比较。

     

    KMP算法

    上述暴力的方法给出一旦某个字符匹配失败,子串的索引会回到一开始,重头再来。这样没有利用到以前的遍历过程中的信息。

    KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的(避免重复的比较),是一种典型的空间换时间的方法。

    用KMP算法的过程如上图, 可以看到,他利用了之前比较的信息,当子串最后一个字符不匹配的时候,首先主串的指针不需要回到下标为1的位置,而是原来的位置保持不变。子串现在寻找新的开始位置。

    这样就可以减少比较次数。

    那么如何去记录应该从哪一个字母开始呢?

    KMP算法的核心next数组

    next数组的作用:找到子串回退的下标位置。

    用刚才的例子对应的子串为

    当前位置 i 的字符回退下标是:寻找以0开始的子串 i-1 结束的子串相等最大长度,就为当前位置的回退下标。具体过程如下图所示:

    现在我们就知道上面的例子为什么会回退到字符 c 这里,也就是子字符串下标为2这里。

    举个复杂一点的例子:子字符串为 abcabcabcabcdabcde

    如何从前一个字符的回退下标 推导出 当前字符的回退值?

    如图所示:

    上图给出的是当 subStr[k] == subStr[i-1] 的时候,如果不相等即 subStr[k] != subStr[i-1]时

    next数组隐含的特性:如果next[i+1] > next[i], 那么next[i+1] == next[i] + 1; 不存在超越大于1的情况,如果有,那一定是错的。

    代码实现:

    1. int KMP(const string& str,const string& subStr)
    2. {
    3. if (subStr.empty())
    4. return -1;
    5. if (str.size() < subStr.size())
    6. return -1;
    7. vector<int> next(subStr.size());//next数组,存储回退值
    8. next[0] = -1;
    9. for (int i = 1; i < subStr.size(); i++)
    10. {
    11. int pre = next[i - 1];//当前元素的回退值在0-(i-1)区间找
    12. while (pre != -1 && subStr[i - 1] != subStr[pre])//根据每个字符的回退值,先找到相等的字母
    13. pre = next[pre];
    14. if (pre == -1)//没找到,则回退值为0,代表需要重头开始
    15. next[i] = 0;
    16. else
    17. {
    18. next[i] = pre + 1;//如果找到了,当前的回退下标就为找到位置的回退下标+1
    19. }
    20. }
    21. //开始匹配
    22. int point_str = 0;//主串的指针
    23. int point_sub = 0;//子串的指针
    24. while (point_str < str.size() && point_sub < subStr.size())
    25. {
    26. if (point_sub == 0 && str[point_str] != subStr[point_sub])
    27. point_str++;
    28. else
    29. {
    30. if (str[point_str] == subStr[point_sub])
    31. {
    32. point_str++;
    33. point_sub++;
    34. }
    35. else
    36. point_sub = next[point_sub];
    37. }
    38. }
    39. if (point_sub == subStr.size())//子串的指针走到最后,说明子串找到了
    40. return point_str - point_sub;
    41. return -1;
    42. }

    next数组的优化

    举个例子

     具体比较过程

    subStr[ next[i] ] = subStr[i]时候,会存在很多重复性的比较。所以为了避免重复性比较,引入nextval数组。

    nextval数组就是如果出现上述情况,让回退的值一次到位,即当subStr[ next[i] ] == subStr[i]时候,nextval[i] = nextval[ next[i] ]; 否则还是和之前相同,nextval[i] = next[i];

    具体实现 

    1. int KMP(const string& str,const string& subStr)
    2. {
    3. if (subStr.empty())
    4. return -1;
    5. if (str.size() < subStr.size())
    6. return -1;
    7. vector<int> next(subStr.size());//next数组,存储回退值
    8. next[0] = -1;
    9. for (int i = 1; i < subStr.size(); i++)
    10. {
    11. int pre = next[i - 1];//当前元素的回退值在0-(i-1)区间找
    12. while (pre != -1 && subStr[i - 1] != subStr[pre])//根据每个字符的回退值,先找到相等的字母
    13. pre = next[pre];
    14. if (pre == -1)//没找到,则回退值为0,代表需要重头开始
    15. next[i] = 0;
    16. else
    17. {
    18. next[i] = pre + 1;//如果找到了,当前的回退下标就为找到位置的回退下标+1
    19. }
    20. }
    21. //计算nextval数组
    22. vector<int> nextval(subStr.size());
    23. nextval[0] = -1;
    24. for (int i = 1; i < subStr.size(); i++)
    25. subStr[i] == subStr[next[i]] ? nextval[i] = nextval[next[i]] : nextval[i] = next[i];
    26. //开始匹配
    27. int point_str = 0;
    28. int point_sub = 0;
    29. while (point_str < str.size() && point_sub < subStr.size())
    30. {
    31. if (point_sub == 0 && str[point_str] != subStr[point_sub])
    32. point_str++;
    33. else
    34. {
    35. if (str[point_str] == subStr[point_sub])
    36. {
    37. point_str++;
    38. point_sub++;
    39. }
    40. else
    41. point_sub = nextval[point_sub] >= 0 ? nextval[point_sub] : 0;//如果为-1表示在第一个字符这
    42. }
    43. }
    44. if (point_sub == subStr.size())
    45. return point_str - point_sub;
    46. return -1;
    47. }

     

  • 相关阅读:
    【Kotlin精简】第4章 函数
    后端返回 date 时间日期格式为 UTC 格式字符串,形如 2022-08-11T10:50:31.050+00:00前端如何修改为yyyy-mm-dd
    气象数据数据处理实例——matlab字符串切割匹配与R语言日期匹配(数据拼接)
    关于OWL-carousel插件在ajax调用后需要重新实例化问题(页面无轮播效果)
    计算机中整数的表示和整数运算
    【VictoriaMetrics的vmbackupmanager】这个一年卖 2 万美元的功能,我做出来了
    Java -- 每日一问:Java并发包提供了哪些并发工具类?
    【Flutter 面试题】main()和runApp()函数在Flutter的作用分别是什么?有什么关系吗?
    算法学习笔记(7.4)-贪心算法(区间调度问题)
    Springboot毕设项目公益众筹管理系统h7sur(java+VUE+Mybatis+Maven+Mysql)
  • 原文地址:https://blog.csdn.net/weixin_43164548/article/details/125860803