KMP算法解析

简介

初学KMP,在网络上找到的字符串基本都是从数组下标为1的位置开始的,因此来补充一个下标从0开始的字符串的KMP算法的C语言实现。

头文件见上篇博客,即C语言字符串基本操作

代码展示

#include<stdio.h>
#include<stdlib.h>
#include"StringForC.h"

//KMP算法
void SolveNext(String Son , int next[]);
int KMP(String Father , String Son , int next[] , int StartPosition);

int main()
{
    String Father , Son;
    if(CreateString(&Father) && CreateString(&Son))
    {
        int *next = (int *)malloc(sizeof(int) * Son.Length);
        SolveNext(Son , next);
        int Position;
        Position = KMP(Father , Son , next , 0);
        printf("%d" , Position);
    }
    return 0;
}

// 求解next数组
void SolveNext(String Son , int next[])
{
    int j = 0;
    int k = -1;
    next[0] = -1;       // 说明主串从下一个字符开始和模式串重新匹配
    while(j < Son.Length)
    {
        if(k == -1 || Son.Value[j] == Son.Value[k])     //如果是重新匹配或字符匹配成功
        {
            next[++j] = ++k;                            
        }
        else                    // 如果匹配失败,则把求值问题看作一个模式匹配问题
        {
            k = next[k];
        }
    }

}

int KMP(String Father , String Son , int next[] , int StartPosition)
{
    int ReturnPosition;
    int i = StartPosition;
    int j = -1;
    int Flag = 1;                       // 作为开始匹配的标记
    while(i < Father.Length && j < Son.Length)
    {
        if(Flag)      
        {
            j++;
            Flag = 0;
        }
        else if(j == -1 || Father.Value[i] == Son.Value[j])
        {
            i++;
            j++;
        }
        else                                                // 如果匹配失败,模式串回退
        {
            j = next[j];
        }
    }

    if(j >= Son.Length)                                     // 如果模式串全部匹配完毕
        ReturnPosition = i - Son.Length;
    else
        ReturnPosition = -1;                                  // 如果未能搜索到
    
    return ReturnPosition;
}

算法阐述

  对于字符串匹配问题,回溯匹配算法的时间复杂度为O(nm),而KMP算法的时间复杂度为O(n+m)。
  直白的说,KMP算法的核心问题就是求解next函数的值,next函数的抽象化表示我就不做赘述了,描述的肯定没有各位大佬清晰,只是说说
  我个人的理解:
  由于KMP算法主串不回溯,只有模式串回溯,并且回溯长度视情况而定,而这个情况,分为三种,第一种,如果主串和模式串第一个字符就不匹配的情况下,此时next的函数值设置为一个标记值,表示模式串不回退,第二种,如果模式串不是在第一个字符就失配,那么就要进行讨论,模式串需要回溯多少:
  我们来看一个例子:
  主串:  ababaf
  模式串:abaf
  进行字符串匹配,首先aba三个字符均匹配成功,第四个字符b与f不匹配,此时再进行观察,发现失配字符前的字符串有一个特征:a字符重复了,此时如果仍然将模式串指针回退至首位,会造成信息 (即a)浪费,因此,我们希望能保留这个a,即将模式串指针回退到b,以模式串第二位(b)去对比主串的失配字符(b)。
  那么问题来了:如何判断需要模式串需要回退多少?
  这就是KMP算法的核心,即next函数,通过寻找模式串中相同的前后 缀的字符数目的最大值,来确定next函数的值,仍然继续看这两个字符串:
  主串:  ababaf
  模式串:abaf
  模式串中,aba具有这一特征,以b为中心,前缀和后缀都是a,我们需要的是回退到b,即回退到第2个字符,而前后缀重复的字符数是1,因此,next的值为重复字符数+1,即2,而next值记录的是失配是回退的位置,因此当 j (失配位置)为4时,next的值为2,以此类推:
  j = 1 : next = 0(标记值)
  j = 2 : next = 0+1 = 1
  j = 3 : next = 0+1 = 1
  j = 4 : next = 1+1 = 2
  这就确定了next的值。

代码解析

  1. 在C语言中求解next的值:
    next作为整型数组出现,长度与模式串长度相同,这里需要做一下改变,由于数组和字符串第一位都是0,因此标记值应为-1,模式串前后缀无重复数值时的值应为-1,加一后为0,退到首位。
// 求解next数组
void SolveNext(String Son , int next[])
{
    int j = 0;
    int k = -1;
    next[0] = -1;       // 说明主串从下一个字符开始和模式串重新匹配
    while(j < Son.Length)
    {
        if(k == -1 || Son.Value[j] == Son.Value[k])     //如果是重新匹配或字符匹配成功
        {
            next[++j] = ++k;                            
        }
        else                    // 如果匹配失败,则把求值问题看作一个模式匹配问题
        {
            k = next[k];
        }
    }
}
  1. 进行主串与模式串匹配:
    匹配时遇到的问题就是如果从头开始就失配,则会时主串指针移动到第二位,导致匹配出错,因此设计一个标记变量,作为开头匹配和第一个字符即失配的区别
int KMP(String Father , String Son , int next[] , int StartPosition)
{
    int ReturnPosition;
    int i = StartPosition;
    int j = -1;
    int Flag = 1;                       // 作为开始匹配的标记
    while(i < Father.Length && j < Son.Length)
    {
        if(Flag)      
        {
            j++;
            Flag = 0;
        }
        else if(j == -1 || Father.Value[i] == Son.Value[j])
        {
            i++;
            j++;
        }
        else                                                // 如果匹配失败,模式串回退
        {
            j = next[j];
        }
    }

    if(j >= Son.Length)                                     // 如果模式串全部匹配完毕
        ReturnPosition = i - Son.Length;
    else
        ReturnPosition = -1;                                  // 如果未能搜索到
    
    return ReturnPosition;
}

初学者,若有错误,欢迎指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值