Bingo, Computer Graphics & Game Developer
本文是对 数据结构–C语言版 书中代码的详解与个人理解
上一节我只讲述了一下代码层面的理解,如果有算法背景不了解的可以去开篇的两篇教程中寻找答案。这里只阐述自己对于算法中较为难以理解部分的想法
。
为了能清晰的讲述KMP总共大概花费了一礼拜的时间; 这里我将书上的failure数组称为next数组以节省篇幅; s代表代求解字符串,p代表模式字符串
这一段代码我认为是我个人最难以理解的。
i = failure[i];
首先这里的背景是,在求next数组某一循环时发现p[j]!=p[i+1],且i>=0。换言之p0,p1, … pi != pj-1, pj-i+1, …pj,因此我希望能够在p0,p1, … pi中找到一个位置x(x < p),使得p0,p1,…px = pj-x, pj-x+1,…pj。
简而言之,就是希望能够找到一条稍微短一些的公共缀。可以参考
v_JULY_v
教程里的图
而算法的答案是:你可以试试x = next[i]
,说不定可行。注意,这里x = next[i]
不是一次就可以找到,因此也就是while存在的价值。
难点依然存在,为什么x = next[i]
就可以快速的找到这个x呢?这也是我这么多天一直困扰的地方。
while(pat[j] != pat[i+1] && i>0)
i = failure[i];
总结了一下,其实难点在于几个指针的意义不明确。这里j很好理解,指向当前需要求解的位置,i才是重点。
注意,这里的指针概念全部都引用自
KMP心得-1
里的代码实现
这里我用一句话概括求解next数组的中心思想:让模式串p与自身匹配
知晓了此中心思想,理解算法就游刃有余了。
假定现在已知模式串为,我将结果和序号一一标示在下边
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
A | B | A | C | A | B | A | B |
-1 | -1 | 0 | -1 | 0 | 1 | 2 | ? |
此时i=2(i=next[j-1]),为求解B的next值,根据算法比对前缀ABAC
与后缀ABAB
(pat[j]!=pat[i],B!=C
),不匹配,这个时候就是关键点了。
直接从字面上寻找,当然很快就能找到s[1] = s[7]的事实,也就是next[7] = 1。
如果这个过程改为p于自身匹配,这个时候就很好理解了。
A B A C A B A B
- - - - A B A C A B A B
模拟pmatch的过程,现前循环是这样的。发现B!=C,此时不是从头开始再次匹配,而是回溯到某一个位置重新比对
,也就是next[i](next[2]=0),这就是为什么i可以通过赋值next[i]来迭代寻找的核心原因!
A B A C A B A B
- - - - - - A B A C A B A B
此时s[7]==p[1],next[7]=1!因为谁也不能保证p[next[i]]=p[j],因此这里用while循环。其实和pmatch里的while是一个道理,我并不清楚回溯之后string[i]和pat[j]
是否相等,所以while里j的值一直迭代。
总结一下,其实求解next数组受益于求解前后缀,其实质就是在匹配自身字符串,大致的核心思想都是相同的。