KMP算法精致教程(附带Java和c的代码)

KMP算法的目的

我们现实生活中少不了字符串的查找,比如我们在搜索的时候,我们输入的字符串需要和系统匹配然后才能得到我们想要的结果,那么匹配我们想要的字符串呢?这时候我们就想着从主串开始一个一个地匹配我们的字串,如图:

1221f30644314a5aab955fdec3e8c9a7.png

我们设置一个i指向主串首字母,j指向字串首字母一次往后匹配,如果匹配成功两个指针都往后走,如果匹配失败则j返回首字母位置,i返回初始位置加1。

c8de9f7f481e43308106e20b48cce496.png

0ae92dde897c424eadf744e42f26be6f.png

这就是最容易想到并理解的BF算法。但是这种方法的弊端就是时间复杂度太高(O(m*n)),程序运行耗时较长。所以我们就想能否用另一种耗时更短的方法求解。这时候由Donald Knuth、Vaughan Pratt和James H. Morris三位计算机科学家于1970年代共同提出的字符串搜索算法。 它是一种改进的字符串匹配算法,主要用于在一个较大的文本串(通常称为“主串”)中查找一个模式串(Pattern)出现的所有位置。这种算法空间复杂度只有O(m+n),被称为KMP算法。


KMP算法的思想

KMP算法主要是通过寻找子串自身的规律来使得每次匹配都不需要退回到原点。

和BF算法相同,KMP算法也是要设置i和j两个变量分别指向主串和字串的首字母。但和BF算法不同的是KMP算法是主串的字符不回退,只回退字串的字符,并且字串的字符只回退到最适合的回退位置。如图:

27e22138b43d45fe8b0ce990ae18ee3b.png

因为abc与首字符abc相同,又因为前面和主串都对上了,所以我们不需要从头开始匹配abc而是从字串首字符abc后面的那个d开始与i匹配,也就是说j的下一个j就是d字符的下标。

那这样我们就可以从字串中找规律,当j不匹配时最佳的下一个j位置就可以找到(找到和j前面的字符串的后缀相同的前缀的下一个字符下标)。

如图

b0f7ab32692e4fcfb2bae3b5dd47e5c2.png

我们把下面那一排叫做next数组,看图发现,其实第j个字符如果和若未匹配上返回的j值(next[j],设为k)下标k的字符相同,那么第j+1个未匹配返回值就是k+1。如图:

但如果j与k不相同,那么就会走到上一个k的位置也就是next[k]。这时候我们会发现一个问题,当k=0时他会一直死循环,所以我们为了能判断他在0下标也无法匹配上,我们把0下标的next值设置为-1,如图:


代码的实现(Java以及C)

理论讲完了我们就开始实践,因为思路大致相同,所以只有Java附带了注释讲解

以下是算法主体(Java)

public int myContains(String arr) {//Kmp算法,a以普通成员变量定在类里了
        char[] test1 = a.toCharArray();
        char[] test2 = arr.toCharArray();
        int i = 0;
        int j = 0;
        int[] next = new int[arr.length()];//创建next数组,长度为字串的长度(为了一一对应)
        setnext(next, arr);
        if (a.length() < arr.length()) {
            return -1;
        }
        while (i < a.length() && j < arr.length()) {
          /* 判断条件当i和j指向超出a或j的时候跳出循环,
            防止越界,接下来在后面的代码判断因为i还是j越界*/
            if (j == -1 || a.charAt(i) == arr.charAt(j)) {
               /* 如果匹配成功则i和j指向都往后走一步,如果j等于-1则重新开始匹配,i和j也都要各自加一
            (j是因为在上述中我们把他设为-1了来判断是否和第一个字符都对不上,j加一是匹配不上要从下一个开始匹配的意思)*/
                i++;
                j++;
            } else {
                j = next[j];//寻找下一个j值
            }
        }
        if (j == (arr.length())) {//判断结束条件,如果是因为j超过限定长度则匹配成功因为字串匹配结束了,全对上了
            return i - j;
        } else {//如果因为i超过限定则匹配失败因为已经没得匹配了
            return -1;
        }
    }

