next數組兩種求法_演算法

來源:互聯網
上載者:User

一、說明
(1)看到網上同一個字串求 next 數組的值有兩種,一種是 -1 開頭,一種是 0 開頭,雖然有差別,但是以 0 開頭的next數組的每一項都比以 -1 開頭的next數組的對應項大1,所以,具體是以 0 開頭還是以 -1 開頭看需要吧,演算法都是一樣的.KMP 的原始論文 (K,M,P 三個傢伙寫的原文)中是以 0 開頭的,所以下面的寫法是以 0 開頭的.
(2)關於 next 數組的求法,網上能找到很多流行簡潔的寫法,也有很多文章對簡潔代碼講解得非常細緻,然而本文並不是對流行演算法的剖析,而只是記錄一下自己比較喜歡的計算方法,並用代碼實現一下.

二、求法的文字描述

(1)第一種求法:根據前一個字元的next值求字串記作 p;next 數組記作 next;

約定: 下標從 1 開始算,注意,不是從 0 開始算 字串長度 >2

1)第一個字母的 next 值置 0 (next[1] = 0),第二個字母的 next 值置 1(next[2] = 1) ;
2)從第 3 個開始,計算第 i 個位置的 next 值時,檢查

p[i-1]== p[next[i-1]] ?(即這兩個值是否相等)

解釋:第 i 個位置的前一個位置的值(即 p[i-1],記作 m)與以 m 的 next 值(即 next[i-1])為下標的值(即 p[next[i-1]],記作 n)是否相等,(看的懵懵的也沒關係,後面會有例子) 若相等,則 next[i] = next[i-1] + 1

若不等,則繼續往回找,檢查

p[i-1]== p[next[next[i-1]]] ? 若相等,則 next[i] = next[next[i-1]] + 1 若不等,則繼續往回找,直到找到下標為 1 還不等(即字串第一個元素),直接賦值 next[i] = 1

(2)第二種求法:根據最大公用元素長度求
首先附上講解的博文地址,裡面有詳細講解
http://blog.csdn.net/v_july_v/article/details/7041827

1)算出每一個字母首碼尾碼的最大公用元素長度
2)最大公用元素長度整體向後移動一個長度,最前面的元素值填 -1,即為 next 數組的第一版本
3)(如果你需要的 next 數組第一個值為 -1,這步就可以省略了)next 數組的每一個值分別+1,即求得 next 數組。

三、執行個體

字串 P =“ababaaababaa”

求解:
(1)對應上面第一種求法
1)初始化 P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1

2)求下標為 3 的字元的 next 值
P[3-1] = P[2] = ‘b’;
next[3-1] = next[2] = 1 ;
P[next[3-1]] = P[1] = ‘a’;
P[3-1] != P[next[3-1]] ,但是此時已經回溯到了第一個元素,
∴ 直接P[3] = 1 ; P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1 1

3)求下標為 4 的字元的 next 值
P[4-1] = P[3] = ‘a’;
next[4-1] = next[3] = 1 ;
P[next[4-1]] = P[1] = ‘a’;
P[4-1] == P[next[4-1]] ;
∴ next[4] = next[4-1] + 1 = 2 ; P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1 1 2

4)求下標為 5 的字元的 next 值
P[5-1] = P[4] = ‘b’;
next[5-1] = next[4] = 2 ;
P[next[5-1]] = P[2] = ‘b’;
P[5-1] == P[next[5-1]] ;
∴ next[5] = next[5-1] + 1 = 3 ; P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1 1 2 3

5)求下標為 6 的字元的 next 值
推導過程同上 => next[6] = next[6-1] + 1 = 4 ; P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1 1 2 3 4

6)求下標為 7 的字元的 next 值
P[7-1] = P[6] = ‘a’;
next[7-1] = next[6] = 4 ;
P[next[7-1]] = P[4] = ‘b’;
P[7-1] != P[next[7-1]] && 此時還未回到第一個,繼續
next[next[7-1]] = next[4] = 2 ;
P[next[next[7-1]]] = P[2] = ‘b’;番外(1)
P[7-1] != P[next[next[7-1]]] && 但是此時還未回到第一個,繼續
next[next[next[7-1]]] = next[2] = 1 ;
P[next[next[next[7-1]]]] = P[1] = ‘a’ ;
P[7-1] == P[next[next[next[7-1]]]] ;
∴ next[7-1] = next[next[next[7-1]]] + 1 = next[2] + 1 = 2 ; P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1 1 2 3 4 2

7)求下標為 8 的字元的 next 值
P[8-1] = P[7] = ‘a’;
next[8-1] = next[7] = 2 ;
P[next[8-1]] = P[2] = ‘b’;
P[8-1] != P[next[8-1]] ,但是還沒回到第一個元素,繼續
next[next[8-1]] = next[2] = 1 ;
P[next[next[8-1]]] = P[1] = ‘a’;
P[8-1] == P[next[next[8-1]]];
∴ next[8] = next[next[8-1]] + 1 = 2 P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1 1 2 3 4 2 2

8)求下標為 9 的字元的 next 值
推導過程同4) => next[9] = next[9-1] + 1 = 3 ; P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1 1 2 3 4 2 2 3

9)求下標為 10 的字元的 next 值
推導過程同4) => next[10] = next[10-1] + 1 = 4 ; P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1 1 2 3 4 2 2 3 4

10)求下標為 11 的字元的 next 值
推導過程同4) => next[11] = next[11-1] + 1 = 5 ; P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1 1 2 3 4 2 2 3 4 5

