如何判断某字符串包含某字符串?
首先,我们需要了解一下朴素匹配算法,这个算法要求主串循环遍历,在遍历主串的时候,最差的情况是每匹配一个主串的字符,子串都需要匹配s次,因此假设主串为M(m),子串为S(s),那么时间复杂度O(n) = (m-s)*s + s,由此我们可以看到这个时间复杂度并不是很理想。
例如:下表中,主串第1个字符匹配后与之后的3个字符,一共匹配4次,然后第2个字符再进行类似的匹配,依次到结束
由此,Knuth,Morris,Pratt发明了一种更优秀的匹配方式,这种方式就是KMP算法。
KMP算法思想:保持主串M(m)不回溯,只移动S(s)子串,也就是主串匹配到某个字符如果不匹配的情况下,主串不会再回到之前与子串匹配的首个字符从其后一个字符开始,而是直接从当前不匹配的字符开始重新与子串匹配,子串则从next数组中查找,看回缩到哪一位,可能子串并非是从第1位开始的,而此算法最坏的情况下,最多M(m)的每个字符匹配两次,因此,此算法的时间复杂度为O(2m)。
例:下表,建议先了解next后再对比此表,第3位为2次是因为第一次与c匹配不等,然后S(s)回溯将A移动到M(m)的第三位再匹配一次,因此2次。
next数组的意义:在使用KMP算法中,我们会用到next这个数组,用来存储主串中不匹配字串,对应的字串位置会回溯到什么位置。
例1:
例2:(如果在j=5的时候与主串不匹配,那么它下一个S(j),就是从next[5]=2开始,也就是此时主串这个位置与j = 2的字符在进行匹配,如果还是不一样,再从next[2] = 1开始再匹配,也就是也就是此时主串这个位置与j = 1的字符在进行匹配,依次类推)
解析:
(1)next[1] = 0,对照上面公式可知;
(2)next[2] = 1,对照上面公式,1~j-1没有前后缀一致的字符,为其他,所以next[1] = 1;
(3)next[3] = 1,同(2);
(4)next[4] = 1,同(2);
(5)next[5] = 2,在1~j-1中,j=5,由与P1=P4,根据公式P1=P4 = Pj-k+1,由此可得4 = 5 - k +1,推出k = 2;
(6)next[6] = 3,在1~j-1中,j=6,由与P1P2=P4P5,根据公式,推出k = 3。
KMP算法改进:为什么要改进呢?他存在什么缺点?假设S(s) = ‘AAAAAB’,那么它的next如下:
例:可知该表的如果在j=6的时候主串与子串不匹配的话,那么j就会回溯到5,再到4,最后到1,那么这种情况与朴素匹配算法其实相差不大了,因为每个主串字符匹配的次数相当于趋向m*n;
例:改进后加入newnext,如何取newnext存储的值,三元表达式判断
newnext[j] = (S[j] == S[next[j]] ? newnext[next[j]] : Max{k | 0 < k <j, 且’P1…Pk-1’ = ‘Pj-k+1…Pj-1’})
就是S[j] == S[next[j]] ,取newnext[j] = newnext[next[j]] ,如果不等再用公式计算。
(1)next[1] = 0,对照上面公式可知;
(2)next[2] = 0,因为S[2] = S[1] ,因此newnext[2] = next[1] = 0;
(3)next[3] = 0,同(2);
(4)next[4] = 0,同(2);
(5)next[5] = 0,同(2);
(6)next[6] = 5,在1~j-1中,j=6,由与P1P2P3P4=P2P3P4P5,根据公式,推出k = 5。
来源:CSDN
作者:三千炼心
链接:https://blog.csdn.net/weixin_44919928/article/details/103609700