• 数据结构(三)串---KMP模式匹配算法实现及优化


    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("
    ");
    }
    串的顺序存储结构
    //通过计算返回子串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
    ", MAXSIZE);
        else
            printf("1.string StrAssign success
    ");
    
        printf("enter s2 to match:");
        scanf("%s", str);
        if (!StrAssign(s2, str))
            printf("1.string length is gt %d
    ", MAXSIZE);
        else
            printf("1.string StrAssign success
    ");
        
        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回溯
        }
    }

    总结:

    若是迷迷糊糊,不如在演草纸上多默写几遍,慢慢就会有思路了...
  • 相关阅读:
    shell笔记
    全引用与部分引用
    R语言 EFA(探索性因子分析)
    Markdown 基本使用
    Oracle截取字符串和查找字符串
    ggplot画基本图形类型
    ggplot画图笔记
    用R语言 画条形图(基于ggplot2包)
    Git基本操作
    R语言为数据框添加列名或行名
  • 原文地址:https://www.cnblogs.com/ssyfj/p/9457898.html
Copyright © 2020-2023  润新知