串匹配問題-BF演算法、KMP演算法、BM演算法
阿新 • • 發佈:2019-02-03
BF演算法
int BF(char A[], char B[]){ int i = 0, j = 0; while(A[i] != '\0' && B[j] != '\0'){ if(A[i] == B[j]){ i++; j++; }else{ i = i - j + 1; j = 0; } } if(B[j] == '\0'){ return i - j + 1; }else{ return -1; } }
KMP演算法:
int KMP(char A[], char B[]){ int i = 0, j = 0; int next[80]; GetNext1(B, next); while(A[i] != '\0' && B[j] != '\0'){ if(A[i] == B[j]){ i++; j++; }else{ j = next[j]; if(j == -1){ i++; j++; } } } if(B[j] != '\0'){ return -1; }else{ return (i - j + 1); } }
KMP演算法獲取next[]值得兩種方法:
方法一:
void GetNext(char B[], int next[]){ int i , j, len; next[0] = -1; for(i = 1; B[i] != '\0'; i++){ //依次求next[i] for(len = i - 1; len > 0; len--){//len:相等時的字首的最大長度為i-1,不滿足相等時就依次減小 直到找到最大長度 即為next【j】 for(j = 0; j < len; j++){ //比較B的字首和字尾是否相等 if(B[j] != B[i - len + j]) break; } if(j == len){//上步for迴圈跳出後,表示j走到了最大相等字首的下一位,也等於字首的長度 next[i] = len ; break; } } if(len < 1){//字首的最大長度為0 時 next[i] = 0; } } }
方法二:
void GetNext1( char B[], int next[]){
int j = 0, k = -1;
next[0] = -1;
while(B[j] != '\0'){
if(k == -1){ //無相等子串
j++;
next[j] = 0;
}else if(B[j] == B[k]){
next[++j] = k + 1;
}else{
k = next[k];
}
}
}
BM演算法:
int BMSearch(char *buf, int blen, char *ptrn, int plen, int *skip, int *shift)
{
int b_idx = plen;
if (plen == 0)
return 1;
while (b_idx <= blen)//計算字串是否匹配到了盡頭
{
int p_idx = plen, skip_stride, shift_stride;
while (buf[--b_idx] == ptrn[--p_idx])//開始匹配
{
if (b_idx < 0)
return 0;
if (p_idx == 0)
{
return b_idx; //匹配到
}
}
skip_stride = skip[(unsigned char)buf[b_idx]];//根據壞字元規則計算跳躍的距離
shift_stride = shift[p_idx];//根據好字尾規則計算跳躍的距離
b_idx += (skip_stride > shift_stride) ? skip_stride : shift_stride;//取大者
}
return 0;
}
BM演算法獲取壞字元表:
int* MakeSkip(char *btrn, int bLen)
{
int i;
//為建立壞字元表,申請256個int的空間
//PS:之所以要申請256個,是因為一個字元是8位,
// 所以字元可能有2的8次方即256種不同情況
int *skip = (int*)malloc(256*sizeof(int)); //頭指標
if(skip == NULL)
{
printf("Error");
return 0;
}
//初始化壞字元表,256個單元全部初始化為pLen
for(i = 0; i < 256; i++)
{
*(skip+i) = bLen;
}
//給表中需要賦值的單元賦值,不在模式串中出現的字元就不用再賦值了
//賦值,從左到右遍歷btrn,這樣如果一個字元出現兩次,後面的覆蓋前面的,
//不在模式中出現的字元不用再賦值,它們使用預設值bLen。
while(bLen != 0)
{
*(skip+(int)*btrn++) = bLen--;
}
return skip;
}
BM演算法獲取好字尾表:
int* MakeShift(char* btrn,int bLen)
{
//為好字尾表申請pLen個int的空間
//這樣,第一個位置放置長度為1的字尾
int *shift = (int*)malloc(bLen*sizeof(int));
int *sptr = shift + bLen - 1;//方便給好字尾表進行賦值的指標
char *pptr = btrn + bLen - 1;//記錄好字尾表邊界位置的指標
char c;
if(shift == NULL)
{
printf("Error");
return 0;
}
c = *(btrn + bLen - 1);//儲存模式串中最後一個字元,因為要反覆用到它
*sptr = 1;//以最後一個字元為邊界時,確定移動1的距離(因為要與壞字元規則比較,所以這個是個假設,1也是最小的移動距離)
pptr--;//邊界移動到倒數第二個字元
while(sptr-- != shift)//該最外層迴圈完成給好字尾表中每一個單元進行賦值的工作
{
char *p1 = btrn + bLen - 2, *p2,*p3;
//該do...while迴圈完成以當前pptr所指的字元為邊界時,要移動的距離
do{
while(p1 >= btrn && *p1-- != c);//該空迴圈,尋找與最後一個字元c匹配的字元所指向的位置
p2 = btrn + bLen - 2;
p3 = p1;
while(p3 >= btrn && *p3-- == *p2-- && p2 >= pptr);//該空迴圈,判斷在邊界內字元匹配到了什麼位置
}while(p3 >= btrn && p2 >= pptr);
*sptr = shift + bLen - sptr + p2 - p3;//儲存好字尾表中,以pptr所在字元為邊界時,要移動的位置
pptr--;//邊界繼續向前移動
}
return shift;
}
主函式:
int main()
{
char A[] = "abcdesdeacd";
char B[] = "acd";
int k = BF(A, B);
printf("%d\n", k);
k = KMP(A, B);
printf("%d\n", k);
char *T = "abcdesdeacd";
char *P = "acd";
int *skip = NULL;
int *shift = NULL;
skip = MakeSkip(P, strlen(P));
shift = MakeShift(P, strlen(P));
printf("%d\n", BMSearch(T, strlen(T), P, strlen(P), skip, shift) + 1);
return 0;
}
測試結果: