數據結構(三)串---KMP模式匹配算法之獲取next數組

(一)獲取模式串T的next數組值

1.回顧

咱們所知道的KMP算法next數組的做用

next[j]表示當前模式串T的j下標對目標串S的i值失配時,咱們應該使用模式串的下標爲next[j]接着去和目標串失配的i值進行匹配

而KMP算法的next求值函數

咱們能夠知道next除了j=1時,next[1]爲0,其餘狀況都是比較前綴和後綴串的類似度(第三種狀況是當類似度爲0時,next值爲0+1=1)
next數組,是用來評判先後綴的相識度,而next值,則是等於類似度加一

2.思考

雖然咱們知道是比較先後綴的類似度,可是咱們如何肯定先後綴位置來獲取next值。---->pj的next值取決於 前綴p1p2....pk-1 後綴pj-k+1.....pj-1 的類似度,next值是類似度加一
pj的next值取決於 前綴p1p2....pk-1 後綴pj-k+1.....pj-1  的類似度,是類似度加一。
咱們將k-1=m,其中m就是類似度,k就是next數組值-->Max{K} pj的next值取決於 前綴p1p2....pm 後綴pj-m.....pj-1  的類似度,是類似度加一。
那麼咱們如今的任務,就由找k-1變爲找m,找類似度

 

例如:
雖然咱們能夠直接看出abab的類似度是2,
也能夠編寫函數獲取到其類似度,
並且當咱們求下一個next值時,串變爲ababa,這時咱們也能夠看出類似度爲3,使用同一個函數能夠實現獲取到類似度。
可是咱們這個函數大概就是從頭或尾開始索引,進行判斷。
每次咱們獲取到了子串都要交給這個函數從頭至尾去索引獲取類似度,彷佛不划算,咱們是否是應該有更好的方法增長程序的性能?

 

3.下面咱們嘗試獲取下面的T串的全部next值,從中找到關聯

步驟一:由上一篇博文能夠知道前j1,j2前兩個的next是固定值爲0,1

步驟二:獲取j=3時的next,此時子串只有'ab',因此子串的前綴只能選擇'a',後綴只能選擇'b';下面咱們對先後綴進行匹配

next數組,是用來評判先後綴的相識度,而next值,則是等於類似度加一
next[j]表示當前模式串T的j下標對目標串S的i值失配時,咱們應該使用模式串的下標爲next[j]接着去和目標串失配的i值進行匹配

 

注意:匹配完畢後後綴會向下加一

步驟三:獲取j=4時的next值,此時子串爲'aba',子串中前綴是p1..pm,後綴是pm+1..pj-1,如果m取一,此時子串的前綴能夠選擇p1,後綴選擇p2;如果m=2前綴選擇p1p2後綴選擇p2p3;那麼具體如何選擇這個m值呢?

重點:這個m值取決於上次失配時的next[]值,即上次j=3是失配了,全部m=next[3]=1,因此咱們選取的前綴爲p1='a',後綴爲pj-1是'a'

根據匹配處的類似度或者下標J=1均可以得出next[4]=2

步驟四:獲取j=5時的next值,此時子串爲'abab',子串中前綴是p1..pm,後綴是pm+1..pj-1,如果m取一,此時子串的前綴能夠選擇p1,後綴選擇p2;如果m=2前綴選擇p1p2後綴選擇p2p3,若m取3,前綴爲p1p2p3後綴爲p2p3p4;那麼具體如何選擇這個m值呢?

 

重點:如果上次匹配成功。並未失配,那麼咱們的m值在上一次的基礎上加1。因此此次m=2,咱們選取前綴p1p2和後綴p3p4

根據匹配處的類似度或者下標J=2均可以得出next[5]=3

步驟五:獲取j=6時的next值,此時子串爲'ababa',子串中前綴是p1..pm,後綴是pm+1..pj-1,由於前面匹配成功,全部m++,m=3因此前綴爲p1p2p3,後綴爲p3p4p5

 

由於前面匹配成功,全部m++,m=3因此前綴爲p1p2p3,後綴爲p3p4p5

根據匹配處的類似度或者下標J=3均可以得出next[6]=4

步驟六:獲取j=7時的next值,此時子串爲'ababaa',子串中前綴是p1..pm,後綴是pm+1..pj-1,由於前面匹配成功,全部m++,m=4因此前綴爲p1p2p3p4,後綴爲p3p4p5p6

 

根據匹配處的類似度或者下標J=1均可以得出next[7]=2

步驟七:獲取j=8時的next值,此時子串爲'ababaaa',因爲上面失配了,因此m=next[7]=2,因此咱們前綴爲p1p2,後綴爲p6p7

因爲上面失配了,因此m=next[7]=2,匹配前綴p1p2,和後綴p6p7

根據匹配處的類似度或者下標J=1均可以得出next[8]=2

步驟七:獲取j=9時的next值,此時子串爲'ababaaab',因爲上面失配了,因此m=next[8]=2,因此咱們前綴爲p1p2,後綴爲p7p8

 

因爲上面失配了,因此m=next[8]=2,因此咱們前綴爲p1p2,後綴爲p7p8

根據匹配處的類似度或者下標J=2均可以得出next[9]=3
另外一種方案:是直接看匹配位置的j值便可,將j值加一便可,這個是實現程序的時候的使用思路
注意:有可能模式串只有一個字符進行匹配,那麼咱們以前說的next[2]=1也須要咱們去匹配一遍,而不是直接獲取結果

重點補充:爲何咱們能夠使用下標值j來表示類似度?

咱們以上圖爲例:
類似度是指前綴串和後綴串之間的類似程度,經過看圖,咱們不難發現類似度和最後匹配的下標使同樣的。
這是由於前綴始終是如下標爲一的字符開始匹配,因此匹配到下標爲多少,那他的類似度就是多少

4.代碼實現

//經過計算返回子串T的next數組
void get_next(String T, int* next)
{
    int m, j;
    j = 1;    //j是後綴的末尾下標      pj-m...pj-1  其實j-1就是後綴的下標,而j就是咱們要求的next數組下標
    m = 0;    //m表明的是前綴結束時的下標,也就是類似度,是等價的  p1p2...pm
    next[1] = 0;
    while (j < T[0])  //T[0]是表示串T的長度
    {
     //這個if,咱們只須要考慮,若是我<後綴最後下標>前面匹配成功,如今我T[j]==T[m]也匹配成功,那麼我對應的next<++j>數組值是多少?
if (m == 0 || T[m] == T[j]) //T[m]表示前綴的最末尾字符,T[j]是後綴的最末尾字符 { ++m; ++j; next[j] = m;  //++j後獲取的纔是咱們要的next[j]下標,咱們要獲取next[j]處的值,就是獲取他前一個匹配時的類似度,也就是前一個匹配的下標+1 } else  //else是匹配失敗的狀況,就要進行回溯 m = next[m]; //如果字符不相同,則m回溯 } }

5.測試結果

int main()
{
    int i;
    String s1;
    int next[MAXSIZE] = { 0 };
    char *str = (char*)malloc(sizeof(char) * 40);
    memset(str, 0, 40);
    printf("enter s1:");
    scanf("%s", str);
    if (!StrAssign(s1, str))
        printf("1.string length is gt %d\n", MAXSIZE);
    else
        printf("1.string StrAssign success\n");

    get_next(s1, next);

    for (i = 1; i <= StringLength(s1); i++)
        printf("%d ", next[i]);
    system("pause");
    return 0;
}

相關文章
相關標籤/搜索