KMP算法怎麼來的和找規律,以及對於BF算法他的主要區別就不再贅述我們,我們來說一下怎麼用最快的速度加上已知規律找到給定字符串的next數組(C語言中和java中都適用):
我們已經找到了部分規律:next數組中的值可能和已匹配部分字符串的前綴和後綴有關係,我們通過例子和解釋來了解一下以後什麼具體點的關係:
假設:模式串T爲:a b a b c
對於各個不匹配位置的已匹配位可能有:
a //第一個b位置不匹配
a b //第二個a位置不匹配
a b a //第二個b位置不匹配
a b a b //c位置不匹配
根據kmp算法給出的結論(這個是以C語言來說的,因爲T1表示的是第一個字符,跟java中第一個字符索引爲0不同但是往下邊看如何求next數組就能找到和java的區別以及可以用java寫出來):
其中的當j=4時T1T2=T2T3和當j=5時T1T2T3=T2T3T4分別爲對應的已匹配的字符串的最長前綴和最長後綴,然後在有次長前綴和次長後綴比較,進而還有次次長前綴和次次長後綴進行比較;所以我們暫且認爲是最長可以相等的前綴和後綴之間的關係 接下來通過上邊例子來看他們之間到底什麼關係:(我們用len來表示可以找到的最長的前綴和後綴相等時候的長度)
a // len=0
a b //len=0
a b a //len=1 T1 = T3 長度爲1
a b a b //len=2 T1T2 = T3T4 長度爲2;
接下來我們通過主管判斷一下這個字符串對應的next數組爲:
a b a b c
0 1 1 2 3
那我們可以發現 除了第一個字符對應的next,其他位對應的都是我們求得的對應位置上len +1;那我們來研究一下爲什麼是這種情況;
舉個例子: 當在第二個b位置不匹配的時候,此時我們已經匹配部分爲 a b a我們如果想要讓T1位置的a 通過移動來正好匹配上T3位置上的那麼需要我們T1的下一個位置移動到此時不匹配的位置,而正好T1的一下個位置就是len的長度再加一;這並不是巧合;你可以帶入a b a b:想要第一個a b 匹配上第二個a b 我們需要讓第一個b的後一個位置對應着現在不匹配的位置的地方,第一個b後一個位置就是len+1;(這裏是對應着C語言來說的是第一個字符的索引爲1,如果對應着java就是第一個索引是0就正好不加一就是對應字符的索引位置);所以說通過這種規律我們可以很快找到給定字符串的next數組,並且理解了爲什麼加一爲什麼不加一;
理解之後我們來看一下爲什麼next給定賦值式子是規定了j=1時next[1]爲0:
當j=1時,對應的就是第一個字符,如果第一個字符都不匹配了我們是不是不需要像其他字符不匹配一樣跳來跳去的吧?第一個字符都不匹配我們需要移動主串S的對應位置進行加一了(後移一位了),所以說這個0就是標誌一下是否是第一個字符不匹配(因爲第一個字符不匹配跟其他字符不匹配做的動作稍微有點不同),意思是這個0我們是完全可以換掉的,因爲他就是一個標誌麼(會在下邊貼出如果不用0標記的代碼);還有就是如果j=2(就是對應於“其他情況”),我們只能有一個動作就是把模式串T後移一位,換句話說就是第二個字符串不匹配我們只能讓第一個字符來進行匹配,我沒有其他選擇,所以說當我們模式串T長度大於二的時候第二位一定會是1,就是對應我第一個字符的索引位置;
我們通過這種找到的關係就可以手動寫給定模式串的next數組了,但是也避免字符串太多人看不過來就可以寫一個程序來運行代碼:
C語言對應算法:
void get_next( String T, int *next )
{
j = 0;
i = 1;//初始化兩個相鄰的索引
next[1] = 0; //確定
while( i < T[0] ) //長度足夠
{
//j==0 初始化j=0 當我們模式串T 的第一個字符都不匹配的時候
if( 0==j || T[i] == T[j] )
{
//必須要先++在賦值原因是這是已匹配字符段的比較我們要將結果賦值給那個不匹配的位置(跟手寫時候講len爲什麼要加一結果一樣)
i++;
j++;
next[i] = j;
}
else
{
j = next[j];
}
}
}
我們上邊說過了因爲java代碼字符串首個字符是從零開始的所以說我們如果要將他修改爲java代碼只需要將對應索引位置減一即可;對應的java代碼(完整帶測試代碼):
public class KMP {
public int[] getNext(String pat) {
int[] next = new int[pat.length()];
int j = -1;
int i = 0;
next[0] = -1;
while(i<pat.length()-1) {
if(j==-1||pat.charAt(i)==pat.charAt(j)) {
i++;
j++;
next[i] = j;
}else {
j = next[j];
}
}
int[] copyOf = Arrays.copyOf(next, next.length);
for(int o=0;o<copyOf.length;o++) {
copyOf[o] = copyOf[o]+1;
}
System.out.println("正常C語言中應該是:"+Arrays.toString(copyOf));
System.out.println("用我們java來寫是:"+Arrays.toString(next));
return next;
}
public boolean contains(String des,String pat) {
int[] next = getNext(pat);
int i = 0;
int j = 0;
while(i<des.length()&&j<pat.length()) {
if(j==-1||des.charAt(i)==pat.charAt(j)) {
i++;
j++;
}else {
j = next[j];
}
}
return j==pat.length();
}
public static void main(String[] args) {
KMP kmp = new KMP();
// System.out.println(kmp.contains("123456", "abaabcac"));
System.out.println(kmp.getNext1("abaabcac"));
}
}
我們之前說過這個j=1時,next[1]=0只是一個標誌用來區分是否是因爲第一個字符不匹配造成的;所以說我們完全可以把這個標誌位做一個更改:
void get_next( String T, int *next )
{
j = 1;
i = 2;
next[1] = -10000;
while( i < T[0] )
{
if(j==-10000)
{
i++;
j = 1;
next[i] = j;
//這個地方不知道C語言中有沒有continue大概就是這個意思;對應java的很好寫了直接就是continue;
countinue;
}
if(T[i] == T[j] )
{
i++;
j++;
next[i] = j;
}
else
{
j = next[j];
}
}
// 因爲前綴是固定的,後綴是相對的。
}
java中因爲你標誌位的改變相應用的時候判斷也是需要改變的;
public int[] getNext1(String pat) {
int[] next = new int[pat.length()+1];
int j = 0;
int i = 1;
next[0] = -10000;
while( i < pat.length()-1 )
{
if(j==-10000)
{
i++;
j = 0;
next[i] = j;
continue;
}
if(pat.charAt(i) == pat.charAt(j) )
{
i++;
j++;
next[i] = j;
}
else
{
j = next[j];
}
}
當前這個KMP算法的next數組還是可以優化的,比如說在求得的 aaaaax中,如果最後一個a不匹配我們能不能讓他直接s串位置向後移?(因爲根據上邊分析求出來的next數組是012345)還是直接根據代碼理解吧:
void get_nextval(SString T, int &nextval[ ] )
{ //next函數修正值存入數組nextval
i=1; j=0;
nextval[1]=0;
while(i<T[0] )
{ if(j= = 0||T[i]= =T[j] )
{ ++i; ++j;
//這裏多了一個判斷
if(T[i]!=T[j] ) nextval[i]=j;
else nextval[i]=nextval[j];
}
else j=nextval[j];
}
}
以上。