KMP算法之我见(初解)

2024-08-28 23:18
文章标签 算法 kmp 之我见 初解

本文主要是介绍KMP算法之我见(初解),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

几天前看到KMP算法的时候,头大如麻,略读一遍,决定跳过,学完了整章串、数组、矩阵和广义表之后回头专心研究KMP算法。

在学习这本《数据结构》的前几章的时候我就开始对这本教程有点失望了,当初在图书馆里对比了十几本教程选择了它,主要原因是它图解较多,便于理解,但是细读发现它的代码不够讲究,实用性不强,可能是我经验匮乏吧,反正这本教材的堆栈部分我很不满意,代码可用性太小,相较其他版本的结构体实现堆栈劣势明显。言归正传,因上述原因,我没有再研究本书,而是看了视频教程,小甲鱼的教程让我对NEXT数组有了直观认识,但是在后来的NEXT数组的实现和KMP算法优化上依然云雾缭绕。

 

NEXT数组核心思想:

当前匹配点失配,模式串该前进几位与当前的主串匹配点重新进行比较,而NEXT数组元素值就是前进的位数。假设模式串在p9点失配,则p0p1p2p3p4p4p5p6p7p8与当前对应的主串字符是一一相等的,下一步改用模式串第几位取代P9位重新对比呢,关键看p0p1p2p3p4p4p5p6p7p8的前缀子串和后缀子串,前后缀子串分别对应为:

P0 —— P8

P0P1 —— P7P8

P0P1P2 —— P6P7P8

……

P0P1P2P3P4P5P6P7 —— P1P2P3P4P5P6P7P8

分别判断这些前缀子串后缀子串对是否相等,如果相等,下一次匹配的时候,就用前缀子串的取代后缀子串的位置,即模式串整体前进了strlen(前后缀子串)个位数.

有了这个思路,就开始通过代码实现NEXT数组的求值。

 

(事实上这个思路过程有些繁琐,所以牵扯到后续的优化问题,后话暂且不提)

 

NEXT数组求法,及测试函数如下:

 

//测试:-1 0 0 0 1 2 3 4 5 0 1   abcabcabbac
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 12
int FindNext(char *t,char x);
int main()
{
char str[MAX] = "abcabcabbac";
char x;       //失配点
printf("当前模式串:%s \nNEXT数组求值过程如下:\n\n",str);
for(x=0;x<MAX-1;x++)
{
printf("next[%d] = %d \n\n",x,FindNext(str,x));
}
return 0;
}
//传入一个模式串t,t串当前失配点字符位置x,返回next值,默认数组长度宏定义MAX
int FindNext(char* t,char p)
{
char i,j;
char qtr[MAX];
char ptr[MAX];
char n=0;
char k=0;
if(p==0)	
{
return -1;
}
for(i=0;i<p-1;i++)
{
//处理前缀子串
for(k=0;k<=i;k++)
{
qtr[k] = t[k];
}
qtr[k]='\0';
//处理后缀子串
k=0;
for(j=p-i-1;j<=p-1;j++)
{
ptr[k] = t[j];
k++;
}
ptr[k]='\0';
printf("前缀子串:%-15s    后缀子串:%-15s\n",qtr,ptr);
if(strcmp(qtr,ptr)==0) n=strlen(qtr);
}
return n;
}


 

能求出NEXT数组,接下来就是利用NEXT数组值进行KMP算法。

 

思路关键如下:

设主串为ZHU_STR[100],模式串为MO_STR[10],则从ZHU_STR[0] == MO_STR[0] ?开始比较,相等,则两串同时前进一位i,对比各串的第二个字符,若不等,则当前匹配点为失配点,根据NEXT数组值向前移动模式串,则主串当前点对应到新的模式串字符,重新判这两个字符相等与否,重复上述过程。

 

把握住,每一次对比只有两个字符,且只有两种结果,等或不等,等该怎样?(同时前进对比下一位)不等该怎样?(模式串前进)

 

实现代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 12
int FindNext(char *t,char x);
int main()
{
char z_str[] = "asfsabc";
char m_str[] = "abc";
char z_num=0;					//主串当前匹配点
char m_num=0;				         //模式串当前匹配点
char len_z = strlen(z_str);
char len_m = strlen(m_str);
char p=0;                                              //模式串失配点
char i=0;
while(z_num<len_z && m_num < len_m)                    //依次用主串的每一个字符和子串对比,主串不回溯
{					         //每次对比区别为子串的前进位数,每一次对比只有两种结果
if(z_str[z_num] == m_str[m_num] )		//相同则,同时+1,
{										
z_num++;
m_num++;
}
else					//不同则,调用NEXT数组决定下一步子串前进多少个字符
{
p = m_num;
i = FindNext(m_str,p);
if(i==-1)				//next数组为-1,则子串置0重头来,主串+1(理解如下)
{				//假设模式串p[0]前一位为p[-1],下一次比较要让p[-1]与当前匹配点比较
m_num = 0;		//那不就是主串前进一步和p[0]进行对位匹配么。
z_num++;
}
else				//否则,主串不变,子串向前滑动相应的位数
m_num = i;
}
if(m_num == len_m)						 
}
if(m_num == len_m)							
{
printf("成功匹配!\n起始位置是:%d\n",z_num-len_m);
}
else
{
printf("没有匹配成功!\n");				
}
return 0;
}
//传入一个模式串t,t串当前失配点字符位置x,返回next值,默认数组长度宏定义MAX
int FindNext(char* t,char p)
{
char i,j;
char qtr[MAX];
char ptr[MAX];
char n=0;
char k=0;
if(p==0)	
{
return -1;
}
for(i=0;i<p-1;i++)
{
//处理前缀子串
for(k=0;k<=i;k++)
{
qtr[k] = t[k];
}
qtr[k]='\0';
//处理后缀子串
k=0;
for(j=p-i-1;j<=p-1;j++)
{
ptr[k] = t[j];
k++;
}
ptr[k]='\0';
if(strcmp(qtr,ptr)==0) n=strlen(qtr);
}
return n;
}


 

KMP算法实现总结:

1、写next数组的时候就失误了,每次传回的是一个元素值,而不是一个数组,导致主函数执行是不停调用子函数去求NEXT值做出反应,应该让NEXT函数只运行一次返回一个整个的NEXT数组供主函数查找,这样简化了很多步奏。

2、NEXT数组的求法上过于繁琐,有待研究之后下期博文优化改进。

3、感觉程序思路好像还算明白,但是相对其他的KMP算法实现代码量大了不少。

这篇关于KMP算法之我见(初解)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1116169

相关文章

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