在字符串中确定会遇到顶顶有名的KMP
算法,下面让咱们一块儿回顾一下吧~算法
什么是KMP?数组
KMP是由Knuth,Morris和Pratt这三位学者发明的一种算法,因此取了三位学者名字的首字母。主要应用于字符串匹配问题上。假如文本串aabaabaaf
的长度为n,模式串aabaaf
的长度为m,咱们要判断文本串中是否包含该模式串,传统的解决方法是在文本串中遍历模式串,若是遇到不匹配的字符了则又要从文本串下一位再从头遍历。时间复杂度为O(m*n)。而KMP的思想在于:「当出现字符串不匹配时,能够知道一部分以前已经匹配的文本内容,避免从头再去作匹配了。」时间复杂度为O(m+n)。因此KMP算法极大的提升了搜索效率。code
前缀表blog
要想了解KMP算法,首先得知道什么是前缀和后缀以及最长相等先后缀。举个栗子🌰:字符串
对于字符串aabaaf
来讲,它的前缀有a
,aa
,aab
,aaba
,aabaa
即除了最右端字符所组成的集合,同理它的后缀有f
,af
,aaf
,baaf
,abaaf
即除了最左端字符所组成的集合。在模式串aabaaf
的全部字串中get
"a"的前缀和后缀都为空集,最长相等先后缀长度为0; "aa"的前缀为[a],后缀为[a],最长相等先后缀长度为1; "aab"的前缀为[a, aa],后缀为[b, ab],最长相等先后缀长度为0; "aaba"的前缀为[a, aa, aab],后缀为[a, ba, aba],最长相等先后缀长度为1; "aabaa"的前缀为[a, aa, aab, aaba],后缀为[a, aa, baa, abaa],最长相等先后缀长度为2; "aabaaf"的前缀为[a, aa, aab, aaba, aabaa],后缀为[f, af, aaf, baaf, abaaf],最长相等先后缀长度为0;
能够看到这里获得了一个序列,也就是你们所熟悉的next[]数组。即该模式串aabaaf
的next[]数组为[0,1,0,1,2,0]
,这也就是前缀表。string
为何要用到前缀表呢?class
由于前缀表主要是用来回溯的,记录了模式串与文本串不匹配的时候,模式串应该从哪开始匹配。效率
如图所示:
搜索
这里面的回退跳转靠的就是前缀表(next[]数组)。字符串在字符f
发生冲突时,就回退到f
前一位next[]数组中的值所指向的位置处,也就是回退到2
再进行匹配。
可能不少同窗看到的next[]数组是所有统一减一操做或者是右移首位补上-1。其实本质都是同样的,回退到应该回退的位置,只不过修改了判断条件。
模式串aabaaf
的next[]数组三种以下,以及对应的回退方式。
求解next[]数组
主要包括四个步骤:
/推荐这种/
//当前字符串冲突时回退到前一位next数组对应的值 /* i:指向后缀末尾 j:指向前缀末尾,也表明包括i在内的以前的最长相等先后缀的长度 */ func getNext(next []int, s string) { //初始化 j := 0 next[0] = 0 for i := 1; i < len(s); i++ { //先后缀不相同的状况 for j > 0 && s[i] != s[j] { j = next[j-1] //回退 } //先后缀相同的状况 if s[i] == s[j] { j++ } next[i] = j } }
next[]数组所有统一减一操做的代码以下:
//统一减一操做 func getNext(next []int, s string) { //初始化 j := -1 next[0] = -1 for i := 1; i < len(s); i++ { //先后缀不相同的状况 for j >= 0 && s[i] != s[j+1] { j = next[j] } //先后缀相同的状况 if s[i] == s[j+1] { j++ } next[i] = j } }