對於字串問題一直只知道使用模板,脫離了模板題就不知道該怎樣更改板子。
一定是沒有理解清楚演算法本身的含義,所以這幾天詳細學習了一下各個演算法本身及其一些比較普遍的拓展。希望能夠開闊以後的解題思路。
人眼優化字串匹配
我們串中的位置指標i,j來說明,第乙個位置下標以0開始,我們稱為第0位。如果是人為來尋找的話,肯定不會再把i移動回第1位,因為主串匹配失敗的位置(i=3)前面除了第乙個a之外再也沒有a了,我們為什麼能知道主串前面只有乙個a?因為我們已經知道前面三個字元都是匹配的!(這很重要)。移動過去肯定也是不匹配的!有乙個想法,i可以不動,我們只需要移動j即可,如下圖:
大牛們是無法忍受「暴力破解」這種低效的手段的,於是他們三個研究出了kmp演算法。其思想就如同我們上邊所看到的一樣:「利用已經部分匹配這個有效資訊,保持i指標不回溯,通過修改j指標,讓模式串盡量地移動到有效的位置。」
所以,整個kmp的重點就在於當某乙個字元與主串不匹配時,我們應該知道j指標要移動到哪?
接下來我們自己來發現j的移動規律:
如圖:c和d不匹配了,我們要把j移動到哪?顯然是第1位。為什麼?因為前面有乙個a相同。
如下圖也是一樣的情況:
可以把j指標移動到第2位,因為前面有兩個字母是一樣的:
如果用數學公式來表示是這樣的
p[0 ~ k-1] == p[j-k ~ j-1]
這個相當重要,如果覺得不好記的話,可以通過下圖來理解:
弄明白了這個就應該可能明白為什麼可以直接將j移動到k位置了。
因為:當t[i] != p[j]時
有t[i-j ~ i-1] == p[0 ~ j-1]
由p[0 ~ k-1] == p[j-k ~ j-1]
必然:t[i-k ~ i-1] == p[0 ~ k-1]
kmp演算法的性質
kmp演算法是利用待匹配的子串自身的這種性質,來提高匹配速度。該性質在許多其他中版本的解釋中還可以描述成:若子串的字首集和後綴集中,重複的最長子串的長度為k,則下次匹配子串的j可以移動到第k位(下標為0為第0位)。我們將這個解釋定義成最大重複子串解釋。
在「aba」中,字首集就是除掉最後乙個字元』a』後的子串集合,同理後綴集為除掉最前乙個字元a後的子串集合,那麼兩者最長的重複子串就是a,k=1;
分解成計算機的步驟,則是如下的過程:
1)找出字首pre,設為pre[0~m];
2)找出字尾post,設為post[0~n];
3)從字首pre裡,先以最大長度的s[0~m]為子串,即設k初始值為m,跟post[n-m+1~n]進行比較:
如果相同,則pre[0~m]則為最大重複子串,長度為m,則k=m;
如果不相同,則k=k-1;縮小字首的子串的乙個字元,在跟字尾的子串按照尾巴對齊,進行比較,是否相同。
如此下去,直到找到重複子串,或者k沒找到。
求next陣列
好,接下來就是重點了,怎麼求這個(這些)k呢?因為在p的每乙個位置都可能發生不匹配,也就是說我們要計算每乙個位置j對應的k,所以用乙個陣列next來儲存,next[j] = k,表示當t[i] != p[j]時,j指標的下乙個位置。
另乙個非常有用且恒等的定義,因為下標從0開始的,k值實際是j位前的子串的最大重複子串的長度。請時刻牢記next陣列的定義,下面的解釋是死死地圍繞著這個定義來解釋的。
**示例1
void
getnext
(int next[
],string t)
}
先來看第乙個:當j為0時,如果這時候不匹配,怎麼辦?
像上圖這種情況,j已經在最左邊了,不可能再移動了,這時候要應該是i指標後移。所以在**中才會有next[0] = -1;這個初始化。
如果是當j為1的時候呢?
顯然,j指標一定是後移到0位置的。因為它前面也就只有這乙個位置了~~~
下面這個是最重要的,請看如下圖:
請仔細對比這兩個圖。
我們發現乙個規律:
當p[k] == p[j]時,
有next[j+1] == next[j] + 1
那如果p[k] != p[j]呢?比如下圖所示:
像這種情況,如果你從**上看應該是這一句:k = next[k];為什麼是這樣子?你看下面應該就明白了。
現在你應該知道為什麼要k = next[k]了吧!像上邊的例子,我們已經不可能找到[ a,b,a,b ]這個最長的字尾串了,但我們還是可能找到[ a,b ]、[ b ]這樣的字首串的。所以這個過程像不像在定位[ a,b,a,c ]這個串,當c和主串不一樣了(也就是k位置不一樣了),那當然是把指標移動到next[k]啦。
記憶點
1)k值是j位前的子串的最大重複子串的長度。
2)陣列next來儲存,每乙個位置j對應的k
next陣列求解演算法優化
最後,來看一下上邊的演算法存在的缺陷。來看第乙個例子:
顯然,當我們上邊的演算法得到的next陣列應該是[ -1,0,0,1 ]
不難發現,這一步是完全沒有意義的。因為後面的b已經不匹配了,那前面的b也一定是不匹配的,同樣的情況其實還發生在第2個元素a上。
顯然,發生問題的原因在於p[j] == p[next[j]]。
修改**示例
void
getnext
(int next[
],string t)
else k = next[k];}
}
kmp演算法
int
kmp(string s,string t)
else j=next[j]
;//j回退}if
(j>=t.length)
return
(i-t.length)
;//匹配成功,返回子串的位置
else
return(-
1);//沒找到
}
參考博文: 字串 KMP演算法
而kmp演算法在字串匹配方法中乙個很著名並且很聰明的演算法,當然也確實比較難理解。甚至於有程式設計師因為無法理解kmp演算法而直接改用暴力匹配。本身自己學演算法起步較晚,第一次接觸到kmp演算法已經是研究生畢業一年了。雖然帶著研究生的學歷背景,但是剛開始看的時候依然是一臉懵逼。看了很多博主的講解總算...
字串 KMP演算法
而kmp演算法在字串匹配方法中乙個很著名並且很聰明的演算法,當然也確實比較難理解。甚至於有程式設計師因為無法理解kmp演算法而直接改用暴力匹配。本身自己學演算法起步較晚,第一次接觸到kmp演算法已經是研究生畢業一年了。雖然帶著研究生的學歷背景,但是剛開始看的時候依然是一臉懵逼。看了很多博主的講解總算...
字串演算法 KMP演算法
給定字串m和n m比n長 找出n在m中出現的匹配位置。說白了,就是乙個簡單的字串匹配。例如 首先,字串 bbc abcdab abcdabcdabde 的第乙個字元與搜尋詞 abcdabd 的第乙個字元進行比較。因為b與a不匹配,所以搜尋詞後移一位。因為b與a不匹配,搜尋詞再往後移。就這樣,直到字串...