以下是next数组的设置

 void setnext(int[] next, String arr) {
        int i = 2;
        int k = 0;
        next[0] = -1;//让0下标的标为-1之前讲过
        next[1] = 0;//1下标因为前面只有一个字符所以无法匹配只能为0
        while (i < arr.length()) {
            if (k == -1 || arr.charAt(i - 1) == arr.charAt(k)) {
                /*因为i这里是从2开始的,所以就相当于是我们图中讲的i+1,所以i就是i-1,
                        这里判断第i-1项和第k像是否相等*/
                next[i] = k + 1;//是则i就是k+1
                i++;//i和k都要往后继续匹配
                k++;
            } else {
                k = next[k];//当i-1下标元素和k下标元素不同时k就是next[k]
            }
        }
    }

主函数

    public static void main(String[] args) {
        MyContains my = new MyContains();
        my.a = "abcdrytxiabcdfyyuq";
        System.out.println(my.a.substring(my.myContains("abcdf")));//这里我们让他输出从匹配开始之后的字符
    }

总代码

class MyContains {
    public String a;

    void setnext(int[] next, String arr) {
        int i = 2;
        int k = 0;
        next[0] = -1;//让0下标的标为-1之前讲过
        next[1] = 0;//1下标因为前面只有一个字符所以无法匹配只能为0
        while (i < arr.length()) {
            if (k == -1 || arr.charAt(i - 1) == arr.charAt(k)) {
                /*因为i这里是从2开始的,所以就相当于是我们图中讲的i+1,所以i就是i-1,
                        这里判断第i-1项和第k像是否相等*/
                next[i] = k + 1;//是则i就是k+1
                i++;//i和k都要往后继续匹配
                k++;
            } else {
                k = next[k];//当i-1下标元素和k下标元素不同时k就是next[k]
            }
        }
    }

    public int myContains(String arr) {//Kmp算法
        char[] test1 = a.toCharArray();
        char[] test2 = arr.toCharArray();
        int i = 0;
        int j = 0;
        int[] next = new int[arr.length()];//创建next数组,长度为字串的长度(为了一一对应)
        setnext(next, arr);
        if (a.length() < arr.length()) {
            return -1;
        }
        while (i < a.length() && j < arr.length()) {
          /* 判断条件当i和j指向超出a或j的时候跳出循环,
            防止越界,接下来在后面的代码判断因为i还是j越界*/
            if (j == -1 || a.charAt(i) == arr.charAt(j)) {
               /* 如果匹配成功则i和j指向都往后走一步,如果j等于-1则重新开始匹配,i和j也都要各自加一
            (j是因为在上述中我们把他设为-1了来判断是否和第一个字符都对不上,j加一是匹配不上要从下一个开始匹配的意思)*/
                i++;
                j++;
            } else {
                j = next[j];//寻找下一个j值
            }
        }
        if (j == (arr.length())) {//判断结束条件,如果是因为j超过限定长度则匹配成功因为字串匹配结束了,全对上了
            return i - j;
        } else {//如果因为i超过限定则匹配失败因为已经没得匹配了
            return -1;
        }
    }
    public static void main(String[] args) {
        MyContains my = new MyContains();
        my.a = "abcdrytxiabcdfyyuq";
        System.out.println(my.a.substring(my.myContains("abcdf")));//这里我们让他输出从匹配开始之后的字符
    }
}

C代码

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void setnext(char* arr, int size,int *next)
{
	int j = 2, k = 0;
	next[0] = -1; next[1] = 0;
	while (j<size)
	{
		if (k==-1||(arr[j - 1] == arr[k]))
		{
			next[j] = k + 1;
			j++;
			k++;
         }
		else
		{
			k = next[k];
		}
	}
}
char*my_strstr(char*arr1, char*arr2)
{
	int i = 0; int j = 0;
	int size1 = strlen(arr1);
	int size2 = strlen(arr2);
	if (size1 < size2)
	{
		return NULL;
	}
	int* next = (int*)calloc(size2, sizeof(int));
	if (next == NULL)
	{
		perror("空间");
		return NULL;
	}
	else
	{
		setnext(arr2, size2,next);
		while (i < size1 && j < size2)
		{
			if (j==-1||arr1[i] == arr2[j])
			{
				i++;
				j++;
			}
			else
			{
				j = next[j];
			}
		}
		if (j >= size2)
		{
			free(next);
			next = NULL;
			return arr1 + i - size2;
		}
		free(next);
		next = NULL;
		return NULL;
	}
}
int main()//KMP算法1
{
	char arr1[] = "abcdabcfdfgabcfebadcba";
	char arr2[] = "abcfeba";
	char*p=my_strstr(arr1,arr2);
	if(p!=NULL)
	printf("%s", p);  

}

  • 35
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值