神奇的算法:KMP Knuth Morris Pratt 算法

精華在於發現不匹配時向後移動的位數。這裏用到“部分匹配值”Partial Match Table的概念,PMT[n]表示字符串前n個字符的最場公共前/後綴的長度。AB的前後綴分別是A和B,前綴不包括最後一個字符,後綴不包括第一個字符。

當第n+1個字符不匹配時,按照PMT[n]向後移動相應位數即可。


例如:ABABC,PMT[2]=0,PMT[3]=1,PMT[4]=2,PMT[5]=0。

如果匹配到:

EEEABABEEEEE

SSSABABC //S表示Space

發現C和E不相同,則向後移動兩位,因爲C前面有4個字符,4-PMT[4] = 2,即移動兩位。


所以算法難點就變成了如何計算PMT表,不過這比KMP算法簡單多了~


在寫grep命令時簡單實現了一下:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdarg.h>

/**
 * 驗證長度爲len的字符串pattern的部分匹配值是不是n
 */
int is_partial_match_equal(int n, const char *pattern, size_t len){
    for (size_t i = 0, j = len - n; i < len && j < len; ++i, ++j){
        if (pattern[i] != pattern[j]){
            return 0;
        }
    }
    return 1;
}
/**
 * 獲取長度是len的字符串pattern的部分匹配值
 * notice:窮舉法實現
 */
int get_partial_match_cnt(const char *pattern, size_t len){
    if (len < 2) return 0;
    for (size_t match = len - 1; match >= 1; --match){
        if (is_partial_match_equal(match, pattern, len)){
            return match;
        }
    }
    return 0;
}
/**
 * next數組的長度至少是pattern的長度。
 * next[i]表示pattern[i]沒有匹配上時應該往後移動的數量。
 */
int get_next(int next[], const char *pattern, size_t len){
    if (len < 2){ 
        return 0;
    }
    for (size_t match_len = 1; match_len <= len; ++match_len){
        next[match_len - 1] = match_len - get_partial_match_cnt(pattern, match_len);
    }
    return 0;
}
#ifdef DEBUG
void debug_print_next_array(int next[], size_t len){
    printf("Next array:\n");
    for (size_t i = 0; i < len; ++i){
        printf("next[%d]=%d\n", (int)i, next[i]);
    }
    printf("End\n");
}
void debug_log(const char *fmt, ...){
    va_list mark;
    va_start(mark, fmt);
    vprintf(fmt, mark);
    va_end(mark);
}
#else
void debug_print_next_array(int next[], size_t len){}
void debug_log(const char *fmt, ...){}
#endif
/**
 * KMP算法
 * 返回正數表示從str[i]開始,str和pattern匹配上了。
 * 返回-1表示無法匹配。
 */
int KMP(const char *str, const char *pattern){
    debug_log("KMP(%s, %s)\n", str, pattern);
    size_t len = strlen(pattern);
    int *next = (int *)malloc(sizeof(int) * len);
    get_next(next, pattern, len);
    debug_print_next_array(next, len);

    unsigned int idx_str = 0, idx_pat = 0; 
    while (str[idx_str] && pattern[idx_pat]){
        debug_log("Compare(%c, %c)\n", str[idx_str], pattern[idx_pat]);
        if (str[idx_str] == pattern[idx_pat]){
            ++idx_str;
            ++idx_pat;
        }
        else
        {
            idx_str += next[idx_pat];
            idx_pat = 0;
        }
    }
    free(next);
    
    if (!pattern[idx_pat]){
        int pos = (int)(idx_str - idx_pat);
        debug_log("Position = %d\n", pos);
        return pos;
    }else{ // (!str[idx_str]){
        debug_log("Pattern not found\n");
        return -1;
    }
}
/**
 * 從文件中讀取每一行,進行grep
 */
int grep_file(FILE *file, char *buf, int buf_len, const char *pattern){
    while (fgets(buf, buf_len, file) != NULL){
        if (KMP(buf, pattern) != -1){
            printf("%s", buf);
        }
    }
    return 0;
}
/**
 * grep的實現
 * 返回1表示出錯,0表示正確
 */
#define BUF_LEN 1024 // 假設一行不超過1024個ascii字符
static char buf[BUF_LEN];
int grep(int argc, char *argv[]){
    // parse [OPTIONS]
    // parse PATTERN
    // parse [FILES]
    // notice:規定實現的grep不包含options,pattern在argv[1]中。如果沒有[FILES]則從stdin中讀取數據。
    int ret = 0;
    if (argc < 2){
        return 1;
    }
    const char *pattern = argv[1];
    if (argc == 2){
        grep_file(stdin, buf, BUF_LEN, pattern);
    }
    else
    {
        // 從文件讀取數據
        for (int idx_file = 2; idx_file < argc; ++idx_file){
            const char *file_name = argv[idx_file];
            FILE * file = fopen(file_name, "r");
            if (file == NULL){
                ret = 1;
                continue;
            }
            grep_file(file, buf, BUF_LEN, pattern);
            fclose(file);
        }
    }
    return ret;
}
int main(int argc, char *argv[]){
    debug_log("Warning: In debug mode\n");
    return grep(argc, argv);
}



/* vim: set expandtab ts=4 sw=4 sts=4 tw=100: */


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