KMP字符串匹配算法


3.3.6 基于《最大长度表》与基于《next 数组》等价

    我们已经知道,利用next 数组进行匹配失配时,模式串向右移动 j - next [ j ] 位,等价于已匹配字符数 - 失配字符的上一位字符所对应的最大长度值。原因是:

  1. j 从0开始计数,那么当数到失配字符时,j 的数值就是已匹配的字符数;
  2. 由于next 数组是由最大长度值表整体向右移动一位(且初值赋为-1)得到的,那么失配字符的上一位字符所对应的最大长度值,即为当前失配字符的next 值。

    但为何本文不直接利用next 数组进行匹配呢?因为next 数组不好求,而一个字符串的前缀后缀的公共元素的最大长度值很容易求。例如若给定模式串“ababa”,要你快速口算出其next 数组,乍一看,每次求对应字符的next值时,还得把该字符排除之外,然后看该字符之前的字符串中有最大长度为多大的相同前缀后缀,此过程不够直接。而如果让你求其前缀后缀公共元素的最大长度,则很容易直接得出结果:0 0 1 2 3,如下表格所示:

    然后这5个数字 全部整体右移一位,且初值赋为-1,即得到其next 数组:-1 0 0 1 2。


  1. void GetNext(char* p,int next[])
  2. {
  3. int pLen = strlen(p);
  4. next[0] = -1;
  5. int k = -1;
  6. int j = 0;
  7. while (j < pLen - 1)
  8. {
  9. //p[k]表示前缀,p[j]表示后缀
  10. if (k == -1 || p[j] == p[k])
  11. {
  12. ++k;
  13. ++j;
  14. next[j] = k;
  15. }
  16. else
  17. {
  18. k = next[k];
  19. }
  20. }
  21. }


行文至此,咱们全面了解了暴力匹配的思路、KMP算法的原理、流程、流程之间的内在逻辑联系,以及next 数组的简单求解(《最大长度表》整体右移一位,然后初值赋为-1)和代码求解,最后基于《next 数组》的匹配,看似洋洋洒洒,清晰透彻,但以上忽略了一个小问题。

    比如,如果用之前的next 数组方法求模式串“abab”的next 数组,可得其next 数组为-1 0 0 1(0 0 1 2整体右移一位,初值赋为-1),当它跟下图中的文本串去匹配的时候,发现b跟c失配,于是模式串右移j - next[j] = 3 - 1 =2位。

    右移2位后,b又跟c失配。事实上,因为在上一步的匹配中,已经得知p[3] = b,与s[3] = c失配,而右移两位之后,让p[ next[3] ] = p[1] = b 再跟s[3]匹配时,必然失配。问题出在哪呢?

   

    问题出在不该出现p[j] = p[ next[j] ]。为什么呢?理由是:当p[j] != s[i] 时,下次匹配必然是p[ next [j]] 跟s[i]匹配,如果p[j] = p[ next[j] ],必然导致后一步匹配失败(因为p[j]已经跟s[i]失配,然后你还用跟p[j]等同的值p[next[j]]去跟s[i]匹配,很显然,必然失配),所以不能允许p[j] = p[ next[j ]]。如果出现了p[j] = p[ next[j] ]咋办呢?如果出现了,则需要再次递归,即令next[j] = next[ next[j] ]。


优化 求next数组
  1. class KMP {
  2. public static int[] GetNext(string str) {
  3. int length = str.Length;
  4. int[] next = new int[length];
  5. next[0] = -1;
  6. int k = -1;
  7. int j = 0;
  8. while (j < length - 1) {
  9. if (k == -1 || str[j] == str[k]) {
  10. ++j;
  11. ++k;
  12. if (str[j] != str[k]) {
  13. next[j] = k;
  14. } else {
  15. next[j] = next[k];
  16. }
  17. } else {
  18. k = next[k];
  19. }
  20. }
  21. return next;
  22. }
  23. }

KMP搜索
  1. public static int Search(string str, string pat) {//搜索第一个匹配位置
  2. int i = 0, j = 0;
  3. int[] next = KMP.GetNext(str);
  4. while (i < str.Length && j < pat.Length) {
  5. if (j == -1 || str[i] == pat[j]) {
  6. j++;
  7. i++;
  8. } else {
  9. j = next[j];
  10. }
  11. }
  12. if (j == pat.Length) {
  13. return i - j;
  14. } else {
  15. return -1;
  16. }
  17. }

  1. static public IList<int> Search(string s, string p) {//搜索所有匹配位置
  2. List<int> result = new List<int>();
  3. int[] next = KMP.GetNext(s);
  4. int j = 0;
  5. int i = 0;
  6. while (i < s.Length) {
  7. while (i < s.Length && j < p.Length) {
  8. if (j == -1 || s[i] == p[j]) {
  9. j++;i++;
  10. } else {
  11. j = next[j];
  12. }
  13. }
  14. if (j == p.Length) {
  15. result.Add(i - j);
  16. i = (i - j) + p.Length;
  17. j = next[j];
  18. }
  19. }
  20. return result;
  21. }





原文地址:https://www.cnblogs.com/xiejunzhao/p/67bd4e2e5a57435d013aa05b92857bc4.html