【KMP算法】

简介: KMP算法核心剖析:关于KMP算法,建议先了解 BF算法KMP算法是用来解决字符串匹配问题的高级算法,看完这篇文章,你应该能理解KMP算法。KMP算法和BF算法唯一的区别在于:主串的i 并不会回退,子串的j也不会回退到0位置。KMP算法的核心在于求出子串的next数组所谓的next数组,其实存放的就是子串回退的位置的下标。下面来演示如何求出一个子串的next 数组假设有一个字符串:“ababcabcdab”, 求该字符串的next数组:规定:next[0] = -1 , next[1] = 0下面就来求next[2] ,next[3]…

KMP算法核心剖析:

关于KMP算法,建议先了解 BF算法

KMP算法是用来解决字符串匹配问题的高级算法,看完这篇文章,你应该能理解KMP算法。

KMP算法和BF算法唯一的区别在于:主串的i 并不会回退,子串的j也不会回退到0位置。

KMP算法的核心在于求出子串的next数组


所谓的next数组,其实存放的就是子串回退的位置的下标。


下面来演示如何求出一个子串的next 数组

假设有一个字符串:“ababcabcdab”, 求该字符串的next数组:


规定:next[0] = -1 , next[1] = 0

下面就来求next[2] ,next[3]…

2d4f218877eb4ed880471cbf3c529279.png


如上图:

那么next[2]如何求呢?

来看下面:

规则:

1、规则:找到匹配成功部分的两个相等的真子串(不包含本身),一个以下标0字符开始,另一个以j - 1下标字符结尾。
2、不管什么数据next[0] =-1;next[1]= 0;在这里,我们以下标来开始,而说到的第几个第几个是从1开始;

第二点已经说过,那么第一点是什么意思呢?

看下面:

对于p数组:该数组存有上面举例的字符串: ababcabcdab ,

p[2] = a, 求这个位置的next 值,根据规则1,找到匹配成功部分的两个相等的真子串,一个下标0 字符开始, 另一个是j-1 结尾

意思就是一个串从下标0开始,另一个串从下标1结尾,对于p[2] , p[0] = a, p[2-1] = b,没有两个相等的真子串,即长度为0 ,故next[2] = 0。


看不懂没关系,继续看下面:

求next[3]:,p[0] = a,p[3-1] = a,所以该子串是以a开始,另一个串以a结尾,所以只有a 和 a 这两个串匹配,长度为1 ,故next[3] = 1;

 2eac19730cdf43859315de59c91c1ce4.png

求next[4]:,p[0]= a,p[4-1] = b,该两串以a开始,以b结尾,故有两个串是ab和ab匹配,长度为2,所以next[4] = 2;

0ec1343a153c46179c1173ce88765e56.png

求next[5]:,p[0] = a,p[5-1] = c,该两串一个以a开始,一个以c结尾,找不出相等的两子串,故长度为0,所以next[5] = 0.


求next[6]:,p[0]= a,p[6-1] = a,该两串以a开始,另一个以a结尾,然而串1往后走,走到以a结尾是 aba, 串2往回走,走到以a开始是

abca ,两者不同,故长度只有a 和 a 两个串,长度为1,即next[6] = 1

13b1f46e057a42398c21971b8b50ed8a.png

求next[7]:,p[0] = a,p[7-1] = b,该串以a开始,以b结尾,有这样的长度的,只有串1 为ab,串2 为ab ,如下图:

3558419e27cd4baa8773f885b43c0f85.png

求next[8],p[0] = a,p[8-1] = c ,由于c是新增的,故长度为0,没有相同的两串是以a开始,另一个以c结尾。next[8] = 0


求next[9]: p[0] = a , p[9-1] = d ,由于d也是新增的,没有相同的两串是一个以a开始,一个以d结尾,故长度为0 .

求next[10] ,p[0] = a,p[10-1] = a,一串以a开始,一串以a结尾,故只有这两个串a和a,故长度为1. next[10] = 1.

2c4a40072c1a4d68a45568980b1eb9e2.png

求完next数组后, KMP算法核心就结束了。

有一条规律:next[i]往后走的时候,后一个数如果比前一个数大,那一定是大一位 , 如果比前一个数小,可能小1,2,…

重点是后一个数如果比前一个数大,那一定是大一位

由此我们可以得到另一条结论:

43388dd90ac442bf8512e96cd9b71029.png

先看上图:假设i 和 k 的位置如上图:(k是如果在i的位置与主串不匹配,那么子串回退到的位置:就是k下标的位置。)

我们求next数组的时候,是肉眼来求的,但是当我们写代码的时候,next数组是一个个往后求的,所以这里,就是已知 i ,求 i+1的next数组,

