日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

KMP的自我理解

發布時間:2024/4/17 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 KMP的自我理解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

KMP算法的核心思想為,當文本串與模式串在某一位置發生失配時,利用已經匹配部分的信息,讓模式串迅速向后移動,以完成快速匹配。

很重要的一點

模式串快速后移多少個單位,或者說失配后,文本串應該繼續和模式串中的哪個字符繼續比對,也就是常說的next[j]的值,這個值是只與模式串有關,而與文本串是無關的。

為什么只與模式串有關

注意:下面討論時,P[0,j)為左閉右開區間,包括下標為0不包括下標為j的字符,而P[0,j]則表示閉區間。

設文本串T和模式串S,假設在T[i]與P[j]處發生失配時,此時,我們已經掌握了文本串T[i-j,j)這部分子串的全部信息,KMP算法就是要利用這部分已知信息,快速確定T[i]應該繼續和模式串中的哪個字符進行比對。

值得慶幸的是,這部分信息和P[0,j)是完全一致的。因為在比對T[i]與P[j]時,說明T[i-j,i)和P[0,j)已經是匹配成功的,才有繼續往后比對的必要。

因此,我們可以根據模式串,提前計算出P[j]與文本串某個字符發生失配時,繼續使用模式串哪個位置的字符與文本串進行比對,這個值就是next[j]。因此,KMP算法的核心就是提前計算出在模式串某位置j發生失配時,應該跳到哪個位置繼續比對,這些位置組合起來,就是next數組。

注意:next[j]的值表示,當模式串P{j]與文本串T[i]發生失配時,使用P[next[j]]代替P[j]繼續和T[i]進行下一次比對。

