【数据结构&&算法系列】KMP算法介绍及实现(c++ && java)
2014-05-31 20:23
1016 查看
KMP算法如果理解原理的话,其实很简单。
KMP算法的名称由三位发明者(Knuth、Morris、Pratt)的首字母组成,又称字符串查找算法。
个人觉得可以理解为最小回溯算法,即匹配失效的时候,尽量少回溯,从而缩短时间复杂度。
KMP算法有两个关键的地方,1)求解next数组,2)利用next数组进行最小回溯。
在简单版本的KMP算法中,每个位置 j 的 next 值表示的是模式串的最长前缀的最后一个字符的位置(假设为 k ),其中最长前缀(长度为 k+1 )需要与模式串截至当前位置长度亦为 k+1 的后缀匹配,且 k 最大为 j-1 ,否则相当于没有回溯。当k=-1的时候,表示找不到这样的最长前缀。
用公式表示为
当k=-1的时候,表示空串。p表示模式串。
下面举一个计算next数组的例子,假设模式串是 “ abaabcaba ” 。
以 j = 8 为例,最长前缀为aba,最后一个字符位置为2,故 next[8] = 2 。
那么如何快速求解next数组呢?
这里有点动态规划的思想在里面,其中位置 j 等于 0 的 next 值为-1,表示找不到这样的最长前缀。 j > 0 时,next值可以通过 j - 1 位置的next值求得。
求解next[ j ]的步骤:
t = next[ j - 1 ] + 1,t 指向可能等于 p[ j ] 的位置,即 p[ t ] 可能等于 p[ j ]。
如果 p[ t ] = p[ j ] , 那么 next[ j ] = next[ j - 1 ] + 1
如果 p[ t ] != p[ j ] , 则令 t = next[ t - 1 ] + 1,继续第 2 步直到 t = 0 或者找到位置。
结束时判断p[ t ] 是否等于 p[ j ] ,如果等于则 next[ j ] = t , 否则等于 -1 。
下图表示了第一次不匹配,第二次匹配的过程,其它过程可以类推。其中 或 覆盖部分表示最长匹配串。 为待判定位置,
为已判定位置。
0123 j
×××××××××××××××××××××××××××××××××××××××××
×××××××××××××××××××××××××××××××××××××××××
p ××××××××××××××
在j处不失配时,前面的有部分匹配,这时需要利用next数组信息进行最小回溯。
s ××××××××××××××××××××××××××××××××××××××××××××
p ××××××××××××××
(这里 i 指向 s , j 指向 p。)
注意在 j = 0 的时候失配时,直接 i++ 即可。
当 j > 0 的时候,需要利用next数组最快找到 p[ j ] == s[ i ] 的位置。
如果 j 移动到了0还找不到,则 i++,然后继续匹配。
这里我们可以发现只有 j 回溯了,i没有回溯,但是由于普通版本的 KMP 算法 j 需要不停地回溯直到找到合适的回溯位置,因此速度不是特别快,还可以继续优化,感兴趣的读者可以想想如何事先求解好next数组从而不需要不停地回溯。
由于有人问有没有java版本的,由于鄙人java比较挫,写java时部分还写成了scala的语法,不知道代码是否规范,有优化的地方还麻烦java方面的大神指点。
import java.util.*;
public class StrStrSolution {
private List<Integer> getNext(String p){
List<Integer> next = new ArrayList<Integer>();
next.add(-1);
for(int i=1; i<p.length(); i++){
int j = next.get(i-1);
while(p.charAt(j+1) != p.charAt(i) && j>=0)
j = next.get(j);
if(p.charAt(j+1) == p.charAt(i))
next.add( j + 1 );
else
next.add( -1 );
}
return next;
}
public String strStr(String haystack, String needle) {
if (haystack == null || needle == null) return null;
if (needle.length() == 0) return haystack;
if (needle.length() > haystack.length()) return null;
List<Integer> next = getNext(needle);
int i = 0;
int j = 0;
int haystackLen = haystack.length();
int needleLen = needle.length();
while(i < haystackLen && j < needleLen){
if(haystack.charAt(i) == needle.charAt(j) ) {
i++;
j++;
if(j == needleLen) return haystack.substring(i - j);
}else{
if(j==0) i++;
else j = next.get(j-1)+1;
}
}
return null;
}
public static void main(String[] args) {
String s = "babcabaabcacbac";
String p = "abaabcac";
StrStrSolution sol = new StrStrSolution();
System.out.println(sol.strStr(s,p));
}
}
KMP算法简介
这里根据自己的理解简单介绍下。KMP算法的名称由三位发明者(Knuth、Morris、Pratt)的首字母组成,又称字符串查找算法。
个人觉得可以理解为最小回溯算法,即匹配失效的时候,尽量少回溯,从而缩短时间复杂度。
KMP算法有两个关键的地方,1)求解next数组,2)利用next数组进行最小回溯。
1)求解next数组
next数组的取值只与模式串有关,next数组用于失配时回溯使用。在简单版本的KMP算法中,每个位置 j 的 next 值表示的是模式串的最长前缀的最后一个字符的位置(假设为 k ),其中最长前缀(长度为 k+1 )需要与模式串截至当前位置长度亦为 k+1 的后缀匹配,且 k 最大为 j-1 ,否则相当于没有回溯。当k=-1的时候,表示找不到这样的最长前缀。
用公式表示为
当k=-1的时候,表示空串。p表示模式串。
下面举一个计算next数组的例子,假设模式串是 “ abaabcaba ” 。
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
p | a | b | a | a | b | c | a | b | a |
next[j] | -1 | -1 | 0 | 0 | 1 | -1 | 0 | 1 | 2 |
那么如何快速求解next数组呢?
这里有点动态规划的思想在里面,其中位置 j 等于 0 的 next 值为-1,表示找不到这样的最长前缀。 j > 0 时,next值可以通过 j - 1 位置的next值求得。
求解next[ j ]的步骤:
t = next[ j - 1 ] + 1,t 指向可能等于 p[ j ] 的位置,即 p[ t ] 可能等于 p[ j ]。
如果 p[ t ] = p[ j ] , 那么 next[ j ] = next[ j - 1 ] + 1
如果 p[ t ] != p[ j ] , 则令 t = next[ t - 1 ] + 1,继续第 2 步直到 t = 0 或者找到位置。
结束时判断p[ t ] 是否等于 p[ j ] ,如果等于则 next[ j ] = t , 否则等于 -1 。
下图表示了第一次不匹配,第二次匹配的过程,其它过程可以类推。其中 或 覆盖部分表示最长匹配串。 为待判定位置,
为已判定位置。
0123 j
×××××××××××××××××××××××××××××××××××××××××
×××××××××××××××××××××××××××××××××××××××××
2)利用next数组进行最小回溯
s ××××××××××××××××××××××××××××××××××××××××××××p ××××××××××××××
在j处不失配时,前面的有部分匹配,这时需要利用next数组信息进行最小回溯。
s ××××××××××××××××××××××××××××××××××××××××××××
p ××××××××××××××
(这里 i 指向 s , j 指向 p。)
注意在 j = 0 的时候失配时,直接 i++ 即可。
当 j > 0 的时候,需要利用next数组最快找到 p[ j ] == s[ i ] 的位置。
如果 j 移动到了0还找不到,则 i++,然后继续匹配。
这里我们可以发现只有 j 回溯了,i没有回溯,但是由于普通版本的 KMP 算法 j 需要不停地回溯直到找到合适的回溯位置,因此速度不是特别快,还可以继续优化,感兴趣的读者可以想想如何事先求解好next数组从而不需要不停地回溯。
代码实现
strStr返回的是首次匹配的地址,如果不能匹配则返回NULL。class Solution { public: vector<int> getNext(char* &s){ vector<int> next(strlen(s), -1); for(int i=1; i<strlen(s); i++){ int j = next[i-1]; /* 前一个字符的最长匹配长度 */ while(s[j+1] != s[i] && j>=0) j = next[j]; if(s[j+1] == s[i]) next[i] = j+1; // else 默认为-1 } return next; } char *strStr(char *haystack, char *needle) { if(haystack==NULL || needle==NULL) return NULL; if(strlen(haystack) < strlen(needle)) return NULL; if(strlen(needle) == 0) return haystack; vector<int> next = getNext(needle); int i = 0; int j = 0; int haystackLen = strlen(haystack); int needleLen = strlen(needle); while(i<haystackLen && j<needleLen){ if(haystack[i] == needle[j] ) { i++; j++; if(j == needleLen) return haystack + i - j; }else{ if(j == 0) i++; else j = next[j-1]+1; /* 该步骤可以优化 */ } } return NULL; } };
由于有人问有没有java版本的,由于鄙人java比较挫,写java时部分还写成了scala的语法,不知道代码是否规范,有优化的地方还麻烦java方面的大神指点。
import java.util.*;
public class StrStrSolution {
private List<Integer> getNext(String p){
List<Integer> next = new ArrayList<Integer>();
next.add(-1);
for(int i=1; i<p.length(); i++){
int j = next.get(i-1);
while(p.charAt(j+1) != p.charAt(i) && j>=0)
j = next.get(j);
if(p.charAt(j+1) == p.charAt(i))
next.add( j + 1 );
else
next.add( -1 );
}
return next;
}
public String strStr(String haystack, String needle) {
if (haystack == null || needle == null) return null;
if (needle.length() == 0) return haystack;
if (needle.length() > haystack.length()) return null;
List<Integer> next = getNext(needle);
int i = 0;
int j = 0;
int haystackLen = haystack.length();
int needleLen = needle.length();
while(i < haystackLen && j < needleLen){
if(haystack.charAt(i) == needle.charAt(j) ) {
i++;
j++;
if(j == needleLen) return haystack.substring(i - j);
}else{
if(j==0) i++;
else j = next.get(j-1)+1;
}
}
return null;
}
public static void main(String[] args) {
String s = "babcabaabcacbac";
String p = "abaabcac";
StrStrSolution sol = new StrStrSolution();
System.out.println(sol.strStr(s,p));
}
}
相关文章推荐
- 【转载】【数据结构&&算法系列】KMP算法介绍及实现(c++ && java)
- 【数据结构&&等差数列】KMP简介和算法的实现(c++ && java)
- 【数据结构&&算法系列】快速排序简单介绍及实现
- 【数据结构&&算法系列】堆排序简单介绍及其实现
- 【数据结构&&等差数列】KMP简介和算法的实现(c++ && java)
- 数据结构Java实现——③串--->串的模式匹配:Brute-Force算法和 KMP算法
- 【算法系列】——Java实现插入&&希尔排序
- 【数据结构&&算法系列】插入排序简单介绍及python代码
- 【数据结构&&算法系列】并查集简单介绍
- 【算法系列】——Java实现冒泡&&快速
- 数据结构和算法 C/C++ Java 和 C# 版 - (2)线性表 精准表述 实现
- 【数据结构&&算法系列】归并排序简单介绍及python代码
- 算法思路重新实现-堆排序 中的 C++ & Java
- 算法与数据结构课程中的c++实现的顺序表和链表
- Javascript & Java & C++系列
- 数据结构与算法之—顺序栈c++面向对象实现
- 用C++实现数据结构中的各种算法
- c++实现"四分位数"算法1
- 数据结构—中缀表达式转后缀表达式算法及实现—栈的应用—计算表达式(C++代码实现)(1)
- 数据结构_串_串的模式匹配_KMP算法_C++实现