標籤:函數 kmp 演算法 struct
strStr()函數的用途是在一個字串S中尋找某個字串P第一次出現的位置,並返回其下標,找不到時返回-1。最簡單的辦法就是找出S所有的子串和P進行比較,然而這個方法比較低效。假設我們從S的下標0和P的下標0開始對每個字元進行比較,如果相等則下標增加,比較後面的字元。如果兩者一直相等直到P的下標達到最大值,則表示在S中找到了P,並且第一次出現的位置為0,返回0,但如果在中間某個位置兩個字元不相等時,這時S的下標要退回到1,P的下標回到0,重新開始比較。後來,有三個牛覺得這樣不爽,於是他們搞了一個KMP演算法,並以他們三人名字的最開始字元命名這個演算法。這個演算法的好處是在比較中的某個位置,兩個串的字元不相等時,不需要S回退,而P的下標回退到某個值,然後繼續和S當前失配的字元進行比較。
關鍵就是在這裡了,P的下標要回退,退到哪兒啊?我們舉個例子,S為“abcabcabeg”,P為"abcabe",從下標0開始比較,一直到下標5才發現字元不相等,S對應字元為c,P對應字元為e。這時候,我們就考查P中下標5以前的字串,即“abcab”。對於這個字串,S下標5之前也存在同樣的串,我們還發現這個串的首碼和尾碼都有“ab”,即下標0、1和3、4分別組成的字串相等,同時等於S串中下標3、4組成的字串。因此,我們只需要把P的下標退到2的位置,然後跟S的下標5對應的字元繼續比較即可,因為P的0、1下標對應字元和S的3、4下標對應的字元相等。如果對於每個失配的位置,我們都這樣對P的下標進行調整,而S的下標繼續往前而不後退,效率會提高很多。現在最大的問題就是怎麼計算P失配時應該退回到的下標值,而從前面的分析中可以看出是對字串的前尾碼相等部分的長度來擷取的,這樣問題就變成了怎麼編寫代碼來較為高效地計算在失配時P調整到的新下標值。對於P中每個位置,我們可以計算出在該位置失配時應該調整到的下標,並存放在一個next數組中,比如上面P在下標為5時失配,調整到的新下標為2,則有next[5]等2。
寫到這裡,發現好像講得越來越亂了,還是貼我自己的代碼吧,其實KMP最關鍵也是不太好理解的地方就是對next數組的理解和計算方法。
void fillNext(char* p, int* next)
{
if(!p) return;
int len = strlen(p);
int i = 1;
int j = 0;
for(int i = 0; i < len; ++i) next[i] = 0;
while(i < len-1)
{
if(p[i] == p[j])
{
i++;
j++;
next[i] = j;
}
else
{
if(j == 0)
{
i++;
next[i] = 0;
}
else j = next[j];
}
}
}
int strStr(char* haystack, char* needle) {
if(!haystack || !needle) return -1;
int len_h = strlen(haystack);
int len_n = strlen(needle);
if(len_n > len_h) return -1;
if(len_n == 0) return 0;
int* next = malloc(sizeof(int)*len_n);
fillNext(needle, next);
int i = 0;
int j = 0;
while(i < len_h)
{
if(haystack[i] == needle[j])
{
j++;
i++;
if(j == len_n)break;
}
else
{
if(j == 0)i++;
else j = next[j];
}
}
free(next);
if(j == len_n)
{
return i - len_n;
}
return -1;
}
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
用KMP演算法實現strStr()