2020年最全最简单KMP算法讲解
目錄
1.KMP算法的來源
2.最大公共前后綴
3.KMP算法原理
4.next數組
5.next數組值的確定
6.KMP算法的缺陷
7.KMP算法的改進
1.KMP算法的來源
其實博主剛看KMP算法的時候的反應是這樣的:
我們引用一個問題
給定一個主串S及一個模式串P,判斷模式串是否為主串的子串;若是,返回匹配的第一個元素的位置(序號從1開始),否則返回0;如S=“abcd”,P=“bcd”,則返回2;S=“abcd”,P=“acb”,返回0
這道題我們最先想到的算法設計思路肯定和下面動圖差不多
我們想到這個思路后,直接兩層for循環輕松解決,雖然這種樸素算法思路簡單而且容易想到,但兩個串都有依次遍歷,時間復雜度為O(n*m),效率不高,那么為了讓效率變高,我們的KMP算法橫空出世
2.最大真公共前后綴
在正式介紹KMP算法之前我們了解一個概念,一會KMP算法要用到,那就是最大公共前后綴,首先我們介紹前綴后綴
真前綴:就是包含第一個字符但不包含最后一個字符的連續子串
真后綴:就是包含最后一個字符但不包含第一個字符的連續子串
我們舉個例子,列出串abcab的所有真前綴后綴
前綴有:
后綴有:
真公共前后綴:一個字符串前綴和后綴相同的的字符串
最大真公共前后綴:一個字符串前綴和后綴相同的最大長度所代表的字符串
對于abcab來說,公共前后綴只有ab,那么最大公共前后綴為ab
3.KMP算法的原理
樸素算法中,P的第j位失配,默認的把P串后移一位。但在前一輪的比較中,我們已經知道了P的前(j-1)位與S中間對應的某(j-1)個元素已經匹配成功了。就意味著,在一輪的嘗試匹配中,我們get到了主串的部分內容,我們能否利用這些內容,讓P多移幾位(我認為這就是KMP算法最根本的東西),減少遍歷的趟數呢?答案是肯定的。再看下面改進后的動圖就是我們KMP算法的匹配過程:
首先我們根據上面的KMP匹配模式圖的規律與樸素的算法做一個對比:
樸素算法: 每次失配,S串的索引i定位的本次嘗試匹配的第一個字符的后一個。P串的索引j定位到1,T(n)=O(n*m)
KMP算法: 每次失配,S串的索引i不動,P串的索引j定位到某個數。T(n)=O(n+m),時間效率明顯提高
再看一組KMP算法一次移動圖解(相同顏色代表相同的字符),初始位置如下:
下一步:
你會發現它與樸素的算法有兩個區別:
再重新看看這一句話
KMP算法: 每次失配,S串的索引i不動,P串的索引j定位到某個數。T(n)=O(n+m),時間效率明顯提高
那么我們究竟能在前一輪失敗的匹配結果里邊得到什么內容,能讓S串的索引i不動,P串的索引j定位到某個數呢?
這時候就要借助我們的最大真公共前后綴了:在上面的兩張圖中,第一張圖的匹配失敗是在模式串的第八個位置,也就是前七個位置都匹配成功,我們根本就不需要再用樸素的匹配方式把主串的匹配指針往后移動一位把模式串的匹配指針重新指向第一個字符。因為模式串再往右移動,只有我們主串的這七個字符的后綴和模式串這七個字符的前綴(也就是這七個字符的公共前后綴)才能完全重合,其他的都不能完全重合,這就是我們找尋真公共前后綴的意義,真公共前后綴必須是最大的,否則我們往往會錯過最佳結果,可以自己找一個例子試一試,博主就不再深究了,這樣就可以保證指向我們主串的指針不發生回溯,即主串的i指針之后往后移動,不會往前移動
4.next數組
為了表示下一輪比較模式串j定位的地方,我們將其定義為next[j],next[j]就是第j個元素前j-1個元素最大真公共前后綴所代表字符串的長度個數加1(加1的目的是直接匹配最大公共前后綴的下一位)
針對上面的兩張動圖,我們可以看出前j-1最大真公共前后綴為3,加1就是4,那么下一輪匹配就從第4個字符開始,所有對于上面的那個動圖,模式串第8位的next數組值為4即next[8]=4;
也就是說只要我們確定一個模式串所有字符的next數組值,那么就知道了下一次我們模式串匹配所要匹配的初始位置即當發生失配時,i不變,j=next[j]就好啦!接下來就是怎么確定next值了。
那么我們手寫一個next數組值,如下面動圖(首尾重合個數就是我們的最大真公共前后綴):
我們規定next[1]=0
也就是說:
5.next數組值的確定
雖然我們可以看出來某一個字符串的最大真公共前后綴,但是程序是不能看出來的,要按照某些規律進行推理,那么按照何種規律呢?
首先我們可以從前面的圖中推理出next[j+1]的最大值為next[j]+1
為啥呢,我們舉個例子:
1.下圖中我們已知next[16]=8,我們來推理next[17]的值;
2.如果P8=P16,則明顯next[17]=8+1=9(也就是我們所說的next[j+1]的最大值為next[j]+1),可能會有疑問了,如果 P8!=P16該咋推導呢,不急我們一步一步來,如果next[8]=4,即:
3.再加上已知next[16]=8,那么我們就可以得出如圖:
4.那么我們證明這四部分相等得到意義其實就是為了證明如圖
5.現在在判斷,如果P16=P4則next[17]=4+1=5,否則,在繼續遞推,若next[4]=2,那么有下圖關系:
6.若P16=P2,則next[17]=2+1=3;否則繼續取next[2]=1、next[1]=0;遇到0時還沒出結果,則遞推結束,此時next[17]=1
也就是說我們求j的next[j]的值,其實就是前j-1個字符的next的數組值一步一步按照上述方法地推得到
7.求一個字符串的next數組值得代碼實現(仔細品完上圖再看)
void get_next(char T[],int next[]) {int i=1;int j=0;next[1]=0;while(i<T[0])//T[0]為字符串長度{if(j==0||T[i]==T[j])//T[i]表示后綴的單個字符,T[j]表示前綴的單個字符 {i++;j++;next[i]=j; }elsej=next[j];//若字符不相同那么j值回溯 } }6.KMP算法的缺陷
KMP算法的改進(臥槽還這么nb的算法還有缺陷?我tm:"…"(虎狼之詞))
比如我們的主串是S="aaaabcde",模式串是P="aaaaax"那么模式串的next數組值我們可以很快寫出(自己寫一遍)是012345,我們的KMP算法是這么匹配的:
我們觀眾老爺人均月收入十萬肯定隨便都看得出來,博主看了好一會
我們可以看出第一步失配的時候是在第五個位置,失配的字符為a,那么此時第二步KMP算法是跳到第五個字符的next數組值(next[5]=4)里進行匹配,但是模式串第四個字符還是a,上一個a都已經匹配失敗了,這一個a肯定也是失敗,換句話來說,這個匹配就是多余的,我們看之后KMP的好幾步都是a與主串第一次失配的b進行匹配,所以這幾步都是多余的,這就是KMP算法的缺陷
7.KMP算法的改進
其實挺簡單,就是加一步當前模式串失配的字符和next數組對應位置的字符進行比較一次,如果相同那么直接再進行一次j=next[j]不相同就再次進行模式串和主串的匹配
我們把改進后的數組名稱為nextval,我們簡單推導一下nextval的數組各個值得確定過程
比如模式串為T="abab"
| 第一個字符a | next[1]=0 | 默認的第一個字符的nextval的字符為0 | nextval[1]=0 |
| 第二個字符b | next[2]=1 | 模式串失配時的字符為b,next表示的下一個匹配字符為a,不相同所以和next[2]值相同 | nextval[2]=1 |
| 第三個字符a | next[3]=1 | 模式串失配時的字符為a,next表示的下一個匹配字符也是a,相同所以不需要再次比較,所以就需要地推一下發現當nextval[3]=nextval[1]=0的時候,才需要進行下次匹配,等于0就是主串的匹配指針右移動一位 | nextva[3]=0 |
| 第四個字符b | next[4]=2 | 由于模式串失配位置為b,next數組指向的下一個匹配字符也是b,所以需要我們進一步遞推,當netival[4]=netival[2]=1的時候,此時下一輪匹配的字符是a不是b所以可以進行下一輪的匹配 | nextval[4]=1 |
其實和next數組差不多,nextval數組索引大的值可以被nextval數組索引小的值推導出來
代碼實現:
void get_nextval(char T[],int nextval[]) {int i=1;int j=0;nextval[1]=0;while(i<T[0])//T[0]為字符串長度{if(j==0||T[i]==T[j])//T[i]表示后綴的單個字符,T[j]表示前綴的單個字符 {i++;j++;if(T[i]!=T[j])nextval[i]=j;elsenextval[i]=nextval[j]; }elsej=nextval[j];//若字符不相同那么j值回溯 } }參考博客:https://blog.csdn.net/qq_37969433/article/details/82947411
總結
以上是生活随笔為你收集整理的2020年最全最简单KMP算法讲解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: go环境搭建
- 下一篇: 详解JVM内存结构(基于JDK8)