字符串的模式匹配:KMP算法
来源:互联网 发布:python recv超时 编辑:程序博客网 时间:2024/06/07 06:26
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。其实KMP算法与BF算法的区别就在于KMP算法巧妙的消除了指针i的回溯问题,只需确定下次匹配j的位置即可,使得问题的复杂度由O(mn)下降到O(m+n)。
与BF算法的比较
普通的匹配算法BF中,目标串和模式串的索引值都需要重新回到起点再次进行匹配,所以该算法的时间复杂度较高,达到O(m*n)。KMP算法的核心思想是寻找模式串本身的特征,在此基础上达到目标串不回溯,模式串有规律回溯的目的,以减少回溯比较的次数,降低时间复杂度,达到O(m+n)。但是这是建立在提高空间复杂度的基础上的。
BF算法:时间复杂度O(m*n);空间复杂度O(1)
KMP算法:时间复杂度O(m+n);空间复杂度O(n)
算法思想
KMP算法的核心是寻找模式串本身的规律。在该算法中表现为反映该规律的next数组。next数组的作用是在每次失配时,模式串根据next数组对应位置上的值回溯模式串索引的位置。next数组的求法也是KMP算法的精华所在。
next数组有如下定义:
(1) next[j] = -1: j = 0
(2) next[j] = max(k): 0 < k < j 且 P[0,k-1]=P[j-k, j-1];
(3) next[j] = 0 其他
该定义的意义就是next数组的首位均为-1。在其他位置上,该位置之前的子串存在与该位置之后的同等长度的子串,我们称之为前后缀(如在a b c a b中,前缀和后缀均为”ab”。)。当没有任何相同的前后缀的情况下,next值为0。否则next值为k。k可以理解为子串的长度。
再举个例子来看一下设么是前缀和后缀:
字符串:”test”
前缀为: t, te, tes
后缀为:est, st, t
next值举例:
P a b a b a
j 0 1 2 3 4
next -1 0 0 1 2
我们来分析一下next值的产生过程:
“a”的前缀和后缀都为空集,共有元素的长度为0;
“ab”的前缀为[a],后缀为[b],共有元素的长度为0;
“aba”的前缀为[a, ab],后缀为[ba, a],共有元素的长度0;
“abab”的前缀为[a, ab, aba],后缀为[bab, ba, b],共有元素的长度为0;
“ababa”的前缀为[a, ab, aba, abab],后缀为[baba, aba, ba, a],共有元素为”a”,长度为1;
由next数组的定义可知,我们可以递推出如下结论:
(1) next[0] = -1
假设next[j]=k, 即P[0, k-1]==P[j-k, j-1]
(2) 若P[j] == P[k],则有P[0, k]==P[j-k, j],很显然,next[j+1]=next[j]+1=k+1;
(3) 若P[j] != P[k],则可以把其看做模式匹配的问题,即匹配失败的时候,k值如何移动,显然k=next[k]。
那么具体的实现可以是(JAVA):
static void getNext(char[] p){ int[] next = new int[p.length]; next[0] = -1; int j = 0; int k = - 1; while(j < p.length - 1){ // 匹配的情况下,p[j]==p[k] if (k == -1 || p[j] == p[k]) { j++; k++; next[j] = k; } else{ //p[j]!=p[k] k = next[k]; } } }
或者可以使用直接求解(java):
static void getNext(char[] p) { int i, j, temp; int[] next = new int[p.length]; for (i = 0; i < p.length; i++) { if (i == 0) { next[i] = -1; // next[0]=-1 } else if (i == 1) { next[i] = 0; // next[1]=0 } else { temp = i - 1; for (j = temp; j > 0; j--) { if (equals(p, i, j)) { next[i] = j; // 找到最大的k值 break; } } if (j == 0) next[i] = 0; } } } // 判断p[0...j-1]与p[i-j...i-1]是否相等 static boolean equals(char[] p, int i, int j) { int k = 0; int s = i - j; for (; k <= j - 1 && s <= i - 1; k++, s++) { if (p[k] != p[s]) return false; } return true; }
KMP算法的思想就是:我们的原则还是从左向右匹配, 在匹配过程称,若发生不匹配的情况,如果next[j]>=0,则目标串的指针i不变,将模式串的指针j移动到next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比较。
KMP的匹配过程:
搜索词: abcdabd匹配值(next):00001201、字符串和模式串(搜索词)对齐bbc abcaab abcdabcdabdecabcdabd2、字符串的第一个字符为b与搜索词的第一个字符a不匹配。搜索词后移一位bbc abcaab abcdabcdabdec abcdabd3、因为b与a不匹配,搜索词再往后移。bbc abcaab abcdabcdabdec abcdabd4、就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。bbc abcaab abcdabcdabdec abcdabd5、接着比较字符串和搜索词的下一个字符,还是相同, 直到字符串有一个字符,与搜索词对应的字符不相同为止。即搜索词的d与字符串的空格不符。这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。bbc abcaab abcdabcdabdec abcdabd6、一个基本事实是,当空格与d不匹配时,你其实知道前面六个字符是"abcdab"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。查表可知,最后一个匹配字符b对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数: 移动位数 = 已匹配的字符数 - 对应的部分匹配值, 即 6 - 2 等于4,所以将搜索词向后移动4位。bbc abcaab abcdabcdabdec abcdabd7、因为空格与c不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("ab"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。bbc abcaab abcdabcdabdec abcdabd 8、因为空格与a不匹配,继续后移一位。 bbc abcaab abcdabcdabdec abcdabd 9、逐位比较,直到发现c与d不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。bbc abcaab abcdabcdabdec abcdabd 10、逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。
代码实现如下(java):
static int KMPMatch(char[] T, char[] p) { int i = 0; int j = 0; int next[] = getNext(p);## 标题 ## while (i < T.length) { if (j == -1 || T[i] == p[j]) { i++; j++; } else { j = next[j]; // 消除了指针i的回溯 } if (j == p.length) return i - p.length; } return -1; }
- 字符串的模式匹配算法--KMP算法
- 字符串的模式匹配算法 KMP
- 字符串模式匹配(所谓的kmp)算法
- 字符串的模式匹配:KMP算法
- 算法:字符串的KMP模式匹配
- 改良的KMP字符串模式匹配算法
- 字符串的模式匹配,KMP算法
- 字符串的模式匹配:KMP算法
- 字符串模式匹配的KMP算法
- 字符串模式匹配的KMP算法实现
- 字符串-KMP的模式匹配算法
- 字符串模式匹配KMP算法
- 字符串模式匹配KMP算法
- KMP字符串模式匹配算法
- 字符串模式匹配KMP算法
- KMP字符串模式匹配算法
- KMP字符串模式匹配算法
- 字符串模式匹配KMP算法
- php获取路径:getcwd()、__DIR__、__FILE__ 的区别
- HTML表单
- P1208 混合牛奶
- Java中Filter、Servlet、Listener的学习
- C++堆、栈、自由存储区、全局、静态存储区和常量存储区详解
- 字符串的模式匹配:KMP算法
- 安卓当前页面关闭上一个界面
- 关于C中编译后RO,RW,ZI的含义
- 浏览器f12的调试介绍
- ssm实战项目视频
- CSS属性样式
- LeetCode No.153 Find Minimum in Rotated Sorted Array
- 远程连接
- 笔记本7槽PCI卡扩展坞 P7R4