串---KMP算法及其优化实现

KMP 算法详解

KMP 算法(Knuth-Morris-Pratt 算法)是一种高效的字符串匹配算法,它可以在 O(n+m) 时间复杂度内完成模式串在主串中的搜索工作,其中 n 是主串的长度,m 是模式串的长度。KMP 算法的核心思想是利用模式串自身的结构特点,避免不必要的回溯,从而提高效率。

本文档将详细介绍 KMP 算法的原理、步骤以及如何在 C 语言中实现。

1. KMP 算法原理

1.1 前缀与后缀

对于模式串 s,一个长度为 k 的前缀是指从模式串开头连续取 k 个字符形成的子串;一个长度为 k 的后缀是指从模式串末尾连续取 k 个字符形成的子串。

1.2 最长公共前后缀

最长公共前后缀是指模式串中最长的一个既是前缀又是后缀的子串。例如,对于模式串 ABCDABCD,最长公共前后缀是 ABCD

1.3 next 数组

next 数组是 KMP 算法中的关键数据结构,它记录了模式串中每个位置的最长公共前后缀的长度。对于模式串 snext[j] 表示 s[1..j] 的最长公共前后缀的长度。

1.4 nextval 数组

nextval 数组是 next 数组的一种优化版本,它可以更高效地跳过不必要的比较。nextval[j] 表示 s[1..j] 的最长公共前后缀的长度,但当 s[next[j]] != s[j] 时,可以直接跳到 nextval[j]

2. KMP 算法步骤

2.1 获取 next 数组 (getNext)

此函数用于计算模式串的 next 数组。

//获得next数组,s为模式串,next为所求next数组 
void getNext(char *s,int *next){
	//i表示从第二个字符开始, 
	int i = 1;
	//k表示最长公共前后缀的数量
	int k = 0;
	//next[0]没有值,next[1]为第一个字符,没有前后缀,直接赋值为0 
	next[1] = 0; 
	//模式串的长度,这里之所以要减一,是因为把\0也算在里面了 
	int length = strlen(s)-1;
	//从第二个字符开始,一直到最后一个字符 
	while(i < length){
		//1.j等于0,说明现在是第二个字符,前面只有一个字符,则赋值为1
		//2.当s[i]==s[k],s[i]表示现在扫描到的字符,与前一个公共最长前后缀的前缀的后一个字符比较
		//如果相等,说明该公共最长前后缀加1 
		//如果不相等,就让k等于前一个公共最长前后缀的前缀的后一个字符比较,之后再次循环判断 
		if(k == 0 || s[i] == s[k]){
			i++;
			k++;
			next[i] = k; 
		} else { 
			k = next[k]; 
		}
	} 
	printf("下面是next数组数据\n"); 
	for(int j = 0; j <= length; j++){
		printf("%d ",next[j]);
	}
	printf("\n");
}

参数:

  • s: 模式串。
  • next: next 数组。

功能:

  • 初始化 next[1] 为 0。
  • 通过比较模式串中的字符,填充 next 数组。

2.2 获取 nextval 数组 (getNextVal)

此函数用于计算模式串的 nextval 数组。

void getNextVal(char *s,int *nextval,int *next){
	nextval[1] = 0;
	for(int j=2;j<=strlen(s)-1;j++){
    	if(s[next[j]]==s[j]){
    		nextval[j]=nextval[next[j]];
		} else {
			nextval[j]=next[j];
		}
	}
	printf("下面是nextval数组数据\n"); 
	for(int j = 0; j <= strlen(s)-1; j++){
		printf("%d ",nextval[j]);
	}
	printf("\n");
} 

参数:

  • s: 模式串。
  • nextval: nextval 数组。
  • next: 已经计算好的 next 数组。

功能:

  • 初始化 nextval[1] 为 0。
  • 根据 next 数组计算 nextval 数组。

2.3 KMP 方法 (KMP)

此函数用于在主串中查找模式串。

