KMP算法的分析及實現(C語言)

一、算法簡介

  KMP算法是由D.E.Knuth、J.HMorris和V.R.Pratt(其中Knuth和Pratt共同研究,Morris獨立研究)發表的一個模式匹配算法,稱爲克努特——莫里斯——普拉特算法,簡稱KMP算法。該算法主要解決字符串匹配時,重複遍歷的問題。和傳統依次遍歷的算法相比,KMP算法更適合於主串與字串之間存在許多部分匹配的情況,否則無法體現出該算法的優勢。

二、算法原理

1、算法實現過程簡介

  算法中用到一個主字符串和一個子字符串,分別簡稱爲 “主串” 和 “字串”,具體的功能是在主串中找到和子串匹配(相等)的連續的字符串,然後根據不同的需求進行相應的操作。
  普通的字符串匹配時,選定好主串和子串中的起始位置下標,然後依次判斷是否主串和子串的字符是否相等,如果出現不相等的字符,則確認本次匹配失敗,結束本次匹配;如果一直判斷直到最後兩個字符相等,則本次匹配成功。如果匹配的結果爲失敗,則需要重新開始下一次的匹配,此時主串的下標需要退回到上一次的起始下標之後,子串的位置下標退回到子串的起始位置,重複上述匹配過程,直到成功匹配或者遍歷完整個主串。
  KMP進行字符串匹配時,首先選定主串和子串的起始位置下標,下標同時依次進行遍歷比較,如果一直相等直到子串結束,則本次匹配成功;如果本次匹配失敗,則主串的位置下標不用退回,保持在匹配失敗的位置,子串的位置下標退回到 next 數組中存儲的位置處,開始下一次的匹配,直到整個主串遍歷完成或者成功匹配。
  在普通字符串匹配的過程中,每開始一次匹配,主串和子串的下標都需要退回至一定的位置。KMP進行字符串匹配時,每開始一次匹配,主串的當前位置下標不用退回,子串的當前位置下標也不用退回至起始位置,而是退回至 next 數組中的指定位置

2、next 數組

2.1、簡介

  nest 數組的長度和子串的長度一致,存放的是子串的當前位置下標需要退回的位置。給定一個字符串 “ababaaaba”,它對應的 next 數組中的值是{0 1 1 2 3 4 2 2 3},假定 next 數組的下標從 1 開始,則 next[7]==2,表示如果出現了主串和子串元素不匹配的情況,子串從 2 的位置重新開始比較即可,不用從 1 的位置開始。

2.2、公式

