kmp模式匹配演算法
我們的前輩d.e.knuth、j.h.morris和v.r.pratt發表了乙個模式匹配演算法,可以大大避免重複遍歷的情況,我們把它稱為克努特--莫里斯----普拉特演算法,簡稱kmp演算法。我們不直接看演算法,我們先通過例項來研究它的原理,懂了原理,算法學起來就easy了。
假設主串s=」abcdefgab「,子串t=」abcdex「。如果是按樸素的模式匹配演算法,需要經過以下步驟:
我們通過①可以知道:1.子串t的首位字元a與其它字元互不相等.2.子串t的前5位字元,與主串前五位字元相等.由已知條件,我們可以知道子串t的首字元a一定與主串2,3,4,5位置的字元不相等。因此,②③④⑤判斷是可以省略掉的。這樣,是不是效率提高了很多。(注意,字串首字元a與子串其中字元不相等,是kmp的前提。)有朋友會問:」如果子串首字元a有子串中其他字元有相同,那麼還可以這麼高效麼?」我們一起來檢驗一下:
s=「abcabcabc」,t=「abcabcbx」。分析一下處理經過:
因為子串首字元a與子串第二個字元b、第三個字元c不相等。又因為字元b、c與主串的第二位,第三位字元相等。那麼可以省略掉②③步,又因為子串第乙個字元a、第二個字元b,分別與子串第五個字元a、第六個字元b相等,又因為字元a、字元b分別與主串第五位、第六位字元相等,所以④⑤可以省略。這樣開來,即使子串出現了與首字元相等的字元,也是可以省略掉一部分不必要的判斷步驟的。對比這兩個例子,我們發現,①中我的i值也就是主串的下標為6,②③④⑤分別為2,3,4,5。通過觀察,我們②③④⑤是可以省略的,也就是說i值不需要回溯(樸素的模式匹配演算法,是通過i判斷字元是否相等,i值回溯)。kmp的原理就是控制i 的值,使其不能變小,從而減除了很多不必要的重複判斷。既然i值不回溯了,那麼就讓j回溯。通過觀察,我們發現j值得變化與主串沒有關係。我們屢屢提到子串的首字元與自身後面的字元比較,我們發現,當首字元與自身後面的字元不同時,j值的變化就會不同。j值的變化取決字串t的結構中是否有重複的問題。
例如上面t=「abcdex」,當中沒有重複的,所以j值就由6變為1。t=「abcabx」,由於字首ab與字尾ab是相等的,所以j由6變為了3。因此,我們得出結論:」j值得多少,取決於當前字元之前的串的前字尾的相似度」。我們把t串 各個位置j值得變化,定義為乙個陣列next。那麼next的長度就是t串的長度。我們可以得出下面的函式定義:
next陣列推導:
當j=1時,next[1]=0;
當j=2時,t串2位置之前只有a,屬於其他情況。next[2]=1;
當j=3時,t串3位置之前有a、b,不相等,屬於其他情況。next[3]=1;
當j=4時,t串的4位置之前有a、b、c,不相等,屬於其他情況。next[4]=1;
當j=5時,t串的5位置之前有a、b、c、d,不相等,屬於其他情況。next[5]=1;
當j=6時,t串的6位置之前有a、b、c、d、e,不相等,屬於其他情況。next[6]=1;
所以子串t各個位置j值的變化陣列為next[6]=011111
當j=1時,t串的一位置之前沒有字元,所以next[1]=0;
當j=2時,t串2位置之前只有乙個字元a,屬於其他情況.next[2]=1;
當j=3時,t串3位置之前有字元a、b,a b不相等,所以屬於其他情況.next[3]=1;
當j=4時,t串4位置之前有abc,互不相等,所以屬於其他情況next[4]=1;
當j=5時,t串5位置之前有abca,其中位置1與位置4相等。根據』p1…pk-1』=pj-k+1…..pj-1,p1=p4,得k=2,next[5]=2;
當j=6時, t串6位置之前有abcab,其中位置1、2與位置4、5相等。根據』p1….pk-1=pj-k+1….pj-1,得k=3,next[6]=3;
通過next陣列推導,我們可以發現,當前位置字元之前的串的,如果前字尾相等字元的個數為n,那麼當前位置的j值為n+1.
kmp模式匹配演算法實現
/*這段**是為了,獲得子串各個位置j值的變化。功能:獲取子串各個位置的j值變化陣列
*/void get_next(string t, int *next)
else}}
/*這段**是kmp模式匹配的完整演算法。功能:返回子串t在主串s中pos後,出現的位置。如果沒有,返回0
*/int index_kmp(string s, string t, int
pos)
else
}if (j > t[0
])
return i - t[0
];
else
return0;
}
kmp模式匹配演算法改進
我們發現這個kmp模式匹配演算法還是有缺陷的。例如:主串s=」aaaabcde」,子串=」aaaaax」.next陣列值分別為012345。看圖:
當i=5,j=5,主串b與子串的a不相等。j回溯到4位置。此時同樣是字元a,還是與主串的5位置b不等。j回溯到3位置,此時還是字元a,與主串的5位置b不等。依次類推,直到i改變位置。那麼②③④⑤步驟,都是可以省略的。我們可以使用next[1]來代替相等字元的next[j]。例如,在①中,此時i=5,j=5,字元不相等,j應該回溯到4位置。此時我們判斷4位置的字元是否與4位置相等,直接回溯到4位置對應的next陣列的值3.我們看一下改進的演算法**:
void get_nextval(string t, int *nextval)else}}
資料結構與演算法 字串
判斷乙個串是不是回文串,往往要分開編寫,造成 的拖沓 int longestpalindrome const char s,int n return max void longestpalindrome test 上面的迴圈中,對於回文長度本身的奇偶性,我們進行區別處理。這樣有點拖沓。我們根據乙個簡...
資料結構與演算法 字串
生成n對括號的所有合法排列 描述 給定乙個非負整數n,生成n對括號的所有合法排列。解答 該問題解的個數就是卡特蘭數,但是現在不是求個數,而是要將所有合法的括號排列列印出來。該問題和 程式設計之美 的買票找零問題一樣,通過買票找零問題我們可以知道,針對乙個長度為2n的合法排列,第1到2n個位置都滿足如...
資料結構與演算法 字串
題型1 如何統計字元中有多少個單詞?方法1 使用空格作為分隔。如果測出某乙個字元為非空格,而它前面的單詞是空格,則表示 新的單詞開始了 此時單詞數count累加1.如果當前字元為非空格而其前面的字元也是非空格,則意味著仍然是原來那個單詞的繼續,count不應再累加1.方法2 使用sstream中的i...