1. 程式人生 > >KMP演算法與樸素模式匹配演算法(C語言)

KMP演算法與樸素模式匹配演算法(C語言)

在上一篇部落格中介紹了KMP演算法和樸素模式匹配演算法的區別,本文主要針對這兩種演算法的C語言實現進行講解。

#include<stdio.h>
#define OK 0
#define ERROR -1
#define FAILED 1
int readFile(char **buffer) {
    FILE *fp;
    int length;
    int error;
    fp = fopen("readFile.txt", "rt");
    if (fp == NULL) {
        printf("open file failed!\n");
        return
ERROR; } fseek(fp, 0, SEEK_SET); fseek(fp, 0, SEEK_END); length = ftell(fp); (*buffer) = (char *)malloc(length); if ((*buffer) == NULL) { printf("malloc failed!\n"); return ERROR; } fseek(fp, 0, SEEK_SET); error = fread((*buffer), sizeof(char), length
/ sizeof(char), fp); if (error == 0 ) { printf("Read file failed!\n"); return ERROR; } fclose(fp); return OK; } //樸素的模式匹配演算法 int index(char *buffer, char *check, int *inx, int sizeBuffer,int sizeCheck) { int i = *inx; int j = 0; while (i < sizeBuffer && j < sizeCheck) { if
(buffer[i] == check[j]) { i++; j++; } else { i = i - j+1; j = 0; } } if (j == sizeCheck) *inx = i - sizeCheck; else return FAILED; return OK; } //KMP模式匹配演算法 //計算next陣列 int compNext(char *check, int *next, int sizeCheck) { int j = 1; int i = 0; int count = 0; while (j < sizeCheck-1) { if (check[i] == check[j]) { i++; count++; j++; next[j] = count; } else { count = 0; if (i == 0) { j++; next[j] = i; } else i--; } } // for (i = 0; i < sizeCheck; i++) { // printf("%d", next[i]); // } return OK; } //KMP字元匹配 int index_kmp(char *buffer, char *check, int sizeCheck, int sizeBuffer, int *inx) { int i = *inx; int j = 0; int flag = 0; int count = 0; int next[5] = { 0 }; compNext(check, next, sizeCheck); while (i <sizeBuffer && j<sizeCheck) { if (buffer[i] == check[j]) { i++; j++; } else { if (j == 0) i++; j = next[j]; } } if (j == sizeCheck) *inx = i - sizeCheck; else return FAILED; return OK; } int main() { int error; char *buffer; char checkChar[6] = "aware"; int i = 0; int flag = 0; int count = 0; int inx = 0; int sizeCheck = sizeof(checkChar)/sizeof(char)-1; error = readFile(&buffer); if (error == ERROR) { getchar(); } while (buffer[i] != '\0') { for (int j = 0; j < sizeCheck; j++) { if (checkChar[j] == buffer[i + j]) flag++; } if (flag == 5) count++; flag = 0; i++; } printf("The total number is %d\n", count); printf("The local of them are:\n"); while (error != 1) { error = index(buffer, checkChar, &inx, i, sizeCheck); if (error == OK) { printf("%d\n", inx); } inx = inx + 4; } inx = 0; error = 0; printf("The local of them are:\n"); while (error != 1) { error = index_kmp(buffer, checkChar, sizeCheck, i,&inx); if (error == OK) { printf("%d\n", inx); } inx = inx + 4; } free(buffer); return 0; }

本文的程式碼主要包含120行到130行,求取該文字中aware的個數;132行到138行,利用樸素模式匹配方法求著8個單詞的位置;142行到148行利用KMP演算法求8個單詞的位置;
感興趣的朋友可以利用樸素匹配方法和KMP演算法求單詞總數;
下面針對程式碼進行說明:
樸素模式匹配方法:

int index(char *buffer, char *check, int *inx,
    int sizeBuffer,int sizeCheck) {
    int i = *inx;
    int j = 0;
    while (i < sizeBuffer && j < sizeCheck) {
        if (buffer[i] == check[j]) {
            i++;
            j++;
        }
        else {
            i = i - j+1;
            j = 0;
        }
    }
    if (j == sizeCheck)
        *inx = i - sizeCheck;
    else
        return FAILED;
    return OK;
}

跳出while迴圈的條件有兩個,i大於等於文件字元總數(sizeBuffer)或者j大於匹配字串字元總數(本文是5:aware)。
當有5個字元連續匹配成功,則j為5,跳出迴圈;*inx = i - sizeCheck;計算出單詞起始位置。
inx表示開始查詢的位置;

KMP演算法
next陣列的求取

//計算next陣列
int compNext(char *check, int *next, int sizeCheck) {
    int j = 1;
    int i = 0;
    int count = 0;
    while (j < sizeCheck-1) {
        if (check[i] == check[j]) {
            i++;
            count++;
            j++;
            next[j] = count;    
        }
        else {
            count = 0;
            if (i == 0) {
                j++;
                next[j] = i;
            }
            else 
                i--;        
        }   
    }
//  for (i = 0; i < sizeCheck; i++) {
//      printf("%d", next[i]);
//  }
    return OK;
}

請注意這裡的i和j與上一篇博文中的i和j意義不同,上一篇文章中的i表示文字的索引;j表示匹配字串的索引;這篇文章中i表示匹配字串字首的索引,j表示字尾的索引;
j的取值最大為當前字元的前一個字元,所以為aware中j最大取到r及j=0到j=3;所以j小於sizeChar.
當check[i] == check[j]成立,count增加,同時i和j繼續增加;
若不成立
說明字元不是連續相等,計數器count清零;
此時,如果字首索引為0及第一個字母,則字尾需要向後增加一個字元;否則字尾不變,字首向前移動一個字元;
關於i的回溯;
舉個例子:
ababaaba
第一個字元和第二個字元不相等;
那麼以後無論哪個字元從第二個字元b開始的字尾都不可能和字首相等;以為第二個b開始的字尾對應的是第一個a開始的字首;
此時最大的前後綴只能是以第三個a開始的字尾;
本程式的回溯存在一些問題,但是目前測試的字串計算的結果都是正確的,以後發現更好的回溯方法再更正,歡迎指正;
關於next陣列的求取辦法及詳細程式碼實現分析和i的回溯問題請檢視如下部落格:
http://blog.csdn.net/u011028771/article/details/52993198
http://blog.csdn.net/u011028771/article/details/52966473
KMP程式

//KMP字元匹配
int index_kmp(char *buffer, char *check,
    int sizeCheck, int sizeBuffer, int *inx) {
    int i = *inx;
    int j = 0;
    int flag = 0;
    int count = 0;
    int next[5] = { 0 };
    compNext(check, next, sizeCheck);
    while (i <sizeBuffer && j<sizeCheck) {
        if (buffer[i] == check[j]) {
            i++;
            j++;
        }
        else {
            if (j == 0)
                i++;
            j = next[j];
        }
    }
    if (j == sizeCheck)
        *inx = i - sizeCheck;
    else
        return FAILED;
    return OK;
}

仔細觀察就會發現,樸素匹配是

else {
            i = i - j+1;
            j = 0;
        }

而KMP演算法是

else {
            if (j == 0)
                i++;
            j = next[j];
        }

可以看到二者的區別是i值得變化;樸素匹配法回溯的是i值;KMP演算法回溯的是j值;所以對於匹配字串中相同的字串比較多時,KMP演算法的效率會優於樸素匹配;若匹配字串中字元全部不相同,KMP演算法優勢並不明顯。
關於KMP演算法還有改進的方法,以後會繼續討論。
這裡寫圖片描述

歡迎指正