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