串的模式匹配之KMP演算法

2021-09-29 07:45:54 字數 3856 閱讀 7186

想理一理kmp演算法的學習思路,但是不會製作**表達,暫時就用文字描述,盡量表達清晰吧。

注:看多了各種解說會發現,有些演算法講解和**實現有出入,主要是因為針對的字串儲存方式不一樣,要先明確串的值從0還是1單元開始存放。

給定主串s和子串t,則在s中找到t的過程稱為模式匹配,其中t稱為模式。如果在s中找到等於t的子串,則匹配成功,返回t在s中首次出現的儲存位置,否則匹配失敗,返回-1。

為了運算方便和保持思路清晰,設字串的長度存放在0號單元,串的值從1號單元開始存放,這樣字元序號和儲存位置一致。

基本思想:首先將s[1]和t[1]進行比較,若不同,則s[2]與t[1]比較(主串向後移動一位)…直到s中的s[i]與t[1]相同,再將它們之後的字元相比較,若下乙個也相同,則如此繼續往下比較,當s[i]與t[j]不相同時,則主串s回到本趟開始字元的下乙個字元,即s[i-j+2],子串則返回到t[1],繼續開始下一趟的比較,重複上述的過程。若t中的字元全部比完,則說明此趟匹配完成,起始位置是i-j+1,或者i-t[0],否則匹配失敗。

例如s=「5abcba」, t=「2bc」,則返回2,含義是主串從第2位字元開始匹配出模式串。

此思想十分自然,根據此法,演算法描述為:

int strindex_bf(char

*s,char

*t)//相等則均後移一位進行下一步比較

else

//出現不匹配情況,則回溯,主串回到此趟開始位的下乙個字元,子串回到第乙個字元}if

(j > t[0]

)return

(i - t[0]

);//匹配完成,返回儲存位置

else

return-1

;//否則返回-1

}

此演算法的時間複雜度為(o(n+m) ~ o(n*m),其中n,m分別是兩個串長度。

暴力匹配演算法簡單易理解,但是效率很低,在此基礎上,發展生成了kmp演算法,其中kmp名稱是取自三個發現人的名字首字母。

原始演算法的效率低在於非必要的回溯,在出現不匹配字元時,該字元之前的字元元素排序若出現重複(前字尾相同),則可以利用此性質,捨去重複部分的比較,直接滑到重複後一位比較,此時主串的比較位(指標)不回溯,保持不變,將模式串的比較位(指標)移動到重複部分的後一位,與主串的比較位開始進行下一輪比較。

具體描述:若s[i]與t[j]不相同,若此時模式串t前k-1個字元t[j]之前的k-1個字元相同,則將模式串指標移到t[k],保持主串比較位不變,此時就是從該**第k個字元與s[i]**進行比較,以此類推。

此思想實現的重點就在於這個模式串指標移動到的位置,此時就引出了著名的next[j]陣列,在t[j]上出現不匹配情況時,就將next[j]值對應的第next[j]個字元拿來進行下一輪比較。此處next[j]的獲取後續再說,假設陣列已知,在此基礎上,**實現演算法為:

int

strindex_kmp

(char

*s,char

*t,int pos)

//從主串s第pos個字元開始比較,在暴力匹配中pos=1

else j = next[j]

;//出現不匹配字元,則模式串比較位回溯}if

(j > t[0]

)return

(i - t[0]

);//匹配完模式串元素,則匹配成功,返回儲存位置

else

return-1

;//否則匹配失敗

}

next是關於模式串的指標移動位置的陣列,只與模式串t的模式(字元元素)有關。
是的,在此之前先了解一波()前字尾知識,乙個字串,字首是首字元開始的不包含尾字元的子串,相應字尾為不包含首字元以尾字元結束的子串。

例如:字串s=「abcab」,字首有a,ab,abc,abca,字尾有bcab,cab,ab,b。

接下來就是提出前字尾重疊概念,對於乙個字串,若存在其某字首與某字尾相同,則描述為前字尾重疊,重疊位數出現最多的前字尾,就是最大前字尾重疊,求解next就是發現每乙個子字串的最大前字尾重疊數。

在上訴例子中,字首ab和字尾ab就出現了重疊,且唯一,最大重疊是2。

