您的位置:首页 > 其它

KMP算法

2015-08-24 13:56 344 查看
我尝试尽量用简洁且通俗易懂的语言去描述这个问题。

定义:字符串匹配是计算机科学中最古老、研究最广泛的问题之一。一个字符串是一个定义在有限字母表∑上的字符序列。例如,ATCTAGAGA是字母表∑ = {A,C,G,T}上的一个字符串。字符串匹配问题就是在一个大的字符串T中搜索某个字符串P的所有出现位置。其中,T称为文本,P称为模式,T和P都定义在同一个字母表∑上。

一种比较朴素的思想是 对主串的每一个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做P的长度的小循环,直到匹配成功或者全部遍历完成为止。伪代码如下:



n ← length[T]

m ← length[P]

for s←0 to n-m

     do if P[1..m] = T[s+1..s+m]

          then print s+1

最坏情况下的运行时间是O(nm)

这种直接暴力的方法很明显不是最优的

举个例子 假设T="abcdefgab" P="abcdex"按照朴素的思想T中当i=1,2,3,4,5时首字母与P的首字母均不相等。但仔细看P中的“a”与后面的“bcdex”中任意一个字符都不想等。也就是说,既然“a”不与自己后面的子串的中任何一字符相等,则P的首字符“a”不可能与T中的第2位到第5位字符相等。那么再一位一位的从头开始循环无疑是低效的。也许有人会问,如果P串后面也含有字符“a”呢?

再看一个例子T=“abcabcabc” P=“abcabx”对于开始的判断,前5个字符完全相等,第6个字符不等,则根据刚才的方法,P的首字符“a”与P的第二位字符“b”和第三位字符“c”均不相等,所以不需要做判断,直接移动两位。因为P的首位“a”与P的第四位“a”相等,第二位的“b”与第五位的“b”相等,而在第一次比较时,第四位的“a"和第五位的“b”已经与T中的相应位置比较过了,是相等的,因此可以判定,P的首字符“a”、第二位的字符“b”与T的第四位和第五位也不需要比较了,肯定也是相等的,之前已经比较过了,所以可以继续移动两位。所以,对于在P中有与首字符相等的字符,也是可以省略一部分不必要的判断步骤的。

总之,我们可以从P字符串中先预处理出一些信息,
供我们随后处理不必要的比较时使用KMP算法是先通过P字符串预处理出若当前字符未能够匹配则直接跳转过没必要进行比较的位置,然后与T字符串进行匹配。预处理的过程就是算出T的部分匹配情况(一般用next数组或pre数组表示,这里用pre表示)首先,要了解两个概念:"前缀"和"后缀"。
"前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。pre[j]的值就是字符串P[0..j]"前缀"和"后缀"的最长的共有元素的长度.具体怎么求呢?伪代码如下:

GET-PRE(P)

j ← 0

pre[1] ← 2

for i ← 2 to m

   do while j > 0 and P[j+1] ≠ P[i]

              do j ← pre[j]

   if  T[j+1] = P[i] then j ← j+1

   pre[i] ← j


KMP算法的伪代码如下:

KNUTH-MORRIS-PRATT(T,P)

j ← 0

for i ← 1 to n

   do while j > 0 and T[j+1] ≠ P[i]

              do j ← pre[j]

   if  T[j+1] = P[i] then j ← j+1

   if  j = m then

          print i

          j ← pre[j]


附上代码模板 

[算法名称]:KMP算法

[任务]:要求实现一种算法使得能够在线性复杂度内求出一个串在另一个串的所有匹配位置

[说明]:设模板串是patten,令next[i]=max{k|patten[0...k-1]=patten[i-k+1...i]},求解next[]可以使用动态规划,即next[i+1]可以由next[i],next[next[i],...得到。

得到next[]数组之后,设两个指针i和j,分别指向文本串和模式串,成功匹配得向后移动j,否则把j移动到next[j]。当j移动到模式串末尾时,就说明匹配成功。

[接口]:

vector <int> find_substring(string patten ,string text);

复杂度O(N+M)

输入:patten模式串

text     文本串

输出:所有匹配点的下标

[代码]:#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<memory.h>
#include<map>
#include<queue>
#include <deque>
#include <list>
#include <ctime>
#include <stack>
#include <vector>
#include<cstring>
#include<set>
#define Maxn
typedef long long ll;
#define FOR(i,j,n) for(int i=j;i<=n;i++)
#define DFR(i,j,k) for(int i=j;i>=k;--i)
#define lowbit(a) a&-a
#define Max(a,b) a>b?a:b
#define Min(a,b) a>b?b:a
const int inf = 0x3f3f3f3f;
const double pi = acos(-1.0);
using namespace std;
int positions[10000];
vector <int> find_substring(string patten ,string text)
{ int n=patten.size();
vector <int> next(n+1,0);
FOR(i,1,n-1)
{int j=i;
while(j>0)
{
j=next[j];
if(patten[j]==patten[i])
{
next[i+1]=j+1;
break;
}
}

}
vector<int>positions;
int m=text.size();
int x=0;
for(int i=0,j=0;i<m;++i)
{
if(j<n&&text[i]==patten[j])j++;
else {while(j>0)
j=next[j];
if(text[i]==patten[j]){j++;break;}

}
if(j==n)positions.push_back(i-n+1);
}

return positions;
}
int main()
{ string p,t;
cin>>p>>t;
vector<int>::iterator it;
vector<int> a= find_substring(p,t);
for(it=a.begin();it!=a.end();it++)
cout<<*it<<" ";
return 0;
}


具体算法的正确性的证明可以看看CLRS把

这里贴个链接如果还不理解的朋友可以看看这个

点击打开链接
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: