KMP算法理解-参照labuladong博文重演

  KMP算法以前上课的时候听的真是云里雾里,似懂非懂,什么next数组可以从0开始,也有从-1开始的,回头叫我写代码肯定完犊子。最近在看labuladong算法笔记的动态规划系列,恰巧有一篇讲KMP,我反复看了几遍,终于能够当场复现代码了。大佬讲的非常清楚透彻,虽然思路不同以往,但这样来看的确简单了许多。先附上大佬的博文链接,建议阅读原文。下面只是做一个简单的介绍,来加深自己的印象。
  事先声明,pat表示模式串,txt表示待搜索的字串,M = pat.length,N = txt.length
  KMP算法相比暴力查找是以空间换时间的策略。

时间复杂度空间复杂度
KMP算法O(M+N)O(M)
暴力算法O(MN)O(1)

  先来看一下KMP算法匹配的过程,外层循环对于待匹配的字串txt的指针i每次自加一次,模式串pat的指针j则被视为匹配的状态。这里算法把模式串pat的每个字符视为一种状态,状态0视为匹配的开始,状态1就表示匹配了模式串第一个字符,后面依次。当指针j变为M(pat.length),也就是说整个模式串都被匹配了,则可以返回首字母的索引,否则未找到则返回-1。这里关键是这个指针j的状态是怎么转换的,(常规的用的next数组)作者用了一个二维数组dp[M][256]表示。这个dp是个M*256的二维数组,其定义很好理解,M代表多少种状态,256则是字符表的元素个数。dp[j][i] = val代表当前状态是j,遇到了待匹配子串txt中的字符i(ASCII码表每个字符都可以用整数表示)则转化为val状态。结合之前,就是说,遍历待匹配子串txt时候,指针i循环一遍,指针j则根据dp[j][txt[i]]来进行状态转移。(利用dp数组打了个表)
更为直观的状态转移图可以参照作者原文
那么,问题的关键就是得到这个dp数组的值。首先我们明确几点:

  1. dp[j][pat[j]]=j+1
  2. dp[j][!pat[j]]=dp[X][!pat[j]]
  3. X=dp[X][pat[j]]

  第一个表达式 表示,当前状态 j,遇到字符 pat[j] ,状态 +1,也就是说模式串 pat 的第 j+1 个字符匹配(字符串数组下标从0开始),变为下一个状态。
  第二个表达式则对应状态 j遇到的字符 txt[i] != pat[j] ,也就是第 j+1 个字符不匹配,则需要状态回退。这里作者 借用了一个影子状态 X,是在状态 j 之前并且已知的,所以通过影子状态 X 在遇到 字符 txt[i] 时转换的状态 来知道本次回退应该到达的状态位置。
  第三个表达式则是更新影子状态 X,也就是状态 X 遇到字符 pat[j] 转换的状态,对比 正式匹配的代码中:

j = dp[j][txt.charAt(i)];

这里状态 j 的转换是在待匹配字串txt中,找到模式串pat;类比 影子状态 X则是为了在模式串pat中找到 pat[1…end]
  下面看一组打印结果,在代码中输出dp数组的值,以及影子状态X的值。假设模式串 pat = "ababaca"
在这里插入图片描述
  这个影子状态X的值是不是很熟悉,我用一份常规的获取next数组的代码处理这个模式串,看看打印的next数组结果。
在这里插入图片描述
  没错,我们可以把影子状态X视为和状态j拥有最长的相同前缀,则获取dp数组的过程就相当于利用动态规划的思想(利用base case来得到之后的结果)根据next数组的值不断得到当前状态j遇到字符 txt[i] 后下一步应该转移的状态的打表流程。
  我们可以借鉴别人分享的经历,但最终万事要亲历亲为,才能转化为自己的力量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值