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演算法還有改進的方法,以後會繼續討論。