C语言数据结构第四章—— KMP算法

C语言数据结构第四章—— KMP算法

get 到的东西:

学习一个算法的思路,流程是得先明白算法的原理,看懂它的代码,知道它的逻辑链,然后自己动手复现一遍,最后改进代码达到类似的高效效率就差不多了√
KMP算法简单来说就是

  1. 想要主串读取不会倒回去读,发现实现这个想法的话需要知道匹配表PMT,知道前缀后缀的概念。
  2. 想怎么去实现这个next表,用自己和自己比。

第一部分可以在这里比较简单的看懂

关于创造next表,其实可以知道它的原理之后自己动手实现一遍,看代码可以看不懂,但是实现起来就行,只要坚持 i 不后退就行。

实现流程

这个函数分为两个步骤

  1. 写子串自身的next数组

先做这个吧,可以验证有没有写对(可以先写PMT就是value的数组,然后在录入数组的方式和加一下前缀就行)(下次试试直接写next,从PMT到next的转变要改东西,可能会漏)
0. 先做value数组,再延后输入就可以变成next数组了,下边的 next都是 value!
1. 用两个自己相比较,指向主串的(上面那串)的位置是i,子串(下面那串)的位置是j
2. 先定义放到数组位置的是当前位置的PMT(包括该位置),此时i和j位置也是指针位置
3. 把第一个next[0]赋好值,然后开始根据知道的算法用代码一步一步的写出来
第一步写的内容
第一步实现的是:

  • 从第一个值开始比较,
  • 如果相等的话就移动两个指针,同时记next是现在下边指针的位置,把指针再往后移,
  • 还有在主串读完之后再结束循环,所以加了一个for
应该再加个不相等的情况,

不相等的情况下还要分两种情况

  • j 的位置是0时,此时不相等那就是PMT=0了 ,value赋值给0,然后i到下一位,j不变,继续比
  • j 的位置不是0时,可以先往前移动j的位置, 移动到0到j位置的PMT的位置(这个比较重要,容易忘,最好画图了解),看看j往前移了之后的j到0能不能成为匹配的前缀。如果不行的话再往前移。j = nextj-1(第几个和数组下标的关系)
    再补充刚刚写的内容
    在这里插入图片描述
    在这里插入图片描述

3.好像对了?!我去把数组改成next了
next里表示的是当前位置之前的(不包括当前位置)的字母的KMP值,可以在录入时先让i+1,再录入
然后改next[0] = -1,next[1] = 0,其它正常(最后一位不用了?先看下下边的主函数需不需要)
在每次输入next的值时做个输出,看看实时的结果

printf("number %d next is %d\n", i, next[i]);

在这里插入图片描述

ok,是输出函数出了问题,因为往后移动了一位,所以next数组会比KMP多一个数,先这样吧,改一下输出函数,继续写主串子串相比了

  1. 用子串,主串,next数组进行匹配

.

    • 字符的含义a,b是主串和子串
      i,j是主串,子串的指针位置
    • i,j都是从第一个开始匹配,如果相等的话,那就继续往下匹配,
    • 一直匹配到j匹配完了,那就是有b是a的子串了
    • 一直匹配到i匹配完了,j没完,那也没有,而且整个匹配过程结束
    • 所以循环的跳出条件是i = strlen(A) || j = strlen(B)
  • 在j结束时, 不管i有没有结束,都是匹配成功。
  • i结束时,如果j结束了,那还是匹配成功,所以i的优先度高于j。出循环后用j来判断是否匹配成功。
  1. 继续想匹配条件
    • 不匹配时,i不回去,这时候就要用到之前定好的i数组了。j往前退回到next[j]+1(next[j]个相等的,然后往后移以为和i比)的位置,
    • 如果 j = 0了还不相等(这时候就是next[0] = -1的作用了,让next[j]+1 = 0 ),那这个i的位置是没有了,然后走下一个,下一个从j=0开始比

3.好像好了,测试一下?
输出的改成 i - j + 1,判断i,j出来时的含义就可
完事√(不过没有把公式揉在一起,再说吧)

C语言代码——实现部分操作

