1. 程式人生 > >數據結構(三)串---KMP模式匹配算法實現及優化

數據結構(三)串---KMP模式匹配算法實現及優化

warn 查看 技術分享 方法 sign 匹配 pan 相同 span

KMP算法實現

技術分享圖片
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define MAXSIZE 40

typedef int ElemType;
typedef int Status;

//設置串的存儲結構
typedef char String[MAXSIZE+1];

//生成串相關
Status StrAssign(String S,char
*chars); //生成一個其值等於字符串常量的chars的串T Status StrCopy(String T, String S); //串S存在,由串S復制得到串T Status Concat(String T, String S1, String S2); //用T返回由S1和S2連接而成的新串 //基礎操作相關 Status ClearString(String S); //串S存在,將串清空 Status StringEmpty(String S); //若串為空,返回true,否則false int StringLength(String S); //
返回串S的元素個數,長度 //比較串,索引串相關 int StrCompare(String S, String T); //若S > T返回 > 0, = 返回0, < 返回 < 0 Status SubString(String Sub, String S,int pos,int len); //串S存在,返回S由pos起,長度為len的子串到Sub int Index(String S, String T,int pos); //主串S,子串T,返回T在S中位置 //增刪改相關 Status Replace(String S, String T, String V); //
串S,T和V存在,T非空,用V替換主串S中T串 Status StrInsert(String S, int pos, String T); //在主串S中的pos位置插入串T Status StrDelete(String S,int pos,int len); //串S存在,從串S中刪除第pos個字符串起長度為len的子串 void PrintStr(String S); //生成串相關 //生成一個其值等於字符串常量的chars的串T Status StrAssign(String S, char *chars) { int i; if (strlen(chars) > MAXSIZE) return ERROR; else { S[0] = strlen(chars); for (i = 1; i <= S[0];i++) S[i] = *(chars+i-1); return OK; } } //串S存在,由串S復制得到串T Status StrCopy(String T, String S) { int i; for (i = 0; i <= S[0];i++) T[i] = S[i]; return OK; } //用T返回由S1和S2連接而成的新串,若是超出,會截斷,但是會進行連接,返回FALSE Status Concat(String T, String S1, String S2) { int i,j,interLen; //interLen是截斷後S2剩余長度 if (S1[0] + S2[0] > MAXSIZE) interLen = MAXSIZE - S1[0]; else interLen = S2[0]; T[0] = S1[0] + S2[0]; for (i = 1; i <= S1[0]; i++) T[i] = S1[i]; for (j = 1; j <= interLen; j++) T[i+j-1] = S2[j]; if (interLen != S2[0]) return ERROR; return OK; } //基礎操作相關 //串S存在,將串清空 Status ClearString(String S) { S[0] = 0; return OK; } //若串為空,返回true,否則false Status StringEmpty(String S) { if (S[0] != 0) return FALSE; return TRUE; } //返回串S的元素個數,長度 int StringLength(String S) { return S[0]; } //比較串,索引串相關 //若S > T返回 > 0, = 返回0, < 返回 < 0 int StrCompare(String S, String T) { int i; for (i = 1; i <= S[0] && i <= T[0]; i++) if (S[i] != T[i]) return S[i] - T[i]; return S[0]-T[0]; //若是相同比較長度即可 } //串S存在,返回S由pos起,長度為len的子串到Sub Status SubString(String Sub, String S, int pos,int len) { int i; if (pos<1 || len<0 || pos + len - 1 > S[0] || pos>S[0]) return ERROR; for (i = 0; i < len;i++) Sub[i + 1] = S[pos + i]; Sub[0] = len; return OK; } //主串S,子串T,返回T在S中位置,pos代表從pos開始匹配 //或者一次截取一段進行比較為0則找到 int Index(String S, String T, int pos) { int i, j; i = pos; j = 1; while (i<=S[0]-T[0]+1&&j<=T[0]) { if (S[i]==T[j]) { j++; i++; } else { i = i - j + 2; //註意這個索引的加2 j = 1; } } if (j > T[0]) return i - T[0]; return 0; } //增刪改相關 //串S,T和V存在,T非空,用V替換主串S中T串 Status Replace(String S, String T, String V) { int idx=1; if (StringEmpty(T)) return ERROR; while (idx) { idx = Index(S, T, idx); if (idx) { StrDelete(S, idx, StringLength(T)); StrInsert(S, idx, V); idx += StringLength(V); } } return OK; } //在主串S中的pos位置插入串T,註意:若是串滿,則只插入部分 Status StrInsert(String S, int pos, String T) { int i,interLength; if (S[0] + T[0] > MAXSIZE) //長度溢出 interLength = MAXSIZE - S[0]; else interLength = T[0]; for (i = S[0]; i >= pos;i--) S[interLength + i] = S[i]; //將後面的數據後向後移動 //開始插入數據 for (i = 1; i <= interLength; i++) S[pos + i - 1] = T[i]; S[0] += interLength; if (interLength != T[0]) return ERROR; return OK; } //串S存在,從串S中刪除第pos個字符串起長度為len的子串 Status StrDelete(String S, int pos,int len) { int i; if (pos < 1 || len<1 || pos + len - 1>S[0]) return ERROR; //將數據前移 for (i = pos+len; i <= S[0];i++) S[i-len] = S[i]; S[0] -= len; return OK; } void PrintStr(String S) { int i; for (i = 1; i <= StringLength(S);i++) { printf("%c", S[i]); } printf("\n"); }
串的順序存儲結構
//通過計算返回子串T的next數組
void get_next(String T, int* next)
{
    int m, j;
    j = 1;    //j是後綴的末尾下標
    m = 0;    //m代表的是前綴結束時的下標
    next[1] = 0;
    while (j < T[0])
    {
        if (m == 0 || T[m] == T[j])    //T[m]表示前綴的最末尾字符,T[j]是後綴的最末尾字符
        {
            ++m;
            ++j;
            next[j] = m;
        }
        else
            m = next[m];    //若是字符不相同,則m回溯
    }
}
int Index_KMP(String S, String T, int pos)
{
    int i = pos;
    int j = 1;
    int next[MAXSIZE];
    get_next(T, next);
    while (i<=S[0]&&j<=T[0])  //其實現與BF算法相似,不過不同的是i不進行回溯,而是將j進行了修改
    {
        if (j==0||S[i]==T[j])
        {
            ++i;
            ++j;  //若完全匹配後,j就會比模式串T的長度大一
        }
        else  //不匹配時,就使用next數組獲取下一次匹配位置
        {
            j = next[j];
        }
    }
    if (j > T[0])
        return i - T[0];
    else
        return 0;
}
技術分享圖片
int main()
{
    int i, j;
    String s1,s2,t;
    
    char *str = (char*)malloc(sizeof(char) * 40);
    memset(str, 0, 40);
    printf("enter s1:");
    scanf("%s", str);
    if (!StrAssign(s1, str))
        printf("1.string length is gt %d\n", MAXSIZE);
    else
        printf("1.string StrAssign success\n");

    printf("enter s2 to match:");
    scanf("%s", str);
    if (!StrAssign(s2, str))
        printf("1.string length is gt %d\n", MAXSIZE);
    else
        printf("1.string StrAssign success\n");
    
    i = Index_KMP(s1, s2, 1);
    printf("index:%d", i);

    system("pause");
    return 0;
}
main函數

