1.朴素字符串匹配算法
将子串逐位与主串比较,若匹配不成功,则从主串的下一位从头比较。简单暴力,理想情况下时间复杂度为:O(n+m),但是很多情况并不能达到理想状态,最坏情况下时间复杂度高达 O(n*m)
程序实现如下:
char[] txt ="AABBACABCBD"; char[] pat = "ACABC"; int searchIndex(char *pat , char * txt) { int m = strlen(pat); int n = strlen(txt); for(int i = 0; i < n-m ;i++) { for(int j = 0 ; j < m ; j++) { if(txt[i+j] != pat[j]) break; } if(j == m) return i; } }
2. KMP字符串匹配算法
朴素匹配算法在存在大量相同字符丶不同字符靠后等情况下算法效率十分低下,相比之下,在模式串与主串之间存在大量部分匹配的情况下时,KMP算法会比朴素匹配算法快很多,并且KMP的主串指针或者标志位不用
回溯,整个匹配过程中,主串仅需从头到位扫描一遍即可,这在处理与从外界传入的大量数据进行匹配时十分有效,可以一边读入一遍匹配,实用性更高。
KMP的匹配过程如下链接,大牛讲的已经非常通俗易懂了(突然感觉还是别死磕严奶奶的教材和视屏了)
可参考:http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/(需FQ)
理解过后,编码实现的关键在于next(i)数组的构建,直接求next(i)不好求,因此,在上述参考中,则求前缀和后缀的最大公共长度(模式串已匹配字串长度减去最大公共长度就是模式串要右滑长度),然后其实通过观察不难发现,使用求得的公共长度整体右移一位,并将空出来的首位赋值为-1便实现了next(i)数组的构建
相应编码就基于此实现:
参考这位讲的:http://wiki.jikexueyuan.com/project/kmp-algorithm/define.html
则next数组构建为:
void GetNextval(char* p, int next[]) { int pLen = strlen(p); next[0] = -1; int k = -1; int j = 0; while (j < pLen - 1) { //p[k]表示前缀,p[j]表示后缀 if (k == -1 || p[j] == p[k]) { ++j; ++k; //较之前next数组求法,改动在下面4行 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]; } } }
假设现在文本串 S 匹配到 i 位置,模式串 P 匹配到 j 位置
- 如果 j = -1,或者当前字符匹配成功(即 S[i] == P[j]),都令 i++,j++,继续匹配下一个字符;
- 如果 j != -1,且当前字符匹配失败(即 S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串 P 相对于文本串 S 向右移动了 j - next [j] 位。
- 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的 next 值,即移动的实际位数为:j - next[j],且此值大于等于1。
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] //next[j]即为j所对应的next值 j = next[j]; } } if (j == pLen) return i - j; else return -1; }