//KMP方法,返回第一个匹配模式串的开始下标
int KMP(char *s,char *modelStr){
	//i表示查找字符串的下标 
	int i = 1;
	//j表示模式串的下标 
	int j = 1;
	//模式串的长度 ,这里之所以要减一,是因为把\0也算在里面了 
	int length = strlen(modelStr)-1;
	printf("模式串的长度:%d\n",length);
	//声明next数组 
	int next[length+1];
	//声明nextval数组
	int nextval[length+1]; 
	//获取next数组
	getNext(modelStr,next);
	//获得更优的nextval数组
	getNextVal(modelStr,nextval,next); 
	while(i <= strlen(s)-1 && j <= strlen(modelStr)-1){
		//当第1个元素匹配失败时,匹配下一个相邻子串,令j=0,i++,j++
		//如果匹配成功再往后匹配一个字符,直到结束
		//如果匹配不成功,那么就让j移动到next[j],再进行比较 
		if(j == 0 || s[i] == modelStr[j]){
			i++;
			j++;
		} else {
			j = nextval[j];
		}
	} 
	if(j > length){
		return i - length;
	} else {
		return -1;
	}
}

参数:

  • s: 主串。
  • modelStr: 模式串。

返回值:

  • 如果找到模式串,则返回模式串在主串中的起始位置(从 1 开始计数)。
  • 如果未找到模式串,则返回 -1。

功能:

  • 调用 getNextgetNextVal 函数计算 next 和 nextval 数组。
  • 在主串中进行模式串的匹配,使用 nextval 数组跳过不必要的比较。

3. 示例程序

下面是一个示例程序,演示了如何使用上述定义的功能。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//优化的KMP算法 
//获得next数组,s为模式串,next为所求next数组 
void getNext(char *s,int *next){
	//i表示从第二个字符开始, 
	int i = 1;
	//k表示最长公共前后缀的数量
	int k = 0;
	//next[0]没有值,next[1]为第一个字符,没有前后缀,直接赋值为0 
	next[1] = 0; 
	//模式串的长度,这里之所以要减一,是因为把\0也算在里面了 
	int length = strlen(s)-1;
	//从第二个字符开始,一直到最后一个字符 
	while(i < length){
		//1.j等于0,说明现在是第二个字符,前面只有一个字符,则赋值为1
		//2.当s[i]==s[k],s[i]表示现在扫描到的字符,与前一个公共最长前后缀的前缀的后一个字符比较
		//如果相等,说明该公共最长前后缀加1 
		//如果不相等,就让k等于前一个公共最长前后缀的前缀的后一个字符比较,之后再次循环判断 
		if(k == 0 || s[i] == s[k]){
			i++;
			k++;
			next[i] = k; 
		} else { 
			k = next[k]; 
		}
	} 
	printf("下面是next数组数据\n"); 
	for(int j = 0; j <= length; j++){
		printf("%d ",next[j]);
	}
	printf("\n");
}

void getNextVal(char *s,int *nextval,int *next){
	nextval[1] = 0;
	for(int j=2;j<=strlen(s)-1;j++){
    	if(s[next[j]]==s[j]){
    		nextval[j]=nextval[next[j]];
		} else {
			nextval[j]=next[j];
		}
	}
	printf("下面是nextval数组数据\n"); 
	for(int j = 0; j <= strlen(s)-1; j++){
		printf("%d ",nextval[j]);
	}
	printf("\n");
} 