技術分享圖片

KMP算法優化---對next數組獲取進行優化

原來我們獲取的next數組是由缺陷的

技術分享圖片

我們可以發現j5與i5失配,所以按照上面的next值,會去匹配j4-j3-j2-j1,但是我們從前面的思路啟發知道,當我們知道j1=j2=j3=j4=j5,而j5≠i5,那麽我們完全可以知道j1到j4也是不與i5匹配,
所以,我們這裏做了太多的重復匹配。這就是我們需要優化的地方
由於T串的第二三四五位的字符都與首位的a相等,那麽可以用首位next[1]的值去取代與他相等的字符後續的next[j]值 

改進方法:

我們將新獲取的next值命名為nextval,則新的nextval與他同列的next值有關,我們找的一next[j]為列值的新的j列,將字符進行比較,若是相同,則將該列的next值變為現在的nextval

例如:

推導1:

 第一步:當j=1時,nextval=0

技術分享圖片

第二步:獲取j=2時,nextval[2]的值,首先我們需要獲取next[2]值為1,然後我們將這個值作為新得前綴獲取j=1時的T串數據a,發現他與當前j=2處的字符b不同,那麽nextval[2]不變,與原來的next[2]值一樣,為1

技術分享圖片

第三步:獲取j=3時,nextval[3]的值,我們先獲取next[3]的值為1,然後將這個值作為新的索引獲取j=該值處的字符T[1]=a,發現T[3]=T[1],所以當前的nextval值為j=1處的nextval值,即nextval[3]=nextval[1]=0

