1. 程式人生 > >KMP演算法(next演算法)

KMP演算法(next演算法)

KMP演算法之前需要說一點串的問題:

字串:ASCII碼為基本資料形成的一堆線性結構。

串是一個線性結構;它的儲存形式:

typedef struct STRING {

  CHARACTER *head;

  int length;

};

樸素的串匹配演算法:

設文字串text = "ababcabcacbab",模式串為patten = "abcac"     其匹配過程如下圖所示。

黑色線條代表匹配位置,紅色斜槓代表失配位置

 

 

 

演算法說明:

一般匹配字串時,我們從目標字串text(假設長度為n)的第一個下標選取和patten長度(長度為m)一樣的子字串進行比較,如果一樣,就返回開始處的下標值,不一樣,選取text下一個下標,同樣選取長度為n的字串進行比較,直到str的末尾(實際比較時,下標移動到n-m)。在普通的匹配中,假如從文字串的第i個字元來開始於模式串匹配。當匹配到模式串的第j位發現失配,即text[i+j] != patten[j]的時候,我們又從文字串的第i+1個位置來重新開始匹配。儘管我們已經知道了好多字元其實根本就匹配不上,我們還是進行了這個過程,這個時候回溯的過程會非常耗費我們的時間。這樣的時間複雜度是O(n*m)

程式碼如下:



int search(const char*str,const char *subStr) {
    int strlen = strlen(str);
    int subStrlen = strlen(subStr);
    int i;
    int j;
    for(i = 0;i <= strlen - subStrlen;i++){
        for(j = 0;j < subStrlen;j++){
            if(str[i + j] != subStr[j])
            break;
        }判斷subStrlen是否比較完成 
    }
}

KMP演算法:

而KMP演算法的實質就是,當遇到text[i+j] != patten[j]的時候,但是我們知道模式串中的 0~j-1 位置上的字元已經於i ~ i+j-1位置上的字元是完全匹配的。就不再重新從text[i+1]開始匹配,而是根據next陣列的下標找到patten的下標,從那個下標開始匹配。從而時間複雜度為O(m+n)。

我們可以看到這次的匹配在藍色的c失配了,而c的下標為5,他的next陣列的下標為2。因此,下次的匹配不再是從text[1]開始,而是從text[2]開始,這樣就省去了不必要的比較。

 

程式碼如下:

#include <stdio.h>
#include <malloc.h>
#include <string.h>

#include "kmpmec.h"

void getNext(const char *str, int *next);
int KMPSearch(const char *str, const char *subStr);

int KMPSearch(const char *str, const char *subStr) {
    int strLen = strlen(str);
    int subLen = strlen(subStr);
    int *next;
    int i = 0;
    int j = 0;

    if (strLen <= 0 || subLen <= 0 || strLen < subLen) {
        return -1;
    }
    next = (int *) calloc(sizeof(int), subLen);
    if (subLen > 2) {
        getNext(subStr, next);
    }

    while (strLen - i + next[j] >= subLen) {
        while (subStr[j] != 0 && str[i] == subStr[j]) {
            i++;
            j++;
        }
        if (subStr[j] == 0) {
            free(next);
            return i - subLen;
        } else if (j == 0) {
            i++;
            j = 0;
        } else {
            j = next[j];
        }
    }

    free(next);
    return -1;
}

void getNext(const char *str, int *next) {  //得到next陣列 
    int i = 2;
    int j = 0;
    boolean flag;

    next[0] = next[1] = 0;  //next陣列的前兩個下標一定為零 
    for (i = 2; str[i]; i++) {
        for (flag = TRUE; flag;) {
            if (str[i-1] == str[j]) { //通過比較失配位置的前一個和前一個下標元素的比較,獲取next陣列的下標。
                next[i] = ++j;
                flag = FALSE;
            } else if (j == 0) {
                next[i] = 0;
                flag = FALSE;
            } else {
                j = next[j];
            }
        }
    }
}

int main(void) {
    char str[80];
    char subStr[80];
    int result;

    printf("請輸入源字串:");
    gets(str);
    printf("請輸入子字串:");
    gets(subStr);

    result = KMPSearch(str, subStr);
    if (result == -1) {
        printf("字串[%s]不存在子串[%s]\n", str, subStr);
    } else {
        printf("子串[%s]第一次出現在字串[%s]中的下標為%d\n", subStr, str, result);
    }

    return 0;
};