next求解:

例項說明,例如字串t=「ababaaab」,以陣列形似儲存,t[0] =8, t = ,其中t[0]即為上訴所說表示字串長度,這樣一來,下標和字元所在位數一致。

下標 j 0 1 2 3 4 5 6 7 8

t next[j]

令next[1] = 0, 意味著t[1] = a出現不匹配時,則將t[0]與主串當前位開始下一輪比較(後移一位)

next[2] = 1,意味著t[2] = b 出現不匹配時,則將t[1]與主串當前位開始下一輪比較(後移一位)

j = 3,t[3] = a 出現不匹配時,觀察t的前2個元素組成的字串的最大前字尾重疊,即「ab」的前字尾重疊,此時無重疊,則next[3] = 0+1 = 1;

j = 4,t[j] = b出現不匹配時,觀察t的前3個元素組成的字串的最大前字尾重疊,即「aba」的前字尾重疊,此時重疊前字尾為「a」,則next[4] = 1+1 = 2;

j = 5,t[j] = a出現不匹配時,觀察t的前4個元素組成的字串的最大前字尾重疊,即「abab」的前字尾重疊,此時重疊前字尾為「ab」,則next[5] = 2+1 = 3;

j = 6,t[j] = a出現不匹配時,觀察t的前5個元素組成的字串的最大前字尾重疊,即「ababa」的前字尾重疊,此時重疊前字尾為「aba」,則next[6] = 3+1 = 4;

j = 7,t[j] = a出現不匹配時,觀察t的前6個元素組成的字串的最大前字尾重疊,即「ababaa」的前字尾重疊,此時重疊前字尾為「a」,則next[7] = 1+1 = 2;

j = 8,t[j] = b出現不匹配時,觀察t的前7個元素組成的字串的最大前字尾重疊,即「ababaaa」的前字尾重疊,此時重疊前字尾為「a」,則next[7] = 1+1 = 2;

**實現

void

get_next

(char

*t,int

* next)

//前字尾相同則繼續下一位

else j = next[j];}

}

優化:

在上訴求解過程中,計算前j-1個字元構成的字串的前字尾重疊時,是沒有考慮*t[j]**的,但是想想一下,如果最大相等字首的後乙個字元與現在不匹配的 t[j] 是相等的話,那我們按照前述方法移動,與主串當位置比較的元素也是沒有變化的,即肯定也是不匹配的,則此次移動匹配非必須。

例項說明:

上訴 j = 5 ,t[j] = a出現不匹配時,觀察t的前4個元素組成的字串的最大前字尾重疊,即「abab」的前字尾重疊,此時重疊前字尾為「ab」,則next[5] = 2+1 = 3,即將模式串右移2位,但是此時相同字首「ab」 的後乙個字元a 等於此時的t[j],也就是說我們把模式串右移兩位後,比較位並沒有發生任何改變,肯定依舊不匹配,根據此思想,我們完全可以將t[j] 的值考慮進去,避免這種肯定無效的移動。

**實現:

void

get_next

(char

*t,int

* next)

else j = next[j];}

}

KMP 模式串匹配演算法

這兩天讀了july的kmp,覺得很受益,寫下以作備忘。kmp最重要的就是求出next陣列,而next陣列則是通過不斷比較 str2 k 與 str2 j 來確定下乙個字元對應的 next數值 相等則直接next j k 不相等則令k next k 進行遞推直到出現 str2 k str2 j 相等的...

串 KMP模式匹配演算法

樸素模式匹配演算法就是簡單的二重迴圈,第一重迴圈主串s從1到n,然後第二重迴圈子串t從1到m進行匹配判斷,時間複雜度為o n m 1 m kmp演算法的核心思想是 第一 如果子串前r個字元均不相等,且子串前r個字元與主串某連續的r個字元匹配,但子串第r 1就不匹配了,則主串的匹配下標可以直接向前跳r...

模式串匹配 KMP演算法

kmp是對字首暴力搜尋的改進,基於的想法其實是很樸素的。首先我們來看一下暴力搜尋。char bf char src,char pattern else if pattern temp 0 return src else return null 如果匹配失敗,則將關鍵字向右滑動乙個字元,從頭開始匹配關...