BMP算法解析(适合反应没那么快的入门者)

https://www.zhihu.com/question/21923021(洛谷讲师阮行至先生以及“海纳”——《自己动手写python虚拟机》作者,他们的回答是连着的。因为这是一个知乎一个问题的回答,我去他们主页翻了一下,但是没翻到,只能是这种形式了,非常抱歉)

先说明这篇博客是对他们讲解的再加工,主要是我自己看完他们的文章后不理解地方的思考吧,也问了毕业的学长,我自己琢磨了很久,所以希望这个博客能帮到一些和我一样的人,可以少走一点弯路。然后我是学的c++,对阮行至先生的Python代码可能理解不是很到位,第一次发博客问题可能也会比较多。如果有问题,请不吝指教,非常感谢。

——温馨提示:建议先看完两位的回答,如果有不理解的地方,再回来看这篇博客(如果没有自己先有了对BMP算法的理解,那我的这个解析对你来说其实用处不大),可能会有一样的疑惑(本人程度比较差,所以很多内容都比较浅显,请大佬们谅解)

Brute-Force 算法:
5.i<=lenS-lenP(len是字符串的长度,如果大于lenS-lenP了,就不可能和P字符串相等了,所以<=)
9.当S[i+j]==P[j]就是true,i是指在S中循环指到的位置序号,i是一直变的,也就是一直往后面数,j是在P里循环的位置序号,也代表了当在S里循环到i的位置时,加上之前循环的位数,如果和P字符串一样,就是true(如图,看pos3,i指到B了,再加上之前指到的三个A,就是P了,j的循环可以说是一个储存的功能了,而i<=lenS-lenP的限制,就保证了个数会一直是4)——他说是将 S[i : i+len(P)](这里的冒号指位域长度,也就是i的长度是i+lenP) 与 P 作比较。如果一致,则找到了一个匹配。
Brute-Force的改进思路:
要优化一个算法,首先要回答的问题是“我手上有什么信息?”我们手上的信息是否足够、是否有效,决定了我们能把算法优化到何种程度。请记住:尽可能利用残余的信息,是KMP算法的思想所在
Find如果 S[i : i+len(P)] 与 P 的匹配是在第 r 个位置失败的,那么从 S[i] 开始的 (r-1) 个连续字符,一定与 P 的前 (r-1) 个字符一模一样(主串的某一个子串等于模式串的某一个前缀)
利用next数组进行匹配:他的做法是Python的
2.那个for循环等同于for(int i=x;i>0;i--)
3.第三行那个数组,应该是字符串的意思,含左边界不含右边界,也就是取p数组的这一段的意思,这个求nxt数组的方法不够好,是一个n平方级别的,可以去KMP模板题那里找题解看看,应该会有写的比较详细的
7.应该是枚举长度,然后求nxt数组
9.Python中elif表示再次判断的意思,是else if的简写。一个if语句中可以包含多个elif语句,但结尾只能有一个else语句。在Python中,可使用if-elif-else 语句来实现多次条件判断。else和elif语句也可以叫做子句,它们不能独立使用,两者都是出现在if、for、while语句内部的。else子句可以增加一种选择;而elif子句则是需要检查更多条件时会被使用,与if和else一同使用
10.第10行,单纯只是因为字符数组的下标从0开始,所以减一,而不是移动的意思
12.12行,因为失配,并且模式串的标尺已经归零无法移动,所以是待匹配串标尺右移[假设左端是头部/下标小的部分]
15.第15行,因为你在带匹配串中完成匹配的位置是tar,而完成匹配时模式串标尺应该指向末端,pos值为字符串长-1,所以开始位置应该是tar-(pos+1)+1=tar-pos[计算开始位置应该是tar-字符串长+1,pos=字符串长-1,所以字符串长=pos+1]
16.第16行,同第10行
——模式串是P,带匹配串也是主串是S,一般来说主串标尺只会右移+1,模式串标尺在右移时只会+1,在失配时会1根据nxt数组左移
标尺指示的是已经完成匹配的位置,标尺左边是已经完成匹配的,所以主串无论如何都不会走回头路,而在失配时,原本应该直接归零的模式串,因为nxt数组的存在,可以尽可能的减少因为失配造成的损失,所以不会直接清零,而是根据nxt数组移动(如果还是不理解为什么:就是前缀和后缀的匹配原因,在主串待匹配前面的位置,已知匹配的部分模式串,如果失配的话,就找出那部分匹配的模式串中后缀和前缀的最长匹配长度,这样即便移动了,还是有一部分模式串,和主串当前位置的前缀相匹配,就是减少损失了)
快速求next数组:快速构建next数组,是KMP算法的精髓所在,核心思想是“P自己与自己做匹配”。

