3. 串和KMP算法

tech2022-09-21  109

KMP算法

串1. 前言2. KMPjava实现c语言实现

定长存储结构

//maxSize表示串的最大长度; #define maxSize 10 typedef struct Str { //str数组长度定义为maxSize+1,因为多出一个'\0'作为结束标记; char str[maxSize + 1]; int length;//串长度 } Str;

变长存储结构

typedef struct Str { char *ch; int length;//串长度 } Str; //初始化 Str S; S.length = L; S.ch = (char*) malloc((L + 1) * sizeof(char)); //S.ch[length范围内的位置] = 某字符变量 //某字符变量 = S.ch[length范围内的位置] //释放内存 free(S.ch)

串的基本操作

赋值操作:将一个常量字符串赋值给str;赋值成功返回1,否则返回0

int strassign(Str& str, char* ch){ if(str.ch){ free(str.ch)//释放原串空间 } int len = 0; char *c = ch; while(*c){//求ch串的长度 ++len; ++c; } if(len ==0){//如果ch为空串,则直接返回空串 str.ch = NULL; str.length=0; return 1; }else{ str.ch = (char*)malloc(sizeof(char) * (len+1)); //取len+1是为了多分配一个空间存放'\0'字符 if(str.ch == NULL){ return 0; }else{ c = ch; for(int i=0;i<=len;++i,++c){ str.ch[i]=*c; } //注意:循环条件中之所以"<=",是为了将ch最后的'\n'复制到新串中作为结束标记 str.length = len; return 1; } } } 函数使用格式: strassign(str,"Hello World")

取串长度的操作

int strlength(Str str){ return str.length; }

在没有给出串长度信息的情况下,求长度的操作可以借鉴函数strassign()中的求输入串长度的部分代码来实现

串的比较操作

int strcompare(Str s1, Str s2){ for(int i=0;i<s1.length && i<s2.length;++i){ if(s1.ch[i] != s2.ch[i]){ return s1.ch[i] - s2.ch[i] } } return s1.length = s2.length; }

串连接操作

求子串操作

串清空操作

int clear(){ if(str.ch){ free(str.ch); str.ch = NULL; } str.length-0; return 1 }

1. 前言

问:有一个字符串"BBCABCDABABCDABCDABDE",里面是否包含另一个字符串"ABCDABD"?

比较傻的穷举匹配:

int naive(Str str, Str substr) { int i = 1, j = 1, k = i; while (i <= str.length && j <= substr.length) { if (str.ch[i] == substr.ch[j]) { ++i; ++j; } else { j = 1; i = ++k; } } if (j > substr.length) { return k; } else { return 0; } }

2. KMP

KMP算法主要是通过消除主串指针的回溯来提高匹配的效率的; KMP算法是利用之前判断过信息,通过一个next数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去了大量的计算时间。

“利用已经部分匹配这个有效信息,保持i指针不回溯,通过修改j指针,让模式串尽量地移动到有效的位置。”


整个KMP的重点在于当某一个字符与主串不匹配时,我们应该知道j指针要移动到哪? 接下来我们自己来发现j的移动规律: 如图:C和D不匹配了,我们要把j移动到哪?显然是第1位。为什么?因为前面有一个A相同啊:


如下图也是一样的情况:

可以把j指针移动到第2位,因为前面有两个字母是一样的:


至此我们可以大概看出一点端倪,当匹配失败时,j要移动的下一个位置k。存在着这样的性质:最前面的k个字符和j之前的最后k个字符是一样的。

如果用数学公式来表示是这样的 P[0 ~ k-1] == P[j-k ~ j-1]

因为:

当T[i] != P[j]时 有T[i-j ~ i-1] == P[0 ~ j-1] 由P[0 ~ k-1] == P[j-k ~ j-1] 必然:T[i-k ~ i-1] == P[0 ~ k-1]

好,接下来就是重点了,怎么求这个(这些)k呢?因为在P的每一个位置都可能发生不匹配,也就是说我们要计算每一个位置j对应的k,所以用一个数组next来保存,next[j] = k,表示当T[i] != P[j]时,j指针的下一个位置。

《部分匹配表》(Partial Match Table) "前缀"指除了最后一个字符以外,一个字符串的剩余全部字符; "后缀"指除了第一个字符以外,一个字符串的剩余全部字符;

"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABABAA"为例:

下标 : 0 1 2 3 4 5 模式串 : A B A B A A next : 0 0 1 2 3 1

||| A 的前缀为空串;后缀为空串 AB ABA ABAB ABABA ABABAA

next[0]

"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,

A前缀:空集;后缀:空集,共有元素的长度 0;AB前缀:[A];后缀:[B],共有元素的长度 0;ABC前缀:[A, AB];后缀:[BC, C],共有元素的长度 0;ABCD前缀:[A, AB, ABC];后缀:[BCD, CD, D],共有元素的长度 0;ABCDA前缀:[A, AB, ABC, ABCD];后缀:[BCDA, CDA, DA, A],共有元素为"A",长度 1;ABCDAB前缀:[A, AB, ABC, ABCD, ABCDA];后缀:[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度 2;ABCDABD前缀:[A, AB, ABC, ABCD, ABCDA, ABCDAB];后缀:[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度 0;

KMP实现代码:

//获取next[]数组 void getNext(Str substr, int next[]) { int j = 1, t = 0; next[1] = 0; while (j < substr.length) { if (t == 0 || substr.ch[j] == substr.ch[t]) { next[j + 1] = t + 1; ++t; ++j; } else { t = next[t]; } } } //KMP算法 int KMP(Str str, Str substr, int next[]) { int i = 1, j = 1; while (i <= str.length && j < substr.length) { if (j == 0 || str.ch[i] == substr.ch[j]) { ++i; ++j; } else { j = next[j]; } } if (j > substr.length) { return i - substr.length; } else { return 0; } }

优化KMP,获取nextval[]数组

void getNextVal(Str substr, int nextval[]) { int j = 1, t = 0; nextval[1] = 0; while (j < substr.length) { if (t == 0 || substr.ch[j] == substr.ch[t]) { if (substr.ch[j + 1] != substr.ch[t + 1]) { nextval[j + 1] = t + 1; } else { nextval[j + 1] = nextval[t + 1]; } ++j; ++t; } else { t = nextval[t]; } } }

待续中…

链接:

参考: https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html. https://www.cnblogs.com/yjiyjige/p/3263858.html. https://www.cnblogs.com/zzuuoo666/p/9028287.html.

当前字符之前的字符串中,有多大长度的相同前后缀; next[j]=k;表示j所指向的字符之前的字符串中,有最长k个相同的前后缀

java实现

/** * 暴力破解法 * @param ts 主串 * @param ps 模式串 * @return 如果找到,返回在主串中第一个字符出现的下标,否则为-1 */ public static int violentMatch(String ts, String ps) { char[] t = ts.toCharArray(); char[] p = ps.toCharArray(); int i = 0; // 主串的位置 int j = 0; // 模式串的位置 while (i < t.length && j < p.length) { if (t[i] == p[j]) { // 当两个字符相同,就比较下一个 i++; j++; } else { i = i - j + 1; // 一旦不匹配,i后退 j = 0; // j归0 } } if (j == p.length) { return i - j; } else { return -1; } } public static int[] getNext(String ps) { char[] p = ps.toCharArray(); int[] next = new int[p.length]; next[0] = -1; int j = 0; int k = -1; while (j < p.length - 1) { if (k == -1 || p[j] == p[k]) { next[++j] = ++k; } else { k = next[k]; } } return next; } public static int KMP(String ts, String ps) { char[] t = ts.toCharArray(); char[] p = ps.toCharArray(); int i = 0; // 主串的位置 int j = 0; // 模式串的位置 int[] next = getNext(ps); while (i < t.length && j < p.length) { if (j == -1 || t[i] == p[j]) { // 当j为-1时,要移动的是i,当然j也要归0 i++; j++; } else { // i不需要回溯了 // i = i - j + 1; j = next[j]; // j回到指定位置 } } if (j == p.length) { return i - j; } else { return -1; } } public static int[] getNextOptimize(String ps) { char[] p = ps.toCharArray(); int[] next = new int[p.length]; next[0] = -1; int j = 0; int k = -1; while (j < p.length - 1) { if (k == -1 || p[j] == p[k]) { if (p[++j] == p[++k]) { // 当两个字符相等时要跳过 next[j] = next[k]; } else { next[j] = k; } } else { k = next[k]; } } return next; }

c语言实现

/* 如果当前字符匹配成功(s[i]==p[j]),则i++;j++; 继续匹配下一个字符。 如果当前字符匹配失败(s[i]!=p[j]),则i=i-j+1;j=0; 相当于每次匹配失败时,i回溯,j被置0。 */ int violentMatch(char* s, char* p){ int sLen = strlen(s); int pLen = strlen(p); int i = 0; int j=0; while(i<sLen && j<pLen){ if(s[i]==p[j]){ i++; j++; }else{ i=i-j+1; j=0; } } if(j==pLen){ return i-j; }else{ return -1; } } /* 如果j==-1,或者当前字符匹配成功(s[i]==p[j]),则i++;j++; 继续匹配下一个字符。 如果j!=-1,并且当前字符匹配失败(s[i]!=p[j]),则i不变;j=next[j]; */ int kmpSearch(char* s,char* p){ int i=0; int j=0; int sLen = strlen(s); int pLen = strlen(p); while(i<sLen && j<pLen){ //如果j==-1或者当前字符匹配成功s[i]==p[j],都令i++;j++; if(j==-1 || s[i]==p[j]){ i++; j++; }else{ //如果就j!=-1且当前字符匹配失败s[i]!=p[j],则令i不变,j=next[j] j=next[j]; } } if(j==pLen){ return i-j; }else{ return -1; } } void getNext(char* p,int next[]){ int pLen = strlen(p); next[0] = -1; int k=-1; int j=0; while(j<pLen -1){ if(k==-1 || p[j]==p[k]){//p[k]表示前缀 p[j]表示后缀 next[++j] = ++k; }else{ k= next[k] } } } void getNextOptimize(char* p,int next[]){ int pLen = strlen(p); next[0] = -1; int k=-1; int j=0; while(j<pLen-1){ if(k==-1 || p[j]==p[k]){//p[k]表示前缀 p[j]表示后缀 ++j; ++k; if(p[j]!=p[k]){ next[j] = k; }else{//因为不能出现p[j]=p[next[j]],所以当出现时需要继续递归,k=next[k]=next[next[k]] next[j] = next[k]; } }else{ k=next[k]; } } }
最新回复(0)