• 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. }

     

  • 相关阅读:
    旧书收藏线上商城
    PowerShell系列(十二):PowerShell Cmdlet高级参数介绍(二)
    win10 安装 Langchain-Chatchat 避坑指南(2023年9月18日v0.2.4版本,包含全部下载内容!)
    〈西游记〉中所有插曲、主题曲
    Exception-Error
    老油条表示真干不过,部门新来的00后测试员已把我卷崩溃,想离职了...
    SQL Server如何建表
    windows10环境下配置Apache-Tomcat并测试联通
    Git版本控制
    【UE】纯蓝图实现:在游戏运行时设置关键点,然后让actor沿着关键点移动
  • 原文地址:https://blog.csdn.net/weixin_43164548/article/details/125860803