回顾next数组的完整定义:

  • 定义 “k-前缀” 为一个字符串的前 k 个字符; “k-后缀” 为一个字符串的后 k 个字符。k 必须小于字符串长度。
  • next[x] 定义为: P[0]~P[x] 这一段字符串,使得k-前缀恰等于k-后缀的最大的k.

  这个定义中,不知不觉地就包含了一个匹配——前缀和后缀相等。接下来,我们考虑采用递推的方式求出next数组。

向后拓展就是往数组下标大的方向拓展,匹配成功的话,自然去考虑之后的位从2到3就是文中向后拓展的意思,说得直白点就是向数组下标增大的方向拓展)

缩短这个now把它改成小一点的值,再来试试 P[x] 是否等于 P[now].

串A和串B是相同的!B的后缀等于A的后缀!因此,使得A的k-前缀等于B的k-后缀的最大的k,其实就是串A的最长公共前后缀的长度 —— next[now-1]!

当P[now]与P[x]不相等的时候,我们需要缩小now——把now变成next[now-1],直到P[now]=P[x]为止。P[now]=P[x]时,就可以直接向右扩展了[这里的问题是为什么似乎P和next数组形成了新的S,P?看了别的题解,说是以模式字符串为主字符串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度]——在求前缀数组时p[now]指向前缀完成匹配的下一位,失配的时候就变nxt[now],这个xnt数组里要不要减一取决于你数组开始的下标是0还是1,而且红色部分就是now和x指向位置,前面已经完成匹配了,所以尝试匹配下一位,这里图片的说法是没问题的事实上建议从1开始,这样不容易因为这个-1产生混乱)

对于字符串“abababca”,它的PMT如下表所示:(index是下标,value是nxt数组的值

(这表里面的信息,说的都是模式串的,应该是计算了nxt数组的模式串,上面是下标,下面是nxt数组)

PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。例如,对于”aba”,它的前缀集合为{”a”, ”ab”},后缀 集合为{”ba”, ”a”}。两个集合的交集为{”a”},那么长度最长的元素就是字符串”a”了,长度为1,所以对于”aba”而言,它在PMT表中对应的值就是1。再比如,对于字符串”ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},其中最长的元素为”aba”,长度为3。
对于“abababca”,它的前缀集合为{a,ab,aba,abab,ababa,ababab,abababc},后缀集合为{bababca,ababca,babca,abca,bca,ca,a},两个集合的交集为{a},其中最长的元素为"a",长度为1(比如bababca和abababc,不一样,子串也是要讲顺序的)——往下看,模式字符串从 0 到 j−1 ,在这个例子中就是”ababab”,其前缀集合与后缀集合的交集的最长元素为”abab”, 长度为4。所以就可以断言,主字符串中i指针之前的4位一定与模式字符串的第0位至第4位是相同的,即长度为4的后缀与前缀相同。这样一来,我们就可以将这些字符段的比较省略掉。
(这张图是告诉你字符串匹配时,主串和模式串失配时,为什么模式串能用nxt移动)
我们看到如果是在 j 位 失配,那么影响 j 指针回溯的位置的其实是第 j −1 位的 PMT 值在尝试匹配第7位时,指针其实还在指着第6位,图中的指针表示的是将要比较的位,和我今早还有前面程序的指针意义不同,往右了一位,从a到b的变化,就是应用了nxt数组的变化,也是为什么nxt数组有效的原因,从匹配了6位,到匹配了4位,只减少了2位)[这里的j回溯的-1是因为,j指向了一个未完成匹配的位置,我们一般认为j应该指在已经成功匹配的位置,所以按照j指在已经成功匹配位置,也就是b时,就是用第j位,所以是b影响回溯,已完成匹配的最后一位影响,用决定这个词感觉更贴切]
温馨提示找博客找题解,最好挑码风相近的,自己看得懂的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值