11)求下標為 12 的字元的 next 值
推導過程同4) => next[12] = next[12-1] + 1 = 6 ; P a b a b a a a b a b a a 下標 1 2 3 4 5 6 7 8 9 10 11 12 next 0 1 1 2 3 4 2 2 3 4 5 6

(2)對應上面第二種求法
1)算出每一個字母首碼尾碼的最大公用子串長度(下一步會把最後一位移走,所以最後一位可以不算)番外(2) P a b a b a a a b a b a a 前尾碼最大公用子串長度 0 0 1 2 3 1 1 2 3 4 5

2)最大公用子串長度整體向後移動一個長度,最前面的元素值填 -1,即為 next 數組的第一版本 P a b a b a a a b a b a a next 數組第一版 -1 0 0 1 2 3 1 1 2 3 4 5

3)(如果你需要的 next 數組第一個值為 -1,這步就可以省略了)next 數組的每一個值分別+1,即求得 next 數組。 P a b a b a a a b a b a a next 數組第二版 0 1 1 2 3 4 2 2 3 4 5 6

四、代碼實現

(1)對應上面第一種方法

#include <iostream>#include <vector>#include <string.h>using namespace std;class Solution{public:    vector<int> getNext2(string ps){        vector<int> next;        int l = (int)ps.length();        if(l == 0 ){            return next;        }else if(l == 1){            next.push_back(0);            return next;        }else if(l == 2){            next.push_back(0);            next.push_back(1);            return next;        }        char p[20];        strcpy(p, ps.c_str());//字串轉字元數組        next.push_back(0);        next.push_back(1);        for(int i = 2;i<(int)ps.length();i++){            int k = next[i-1];            while(k!=0){                if(p[i-1] == p[k-1]){//k-1是因為,在電腦裡,數組下標是從0開始的                    next.push_back(k+1);                    break;                }else{                    k = next[k-1];                }            }            if(k==0) {                next.push_back(1);            }        }        return next;    }};

(2)對應上面第二種方法

#include <iostream>#include <vector>#include <string.h>using namespace std;class Solution{public:    vector<int> getNext2(string ps){        vector<int> next;        int l = (int)ps.length();        if(l == 0 ){            return next;        }else if(l == 1){            next.push_back(0);            return next;        }        next.push_back(0);//next數組第一個值為0        for(int i = 0; i < l-1; i++ ){//最後一個最大公用子串長度不用算            //計算每一個子字串的首碼尾碼最大公用子串長度            int l = max_pub_substr(ps.substr(0,i+1));            //右移後+1            //for迴圈之前已加了一個資料0進next數組,此時再加進去元素時,元素在next數組裡的下標比得到該元素值的子串最後字元的下標大1,也就相當於向後移一位了            next.push_back(l+1);        }        return next;    }    //計算首碼尾碼的最大公用子串長度    int max_pub_substr(string ps){        int l = (int)ps.length();        if(l == 0 || l == 1){            return 0;        }        char p[20];        strcpy(p, ps.c_str());//字串轉字元數組        int len = 0;        int m = -1;//最後一個字元(不包括)之前與最後一個字元相等的字元下標 m        int k = -1;//已經尋找過的字元下標        while( k != l-1 ){            k = m+1;            for(; k < l-1; k++){                if(p[k] == p[l-1]){                    m = k;                    break;                }            }            if( m==-1 || k==l+1){//表示沒有與最後一個字元相等的字元                return len;            }else{//檢查首碼串和尾碼串是否相等                int i = 0,j = l-1-m;                for(; i <= m,j < l; i++,j++){//i首碼下標,j尾碼下標                    //只要有不相等的就失敗                    if(p[i] != p[j]){                        break;                    }                }                if( i == m+1 ){//說明前後串相等,因為全都比較了,沒有中斷                    len = m+1;                }            }        }        return len;    }};

五、驗證

int main(){    string s = "ababaaababaa";    Solution slt;    vector<int> next = slt.getNext2(s);    vector<int>::iterator it;    for( it = next.begin(); it != next.end(); it++){        cout<<*it;    }    return 0;}

六、番外
(1)在這個地方,我們可以發現,P[2] == P[4] == ‘b’ ,由於P[4] != P[6] ,∴ P[2] != P[6] 是一定的,就可以跳過 P[2] 和 P[6] 的比較,直接比較 P[1] 和 P[6];
(2)首碼尾碼的最大公用元素長度

首碼:簡單來說,也就是,從第一個字母(必包括)開始往後看到最後一 個字母(不包括)為止的字串的以第一個字母開頭的子串
(比如“abab”的首碼有a,ab,aba);

尾碼:簡單來說,也就是,從最後一個字母(必包括)開始往前看到第一個字母(不包括)為止的字串的子串
(比如“abab”的尾碼有b,ab,bab);

最大公用子串長度:也就是首碼和尾碼擁有的相同子串的最大長度
以“abab”為例: 模式串的各個子串 首碼 尾碼 最大公用元素長度 a 空 空 0 ab a b 0 aba a,ab a,ba 1 abab a,ab,aba b,ab,bab 2

一種稍微快一點的小方法:
“abab”前尾碼的公用子串必然是以 a(字串第一個字母) 開頭,b(字串最後一個字母)結尾的子串,“abab”的首碼串中滿足條件的子串集合為A={“ab”},尾碼串中滿足條件的子串集合為B={“ab”},再找出A,B集合中相等的子串集合C,最後算出C中最長子串的長度即為最大公用子串長度。

好啦~~~寫了這麼多,我自己都懶得看T_T

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.