快速字符串搜索算法KMP

在C/C++語言編程過程中,一般的字符串搜索操作都是通過標準庫的strstr()函數來完成的,這在通常的情況下,因爲字符串的搜索操作不多,並不會產生效率問題。實際上,這個函數的時間複雜度不容樂觀。如果要從長度爲n的字符串中查找長度爲m的子字符串,那麼這個strstr()函數的最壞時間複雜度爲O(n*m),可見,隨着子字符串長度m的增大,strstr()函數的時間複雜度也相應地成倍增加,有沒有更加高效的算法呢?
KMP(Knuth-Morris-Pratt)算法通過預先計算模式字符串中相應字符處的回溯索引,避免了模式匹配時不必要的回溯操作,從而提高了效率,將時間複雜度變成了O(m+n)。至於更加詳細的內容,請教Google老師是個不錯的主義,其中“
KMP算法詳解”這篇文章講解的比較透徹,值得一看。
KMP算法因爲要保存每個字符的回溯索引,所以空間複雜度會略微有所增加

sizeof(idx)*length(pattern)

另外,當n比較小時,建立回溯索引所引入的O(m)個時間複雜度也許並不輕鬆。這些條件致使KMP算法適用於n和m都比較大,且字符串搜索操作比較頻繁的環境,例如:網絡入侵檢測系統和QoS系統等。
實際上Linux 2.6版內核從2.6.14開始就引入了名爲string的iptables匹配(match)模塊,他提供有KMP、BM(Boyer-Moore)和FSM(finite state machine)算法,可以實現基於關鍵字的網絡過濾。
在學習這個算法的過程中,將Linux內核中的實現代碼搬到了用戶空間:


#include
#include
void kmp_init(const char *patn, int len, int *next)
{
int i, j;
assert(patn != NULL && len > 0 && next != NULL);
        next[0] = 0;
for (i = 1, j = 0; i < len; i ++) {
while (j > 0 && patn[j] != patn[i])
                        j = next[j - 1];
if (patn[j] == patn[i])
                        j ++;
                next[i] = j;
}
}
int kmp_find(const char *text, int text_len, const char *patn,
int patn_len, int *next)
{
int i, j;
assert(text != NULL && text_len > 0 && patn != NULL && patn_len > 0
&& next != NULL);
for (i = 0, j = 0; i < text_len; i ++ ) {
while (j > 0 && text[i] != patn[j])
                        j = next[j - 1];
if (text[i] == patn[j])
                        j ++;
if (j == patn_len)
return i + 1 - patn_len;
}
return -1;
}
int main(int argc, char *argv[])
{
int *next;
int i, pos, len = strlen(argv[2]);
if (argc < 3) {
printf("Usage: %s text pattern/n", argv[0]);
return 1;
}
        next = calloc(strlen(argv[2]), sizeof(int));
        kmp_init(argv[2], strlen(argv[2]), next);
printf("next array:/n");
for (i = 0; i < len; i ++)
printf("/t%c", argv[2][i]);
printf("/n");
for (i = 0; i < len; i ++)
printf("/t%d", next[i]);
printf("/n");
        pos = kmp_find(argv[1], strlen(argv[1]), argv[2], strlen(argv[2]), next);
printf("find result:/n");
if (pos < 0) {
printf("None found!/n");
} else {
printf("%s/n", argv[1]);
for (i = 0; i < pos; i ++)
printf(" ");
printf("^/n");
}
return 0;
}

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