KMP算法理解

KMP算法理解


字符串匹配算法之KMP算法一直以來都很難理解,雖然知道要減少不必要的匹配,但是仍然不懂裏面的思想,即使看着代碼。後來看到(2)中博客中的解釋才清晰了許多,不至於在什麼DFA,前綴表,部分匹配表等概念中迷失自己。主要的指導思想在於當發生不匹配的時候如何更有效的利用現在已經匹配的字符串的信息來加速移動過程,部分匹配表的存在正是挖掘一個字符串中前綴和後綴中最長公共串,比如ABCABCD,在匹配'D'時失敗,考察字符"ABCABC",得到pmt['ABCABC']=3,所以外圍循環索引直接前進3個step。


下面利用這種最原始的想法來實現KMP,雖然在找最長公共前綴後綴時顯得很low,但是很能說明最初的想法。
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;


public class KMP2 {
    private int[] pmt;       // Partial Match Table

    private String pat;        // the pattern string

    // create the Partial Match Table from a pattern String
    public KMP2(String pat) {
        this.pat = pat;

        int M = pat.length();
        pmt = new int[M]; 
        computePMT();
    } 

    // 利用最蠢的方法來計算部分匹配表,前綴和和後綴共有元素的最大長度
    public void computePMT() {
    	pmt[0] = 0; 
    	for(int i=1; i<pat.length(); i++){
    		String current = pat.substring(0,i+1);
    		// 得到current的所有前綴和後綴,然後求交集,利用集合
    		Set<String> common = getCommonFromPrefixSuffix(current);
    		// 然後求得最大的公共元素長度
    		int longest = 0;
    		if(!common.isEmpty()){
    			Iterator<String> iter = common.iterator();
    			int cur = 0;
    			while(iter.hasNext()){
    				cur = iter.next().length();
    				if(cur > longest)
    					longest = cur;
    			}
    		}
    		pmt[i] = longest;
    	}
	}

	public Set<String> getCommonFromPrefixSuffix(String current) {
		if(current.length() < 1)
			return null;
		Set<String> prefixs = new HashSet<String>();
		Set<String> suffixs = new HashSet<String>();
		int len = current.length();
		
		for(int i=0; i<len-1; i++){
			prefixs.add(current.substring(0, i+1));
		}
		for(int i=1; i<len; i++){
			suffixs.add(current.substring(i, len));
		}
		
		prefixs.retainAll(suffixs);
		//System.out.println(prefixs);
		return prefixs;
	}

	// return offset of first match; N if no match
    public int search(String txt) {
        int M = pat.length();
        int N = txt.length();
        int i = 0, j=0;
        while(i<N){
        	System.out.println("i:" + i);
        	j = 0;
        	while(j<M){
        		if(txt.charAt(i+j) != pat.charAt(j)){
        			// 移動位數 = 已經匹配的字符數 - 對應的此刻的部分匹配值
        			int offset = 0; // 後面體現了i右移 
        			if(j>=1){
        				offset = j - pmt[j-1] - 1; //特別注意這裏
            			
        			} 
        			i += offset;
        			break;
        		}else 
        			j++;
        	}
        	if (j == M) return i;            // found at offset i
        	 
        	i++;
        }
        return N;                            // not found    
    }

	public static void test1(){
    	String pat = "ABABAC";
        String txt = "BCBAABACAABABACAA";
        KMP2 kmp = new KMP2(pat);
        System.out.println(Arrays.toString(kmp.pmt));
        int offset1 = kmp.search(txt);
        System.out.println(offset1);
        System.out.println(txt);
        for (int i = 0; i < offset1; i++)
        	System.out.print(" ");
        System.out.println(pat);

    }


TODO:如何高效的計算部分匹配表??





Reference:
(1)http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
(2)http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/
(3)http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章