KMP算法和求next数组以及其优化

kmp算法在搞懂以后,会觉得求next的过程是非常的美。

//数据结构作业 KMP算法。
//ABAABAAABAAAAB   AAAAB(next[])( next_val[])  1用next[]匹配一遍   2用next_val[]匹配一遍

//    A   A   A   A   B
//    0   1   2   3   4           坐标
//   -1   0   1   2   3           next数组
//   -1   0  -1  -1   3           nextVal数组

int* getNext(char* str ,int length){
    //length的长度小于1就直接扔掉。
    if (length <1){
        return new int[1]{-1};
    }
    //动态声明一个next数组用来存放最长匹配子串
    int *next = new int[length];
    //人为规定,第一个位置初始化为-1,第二个位置初始化为0
    next[0]=-1;
    next[1] =0;
    //j是从第二个位置开始后的,是next数组的一个位置。
    int j = 2;
    //k是最大前缀的下一个字符的位置,也代表了前缀的长度
    int k = 0;
    //这里的while 其实是使用了数学归纳法:假设前一个正确,那么当前值也一定正确。
    while (j<length) {
        //判断j前一个位置的字符是否和k位置的字符相同,这里的K使用的是最大前缀的下一个字符的位置的含义。
        if (str[j-1] == str[k]) {
            //如果相等,先让k自加(++k后,k代表了一个新的最大前缀的下一个位置,)
            //赋值的含义是 使用了k是最大前缀的字符数
            next[j++] = ++k;
        }else if(k > 0){
            //如果不相等,且K现在还是大于0的
            //直接让k跳到字符串的next[k]的位置。进行下一轮while循环。
            //这里 可能比较难理解。
            //可以看下博客。
            //https://www.cnblogs.com/tangzhengyue/p/4315393.html
            k = next[k];
        }
        else{
            //此时的k是等于0的;但是不能赋值赋值为next[k],因为next[k]为-1;
            //故直接给next的j位置赋值为0;表示没有匹配位置。
            next[j++] = 0;
        }
    }
    return next;
}
int* getNext_val(char* str ,int length){
    if (length <1){
        return new int[1]{-1};
    }
    int j = 2;
    int k = 0;
    int *next_val = new int [length];
    next_val[0]=-1;
    next_val[1] =0;
    while (j<length) {
        if (str[j-1] == str[k]) {
            //kmp 的优化在这里。
            //在当前元素的前一个元素 与 k是代表了数学归纳法之后 j前一个元素的最大前缀数。
            //k也代表了这个最大前缀的下一个next的位置

            //上面判断的是j-1和k相等。这里判断j值k+1
            //j-1和k判断的是最大值的下一元素。
            //j和k+1判断的是最大值的后面的第二个元素,因为如果后第二个元素相同时,就没有必要比较了。
            if (str[j] == str[k+1]) {
                //如果是相同的,j位置直接赋值为++k位置的next值。
                //这个地方理解的时候一定要往 str[++k]和 str[j] 相等的方向考虑,否则难以理解。
                next_val[j++] = next_val[++k];
            }
            else{
                //如果不同,直接赋值为++k的 也就是j-1位置的最长前缀的数+1
                next_val[j++] = ++k;
            }
        }else if(k == 0){
            next_val[j++] = 0;
        }
        else{
            k = next_val[k];
        }
    }
    return next_val;

}
/*
 s[]为第一个字符串
 m[]为第二个字符串
 sLength为第一个字符串的长度
 mLength为第二个字符串的长度
 */
int getIndexOf(char s[] ,char m[],int sLength,int mLength){
    if (mLength < 1 || sLength < 1) {
        return -1;
    }
    int i = 0;
    int j = 0;
    //拿到next数组
    int* next = getNext_val(m,mLength);
    while (i < sLength && j< mLength) {
        //相等一起移动
        if (s[i] == m[j]) {
            i++;
            j++;

        }
        //不等,只要返回值大于-1,这说明这两个字符串还能玩下去。直接跳下一个next;如果下一个next为0,m就跳0位置
        else if(next[j] > -1){
            j = next[j];
        }
        //不等,但是返回值为-1,说明 m字符串的第一个字符也不能和s匹配了,直接让s跳下一个字符,继续匹配
        else if (next[j] == -1){
            i++;
        }
    }
    return j == mLength ? i - j: -1;
}

//string类型 转 char类型的字符串指针
char *toCharArray(string oldStr ,int length){
    char* newChar = new char[length];
    strcpy(newChar, oldStr.c_str());
    return newChar;
}
//测试数据
int main() {
    string str1 = "ABAABAAABAAAAB";
    string str2 = "AAAAB";
    int str1Length =  (int)str1.length();
    int str2Length =  (int)str2.length();

    char* str1Char = toCharArray(str1, str1Length);
    char* str2Char = toCharArray(str2, str2Length);

    int index = getIndexOf(str1Char, str2Char, str1Length, str2Length);
    cout<< index<<endl;

    return 0;
}

猜你喜欢

转载自blog.csdn.net/opooc/article/details/81188687
今日推荐