一、字符串問題形式化定義:假設文本是一個長度爲n的T[1..n],而模式是一個長度爲m的數組P[1..m],其中m<=n,若是有T[s+1..s+m]==P[1..m],那麼就稱模式P在T中出現。s爲有效偏移,不然稱爲無效偏移。算法
二、方法:首先基於模式進行預處理,而後找到全部有效偏移(匹配)。數組
幾種方法的預處理時間和匹配時間函數
算法優化 |
預處理時間spa |
匹配時間code |
樸素算法orm |
0字符串 |
o((n-m+1)*m)it |
有限自動機io |
o(m|全部有限長度字符串的集合|) |
o(n) |
KMP |
o(m) |
o(n) |
Rabin-karp |
0(m) |
o((n-m+1)*m) |
三、樸素字符串匹配算法:經過循環的方式找到全部有效偏移s。有效偏移s的可能有n-m+1個,每次匹配須要m次,所以共需匹配(n-m+1)*m次。
僞代碼:
NAIVE-STRING-MATCHER(T,P)
1. n=T.length
2. m=P.length
3. for s=0 to n-m
4. if P[1..m] == T[s+1..s+m]
5. printf "Pattern occurs with shift" s
缺點:忽略了檢測無效s值時得到的文本信息。
四、Rabin-Karp算法:初等數論的概念。暫且不研究。
五、利用有限自動機進行字符串匹配:首先創建好一個有限自動機,而後根據有限自動機進行匹配。
有限自動機:包括五個元素,全部狀態的集合,初始狀態,接收狀態的集合,有限輸入字母表,轉移函數。
六、KMP算法:經過前綴函數避免對無用偏移進行檢測。也能夠避免在自動機匹配中,對整個轉移函數的計算。主要緣由在於字符串中存在部分匹配的現象。
本質:針對待匹配的模式串的特色,判斷它是否有重複的字符,從而找到它的前綴與後綴,進而求出相應的Next數組,最終根據Next數組而進行KMP匹配
next數組:記錄下字符串P中的共有元素的位置,即第一個共有元素向後便宜多少能夠到達第二個相同的元素哪兒。
"部分匹配"的實質是,有時候,字符串頭部和尾部會有重複。好比,"ABCDAB"之中有兩個"AB",那麼它的"部分匹配值"就是2("AB"的長度)。搜索詞移動的時候,第一個"AB"向後移動4位(字符串長度-部分匹配值),就能夠來到第二個"AB"的位置。
大體思路:
kmp的比較函數:
1.首先初始化好NEXT數組, next[0]=0,next[1]=1
2.循環查找模式P是否在T中
1)首先比較P[i] == T[j],若是相等,繼續比較下一個,不然執行2.2)
2)令j=next[j],繼續比較(這一步避免了回溯)
3)若是j==0; 代表沒有匹配,則i++, j++
3.直到找到P在T中的位置或者T已經被比較晚結束。
當發生失配的狀況下,j的新值next[j]取決於模式串中T[0 ~ j-1]中前綴和後綴相等部分的長度, 而且next[j]剛好等於這個最大長度
next數組的初始化
1.定義next數組, 令next[0]=0
2.從str[1]開始循環計算對應的next數組
3. 循環計算next[j]的值
4. 從k往前找到某個p[j]=p[k],若是相等則next[j]=k+1.(優化:若是p[k+1]和p[j+1]相等,next[j+1]=next[k+1])
5. 不然,令k=next[k]繼續回溯查找,直到找到相等的爲止。
6. 若是i=-1,則代表模式P中沒有p[j]相同的前綴,令next[j]=1
#include <stdio.h> int nextArr(char* str, int* next, int m){ assert(m>1); next[0] = -1; int j=0,k=-1; while(j<m) if(k==-1||(str[j]==str[k]){ j++; k++; if(str[j]==str[k]){ next[j]=next[k]; }else{ next[j]=k; } }else{ k=next[k]; } } return 0; } int kmpCmp(char* t, char* p, int n, int m){ int i=0, j=0; int next[10]; next[0] = 0; nextArr(p, next, m); for(i=0; i<m; i++){ if(t[i]==p[j]){ if(j==m-1){ printf("%d\n", i-m+1); return 1; }else{ j++; continue; } }else{ j=next[j]; } if(j==0){ j=0; } } return 0; }