//KMP方法,返回第一个匹配模式串的开始下标
int KMP(char *s,char *modelStr){
	//i表示查找字符串的下标 
	int i = 1;
	//j表示模式串的下标 
	int j = 1;
	//模式串的长度 ,这里之所以要减一,是因为把\0也算在里面了 
	int length = strlen(modelStr)-1;
	printf("模式串的长度:%d\n",length);
	//声明next数组 
	int next[length+1];
	//声明nextval数组
	int nextval[length+1]; 
	//获取next数组
	getNext(modelStr,next);
	//获得更优的nextval数组
	getNextVal(modelStr,nextval,next); 
	while(i <= strlen(s)-1 && j <= strlen(modelStr)-1){
		//当第1个元素匹配失败时,匹配下一个相邻子串,令j=0,i++,j++
		//如果匹配成功再往后匹配一个字符,直到结束
		//如果匹配不成功,那么就让j移动到next[j],再进行比较 
		if(j == 0 || s[i] == modelStr[j]){
			i++;
			j++;
		} else {
			j = nextval[j];
		}
	} 
	if(j > length){
		return i - length;
	} else {
		return -1;
	}
}

int main() {
    char s[1001]; // 主字符串
    char modelStr[1001]; // 模式串

    printf("请输入主字符串: ");
    scanf("%s", &s[1]);
	
    printf("请输入要查找的模式串: ");
    scanf("%s", &modelStr[1]); 
    
	printf("主字符串:"); 
	for(int i = 1; i < strlen(s); i++){
		printf("%c",s[i]);
	}
	printf("\n");
	printf("模式串:");
	for(int i = 1; i < strlen(modelStr); i++){
		printf("%c",modelStr[i]);
	}
	printf("\n");

    int pos = KMP(s, modelStr);

    if (pos != -1) {
        printf("模式串在主字符串中的起始位置是: %d\n", pos); // 输出匹配的位置,注意下标从1开始
    } else {
        printf("模式串在主字符串中未找到。\n");
    }

    return 0;
}

4. 使用说明

  1. 编译并运行上述示例程序。
  2. 根据提示输入主串和模式串。
  3. 观察并理解 KMP 算法的输出结果。

5. 总结

通过本文档,您可以了解到 KMP 算法的基本概念和实现方法。使用这些知识,您可以轻松地处理字符串匹配问题,特别是在大数据量的情况下,KMP 算法能显著提升性能。希望这份指南能够帮助您更好地理解和使用 KMP 算法。

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是KMP算法及其改进算法优化next数组)的C语言实现KMP算法: ```c void getNext(char *pattern, int *next) { int i = 0, j = -1; next[0] = -1; while (pattern[i]) { if (j == -1 || pattern[i] == pattern[j]) { i++; j++; next[i] = j; } else { j = next[j]; } } } int KMP(char *text, char *pattern) { int i = 0, j = 0; int text_len = strlen(text); int pattern_len = strlen(pattern); int *next = (int *)malloc(sizeof(int) * pattern_len); getNext(pattern, next); while (i < text_len && j < pattern_len) { if (j == -1 || text[i] == pattern[j]) { i++; j++; } else { j = next[j]; } } free(next); if (j == pattern_len) { return i - j; } else { return -1; } } ``` 改进算法优化next数组): ```c void getOptimizedNext(char *pattern, int *next) { int i = 0, j = -1; next[0] = -1; while (pattern[i]) { if (j == -1 || pattern[i] == pattern[j]) { i++; j++; if (pattern[i] != pattern[j]) { next[i] = j; } else { next[i] = next[j]; } } else { j = next[j]; } } } int KMP_Optimized(char *text, char *pattern) { int i = 0, j = 0; int text_len = strlen(text); int pattern_len = strlen(pattern); int *next = (int *)malloc(sizeof(int) * pattern_len); getOptimizedNext(pattern, next); while (i < text_len && j < pattern_len) { if (j == -1 || text[i] == pattern[j]) { i++; j++; } else { j = next[j]; } } free(next); if (j == pattern_len) { return i - j; } else { return -1; } } ``` 在上面的示例代码中,KMP算法中的getNext函数用来计算next数组,KMP函数用来执行KMP算法的主体部分。改进算法中的getOptimizedNext函数用来计算优化后的next数组,KMP_Optimized函数用来执行优化后的KMP算法。这些函数可以根据需要修改函数参数和返回值类型以适应实际情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KYGALYX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值