KMP算法原理
2015-10-21 22:55
211 查看
1. 摘要
这篇博客系统地介绍了模式匹配(KMP)算法的基本原理。在字符串搜索过程中,传统的暴力搜索方法在失配时,必须回溯重新搜索。回溯产生了时间复杂度高的问题,降低了字符串搜索的效率。针对暴力搜索这一缺点,Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年提出了KMP算法。KMP算法在搜索发生失配时,将模式串向右滑动到某个位置重新开始匹配而不是回溯,提高了搜索效率。经过分析,KMP算法的时间复杂度为O(m+n),暴力搜索算法的时间复杂度为O(mn),KMP算法的搜索效率远远高于暴力搜索算法。
2. 暴力搜索算法原理
暴力搜索算法从文本串和模式串首部逐个地进行匹配,当发生失配时,文本串回溯到文本串本次比较开始处的下一个位置,而模式串回溯到首位置。
暴力搜索算法步骤:
说明:假设要在文本串text中查找模式串pattern。文本存储在text的0~n-1的单元中,模式存储在pattern的0~m-1中。
输入: 串text和pattern
输出:如果在text中找到pattern,则返回pattern在text中的起始位置,否则,返回-1
(1)初始化index, i和j为0;index是text和pattern比较的起始位置,i扫描text,j扫描pattern;
(2)当i<n且j<m且n-i>=m时,
若text[i] =pattern[j],则i和j都增1;
否则,i 回溯到 i-j+1, j回溯到0,并且index增1。
(3)如果j不等于m,则index等于-1;
(4)返回index。
暴力搜索算法代码:
3. KMP算法原理
考虑两个普通的文本串text和pattern。text串用{t0, t1, ..., tn}表示,pattern串用{p0, p1, ..., pm}表示。则text[i]=ti,pattern[j]=pj。在text中搜索pattern过程中,进行到这样一个状态:pattern[j]前j个字符和text[i]前i个字符一一匹配,但是pattern[j]与text[i]比较时失配。如下图1所示。
![](https://img-blog.csdn.net/20151021173533800?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
图1 模式串pattern在位置 j 失配的状态
针对暴力搜索算法的缺点,要避免回溯,必须将pattern向右移动,让text[i]和pattern[k]开始匹配,这里k<j。图2 展示了pattern串在位置j处失配后,向右滑动在位置k处恢复搜索的状态。
![](https://img-blog.csdn.net/20151021182801424?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
图2
pattern串向右滑动后匹配状态
从图2 明显可以看出,为了让text[i]和pattern[k]将搜索继续下去,pattern前的k个字符必须和pattern[k]之前的k个字符相等,并且pattern[k]不能等于pattern[j]。把这个k值记为next[j]。next[j] 是pattern中通过比较pattern[k]和text[[i]从而可以继续搜索下去的位置。也就是说,将pattern向右滑动,使得pattern[k]和text[i]对齐,并从该点继续搜索。如果没有这样的k存在,令next[j]=-1,让搜索从pattern[0]和text[i+1]开始。(这里相当于将pattern向右滑动来讲不存在的位置-1与text[j]对齐,然后恢复搜索)
假设我们已经得到next数组,那么KMP算法步骤如下:
说明:假设要在文本串text中查找模式串pattern。文本存储在text的0~n-1的单元中,模式存储在
pattern 的0~-m-1中。
输入: 串text和pattern
输出:如果在text中找到pattern,则返回pattern在text中的起始位置,否则,返回-1
(1)初始化index, i和j为0;index是t
4000
ext和pattern比较的起始位置,i扫描text,j扫描
pattern;
(2)当i<n且j<m时,
若text[i] =pattern[j],则i和j都增1;
否则,做一下工作:
将pattern向右滑动合适的距离, index=index+j-next[j];
如果next[j]不等于-1, j = next[j];否则 j等于0,i增1。
(3)如果j<m,则index等于-1;
(4)返回index。
4. Next数组
KMP算法的关键在于如何得到next数组。根据图2,我们可以直观地看出,next[j]就是pattern中最长的并且和pattern[j]前k个字符匹配的前缀pattern[0],pattern[1],...,pattern[k-1],而且pattern[k]不等于pattern[j]。事实上,next中保存的是模式串中所有有相同前缀和后缀的子串的前缀后缀长度。这些前缀后缀相同的子串可以将模式串pattern在其自身的一个副本上滑动来得到。再次考虑一个普通形式的模式串{p0,
p1, ..., pm-1}。将它与自身的副本进行模式匹配。如图3所示,显然next[0]=-1,因为pattern[0]没有前缀。现在,如果next[0, next[1], ..., next[j-1]已经得到,可以使用这些值来计算next[j]。
![](https://img-blog.csdn.net/20151021202142385?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
图3 模式串与其自身的一个副本进行模式匹配
如果前缀长度为k,并且pattern[k]不等于pattern[[j], 那么根据next的定义,next[j] = k。如果pattern[k]等于pattern[[j],显然next[j] = next[k]。根据以上分析,得到计算next的方法如下:
输入:串pattern
输出:数组next
(1)初始化next[[0]=-1, k为-1,j为0;
(2)当j<m时,
如果k不等于-1并且pattern[k]不等于pattern[j],k=next[k];
k和j增1;
如果pattern[j]==pattern[j],则next[j]=next[k];否则,next[j] = k。
计算next的代码如下:
void GetNext(char* pattern, int next[])
{
assert(next);
next[0] = -1;
int k = -1;
int j = 0;
int m = strlen(pattern);
while(j<m)
{
if(k != -1 && pattern[k] != pattern[j])
k = next[k];
k++;
j++;
if(pattern[j] == pattern[k])
next[j] = next[k];
else
next[j] = k;
}
}
有了next数组,就可以得到KMP的完整算法。KMP完整的C代码如下:
int KMP(char* text, char* pattern)
{
int n = strlen(text);
int m = strlen(pattern);
int index = 0; //index 是ptn在text中的其实位置
int i = 0;
int j = 0;
int* next = (int*)malloc(m*sizeof(int));
GetNext(pattern, next);
while(i<n&& j<m)
{
if(text[i] == pattern[j])
{
i++;
j++;
}
else
{
index = index + j - next[j];
if(-1 != next[j])
j = next[j];
else
{
j = 0;
i++;
}
}
}
if(next)
{
free(next);
next = NULL;
}
if(j == m)
return index;
else
return -1;
}
5. 总结
KMP算法的关键在于求解next数组。借助next数组,可以避免暴力搜索算法中在失配时的回溯,降低算法时间复杂度,提高搜索效率。分析得知,暴力搜索算法的时间复杂度为O(mn), 而KMP为O(m+n)。明显O(mn)>>O(m+n)。在理解KMP的过程中,首先应该从暴力搜索算法开始,理解为什么暴力算法会产生回溯和如何避免回溯问题。对于next数组,需要深刻理解“具有相同前缀和后缀的子串”这一概念。动手一步步迭代算法的过程是最好的。
6. 参考文献
[1] http://write.blog.csdn.net/postedit?ref=toolbar<http://write.blog.csdn.net/postedit?ref=toolbar>
[2] C++数据结构导引
这篇博客系统地介绍了模式匹配(KMP)算法的基本原理。在字符串搜索过程中,传统的暴力搜索方法在失配时,必须回溯重新搜索。回溯产生了时间复杂度高的问题,降低了字符串搜索的效率。针对暴力搜索这一缺点,Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年提出了KMP算法。KMP算法在搜索发生失配时,将模式串向右滑动到某个位置重新开始匹配而不是回溯,提高了搜索效率。经过分析,KMP算法的时间复杂度为O(m+n),暴力搜索算法的时间复杂度为O(mn),KMP算法的搜索效率远远高于暴力搜索算法。
2. 暴力搜索算法原理
暴力搜索算法从文本串和模式串首部逐个地进行匹配,当发生失配时,文本串回溯到文本串本次比较开始处的下一个位置,而模式串回溯到首位置。
暴力搜索算法步骤:
说明:假设要在文本串text中查找模式串pattern。文本存储在text的0~n-1的单元中,模式存储在pattern的0~m-1中。
输入: 串text和pattern
输出:如果在text中找到pattern,则返回pattern在text中的起始位置,否则,返回-1
(1)初始化index, i和j为0;index是text和pattern比较的起始位置,i扫描text,j扫描pattern;
(2)当i<n且j<m且n-i>=m时,
若text[i] =pattern[j],则i和j都增1;
否则,i 回溯到 i-j+1, j回溯到0,并且index增1。
(3)如果j不等于m,则index等于-1;
(4)返回index。
暴力搜索算法代码:
int violent_kmp(char* text, char* pattern) { int i=0; int j=0; int index=0; int n = strlen(text); int m = strlen(pattern); while(i<n && j<m && n-i>=m) { if(text[i] == pattern[j]) { i++; j++; } else { i = i - j + 1; j = 0; index += 1; } } if(j != m) index = -1 ; return index; }
3. KMP算法原理
考虑两个普通的文本串text和pattern。text串用{t0, t1, ..., tn}表示,pattern串用{p0, p1, ..., pm}表示。则text[i]=ti,pattern[j]=pj。在text中搜索pattern过程中,进行到这样一个状态:pattern[j]前j个字符和text[i]前i个字符一一匹配,但是pattern[j]与text[i]比较时失配。如下图1所示。
图1 模式串pattern在位置 j 失配的状态
针对暴力搜索算法的缺点,要避免回溯,必须将pattern向右移动,让text[i]和pattern[k]开始匹配,这里k<j。图2 展示了pattern串在位置j处失配后,向右滑动在位置k处恢复搜索的状态。
图2
pattern串向右滑动后匹配状态
从图2 明显可以看出,为了让text[i]和pattern[k]将搜索继续下去,pattern前的k个字符必须和pattern[k]之前的k个字符相等,并且pattern[k]不能等于pattern[j]。把这个k值记为next[j]。next[j] 是pattern中通过比较pattern[k]和text[[i]从而可以继续搜索下去的位置。也就是说,将pattern向右滑动,使得pattern[k]和text[i]对齐,并从该点继续搜索。如果没有这样的k存在,令next[j]=-1,让搜索从pattern[0]和text[i+1]开始。(这里相当于将pattern向右滑动来讲不存在的位置-1与text[j]对齐,然后恢复搜索)
假设我们已经得到next数组,那么KMP算法步骤如下:
说明:假设要在文本串text中查找模式串pattern。文本存储在text的0~n-1的单元中,模式存储在
pattern 的0~-m-1中。
输入: 串text和pattern
输出:如果在text中找到pattern,则返回pattern在text中的起始位置,否则,返回-1
(1)初始化index, i和j为0;index是t
4000
ext和pattern比较的起始位置,i扫描text,j扫描
pattern;
(2)当i<n且j<m时,
若text[i] =pattern[j],则i和j都增1;
否则,做一下工作:
将pattern向右滑动合适的距离, index=index+j-next[j];
如果next[j]不等于-1, j = next[j];否则 j等于0,i增1。
(3)如果j<m,则index等于-1;
(4)返回index。
4. Next数组
KMP算法的关键在于如何得到next数组。根据图2,我们可以直观地看出,next[j]就是pattern中最长的并且和pattern[j]前k个字符匹配的前缀pattern[0],pattern[1],...,pattern[k-1],而且pattern[k]不等于pattern[j]。事实上,next中保存的是模式串中所有有相同前缀和后缀的子串的前缀后缀长度。这些前缀后缀相同的子串可以将模式串pattern在其自身的一个副本上滑动来得到。再次考虑一个普通形式的模式串{p0,
p1, ..., pm-1}。将它与自身的副本进行模式匹配。如图3所示,显然next[0]=-1,因为pattern[0]没有前缀。现在,如果next[0, next[1], ..., next[j-1]已经得到,可以使用这些值来计算next[j]。
图3 模式串与其自身的一个副本进行模式匹配
如果前缀长度为k,并且pattern[k]不等于pattern[[j], 那么根据next的定义,next[j] = k。如果pattern[k]等于pattern[[j],显然next[j] = next[k]。根据以上分析,得到计算next的方法如下:
输入:串pattern
输出:数组next
(1)初始化next[[0]=-1, k为-1,j为0;
(2)当j<m时,
如果k不等于-1并且pattern[k]不等于pattern[j],k=next[k];
k和j增1;
如果pattern[j]==pattern[j],则next[j]=next[k];否则,next[j] = k。
计算next的代码如下:
void GetNext(char* pattern, int next[])
{
assert(next);
next[0] = -1;
int k = -1;
int j = 0;
int m = strlen(pattern);
while(j<m)
{
if(k != -1 && pattern[k] != pattern[j])
k = next[k];
k++;
j++;
if(pattern[j] == pattern[k])
next[j] = next[k];
else
next[j] = k;
}
}
有了next数组,就可以得到KMP的完整算法。KMP完整的C代码如下:
int KMP(char* text, char* pattern)
{
int n = strlen(text);
int m = strlen(pattern);
int index = 0; //index 是ptn在text中的其实位置
int i = 0;
int j = 0;
int* next = (int*)malloc(m*sizeof(int));
GetNext(pattern, next);
while(i<n&& j<m)
{
if(text[i] == pattern[j])
{
i++;
j++;
}
else
{
index = index + j - next[j];
if(-1 != next[j])
j = next[j];
else
{
j = 0;
i++;
}
}
}
if(next)
{
free(next);
next = NULL;
}
if(j == m)
return index;
else
return -1;
}
5. 总结
KMP算法的关键在于求解next数组。借助next数组,可以避免暴力搜索算法中在失配时的回溯,降低算法时间复杂度,提高搜索效率。分析得知,暴力搜索算法的时间复杂度为O(mn), 而KMP为O(m+n)。明显O(mn)>>O(m+n)。在理解KMP的过程中,首先应该从暴力搜索算法开始,理解为什么暴力算法会产生回溯和如何避免回溯问题。对于next数组,需要深刻理解“具有相同前缀和后缀的子串”这一概念。动手一步步迭代算法的过程是最好的。
6. 参考文献
[1] http://write.blog.csdn.net/postedit?ref=toolbar<http://write.blog.csdn.net/postedit?ref=toolbar>
[2] C++数据结构导引
相关文章推荐
- On Error Resume Next 语句
- VBS For Next循环的陷阱分享
- VBS For Next循环的一些细节
- jQuery中next方法用法实例
- 用JavaScript对JSON进行模式匹配 (Part 2 - 实现)
- 解析PHP中的正则表达式以及模式匹配
- JavaScript 通过模式匹配实现重载
- Lua中的string库和强大的模式匹配学习笔记
- Ruby中的return、break、next详解
- KMP算法的C#实现方法
- JavaScript中数据结构与算法(五):经典KMP算法
- Lua字符串模式匹配函数小结
- php中current、next与reset函数用法实例
- java模式匹配之蛮力匹配
- MySQL 字符串模式匹配 扩展正则表达式模式匹配
- JS使用replace()方法和正则表达式进行字符串的搜索与替换实例
- awk next 使用
- fetch next form用法
- 字符串算法--KMP--Java实现
- C语言KMP匹配算法的实现