KMP算法最通俗易懂的理解(超详细)

发布于:2022-11-06 ⋅ 阅读:(437) ⋅ 点赞:(0)

前提说明:KMP算法是由串来实现的,通常的串写法有两种

一种是结构体写法  一种是数组写法(两种)

//定长顺序存储表示
typedef struct{
	char ch[maxSize+1];	//多出一个‘\0’作为结束标记
	int length;	
}SString;

//变长分配存储表示
typedef struct{
	char *ch;				//指向动态分配存储区首地址的字符指针
	int length;	
}SString;

一.暴力的BF算法     

// 暴力匹配
int Index(SString S, SString T) {
    int t = 1 ;
    int j = 1 ;
    while (i<=S.length&&j<=S.length){
        if(S.ch[i]==T.ch[i]){
            ++i;++j;
        }else{
            i=i-j+2;//推导,后记忆 
            j=1;    //指针后退重新匹配
        }
        if(j>=T.length)
            return i-T.length;
        else return 0;//判断
    }
}

对于暴力算法,如果出现不匹配字符,同时回退 i 和 j 的指针,嵌套 for 循环,时间复杂度 O(S.length*T.length),空间复杂度O(1)。最主要的问题是,如果字符串中重复的字符比较多,该算法就显得很蠢。

比如 txt = "aaacaaab" pat = "aaab":

 

二.KMP算法

KMP 算法的不同之处在于,它会花费空间来记录一些信息,在上述情况中就会显得很聪明 

      

KMP 算法永不回退 txt 的指针 i,不走回头路(不会重复扫描 txt),而是借助 dp 数组中储存的信息把 pat 移到正确的位置继续匹配,时间复杂度只需 O(N),用空间换时间,所以我认为它是一种动态规划算法。

这种思想,在很多算法中有体现,其实我觉得算法的基本思想就那么几种(基本的,就我这种2b也能掌握的),评论区有无大佬解释一下。

要搞定KMP算法,首先我们要理解以下内容:

1.字符串的前缀,后缀,部分匹配值

引用阮一峰的例子

字符串匹配的KMP算法 - 阮一峰的网络日志 (ruanyifeng.com)

 部分匹配值=  共有元素长度

那么此时的你一定会手算KMP了,那就是:移动位数=已匹配的字符数-部分匹配值相同前后缀)

2.算法改进详解

我们知道,已匹配的字符数为(j-1)编号的数组,匹配不到的数组编号为j,同时减的部分匹配值数组编号为(j-1)

所以引入一个数组,即将部分匹配值数组右移一位的数组

移动位数=已匹配的字符数-部分匹配值

引入数组=到前一个串的 部分匹配值

移动位数=(j-1)-引入数组

j=j-移动位数 = 引入数组+1

NEXT数组即为引入数组+1

好,现在知道啥是next数组。但是这玩意怎么求呢?

我们是人,一眼看出相同前后缀。电脑不能看出来。

显然要设计一种算法,来求一个字符串的next数组。

哪么就到了本章最难的:get-next函数 

3.get-next函数

KMP算法之求next数组代码讲解_哔哩哔哩_bilibili 

可以康下这个视频

 

 

void getnext(String t,int next[])
{
   int i=1;//不回溯指针,代表比到哪里了
   int j=0;//回溯指针j==0的
   next[1]=0;//初始化
   while(j<t.length)
   {
      if(j == 0 || t.ch[i] == t.ch[j])
/*
这里分开讲
    j==0的意思是回溯指针j==0的,意味着前缀后缀是不相等的,所以进1,next数组为1
(前无相等)
     t.ch[i] == t.ch[j]意思是字符串相等,即前缀串后缀串相对数+1,可以推出next数组加1
(next[++i] = ++j;下面拆开写的)
*/
      {
         ++i;++j;
         next[i] = j;
      }
      else j = next[j];
/*
如果不相等,而前缀后缀相等的部分可以不比较,因为前面推的kmp算法定理,即下一步比较t.ch[next[j]]与字符是否相对。
*/
   }
}

 

 贴个图醒目一些哈哈

4.kmp算法

int Index_KMP( String s, String T, int next[]){
int i=1,j=1;
while(i<=S. length&&j<=T. length) {
if ( j==0 | |S. ch[i]==T. ch[j]) {
++i; ++j; //继续比较后继字符
}
else
j=next[j]; //模式串向右移动
}
if(j>T. length)
return i-T. length; //匹配成功
else
return 0;
)

这个不加解释,弄得懂getnext函数自然懂

5.小结

关于指针回溯求next的理解
每次求next【i】,可看作前缀与后缀的一次匹配,在该过程中就可以用上之前所求的next,若匹配失败,则像模式串与父串匹配一样,将指针移到next【j-1】上。
求next过程实际上是dp(动态规划),只与前一个状态有关:
若不匹配,一直往前退到0或匹配为止
若匹配,则将之前的结果传递:
因为之前的结果不为0时,前后缀有相等的部分,所以j所指的实际是与当前值相等的前缀,可视为将前缀从前面拖了过来,就不必将指针从前缀开始匹配了,所以之前的结果是可以传递的。


网站公告

今日签到

点亮在社区的每一天
去签到