1. 程式人生 > >資料結構——串的模式匹配演算法

資料結構——串的模式匹配演算法

2、串的模式匹配演算法

       串的查詢操作也稱作串的模式匹配操作,模式匹配操作的具體含義是:在主串(也稱作目標串)中,從位置start開始查詢是否存在子串(也稱作模式串),如在主串中查詢到一個與模式串相同的子串,則稱查詢成功;如在主串中為查詢到一個與模式串相同的子串,則稱查詢失敗。當模式匹配成功時函式返回模式串的第一個字元在主串中的位置,當模式匹配失敗時返回-1。

2、1 Brute-Force演算法

       Brute-Force演算法實現模式匹配的思想是:設主串為s="
s 0 s 1 …s n-1 ",模式串為t=" t 0 t 1 …t n-1 "。 (1)從主串s的第一個字元開始和模式串t的第一個字元比較,若相等則繼續比較後續字元。 (2)若主串s的第一個字元和模式串t的第一個字元比較不相等,則從主串s的第二個字元開始重新與模式串t的第一個字串比較,若相等則繼續比較後續字元。 (3)如此不斷繼續。若存在模式串t中的每個字元依次和主串s中的一個連續字元序列相等,則模式匹配成功,函式返回模式串t的第一個字元在主串s中的下標;若比較完主串s的所有字元序列,不存在一個和模式串t相等的子串,則模式匹配失敗,函式返回-1。

public class BruceForce {
	public static int bruceForce(String str , String subStr) {
		int result = 0;
		int len = str.length();
		int lenSub = subStr.length();
		if (lenSub > len) {
			result = -1;
		}
		int i = 0 , j = 0;
		while (i < len && j < lenSub) {
			if (str.charAt(i) == subStr.charAt(j)) {
				i ++;
				j ++;
			}
			else {
				i = i - j + 1;
				j = 0;
			}
		}
		if (j == lenSub) {
			result = i - lenSub + 1;
		}
		return result;
	}
	
	public static void main(String[] args) {
		String str = "cddcdc";
		String subStr = "cdc";
		int result = bruceForce(str, subStr);
		if (result > 0) {
			System.out.println("pos = " + result);
		}
		else if (result == 0) {
			System.out.println("未找到!");
		}
		else if (result == -1) {
			System.out.println("子串比主串長");
		}
	}
}
輸出結果為: pos = 4        這個演算法簡單並易於理解,但是有些情況下時間效率並不高。主要原因是:在主串和子串已有相當多個字元比較相等的情況下,只要有一個字元比較不相等,便需要把主串的比較位置(即函式中變數i的值)回退。設主串的長度為n,子串的長度為m,則Brute-Force演算法在最好情況下的時間複雜度為O(m),在最壞的情況下的時間複雜度為O(n*m)。

2、2 KMP演算法

1、Bruce-Force演算法的缺點以及解決方法分析

        KMP演算法是Brute-Force演算法基礎上的改進演算法。KMP演算法的特點主要是,消除了Brute-Force演算法的主串比較位置在相當多字元比較相等後,只要有一個字元比較不相等,主串位置便需要回退的特點。        分析Brute-Force演算法的匹配過程可以發現,演算法中的主串比較位置的回退並非一定比較。這可分為以下兩種情況。 (1)第一種情況如上節的圖中所示。主串s="cddcdc"、模式串t="cdc"的模式匹配過程為:當s0= t0,s1= t1,s1≠t1時,演算法中下一次的比較位置為i=1,j=0,接下來比較s1和t0。但是因為t0≠t1,而s1=t1,所以一定有s1≠t0。所以此時比較s1和t0無意義,實際上隨後可直接比較s2和t0 (2)第二種情況,主串s="abacabab"、模式串t="abab"的第一次匹配如下圖所示。此時有 s0= t0='a',s1= t1='b',s2= t2='a',s3≠t3。因此有t0≠t1,s1= t1,所以必有s1≠t0。又因有t0= t2,s2= t2,所以必有s2=t0。因此下面可以直接比較s3和t1

       總結以上兩種情況可以發現,一旦si和tj比較不相等時,主串s的比較位置不必回退,主串的si可直接和模式串的t k (0≤k<j)比較,k的確定與主串s並無關係,k的確定只與模式串t本身的構成有關,即從模式串本身就可以求出k的值。        現在討論一般情況。設s=" s 0 s 1 …s n-1 ",t="t 0 t 1 …t m-1 ",當模式匹配比較到s i ≠t j (0≤i<n,0≤j<m)時,必存在"s i-j s i-j+1 …s i-1 "="t 0 t 1 …t j-1 "。①若此時模式串"t 0 t 1 …t j-1 "中不存在任何"t 0 t 1 …t k-1 "="t j-k t j-k+1 …t j-1 "(0<k<j)形式的真子串,則說明在模式串 " t 0 t 1 …t j-1 "中不存在任何以為t 0 首字元的字串與主串 " s i-j s i-j+1 …s i-1 "中分別以 s i-j s i-j+1 、...、 s i-1 為首字元的字串匹配,下一次可直接比較 s i t 0 。②此時若模式中存在如 " t 0 t 1 …t k-1 "=" t j-k t j-k+1 …t j-1 "形式的真子串,則說明模式中的子串已經和主串匹配,下次可以直接比較 s i t k

2、KMP演算法的改進

