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