一文搞懂BF算法與KMP算法
一.暴力算法(BF算法)
假設我們現在要對上面的字符串進行匹配(假設上面那個串是P,下面那個串是T串)。最開始我們看T的前三個串和P是匹配的,第四個發現不匹配於是我們右移一下
發現不匹配,再右移一步
一直這樣直到
於是停止,當然也有可能最後沒有對應的匹配串。
代碼實現:
import org.junit.Test;
public class Main {
//從P裏面找T的匹配串並返回對應索引的位置,如果沒有返回-1
int BF(String P,String T){
int i=0,j=0,current=0;
while(j<T.length()&¤t<P.length()){
while(current<P.length()&&j<T.length()&&T.charAt(j)==P.charAt(current)){
current++;
j++;
}
if(j==T.length())
return i;
else
{
j=0;
i++;
current=i;
}
}
if(j==T.length())
return i;
else
return -1;
}
@Test
public void Test(){
System.out.println(BF("abaacababcac","ababc"));
}
}
算法複雜度O((m-n)*n)=O(m*n-n^2)
二.KMP算法
在看KMP算法前我們需要知道爲什麼需要KMP算法?BF算法他不香嗎?
我們先看個例子:
像這樣的就會無辜的浪費很多事間去比較。於是KMP就誕生了。
1.prefix table(前綴表)
我們還是來匹配這兩個串。
我們看下面的T串"ababc"
他的前綴串爲(我們不看他本身)
編號 | 前綴串 |
---|---|
1 | a |
2 | ab |
3 | aba |
4 | abab |
現在我們需要找到每個前綴串他的前綴與後綴(不考慮串本身)相等的最大長度
於是
前綴串 | 長度 |
---|---|
a | 0 |
ab | 0 |
aba | 1 |
abab | 2 |
接下來就是我們的前綴表:
索引 | T串 | 前後綴相等最大長度 |
0 | a | -1 |
1 | b | 0 |
2 | a | 0 |
3 | b | 1 |
4 | c | 2 |
上面這個前綴表我們可以看成一個數組,取名爲Pre;大小爲5
那麼pre[i]的含義是:
0~(i-1)這i個字符的前後綴相等最大長度,特別的當i=0時我們賦值爲-1來區分
2.前綴表的作用
還是前面的P串與T串
最開始從P[0]與T[0]對齊,開始匹配發現P[3]與T[3]不匹配,於是我們看Pre[3]=1,那麼就把T[1]與P[3]對齊再來比較:
從新的對齊位置P[3]與T[1]開始往後比較(也即綠線處那裏),不匹配,我們看Pre[1]=0,於是我們把T[0]與P[3]對齊
這時從上面的綠線處開始比較,往後比發現T[1]與P[4]不匹配了,這時Pre[1]=0,於是P[4]與T[0]對齊
這時從上面的綠線處開始比較,發現T[0]與P[4]不匹配了,這時Pre[0]=-1,於是P[4]與T[-1]對齊,而實際上T[-1]使我們虛構出來的,實際上是T[0]與P[5]對齊了
這個時候已經匹配上了,當然在這裏還沒結束,我們確實找到了第一個,但是後面可可能還有,因此下一步我們應該看T匹配完成的最後一個字符即T[4],這裏Pre[4]=2,於是我們T[2]與P[9]對齊
這時繼續前面的操作,最後到這裏纔是真正的結束了,現在我們對KMP算法已經瞭解了
3.代碼實現
實際上這裏的Pre數組就是我們常說的next數組
import org.junit.Test;
/**
* @author jackTan
*/
public class MainTest {
/**
* 在串P中匹配T串,只匹配第一個,如果沒有就返回-1,否則返回匹配到的P中的索引
* @param p
* @param t
* @return
*/
int Kmp_Search(String p,String t){
//第一步獲取前綴串
int []pre = getPre(t);
//i指向p串的字符,j指向t串的字符
int i=0,j=0;
while(i<p.length()&&j<t.length()){
if(j==-1){
j++;
i++;
}
while(i<p.length()&&j<t.length()&&p.charAt(i)==t.charAt(j)){
i++;
j++;
}
if(j==t.length()){
break;
}
else{
j=pre[j];
}
}
if(j==t.length()){
return i-t.length();
}else{
return -1;
}
}
/**
* 該方法獲取字符串的前綴表
* @param T
* @return
*/
int[] getPre(String T){
int []pre= new int[T.length()];
pre[0] = -1;
for(int i=1;i<T.length();i++){
pre[i]=getPreOne(T,i);
}
return pre;
}
/**
* @param T 目標串
* @param endIndex 表示要獲取0-endIndex的前綴值
* @return
*/
int getPreOne(String T,int endIndex){
int length = T.length();
int i=0;
for(;i<length-1;i++){
if(T.charAt(i)!=T.charAt(length-1)){
break;
}
}
return i;
}
@Test
public void Test(){
System.out.println(Kmp_Search("abaacababcac", "ababc"));
}
}