【数据结构-查找】2.字符串(逐步演绎过程,超级详解KMP算法)
串的定義
串(string)是有0~n個字符組成的有限序列,一般記為
S=′a1a2…an′(n≥0)S = 'a_1a_2…a_n'(n≥0) S=′a1?a2?…an′?(n≥0)
S 是字符串的名稱,aia_iai? 可以是字母數字或其他字符。
其中,串中任意連續的字符組成的子序列稱為該串的 子串。
字符在串中的位置 通常是該字符在序列中的序號
子串在主串中的位置 指的是子串第一個字符在主串中的位置
串的模式匹配
子串的定位操作通常稱之為串的模式匹配,它的目的是要獲取子串在主串的位置。
一般的,我們想到的是一種暴力破解的方法
也就是說,從主串 S 的第 pos 個字符起,與模式串的一個字符比較,若相等,則繼續逐個比較主串和模式的后續字符;否則,從主串的下一個字符起,重新和模式的字符比較,以此類推,直至成功或失敗。
int IndexSubstring(string s, string sub, int pos) {int i = pos;int j = 0;while(i < s.size() && j < sub.size()) {if(s[i]==sub[j]) {i++;j++;} else {i = i - j + 1;j = 0;}}if(j >= sub.size()) return i - sub.size() + 1; // 查找成功else return 0; // 查找失敗 }但這種暴力破解的算法的最壞時間復雜度為O(mn),m和n分別是主串和模式串的長度。
KMP算法——改進的模式匹配算法
在回顧上面暴力破解的算法,我們可以得到,暴力算法每一次回溯都是回到模式串最初的起點,事實上,這一步是可以改進的。
【KMP算法】利用比較過的信息,i 指針不需要回溯,僅將子串向后滑動一個合適的位置,并從這個位置開始和主串比較,這個合適的位置僅與 子串本身結構有關,與主串無關。
【KMP算法】實際上是一種【備忘錄設計模式】,下面通過幾個步驟或許可以讓你了解這個算法的使用。
我們以主串 s='ababcabcacbab',模式串 sub=‘abcac’
1.獲取模式串的 next 數組(備忘錄)
| a | - | - | 0 |
| ab | a | b | 0 |
| abc | a,ab | c,bc | 0 |
| abca | a,ab,abc | a,ca,bca | 1 |
| abcac | a,ab,abc,abca | c,ac,cac,bcac | 0 |
所以 模式串sub 的 next數組 為
| next | 0 | 0 | 0 | 1 | 0 |
2.匹配過程
要滿足一個公式:移動位數 = 已經匹配的位數 - 對應的部分匹配值
第一趟:發現 a 與 c 不配
| a | b | c |
但是前面兩個字符 'ab' 是匹配的,查表可知,最后一個匹配字符 b 對應匹配部分值為 0,按公式,計算 $ 2-0=2 $,所以,模式串向后移動 2 位。
第2趟:模式串向后移動 2 位后,發現 b 與 c 不配
| a | b | c | a | c |
但是前面兩個字符 'abca' 是匹配的,查表可知,最后一個匹配字符 a 對應匹配部分值為 1 ,按公式,計算 4?1=34-1=34?1=3,所以,模式串向后移動 3 位。
第3趟:模式串向后移動 3 位后,匹配成功
| a | b | c | a | c |
使用【KMP算法】后,時間復雜度變為了O(m+n)
KMP原理
已知公式:移動位數 = 已經匹配的位數 - 對應的部分匹配值
改為代碼:
move=(j?1)?next[j?1]move = (j -1) - next[j-1] move=(j?1)?next[j?1]
優化公式,如果我們將 next數組 向后挪一位,就不需要 next[j-1] ,只需要直接使用 next[j] 即可
| next | 0 | 0 | 0 | 1 | 0 |
| 右移 | -1 | 0 | 0 | 0 | 1 |
于是上述公式(2)既可以改為
move=(j?1)?next[j]move = (j -1) - next[j] move=(j?1)?next[j]
此時,得到串移動的公式
j=j?move=j?((j?1)?next[j])=next[j]+1j = j - move =j -((j -1) - next[j])=next[j]+1 j=j?move=j?((j?1)?next[j])=next[j]+1
進一步優化,我們可以將右移后是 next數組 總體+1,那么,最后的數組就變成了
j=next[j]j = next[j] j=next[j]
| next | 0 | 0 | 0 | 1 | 0 |
| 右移 | -1 | 0 | 0 | 0 | 1 |
| 右移+1 | 0 | 1 | 1 | 1 | 2 |
最后一行的意思是,把模式串的第 next[j] 個值,移動 j 這個位置。或者說,從模式串的第 next[j] 個數組開始查找,主串繼續移動即可。
3.使用了改進后的next數組再匹配過程
第一趟:發現 a 與 c 不配
| a | b | c |
在 c(pos=2) 這個位置匹配失敗,查表得,c 的對應值是 1 ,也就是將模式串的第一個字符 (a) 移到 c(pos=2) 這個位置
第2趟:模式串向后移動 2 位后,發現 b 與 c 不配
| a | b | c | a | c |
在 c(pos=6) 這個位置匹配失敗,查表得,c 的對應值是 2 ,也就是將模式串的第二個字符 (b) 移到 c(pos=6) 這個位置
第3趟:模式串向后移動 3 位后,匹配成功
| a | b | c | a | c |
參考代碼
獲取next數組
void getNext(string sub, int next) {int i = 0;int j = 0;next[0] = 0;while(i<sub.size()) {if(j == 0|| s[i]==s[j]){i++;j++;next[i] = j;} else {j = next[j];}} }【KMP算法】
int Index(string s, string sub, int next[], inr pos) {int i = pos;int j = 0;while(i<s.size()&&j<sub.size()) {if(j==0||s[i]==sub[j]){i++;j++; } else {j = next[j]; // 模式串右移}if(j >= sub.size()) {return i - sub.size() + 1;} eles {return 0;}} }總結
以上是生活随笔為你收集整理的【数据结构-查找】2.字符串(逐步演绎过程,超级详解KMP算法)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【数据结构-查找】1.通俗易懂讲解 ——
- 下一篇: 【数据结构-查找】3.散列表详解