今天突然被問起字符串匹配的算法,竟啞口無言,只剩下一些模糊的印象,回來惡補了一番。
樸素的匹配算法很簡單,往前找如果有部分成功的也回退,如果目標字符串長度爲m,源字符串長度爲n則時間複雜度爲O(m*n);
而克努斯等人發現了更快的不用回退的算法,即KMP(Knuth,Morris,Pratt)算法,在部分查找成功之後,用成功的記錄確定下次匹配開始位置。時間複雜度爲O(m+n);
KMP算法在目標字符串及源字符串較小的情況下運行時間和樸素匹配的時間複雜度相差不大,但一旦查找很長的字符串並且 目標字符串首尾相同的子字符串很多。
那麼KMP的優勢一下就體現出來了,它是跳躍式前進,比O(m*n)快了幾個數量級。
public class KMPMatch {
/*KMP算法是樸素模式匹配算法(複雜度爲O(m*n))的改進
* 在查找過程中不會退,運用失敗函數提供重新匹配起始位置
* 時間複雜度爲O(m+n)
* 是Knuth,Morris,Pratt一起提出的算法,故此命名
*/
/*這裏爲了性能,不用Java封裝類型,一般需要匹配的字符不至於超過100,如果有需求
*的話可以換種寫法,使用構造函數來給數組指定大小
*/
private int [] failArray = new int [100];
/*
* 如 public KPMMatch(int size){
* failArray = new int [size];
* }
*
* */
public int match(char [] pattern,char[] source ){
//定義2個變量存儲目標字符串長度和待查找字符串長度
int pLength = pattern.length;
int sLength = source.length;
//定義2個變量用於表示2個字符串下標
int pi = 0,si = 0;
//在查找未結束之前
while(pi< pLength && si <sLength){
//如果相等,一直往前
if(pattern[pi]==source[si]){
++si;
++pi;
}//如果第一次就失敗
else if(0 == pi){
++si;
}//這裏使用失敗數組確定重新匹配的起始位置
else{
pi = failArray[pi-1]+1;
}
}
//如果知道源字符串到結尾仍未找到,那麼失敗,返回-1
if(pi < pLength)
return -1;
//否則查找成功
return si - pLength;
}
public int fail(char [] pattern ){
int pLength = pattern.length;
failArray[0] = -1;
//對所有進行計算,從第二位開始
for(int j = 1; j < pLength - 1;j++){
//開始時,pi爲-1
int pi = failArray[j-1];
//如果p0p1p2 == p6p7p8 但是 p3 != p9,那麼失敗
while(pattern[j]!=pattern[pi+1] && pi > 0){
pi = failArray[j-1];
}
//如果成功,值加1
if(pattern[j]==pattern[pi+1])
failArray[j] = pi+1;
else
failArray[j] = -1;
}
return 0 ;
}
}
public class Test {
public static void main(String[] args) {
KMPMatch kmpTest = new KMPMatch();
char [] p = {'c','a','b'};
char [] s = {'c','c','a','e','f','h','b','<span style="color:#FF0000;">c','a','b'</span>,'c'};
kmpTest.fail(p);
System.out.println(kmpTest.match(p, s));
}
}
額,運行的時候,電腦內存突然爆炸,而且一直在運行,那就說明程序陷入了死循環,用System.out.println("hello");來逐步調試,才發現
竟然沒調用fail方法,而failArray中全是0,然後match方法一直走第三個判斷語句,source不向前,pattern也不向前,所以就一直在while循環裏,死掉了。。。