next數組頭腦風暴
  • T與P進行匹配,T[i]與P[j]相等時,和蠻力算法一致,二者一起后移,繼續比對T[i+1]和P[j]。
  • 當T[i]與P[j]發生失配時,則利用next數組獲得下一個應該比對的位置,繼續和T[i]進行比對即可。

    T[i]與P[j]失配時,拿到next[j],讓P[next[j]]繼續和T[i]進行比對,當然如果還是失配,那么繼續和P[next[next[j]]]進行比對,一直這樣迭代下去

    按上述思路一直迭代下去,最終必然會出現兩種情況:

    2.1 j一直往前迭代,找到了某個位置j',使得P[j']=T[i],那么此時可以確定T[i-j', i]和P[0,j']已經匹配,二者一起后移,繼續比對T[i+1]和P[j'+1],這又是新一輪的比對

    2.2 j一直往前迭代,都沒有找到和T[i]一致的字符,此時,j'一定會越界(程序中一般設置為-1),這種情況,說明找不到和T[i]對應的字符,應該跳過T[i],讓T[i+1]和P[0]開始新一輪的比對

    上述第二種情況,和T[i]與P[0]比對后不一致是類似的,此時,首字母不匹配,直接拋棄T[i],讓T[i+1]和P[0]開始新一輪的比對

  • 利用假想哨兵統一上述兩種情況

    可以假想在模式串的下標為-1處有一個通配哨兵,這個哨兵與任何字符都是匹配的。當T[i]與P[0]失配時,繼續往前,讓T[i]與P[-1]進行比對,此時,必然比對成功,按照2.1的思路,即找到j'=-1,這樣,二者一起后移,繼續比對T[i+1]和P[j'+1],即T[i+1]和P[0],這樣,便可以將2。2統一到和2.1一致的思路中

    注意:利用哨兵時,next[0]必須為-1,這樣,在越界時,使用2.1中的思路,找到的j'是-1,,下輪比對才會恰巧是T[i+1]和P[0],因此,next[0]=-1也是next數組構造的初始條件

    已知next數組情況下,進行KMP模式匹配的代碼
    /*KMP匹配@param: T 文本串@param: P 模式串@return: 失敗返回-1,成功則返回模式串在文本串的起始下標 */ int kmpMatch(const char* T, const char* P){int len = strlen(T);int patternLen = strlen(P);//生成next數組 int* next = new int[patternLen];getNext(next, P);int i = 0, j = 0;//i<len,表示還能繼續匹配//j<patternLen,表示還沒匹配成功 while(i < len && j < patternLen){//j<0相當于匹配到通配哨兵//T[i] == P[j]則表示當前比對通過//這兩種情況都為比對通過,文本串和模式串一起后移,繼續比對T[i+1]和T[j'+1] if(j < 0 || T[i] == P[j]){++i;++j;}else{//按照2.2的思路,若找不到合適的j',則一直利用next數組往前迭代,直到找到相等的或者匹配到哨兵(哨兵是通配的) j = next[j];}}delete[] next;//由于串長為patternLen,那么比對成功的起始位置不可能超過Len-patternLen if(i-j > len-patternLen){return -1;}return i-j; }
    next數組的構造

    再次強調,next數組的構造只和模式串有關

    next[j]的值表示P[j]與T[i]失配后,使用P[nex[j]
    繼續和T[i]進行新一輪的比對

    由于next數組的構造理解起來較為困難,因此先通過一個例子找一下規律:

    設模式串P="aabbccaabbd",在下標為10的字母d處發生失配時,此時我們能掌握前綴P[0,10)信息,分析這個前綴,可以發現它的前綴"aabb"和它的后綴"aabb"是完全一致的,那么我們可以直接讓P[4]替代P[10]繼續下一輪的比對,在這個例子中,next[10]=4

    仔細思考和分析,可以總結出這個規律:在P[j]處發生失配時,分析該位置之前的子串,找到它的最長公共前后綴,那么這部分公共前后綴的信息是可以不用比對的,可以讓前綴的下一個字符替代P[j]和T[i]進行下一輪的比對

    再注意一個微妙的地方:由于字符串的下標從0開始,因此最長公共前后綴的長度恰好就是下一個應該比對的字符的位置,因為長度正好等于最后一個字符位置+1

    例如:前面的例子中,對于模式串P="aabbccaabbd",在P[10]處發生失配時,P[0,10)的最長公共前后綴為"aabb",其長度為4,而前綴"aabb"的最后一個字符b在字符串中的位置為3,那么跳過前綴,下一個比對的位置正好應該是3+1=4,其恰好也為字符串的長度4。

    但是我們不可能對于模式串的每個位置,都直接去計算最長公前后綴,而是利用模式串自相似的特性,快速構造next數組

    注意構造next數組的過程和KMP使用next的過程是類似的,構造模式串的過程就像是利用已經求的的前半部分的next數組,讓前綴去匹配后綴,求出最新的next[j],一直這樣往后迭代

    因此,我們構造next數組都是基于已知的next[0,j],求next[j+1]的迭代過程:

    • 當P[j]=P[next[j]時,那么P[0,j]的最長公共前后綴會在原來的基礎上+1,即next[j+1] = next[j] + 1
    • 當P[j]!=P[next[j]]時,設next[j]=j',可以理解為這是一次前綴P[0,j']和后綴P[j-j', j]失配的過程此時P[j-j',j]相當于文本串,那么,按照前面的思路,應該讓j'繼續王錢迭代,知道找到P[j']=P[j],此時按照和上面相同的處理思路即可

    next數組構造的模擬

    設模式串P="aabbccaabbd",len=11,下標為0-10,設變量i為已求出next值的最后一個下標,而j即為前綴P[0,i)的最長公共前后綴長度,也就是在P[i]處失配時替換的位置,即j=next[i]

  • 初始化:i=0,j=-1,next[0]=-1,表示首字母失配時,和哨兵比對

    注意:i為已經求出next值的最后一個位置,并利用next[0,i]的信息求解next[i+1],因此i<len-1. i+1最大為len-1。

  • i=0,j=-1,欲求next[1]:
  • 判斷p[i]==p[j],是,則next[++i]=++j
  • 此時next[1] = 0,i = 1,j = 0
  • i=1,j=0,欲求next[2]:
  • 判斷p[i]==p[j],是,則nezt[++i]=++j
  • 此時next[2] = 1,i = 2,j = 1
  • i=2,j=1,欲求next[3]:
  • 判斷p[2]==p[1],否,失配,則j=next[j]=0
  • 判斷p[2]==p[0],否,失配,則j=next[j]=-1
  • 判斷p[2]==p[-1],是,匹配成功,二者一起后移,next[++i]=next[++j]
  • 此時next[3]=0,i = 3,j= 0
  • 其實1-3是遞歸求解可以匹配的j的過程,其過程和已知next數組進行KMP模式匹配的過程是一致的,只不錯這里前綴充當模式串,后綴充當文本串

  • i=3,j=0,欲求next[4]:
  • 判斷P[3]==P[0],否,遞歸求解j=-1
  • next[++i] = ++j
  • 此時,next[4]=0,i=4,j=0
  • i=4,j=0,欲求next[5]:
  • 判斷P[4]==P[0],否,遞歸求解得j=-1
  • next[++i] = ++j
  • 此時,next[5]=0,i=5,j=0,
  • i=5,j=0,欲求next[6]:
  • 判斷P[5]==P[0],否,遞歸求解得j=-1
  • next[++i]=next[++j]
  • 此時,next[6]=0,i=6,j=0
  • i=6,j=0,欲求next[7]:
  • 判斷p[6]==p[0],是,二者一起后移
  • next[++i]=++j;
  • next[7]=1,i=7,j=1
  • i=7,j=1,欲求next[8]:

  • 判斷P[7]==p[1],是,二者一起后移
  • 則next[++i]=++j
  • 此時,next[8]=2,i=8,j=2
  • i=8,j=2,欲求next[9]:
  • 判斷P[8]==P[2],是,則二者一起后移
  • next[++i]=next[++j]
  • 此時,next[9]=3,i=9,j=3
  • i=9,j=3,欲求next[10]:
  • 判斷P[9]==P[3],是,二者一起后移
  • next[++i]=++j;
  • 此時,next[10]=4,i=10,j=4,next數組求解完畢
  • 其實next數組的迭代求解其實是利用了這樣一個規律:當前僅當P[j]==P[next[j]]時, 這P[0,j+1]的最長公共前后前綴包含了P[0,j]的最長公共前后綴,且長度為P[0,j]的最長公共前后綴+1;若是不同,則說明P[0,j+1]的最長公共前后綴不包括P[0,j]的最長公共前后最,但可能包括P[0,next[j]]的最長公共前后綴,因此要一直往前找,知道找到相等的或者匹配到哨兵為止。

    構造next數組C++版本
    /*構造next數組 */ void getNext(int* next, const char* P){next[0] = -1;int len = strlen(P);int i = 0;int j = -1;while(i < len-1){//匹配到哨兵或者相等 if(j < 0 || P[i]==P[j]){next[++i] = ++j;}else{//遞歸往前搜索 j = next[j];}} }
    KMP算法的優化

    上述的next數組的構造方式其實還是有可優化的空間的,我們來看一個極端的例子:

    設模式串T="aa",則next[0]=-1,i=0,j=-1,判斷P[0]==P[-1],是,那么next[++i]=++j,即next[1]=-1+1=0

    問題就在于這個next[++i]=++j,next[1]=-1+1=0

  • 首先,next[1]等于0表示,當P[1]與文本失配時,用P[0]替代P[1]繼續和文本串進行比對,但毫無疑問,這次比對必然會失配,因為P[0]和P[1]是相等的,這樣我們做了一次多余的比對后,讓程序繼續j=next[0]=-1;

  • 由于我們在構造next數組時,求的最長公共前后綴的長度后,便直接將這個值作為下一個比對的位置(next[++i]=++j,這里的++j其實就是最長公共前后綴的長度),卻不管這個位置上的字符是否已經和當前確定失配的字符是一樣的,因此,才會多出來這些多余的比較

  • 那前面的T="aabbccaabbd"來說,其原有的next數組為[-1,0,1,0,0,0,0,1,2,3,4],假設我們在第9個字符b處發生失配,求的P[0,8]的最長公共前后綴為3,我們便直接另next[9]=3,卻不管P[3]是否和P[9]相等,而在這個例子中P[3]恰好等于P[9],P[9]失配則毫無疑問P[3]一定會失配,程序繼續使用next[3]的值代替P[3]繼續往前搜索。

    但是,我們如果在構造next的過程中,提前判斷P[next[j]]是否等于P[j],若相等,則跳過next[j]位置的比對,直接使用next[next[j]]處的值替代next[j],這樣,便可以避免此次比對

    如上述例子:j=9,原next[9]=3,由于P[3]==P[9],直接跳過P[3],使用next[3]代替3,這樣便可以跳過很多不必要的比對

    next數組構造的優化
    /*構造next數組 */ void getFastNext(int* next, const char* P){next[0] = -1;int len = strlen(P);int i = 0;int j = -1;while(i < len-1){if(j < 0 || P[i]==P[j]){++i; ++j;if(P[i]!=P[j]){//若和當前已經失配的字符不相等,直接使用最長公共自前綴賦值 next[i] = j;}else{//若和當前已經失配的字符相等,則跳過本次比對,直接往前 next[i] = next[j];}}else{j = next[j];}} }

    轉載于:https://www.cnblogs.com/tommychok/p/9029082.html

    總結

    以上是生活随笔為你收集整理的KMP的自我理解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。