KMP 快速匹配算法

最近学习了KMP快速匹配算法,自己动手尝试着编写,最终也完成了该算法,理清思路后发表该博客,希望不懂的小白可以参考(我也是小白)

      首先KMP算法是字符串快速匹配算法。简而言之就是快速在一个字符串里查找相应的内容。比如word的查找功能

模拟应用场景: 在asdasdasdddasdasd  快速找到asddd第一次出现时对应下标。

一般的串匹配算法(非KMP算法)KMP 快速匹配算法

KMP算法:

设主串(T)为:a b a c a a b a c a b a c a b a a b b

模式串(W)为:a b a c a b

而在KMP算法中,对于每一个模式串我们会事先计算出模式串的内部匹配信息,在匹配失败时最大的移动模式串,以减少匹配次数。

比如,在简单的一次匹配失败后,我们会想将模式串尽量的右移和主串进行匹配。右移的距离在KMP算法中是如此计算的:在已经匹配的模式串子串中,找出最长的相同的前缀后缀,然后移动使它们重叠。

在第一次匹配过程中

T :   a b c a b c a b c a a a b c

W:   a b c a b c a a

在T[7]与W[7]出现了不匹配,而T[0]~T[6]是匹配的,现在T[0]~T[6]就是上文中说的已经匹配的模式串子串,现在移动找出最长的相同的前缀和后缀并使他们重叠:移动一次出现以下(仅仅一次)

T :   a b c a b c a b c a a a b c

W:            a b c a b c a a

此时直接从W[4]与T[7]开始一一对应往下匹配,发现立即找到了符合条件的下标

然后在从上次匹配失败的地方进行匹配,这样就减少了匹配次数,增加了效率。

但是如何找到W[4],让其与T[7]开始匹配首先我们分析下a b c a b c a a这个数据

当a b c a b c a a中红色a与T开始匹配时,失陪(没有匹配成功)但是前面的abc出现了两次也就说明T串中前7个是匹配成功的。

此时我们可以直接让W[0]与T[3]对应,对应后发现

T :   a b c a b c a b c a a a b c

W:            a b c a b c a a

W中红色b之前的还是匹配的,也就是T[7]不需要回到T[3]开始匹配。这样的话我们只需要找到移动W串相应的步数即可!

j下标 0 1 2 3 4 5 6 7
W[j] a b c a b c a a
F(j) 0 0 0 0 1 2 3 1

F(j)为不需要匹配的个数(即从F(j)出开始匹配)如W[6]对应的F[j]为3  即为W中前三个不需要匹配.

以下为计算F[j]代码:

int *getfreq(char *str) {
    int *freq = NULL;
    int i = 0;
    int j = -1;
    
    freq = (int *) calloc(strlen(str) + 1, sizeof(int));
    freq[0] = -1;
    while(i < strlen(str)) {
        if(j == -1 || str[j] == str[i]){
            i++;
            j++;
            freq[i] = j;
        }
        else{
            j = freq[j];
        }    
    }
    freq[0] = 0;
    return freq;
}

做到这一步已经完成了一大半:下面就是用freq数组进行移动!

整体代码如下

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

void getindex(char *str1, char *str2, int *freq) {
    int i = 0;
    int j = 0;
    int flog = 1;
    
    while(str1[i] != 0 && flog == 1){
        if(str1[i] == str2[j]){
            i++;
            j++;
        }
        else{
            j = freq[j];
            i++;
        }
        if(j == strlen(str2)){
            flog = 0;
        }
    }
    if(flog != 0){
        printf("没有找到");
    }
    else{
        printf("第一次该内容对应的下标为:%d", i - strlen(str2));
    }
}
int *getfreq(char *str) {
    int *freq = NULL;
    int i = 0;
    int j = -1;
    
    freq = (int *) calloc(strlen(str) + 1, sizeof(int));
    freq[0] = -1;
    while(i < strlen(str)) {
        if(j == -1 || str[j] == str[i]){
            i++;
            j++;
            freq[i] = j;
        }
        else{
            j = freq[j];
        }    
    }
    freq[0] = 0;
    /*for(i = 0; i < strlen(str); i++){
        printf("%d",freq[i]);
    }*/
    return freq;
}
int main(){
    char s1[100];
    char s2[100];
    int *freq = NULL;
    
    printf("请输入源字符串:\n");
    gets(s1);
    printf("请输入要查找的内容:\n");
    gets(s2);
    freq = getfreq(s2);
    getindex(s1, s2, freq);
    free(freq);
    return 0;
}