// 实现BF和KMP算法用 2020/2/19-2020/2/20
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define Size 5

typedef struct list_str2{  //str2 的结构体再上一层的定义
    char *str2;
    int max; //申请到空间的最大值
}List_str2;


void Init_str2(List_str2 *plist_str2);
void getNext(char * p, int * next);
void Print_next(int* next);
void KMP(char* a, char *b, int* next);

int main()
{
    int next[100];
    List_str2 Primary_str;
    List_str2 Substr;
    printf("输入字符给主串,一直到输入为#时停止输入\n");
    Init_str2(&Primary_str);
    printf("输入字符给子串,一直到输入为#时停止输入\n");
    Init_str2(&Substr);
    getNext( Substr.str2 , next);
    KMP(Primary_str.str2, Substr.str2, next);
    Print_next(next);



    printf("Hello world!\n");
    return 0;
}

void Init_str2(List_str2 *plist_str2) //初始化并第一次输入str2
{
    plist_str2->str2 = (char *)malloc(Size * sizeof(char));
    plist_str2->max = Size;

 //   printf("输入字符给str2,一直到输入为#时停止输入\n");
    char input_data;
    int count = -1;
    do{
        scanf("%c", &input_data);
 //       printf("看看scanf读到了啥:%c\n", input_data);
        if(input_data!= '#')
        {
            count ++;                       //结束的count是当前最后一位输入的data的数组坐标
            (plist_str2->str2)[count] = input_data;
        }
        if(count==plist_str2->max-1) //如果数据空间快不够用了,就申请新的
        {
            plist_str2->str2 = (char *)realloc(plist_str2->str2, (Size + plist_str2->max) * sizeof(char *));
            plist_str2->max += Size;
        }
    }while(input_data!='#');
    fseek(stdin, 0, SEEK_END);//清除缓存区
    (plist_str2->str2)[count+1] = '\0'; //加一个字符串结束的标志
}

void getNext(char * p, int * next)//得到字符串的next函数,不过暂时是多一位的
{
    int i, j;
    next[0] = -1;
    next[1] = 0;
    i = 1, j = 0;
    for( ; i+1 <= strlen(p); )  //i在没循环完时会一直重复
    {
        if( p[i] == p[j] )
        {
            i++;
            next[i] = j + 1; //next i 要的值是子串的第几个,所以用子串+1
//            printf("number %d next is %d\n", i, next[i]);
            j++;
        }
        else
        {
            if( j==0){   //子串第零个和主串不一样,那就没救了,这个位置的PMT是0,然后主串后移
                i++;
                next[i] = 0;
//                printf("number %d next is %d\n", i, next[i]);
            }
            else{   //不是0的话,j还能往前找 value移位之后,next数组的东西都要看下要不要改
                j = next[j];
            }
        }
    }
    next[i+1] = -5; //方便输出next数组检查对错用,在数组最后加了个-1
}

void Print_next(int* next)//比字符串多一位的输出,不过暂时是多一位的
{
    int i;
    for(i=0; next[i]!=-5; i++ )
    {
        printf("%d\t", next[i]);
    }

}

void KMP(char* a, char *b, int* next) //主串是a,子串是b
{
    int i, j; //i是主串位置,j是子串位置
    i = 0, j = 0;
    for( ; i <= strlen(a) && j <= strlen(b) ; ) //i或者j结束了就出来了
    {
        if( a[i] == b[j] ){ //每个i比的时候相等时,会继续走不回头,所以直接i++了
            i++;
            j++;
        }
        else if( j != 0){
            j = next[j] + 1;
        }
        else if( j == 0){
            i++;
        }
    }

    if( j > strlen(b) ){ //这个时候的i和j都是多加了1的,然后j满了就溢出了.j是子串的长度,i是匹配完的位置(第i个),
        printf("有子串,数组下标是%d\n", i - j + 1); // (i-1)-(j-1) = i-j表示i在匹配j的第一个之前有i-j个字符
    }                                           // 开始匹配的位置(子串的第一个位置)就是i-j+1
    else{
        printf("无子串\n");
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值