字符串匹配算法的思考

最近剛好回看了一下KMP算法,對KMP算法有了更深的理解,於是在這裏記錄一下,使用清晰的文字,將自己的理解記錄歸納,目的是讓沒有看過KMP算法的能夠通過此文理解KMP算法,因爲我自己有可能某一天也會忘記。所以此文要能夠令自己從一個“無知”的狀態到理解。甚至進行代碼的實現。

前置

待搜索串: Sn=S0S1S2…Sn。如:abbaabbaaba
待匹配串:Pm=P0P1P2…Pn。如:abbaaba

從暴力匹配講起

暴力匹配: 對 待搜索串每個位置,都進行待匹配串的搜索,當前位置不匹配,則到下一個位置進行查找。即,從S0開始查找Pm,查不到則在S1再查Pm,直到 S(n-m)的位置,Pm每次都從P0開始匹配。
最差時間複雜度:遍歷 n-m 的位置,每個位置進行 m 次比較。 即, (n-m) * m
例如: Sn= aaaaaaaaab,Pm=aab
說明:暴力匹配並非一無是處,思路比較清晰簡單,因此代碼實現也比較簡單,在 子串較小時是有用的(如m=1)。
優化方向:減少已經做過的事。如:走過的S位置不要再走,不要每次都從P0位置開始匹配。

字符串匹配的第一次優化

從暴力匹配中,如 Sn= aaaaaaaaab,Pm=aab,可以發現,從S的位置,每次匹配完,即使匹配到P的最後一個位置不匹配,假設S的位置爲x,S已經走了x+m了,但是還是會回到x+1處,重新匹配。因此,這是一個浪費的地方。假設P的位置爲y ,我們能不能從 S的x+y處位置接下去操作,即S不發生回退。
當然 x , y的限制條件很明顯,這裏只點一下,y<=m
順着S不發生回退的方向,就可以進行第一次的優化。
考慮一下:其實,S 從 x+y 位置的字符是什麼我們已經知道了的。即子串P的0到y的位置,所以即使S即使不回退,我們也有了 S(x+y)的知識,我們接下來就應該利用這些知識讓S不回退。
再考慮一下:假設S從x+1的位置開始匹配了P,最後還是需要比較Sx+y的位置的。那麼此時,匹配S(x+y)應該與P(y-1)進行比較,那麼,我們可以先進行 S(x+y)與P(y-1) 的比較
如果 S(x+y)與P(y-1) 不匹配,那麼 S(x+1)的位置已經不可能匹配子串,如果不匹配,S的x-y的位置都可以這樣子判斷,如果匹配,就要進行另外的展開判斷。
如果 S(x+y)與P(y-1)匹配,如果我們能通過這些已有的條件進行驗證是否接下來是否匹配,那麼,我們就確實不再需要進行 S的回退了。
那麼,我們能夠驗證嗎?還知道我們第一個考慮嗎,我們是擁有 Sx到S(x+y)的知識的。即接下來要知道的Sx到S(x+y)的知識我們都有,所以我們是可以驗證。
爲了便於理解,我們假設 n=10,m=5,x=0,y=4,由於我們不回退,假設S1是可能是合法匹配的開始(即x=1),並驗證了 S4=P3,那麼如果 S1S2S3=P0P1P2,即 可得 S1S2S3S4=P0P1P2P3,而驗證 S1S2S3=P0P1P2 是否正確,我們是有辦法的,因爲我們上一次匹配的知識裏面已經知道了 S1S2S3 (即 P1P2P3),那麼,確實匹配,我們就可以接下去比較S5和P4了,這部分是屬於常規操作了。
如果不匹配,那麼就是接下去的嘗試了,即x=2時的情況,直到嘗試完。最後確實不匹配,S的位置爲S(x+y+1),P的位置從0開始。
通過這個驗證,我們將不用再回退S,將 S與 P 進行了分離(驗證前面的部分是否匹配不再與S有關)。進行了一次優化。

字符串匹配的第二次優化

由於第一次優化我們已經把 S 和 P 進行了分離(驗證前面的部分是否匹配不再與S有關)(後半部分是否匹配我們沒做過驗證,這一部分是沒法少的,不是優化的方向),所以,接下來的優化也與S無關。
在我們操作的過程中,我們發現,可能要重複驗證 P 某個範圍的值是否相等。
如:還是上面的例子。由於上一次匹配的x=0,y=4, 可得: S0S1S2S3 = P0P1P2P3,即可得 S1S2S3 = P1P2P3。 而當前要從x=1匹配要驗證 S1S2S3=P0P1P2 是否正確。 即 S1S2S3=P1P2P3 = P0P1P2 。 那麼,下次y爲另外值的時候,又用到這樣的比較,即可能 P1P2P3=P0P1P2經常在重複比較,並且有些比較是多餘的,因爲我是可以提前直到 P1P2P3 !=P0P1P2的,所以我們可以提前處理P,產生一個位置對照表(從0到m的最長重複串位置,我們後面再說一下這個對照表怎麼產生,現在先跳過)。這樣,下次就可以從y位置通過對照表得到另一個位置 y’,直接比較 S(x+y)與S(y’) 就可以直接直到前面是否匹配,而不用P(y-1)一次次比較,如果不匹配,則y’ 可能有 y’’ ,如果沒有,那麼說明前面都不可能匹配,進入 S(x+y+1)的流程。這樣,通過預處理對照表,大大減少了子串的比較。
對照表的產生:我把這個表理解成0-m每個位置從0開始的最長重複子串的位置。即:m=y時,存在 i ,使 P0…Pi = P(m-i) P(m-i-1)…Py,當然,i 不能和y相等,對照表存放的就是每個位置的 i 值。舉幾個例子。如下,此處i不存在時記爲*
aab *1*
abcabcacab ***0123*01
這裏找y位置 i 的值簡單直接一點的算法思路:
y=0時,*
y>0時,假設 i=y-1,那麼 Py-1 =Py,Py-2=Py-1…假設i=y-2,那麼 Py-2=Py,Py-3=Py-1…最後可以得到 i 的確定值。

通過對照表,我們把子串匹配的驗證也進行了優化。

KMP算法

KMP算法的基本原理即爲上面的兩次優化。即:
不回溯S,將前部分匹配判斷留給子串
用預處理對照表,減少子串判斷。
空間複雜度:O(m) 存放對照表。
時間複雜的:O(n+m)

寫到最後

重新看了一下,並按照自己的理解寫了一下。對這個算法總算能夠很清晰的描述出來了,知其然。搭配一些圖片精簡一些文字可能更好一點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章