KMP算法是一种改进的字符串匹配算法,由D.E.Knuth、J.H.Morris和V.R.Pratt提出,取三个人名字的首字母组成KMP,这是KMP算法名字的来历。
主串: 需要找到和模式串具有相同字符和字符次序子串的字符串 模式串: 想要在主串中找到与该字符串字符相同以及排列顺序相同子串的字符串 字符串前缀: 包含字符串的起始字母,而不包含字符串最后一个字母的任意子串。 字符串后缀: 包含字符串的最后一个字母,而不包含字符串起始字母的任意子串。 PS:其它关于字符串的基本概念应该在数据结构课本上讲解字符串的部分有介绍,因此不再此赘述。
假设有两个指针i,j这两个指针分别指向主串中的一个字符以及模式串中的一个字符。在初始状态时i指向主串的起始位置1(假设字符串的起始位置从1开始),j也指向模式串的初始位置0,之后检索子串到一定位置时,i指向一个位置,j指向一个位置,此时若这两个位置上的字符不相等那么i返回到主串中的一个位置,而j将返回到模式串的起始位置与主串中i指向位置的字符做比较,显然这种方式虽然思路简单,但其实是非常费时费力的。
如果遇到如下情形的字符串:
蓝色部分是主串,绿色部分是模式串。当蓝色位置的第9个元素与绿色位置的第5个元素不匹配的时候,模式串的前四位前缀g(4)和后缀g(4)相同因此我们可以直接将模式串的指针j指向第2个字符与主串中9号位置元素做比较看是否相同。此时不需要i回到第6个元素。(为什么这样做可以防止主串的i指针回溯字符的问题?)
再看这样一个例子模式串为abababc,j指向模式串的第五个字符,此时和i指向的字符不匹配,这时我们对于模式串前4个字符前缀ab和后缀ab是相同的此时我们直接让j指针指向模式串第3个字符与i指向的字符匹配。
通过上述两个例子我们应当有一个朴素的认知,假如j指向的某一个字符与i指向的字符发生不匹配的时候,我们只要找到模式串中在j指针之前的子串中相等前后缀的最大长度子串,之后将j指针指向起始位置加这个最大长度位置的字符,并与i指向的字符相比较,存放当发生不匹配的时候j指向位置的是next数组。
字符串的基本结构体
#define MAXSIZE 100 typedef struct{ char str[MAXSIZE]; int length; }seqstring;KMP算法
/* kmp算法 seqstring p:模式串 seqstring t:文本串 int next[]:next数组 return 模式串在文本中第一次出现的起始位置 */ int kmp(seqstring t, seqstring p, int next[]) { int i = 0; int j = 0; while (i < t.length&&j < p.length) { if (j == -1 || t.str[i] == p.str[j]) { i++; j++; } else j = next[j]; } if (j == p.length) return (i - p.length); else return -1; }求解next数组
/* 求next数组的值,规定next[0]=-1 输入: seqstring p:模式串 int next[]:next数组 */ void getNext(seqstring p, int next[]) { int i = 0; int j = -1; next[0] = -1; while (i < p.length) { if (j == -1 || p.str[i]==p.str[j]) { ++i; ++j; next[i] = j; } else { j = next[j]; } } }在这里的next数组中的起始地址为0,在next数组的0位置上我们有next[0]=-1作为一个判断条件,当模式串的第一位和主串不匹配的时候主串和模式串得i,j指针分别后移一位,之后我们考虑当next[i]确定之后next[i+1]该如何确定,首先明确next[i]为在模式串第i个位置之前前后缀相等的最大字符串,它是唯一的,指向其前缀的末位的后一位。
在求next数组时,相当于模式串既是模式串同时也是主串(需要匹配的串),那么当模式串第j个位置与主串中指针i指向的字符相等,这说明模式串已经匹配了j个字母,这j个字母与主串i前面的j个字母相同,因为主串和模式串都其实是模式串,那么相当于此时前缀长度加1后缀长度也加1依然匹配那么next[i+1]=next[i]+1。
当不相等的时候,进入模式匹配kmp算法,此时next[j]已经确定,只需要将模式串指针j指向next[j]模式串所对应的字符,看此时的模式串指针j和主串指针i位置上的字符是否相等,如果相等,那么就和上述相等的情况是一致的,如果不相等那么继续让j=next[j]
改良的next数组:
/* 改良的next数组的值,规定next[0]=-1 输入: seqstring p:模式串 int next[]:next数组 */ void getNext(seqstring p, int next[]) { int i = 0; int j = -1; next[0] = -1; while (i < p.length) { if (j == -1 || p.str[i]==p.str[j]) { ++i; ++j; if(p.str[i]!=p.str[j]) next[i]=j; else next[i] = next[j]; } else { j = next[j]; } } }