分析式"t0t1…tk-1"="tj-ktj-k+1…tj-1"(0<k<j),模式串中是否存在可相互重疊的真子串,只與模式串自身有關,與主串無關。因此,對於主串s="s0s1…sn-1",子串,t="t0t1…tm-1",可以首先計算出模式串t中每個字元的最大榛子穿的字元個數k。當模式匹配到si≠tj(0≤i<n,0≤j<m)時,隨後要比較的主串的下標值不變,模式串的下邊值即為k。

3、模式串中最大真子串的求法

       模式串中每個字元的最大真子串構成一個數組,定義為模式串的next[j]函式。模式串的next[j]函式定義如下:
       next[j]函式表示的是模式串t中是否存在最大真子串,以及最大真子串的字元個數k。這裡之所以稱為最大真子串,是因為:①求出的是所有子串中最大子串;②不允許k等於j。
             next[j]定義中的第一種情況,是在模式串"t0t1…tj-1"中存在這樣兩個長度均小於j的字串,其中一個字串以t0為首字元,另一個字串以tj-1為末字元,滿足 "t0t1…tk-1"="tj-ktj-k+1…tj-1", 且這樣的相等子串是所有這種相等子串中長度最大的。         next[j]定義中的第二種情況,是在模式串 " t 0 t 1 …t j-1 " 中不存在任何滿足"t0t1…tk-1"="tj-ktj-k+1…tj-1"條件的真子串。
        next[j]定義中的第三種情況,是當j=0是給出的特殊取值。當j=0是,令netx[j]函式取值為-1。在函式這幾種,當netx[j]=-1時,令主串的下標和模式串的下標同時增1,即隨後用子串的第一個字元和當前字元的下一個字元進行比較。

4、KMP函式設計

       KMP函式中,當模式串t中的tj與主串s的si(i≥j)比較不相等時,若模式串t中不存在如上所說的真子串,有next[j]=0,則下一次比較si和t0,這是第一種情況;若模式串t存在真子串"t0t1…tk-1"="tj-ktj-k+1…tj-1",且滿足0<k<j,則有next[j]=k,則下一次比較主串s的si和子串t的tk,這是第二種情況;當j=0時有next[j]=-1,則令主串的下標和模式串的下邊同時增1,即隨後用主串s當前字元的下一個字元和子串t的t0比較。        KMP函式可按如下方法設計:設s為主串,t為模式串,i為主串當前比較字元的下標,j為模式串當前比較字元的下標。令i的初值為start,j的初值為0。當si=tj時,i和j分別增1再繼續比較;否則i不變,j改為next[j]值在繼續比較。筆記哦啊過程中有兩種情況:一是j增加到某個值或j退回到某個j=next[j]值時有si=tj,則此時i和j分別增1再繼續比較;二是j退回到j=-1時,令主串和子串的下標各增1,隨後比較si+1t0。這樣的迴圈過程知道變數i大於等於主串s的長度或變數j大於等於子串t的長度終止。

5、計算next[j]值的函式設計

next[j]值的計算問題是一個遞推計算問題。設有next[j]=k,即模式串t中存在,其中k為滿足等式的最大值"t 0t 1…t k-1"="t j-kt j-k+1…t j-1"(0<k<j),則計算next[j+1]的值有以下兩種情況。 (1)若t k=t j,則表明在模式串中有 "t 0 t 1 …t k "="t j-k t j-k+1 …t j ",且不可能存在任何一個k'>k滿足上式,因此有:next[j+1]=next[j]+1=k+1。 (2)若 t k ≠t j ,則可把計算next[j+1]值的問題看成是把模式串t'向右滑動至k'=next[k](0<k'<k<j)。若此時tk'=tj,則表明在模式串t中有 "t0t1…tk'"="tj-k'tj-k'+1…tj"(0<k'<k<j)有:next[j+1]=k'+1=next[k]+1。若此時tk'≠tj,則將模式串t'右滑到k''=next[k']後繼續匹配。以此類推,直到某次比較有tk=tj(此即為上述情況),或某次比較有tk≠tj且k=0,此時有:next[j+1]=0。

6、程式碼示例

public class KMP {
	public static int indexKMP(String str , String subStr , int start) { //str表示主串,subStr表示子串,start表示開始比較的下標
		int[] next = getNext(subStr);
		int i = start , j = 0 , result;
		while ( i < str.length() && j < subStr.length() ) {
			if ( (j == -1) || (str.charAt(i) == subStr.charAt(j)) ) {
				i++;
				j++;
			}
			else
				j = next[j];
		}
		if(j == subStr.length())
			result = i - subStr.length() + 1;
		else
			result = -1;
		return result;
	}
	
	public static int[] getNext(String subStr) {
		int j = 1 , k = 0;
		int[] next = new int[subStr.length()];
		
		next[0] = -1;
		next[1] = 0;
		while (j < subStr.length() - 1) {
			if (subStr.charAt(j) == subStr.charAt(k)) {
				next[j + 1] = k + 1;
				j++;
				k++;
			}
			else if ( k == 0 ) {
				next[j + 1] = 0;
				j++;
			}
			else 
				k = next[k];
		}
		return next;
	}
	
	public static void main(String[] args) {
		String str = "cddcdc";
		String subStr = "cdc";
		int result = indexKMP(str , subStr , 0);
		if ( result == -1 ) 
			System.out.println("Not find!");
		else 
			System.out.println("pos = " + result);
	}
}
輸出結果為: pos = 4