【转载】【数据结构&&算法系列】KMP算法介绍及实现(c++ && java)

原文地址:http://blog.csdn.net/ksearch/article/details/27837847


KMP算法如果理解原理的话,其实很简单。


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 ” 。


j012345678
pabaabcaba
next[j]-1-1001-1012

以 j = 8 为例,最长前缀为aba,最后一个字符位置为2,故 next[8] = 2 。


那么如何快速求解next数组呢?

这里有点 动态规划的思想在里面,其中位置 j 等于 0 的 next 值为-1,表示找不到这样的最长前缀。 j > 0 时,next值可以通过 j - 1 位置的next值求得。

求解next[ j ]的步骤:
  1. t = next[ j - 1 ] + 1,t 指向可能等于 p[ j ] 的位置,即 p[ t ] 可能等于 p[ j ]。
  2. 如果 p[ t ]   =  p[ j ] , 那么 next[ j ] = next[ j - 1 ] + 1
  3. 如果 p[ t ]  !=  p[ j ] , 则令 t = next[ t - 1 ] + 1,继续第 2 步直到 t = 0 或者找到位置。
  4. 结束时判断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。


  1. class Solution {  
  2. public:  
  3.     vector<int> getNext(char* &s){  
  4.         vector<int> next(strlen(s), -1);  
  5.   
  6.         for(int i=1; i<strlen(s); i++){  
  7.             int j = next[i-1]; /* 前一个字符的最长匹配长度 */  
  8.               
  9.             while(s[j+1] != s[i] && j>=0)   
  10.                 j = next[j];  
  11.   
  12.             if(s[j+1] == s[i])   
  13.                 next[i] = j+1;  
  14.             // else 默认为-1  
  15.         }  
  16.   
  17.         return next;  
  18.     }  
  19.   
  20.     char *strStr(char *haystack, char *needle) {  
  21.         if(haystack==NULL || needle==NULL) return NULL;  
  22.         if(strlen(haystack) < strlen(needle)) return NULL;  
  23.         if(strlen(needle) == 0) return haystack;  
  24.   
  25.         vector<int> next = getNext(needle);  
  26.         int i = 0;  
  27.         int j = 0;  
  28.         int haystackLen = strlen(haystack);  
  29.         int needleLen = strlen(needle);  
  30.         while(i<haystackLen && j<needleLen){  
  31.             if(haystack[i] == needle[j] ) {  
  32.                 i++;  
  33.                 j++;  
  34.                 if(j == needleLen) return haystack + i - j;  
  35.             }else{  
  36.                 if(j == 0) i++;  
  37.                 else j = next[j-1]+1; /* 该步骤可以优化 */  
  38.             }  
  39.         }  
  40.   
  41.         return NULL;  
  42.     }  
  43. };  



由于有人问有没有java版本的,由于鄙人java比较挫,写java时部分还写成了scala的语法,不知道代码是否规范,有优化的地方还麻烦java方面的大神指点。


  1. import java.util.*;  
  2.   
  3. public class StrStrSolution {  
  4.     private List<Integer> getNext(String p){  
  5.         List<Integer> next = new ArrayList<Integer>();  
  6.         next.add(-1);  
  7.   
  8.         for(int i=1; i<p.length(); i++){  
  9.             int j = next.get(i-1);  
  10.   
  11.             while(p.charAt(j+1) != p.charAt(i) && j>=0)   
  12.                 j = next.get(j);  
  13.   
  14.             if(p.charAt(j+1) == p.charAt(i))   
  15.                 next.add( j + 1 );  
  16.             else  
  17.                 next.add( -1 );              
  18.         }  
  19.   
  20.         return next;  
  21.     }  
  22.   
  23.     public String strStr(String haystack, String needle) {  
  24.         if (haystack == null || needle == nullreturn null;  
  25.         if (needle.length() == 0return haystack;  
  26.         if (needle.length() > haystack.length()) return null;  
  27.           
  28.         List<Integer> next = getNext(needle);    
  29.         int i = 0;    
  30.         int j = 0;  
  31.         int haystackLen = haystack.length();  
  32.         int needleLen = needle.length();    
  33.         while(i < haystackLen && j < needleLen){    
  34.             if(haystack.charAt(i) == needle.charAt(j) ) {    
  35.                 i++;    
  36.                 j++;    
  37.                 if(j == needleLen) return haystack.substring(i - j);    
  38.             }else{    
  39.                 if(j==0) i++;    
  40.                 else j = next.get(j-1)+1;   
  41.             }    
  42.         }    
  43.   
  44.         return null;  
  45.     }  
  46.   
  47.     public static void main(String[] args) {  
  48.         String s = "babcabaabcacbac";  
  49.         String p = "abaabcac";  
  50.         StrStrSolution sol = new StrStrSolution();  
  51.         System.out.println(sol.strStr(s,p));   
  52.     }  
  53. }  


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值