q: 博主讲得如此糊里糊涂,next[]数组到底是啥,怎么节省其重复匹配的时间。
以下一一作讲解,首先我们不谈KMP,我们来做题
举例:目标串为:“cbaabababacb”;模式串为:“babac”; 题目:找出目标串中是否存在模式串。
下面 i 'c'表第i个的c字符
最简单枚举方式: 枚举目标串每个字符作为匹配模式串的首字母,若模式串完全匹配,此字符作为匹配模式串的首字母可行。如此枚举需要(n*m)的复杂度,然而在 cbaabababacb cbaabababacb babac babac 模式串的5'b'作为匹配模式串的首字母,匹配失败后,发现以6'a'作为首字母肯定是不行的,之前匹配过,再次枚举6'a'发生重复匹配; 发现以7'b'作为首字母其前两位是匹配过的,如果再从首位开始枚举就发生了重复匹配; 假如我们事先知道,6'a'作为首字母肯定是不行的;7'b'作为首字母其前两位是匹配过的,那么就能防止其重复匹配,从而引出KMP算法。 对于KMP算法: 拿目标串去对比模式串 过程如下: 1'c'-1'b'不等, 2'b'-1'b',3'a'-2'a',4'a'-3'b'不等,(可以不用返回3'a'再开始,后面做解释) 5'b'-1'b',6'a'-2'a',7'b'-3'b',8'a'-4'a',9'b'-5'c'不等,(对于babab不能匹配成功,但是前缀ba已经匹配过,那么模式串可以跳到3'b'来计算,后面做解释) 9'b'-3'b',10'a'-4'a',11'c'-5'a',匹配成功一对。 1.为什么不用返回3'a'再开始,因为之前匹配'baa'-'bab'匹配失败,假设目标串'aa?'(即3'a'为首字母)能与模式串'bab'匹配成功,(即目标串'aa'==模式串'ba') 那么之前匹配成功的目标串'ba'== 模式串'ba',那么有目标串'aa'== 目标串'ba',矛盾。同理以4'a'为首字母。 其实很显然了,但是对于上面具体的字母存在,会有人疑惑。 我们没有具体字母,如果 目标串'???'与模式串'???'在3'?'匹配失败,那么我们至少知道目标串1'?'2'?'和模式串1'?'2'?'是匹配成功的, 怎么样知道目标串2'?'作为首字母是否匹配,就是模式串的1'?'和2'?'是否相等,相等一位匹配成功 那么其实就是算匹配完成的部分模式串的前缀和后缀的最长公共部分长度。next数组就是预处理这个的。 2.为什么可以直接跳到3'b'来计算,之前匹配的'babab'-'babac'失败,以哪一点为开始点,就如上所说 对于'baba'前缀和后缀最长的公共部分 前缀为模式串起点的前一位,和后缀目标串起点的前一位(其实就继续往后,就不用回前面匹配了),节省了重复匹配的时间。 那么其实大家也就知道next数组的意义所在 还是提一下:next[i]数组代表 模式串0-i部分的前缀和后缀的最长公共部分的长度 如果next[0]初始化为-1,那么next[]+1才能表示其公共部分长度。其求法有点像递归, 举例:对于 “bababa”中原next[]数组应为“-1,-1,0,1,2,3”, 在加一个字符'b'由之前已知的最长公共前缀'baba'后面一位与字符对比,即(bababab)中 5'b'-7'b'。 若加的字符是'a'那么5'b'-7'a'不等,再往前回溯,与'baba'的最长公共前缀'ba'后面一位与字符对比,直到值相等或回溯到-1(即最长公共前缀长度为0)。 为什么从'baba'往前回溯,且回溯到'baba'的最长公共前缀? 为了保证'bababa'的前缀后缀相等,只有4,2,0长度,即next的回溯过程。 int nex[N]; void getnext(char *p,int len) { nex[0] = -1; //初始值 int k = -1; //根问题的next[]值。 for(int i = 1; i < len; i++) { while(k!=-1 && p[k+1]!=p[i]) k = nex[k]; //向前回溯,直到找到与其相等,或回溯到根 if(p[k+1] == p[i]) //判断是否找到与其相等 k++; nex[i] = k; } } 进行匹配 int kmp(char *s,char *p) { int len = strlen(s); int lenp = strlen(p); int k = -1; int ans = 0; for(int i = 0; i < len; i++) { while(k!=-1 && p[k+1]!=s[i]) k = nex[k]; if(p[k+1]==s[i]) k++; if(k == lenp-1){ k = nex[k]; ans++; } } return ans; }