情况1>>如果p[i] == p[k],那么next[i+1] = k+1,(注意:i和k都是下标)

所以next[i+1] = 1+1 = 2。

情况2>> 如果p[i] !=p[k] , 那么k就不是我们要找的, 所以k需要继续回退,k回退到他所在的next[k] 位置,即 k = next[k],next[k] = 0,

所以k继续回退到0位置,此时 p[i] == p[k] ,所以next[i+1] = k+1 = 0+1 = 1.


情况2其实就是继续回退,找到情况1。

上面这两条结论,就是我们要写代码时 要用到的结论,

但是我们正常来求next数组,是用肉眼来求的,不需要这些结论。

总结:

写代码求next数组时用到的两条结论:


情况1>>如果p[i] == p[k],那么next[i+1] = k+1,(注意:i和k都是下标)

所以next[i+1] = 1+1 = 2。


情况2>> 如果p[i] !=p[k] , 那么k就不是我们要找的, 所以k需要继续回退,k回退到他所在的next[k] 位置,即 k = next[k]


下面就可以开始写代码了,

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
void GetNext(const char* sub, int*next,int len_sub)
{
  assert(sub);
  next[0] = -1;
  next[1] = 0;
  int j = 2;//j是下标->下标从0开始
  int k = 0;//k是下标
  while (j < len_sub)//所以这里没有等号
  {
    if (k==-1 || sub[j-1] == sub[k]) // 情况1
    { // k==-1也就是极端情况,一开始匹配就不相等,既然不相等,长度就为0,next对应的长度就为0
      next[j] = k + 1;
      j++;
      k++;
    }
    else //情况2
    {
      k = next[k];
    }
  }
}

先来趁热打铁,求next数组。


cc2a9f838cf3411f9d81b6a29a4956e8.png

看不懂代码先不急,看看上图,

我们求的是j 所对应的next数组的值,现在知道了 j-1 了,故从j-1开始比较,(也因为j 是下标,下标从0开始 )

会求next数组,也就成功了%60

int KMP(const char* str, const char* sub , int pos)
{
  assert(str && sub);
  int len_str = strlen(str);
  int len_sub = strlen(sub);
  if (len_str == 0)
  {
    return -1;//主串长度为0,找不到
  }
  if (len_sub == 0)
  {
    return 0;//子串长度为0,返回主串起始位置
  }
  if (pos<0 || pos>=len_sub)
  {
    return -1;//参数不在主串长度的有效范围内
  }
  int* next = (int*)malloc(sizeof(int) * len_sub);
  GetNext(sub,next,len_sub);
  //求子串的next数组
  int i = pos;//记录主串位置
  int j = 0;//记录子串位置
  while (i < len_str && j< len_sub)
  {
    if (j==-1 || str[i] == sub[j])
    {//j==-1是极端情况,即一开始匹配就不相等,
      i++;
      j++;
    }
    else
    {
      j = next[j];//j回退到next[j]的位置
    }
  }
  if (j >= len_sub)
  {
    return i - j;//子串找完了,找到了
  }
  return -1;//退出循环只有两者情况,不是上面那种,就是找完主串都找不到
}
int main()
{
  printf("%d\n", KMP("abbbcdef", "bbc", 0));
  return 0;
}

这是剩下的%40,情况与BF算法类似,你可以尝试着运行,看看结果正确与否。


相关文章
|
2天前
|
算法 C语言
KMP算法(C语言实现)
KMP算法(C语言实现)
10 0
|
8天前
|
算法 搜索推荐 程序员
第六十三练 字符串匹配 - KMP算法
第六十三练 字符串匹配 - KMP算法
12 2
|
8天前
|
自然语言处理 算法
KMP算法(A + B for you again—HDU - 1867 )
KMP算法(A + B for you again—HDU - 1867 )
|
8天前
|
存储 算法
图解Kmp算法——配图详解(超级详细)
图解Kmp算法——配图详解(超级详细)
|
8天前
|
算法 测试技术 C#
【字典树】【KMP】【C++算法】3045统计前后缀下标对 II
【字典树】【KMP】【C++算法】3045统计前后缀下标对 II
|
8天前
|
算法
KMP算法 与 strstr()函数
KMP算法 与 strstr()函数
|
8天前
|
算法
【算法学习--字符串】(不含KMP算法)
【算法学习--字符串】(不含KMP算法)
|
8天前
|
算法
白话 KMP 算法
白话 KMP 算法
17 2
|
8天前
|
算法 Java 索引
算法基础:KMP算法详细详解
算法基础:KMP算法详细详解
|
8天前
|
算法 测试技术 C++
【KMP】【二分查找】【C++算法】100207. 找出数组中的美丽下标 II
【KMP】【二分查找】【C++算法】100207. 找出数组中的美丽下标 II