技術分享圖片

第四步:獲取j=4時,nextval[4]的值,相應的next[4]為2,獲取T[2]字符‘b’,與當前所以T[4]=b相同,那麽當前nextval[4]=nextval[2]=1

技術分享圖片

第五步:獲取j=5時,nextval[5]的值,相應next[5]=3,查看T[3]字符為a,而當前T[5]=a,相同,那麽nextval[5]=nextval[3]=0

技術分享圖片

第六步:獲取j=6時,nextval[6]的值,相應next[6]=4,查看T[4]字符為b,而當前T[6]=a,不相同,那麽nextval[5]就等於原來的next[5]值為4

技術分享圖片

第七步:獲取j=7時,nextval[7]的值,相應next[7]=2,查看T[2]字符為b,而當前T[7]=a,不相同,那麽nextval[7]就等於原來的next[7]值為2

技術分享圖片

第八步:獲取j=8時,nextval[8]的值,相應next[8]=2,查看T[2]字符為b,而當前T[8]=b,相同,那麽nextval[7]=nextval[2]=1

技術分享圖片

第九步:獲取j=9時,nextval[9]的值,相應next[9]=3,查看T[3]字符為a,而當前T[9]=a,相同,那麽nextval[9]=nextval[3]=0

技術分享圖片

推導2:對上面進行優化的步驟演示

技術分享圖片

第一步:當j=1時,nextval=0

第二步:當j=2時,next[2]=1,T[2]=T[1]=‘a‘,nextval[2]=nextval[1]=0

第三步:當j=3時,next[3]=2,T[3]=T[2]=‘a‘,nextval[3]=nextval[2]=0

第四步:當j=4時,next[4]=3,T[4]=T[3]=‘a‘,nextval[4]=nextval[3]=0

第五步:當j=5時,next[5]=4,T[5]=T[4]=‘a‘,nextval[5]=nextval[4]=0

第六步:當j=6時,next[6]=5,T[6]=‘x‘,T[5]=‘a‘,T[6]≠T[5],nextval[6]=next[6]=5

技術分享圖片

nextval數組優化代碼

void get_nextval(String T, int* nextval)
{
    int m, j;
    j = 1;    //j是後綴的末尾下標
    m = 0;    //m代表的是前綴結束時的下標
    nextval[1] = 0;
    while (j < T[0])
    {
        if (m == 0 || T[m] == T[j])    //T[m]表示前綴的最末尾字符,T[j]是後綴的最末尾字符
        {
            ++m;
            ++j;
            if (T[j] != T[m])        //若當前字符與前綴字符不同
                nextval[j] = m;        //則當前的j為nextval在i位置的值
            else
                nextval[j] = nextval[m];    //若與前綴字符相同,則將前綴字符的nextval值賦給nextval在i位置的值
        }
        else
            m = nextval[m];    //若是字符不相同,則m回溯
    }
}

數據結構(三)串---KMP模式匹配算法實現及優化