{0j=1Max{k1<k<j,P1...Pk1=Pjk+1...Pj1}1 \left\{ \begin{matrix} 0,當 j = 1時 \\ Max\left\{k|1<k<j,且'P_1...P_k-1'='P_j-k+1...P_j-1' \right\} \\ 1,其它情況 \\ \end{matrix} \right.

2.3、求值過程

  在字符串 “abcabx” 中,next[6] 對應的子串爲 “abcab”,其中前綴 “ab” 等於後綴 “ab”,共計兩個字符相等,以此類推。根據經驗,前後綴一個字符相等,對應的 next 爲 2;前後綴 n 個字符相等,對應的 next 值爲 n+1。

2.4、求值原理

  在字符串子串 “abcabx” 中,next[6] 的值爲3,假設主串爲 “abcabedfd”。匹配到主串中的 “e” 和子串中的 “x” 不相等,本次匹配失敗。因爲本次匹配已經確定了主串中下標爲 4、5 的元素和子串中下標爲 4、5 的元素相等,下一次匹配時,主串的位置下標從 6 開始,主串元素下標爲 6 的元素前兩個元素爲 “ab”,而子串的前兩個元素也爲 “ab”,所以子串從下標爲 3 的元素起開始遍歷。
  在 next 數組的求值過程中,具體的值僅僅取決於子串的內容,和主串無任何關係。

2.5、案例分析

  具體的求值過程可以根據公式去求取,也可以用經驗去求取。
2.5.1、abcabx 公式
  設子串的位置下標爲 j,next[j] = k
  (1)、j = 1 時,next[1] = 0;
  (2)、j = 2 時,j 由 1 到 j-1 只有字符 “a”,屬於其它情況,next[2] = 1;
  (3)、j = 3 時,j 由 1 到 j-1 的串爲 “ab”,顯然 “a” 不等於 “b”,屬於其它情況,next[3] = 1;
  (4)、j = 4 時,j 由 1 到 j-1 的串爲 “abc”,前綴字符串與後綴字符串沒有相等的可能性,因此 next[4] = 1;
  (5)、j = 5 時,j 由 1 到 j-1 的串爲 “abca”,前綴字符串 “a”與後綴字符串 “a” 相等,則 k = 2(j-k+1=4,j=5),所以 next[5] = 2;
  (6)、j = 6 時,j 由 1 到 j-1 的串爲 “abcab”,前綴字符串 “ab” 與後綴字符串 “ab” 相等,則 k = 3(j-k+1=4,j = 6),所以 next[6] = 3;
  所以子串 “abcabx” 的 next 數組爲 {0 1 1 1 2 3}。

2.5.2、ababaaaba 經驗
  設子串的位置下標爲 j,next[j] = k
  (1)、j = 1 時,next[1] = 0;
  (2)、j = 2 時,j 由 1 到 j-1 只有字符 “a”,屬於其它情況,next[2] = 1;
  (3)、j = 3 時,j 由 1 到 j-1 只有字符 “ab”,前綴字符串與後綴字符串不相等(0個相等)相等,因此next[3] = 1;
  (4)、j = 4 時,j 由 1 到 j-1 只有字符 “aba”,前綴字符串 “a” 與後綴字符串 “a”相等,相等的字符個數爲 1,因此next[4] = 2;
  (5)、j = 5 時,j 由 1 到 j-1 只有字符 “abab”,前綴字符串 “ab” 與後綴字符串 “ab”相等,相等的字符個數爲 2,因此next[5] = 3;
  (6)、j = 6 時,j 由 1 到 j-1 只有字符 “ababa”,前綴字符串 “aba” 與後綴字符串 “aba”相等,相等的字符個數爲 3,因此next[6] = 4;
  (7)、j = 7 時,j 由 1 到 j-1 只有字符 “ababaa”,前綴字符串 “a” 與後綴字符串 “a”相等,相等的字符個數爲 1,因此next[7] = 2;
  (8)、j = 8 時,j 由 1 到 j-1 只有字符 “ababaaa”,前綴字符串 “a” 與後綴字符串 “a”相等,相等的字符個數爲 1,因此next[8] = 2;
  (9)、j = 9 時,j 由 1 到 j-1 只有字符 “ababaaab”,前綴字符串 “ab” 與後綴字符串 “ab”相等,相等的字符個數爲 2,因此next[9] = 3;
  所以子串 “ababaaaba” 的 next 數組爲 {0 1 1 2 3 4 2 2 3}。

三、程序實現

//標準的 KMP 算法
//參考《大話數據結構》 

#include <stdio.h> 

void GetNext(char string[], int next[], int length); 
int IndexKMP(char s[], int s_length, char t[], int t_length, int pos);

int main(){
	
	char t_string[10] = " ababaaaba";
	char s_string[13] = " cdababaaaba2"; 
	
	printf("\n%d",IndexKMP(s_string,12+1,t_string,9+1,1));
	
	return 0;
}

//通過計算返回字串 string 的 next 數組 
//《大話數據結構》上的源碼,有改動 
//string 數組的第一個數據元素在無意義。在源程序中存儲了 string 子串的長度
//next 數組的實際長度爲 length+1。在程序中,next數據從下標 1 開始存儲 
//length:子串的長度 + 1
void GetNext(char string[], int next[], int length){
	
	int i = 1;	
	
	int j = 0; 
	
	next[1] = 0;	
	
	while(i < length){
		
		
		//string[i]:後綴單個字符
		//string[j]:前綴單個字符 
		if(j == 0 || string[i] == string[j]){	
			++i;
			++j; 
			next[i] = j;
			
		}else{
			
			j = next[j];
		}
		
	}
}


//返回子串 t 在主串 s 中第 pos 個字符之後的位置,若不存在,則返回 0
//根據《大話數據結構》的源碼稍有改動
//s[]:主串;且第一個數組元素無效 
//s_length:主串的長度 + 1
//t[]:子串;且第一個數組元素無效 
//t_length:子串的長度 + 1
//pos:主串的起始位置 
int IndexKMP(char s[], int s_length, char t[], int t_length, int pos){
	
	int i = pos;	//主串中當前下標值 
	
	int j = 1;		//子串中當前下標值 
	
	int next[255] = {};	//定義 next 數組 
	
	GetNext(t, next, t_length);		//獲取子串的 next 數組 
	
	while(i < s_length && j < t_length){//	i 小於 s 的長度且 j 小於 t 的長度時 
	
		if(j == 0 || s[i] ==t[j]){
			++i;
			++j;
		}else{
			j = next[j];
		}
	} 
	
	if(j > t_length - 1){	//子串的實際長度 
		return i - t_length + 1;
	}else{
		return 0;
	}
}

  下一篇對 KMP 模式匹配算法存在的缺陷進行改進。

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