零零散散學算法之再敘字符串匹配

零零散散學算法之再敘字符串匹配

 

正文

 

       字符串匹配問題這是個老話題了,而我們也熱衷於學習和探討這個問題,並且我們也經常會用到它。比如說,我們用vim打開一個文本文件,要在這個文件中查找某一個字符串時,我們只需在底行模式下輸入/String即可;再比如,在linux終端中,我們要把當前目錄下所有的c文件打印出來,那麼這時候我們就會利用正則表達式來進行匹配操作(所有的c文件可表示爲*.c),而不是挨個去找。

好了,書接正文。寫本篇文章的目的有二:

  其一:先前也寫過字符串匹配的文章,不過那篇文章只說了字符串固定匹配以及KMP算法,沒有對字符串動態匹配進行講述。所以一直想寫關於動態匹配的文章;

       其二:對於一個算法愛好者,如一位仁兄所說:Say what you think and show your code!如若這樣,豈不快哉!

第一章 固定串匹配

       我爲什麼叫它固定串呢?我還是舉個例子說明一下吧(沒辦法,語言表達能力不好啊)!假設有源字符串…superfcing…,我們想知道子串superfc是否在源串中出現過?此時不難看出子串是一個確定的字符串(不含有不確定的字符),那麼我就將它成爲固定字符串匹配法。

 

       解決這種情況的算法不用我多說,自然是KMP算法(最壞情況爲O(strlen(source_string) + k))了。對於這個算法先前也說過,在這就不細說了。這個算法的關鍵步驟就是源串前綴數組的生成。下面我用圖例給出前綴數組的生成過程:

 

 

       對於KMP算法,歸總起來就是:掃描源字符串,更新並標記子串在源串中出現的位置。

       附註:關於KMP算法的前綴函數,當數組的起始下標爲0時,務必不要讓前綴函數的初值設爲0,如果這樣的話,可能會死循環。你如果對此有興趣,自己可以試試。

 

好了,固定串匹配的情況就是這樣,解決方法也有了,接下來我們看看代碼的實現:

代碼中使用到的變量代表的含義:

/*	Source:源串 **	SourceLength:源串長度 **	Prefix:前綴數組 **	Pattern:匹配串,即子串 **	P_Length:子串長度 **	Buffer:當源串中有子串匹配時,用Buffer保存這些子串 */
/**爲KMP算法得到前綴數組**/ void GetPrefixArray(char *Source, int *Prefix, int SourceLength) {     int k = 0, i;     Prefix[k] = -1;//當下標起始爲0時,起始值務必設爲1(原因見如下解釋)      for(i = 1; i < SourceLength; i++)     {         while(Source[k] != Source[i]  &&  k > 0)         {             k = Prefix[k];         }         if(Source[k] == Source[i])         {             ++k;         }         Prefix[i] = k;     } }
void StringMatchOfKMP(char *Source, int SourceLength, int *Prefix, 			char *Pattern, int P_Length, char *Buffer) {     int i = 0, j = 0, index = 0;      GetPrefixArray(Source, Prefix, SourceLength);      while(i < SourceLength)     {         while(j > 0 && Source[i] != Pattern[j])         {             j = Prefix[j];         }         if(Source[i] == Pattern[j])         {             j++;         }          if(j == P_Length)         { 	        memcpy((Buffer + index * P_Length), Pattern, strlen(Pattern)); 	        index++;//當有多個匹配時,index * P_Length爲每個子串在Buffer中的起始位置         }         i++;     } }

 第二章 單字符動態匹配

 

      所謂單字符動態匹配,就是:當字串中出現?字符時,該字符表示匹配任意的一個字符(因爲它是任意的一個字符,所以說它是單字符動態匹配)。那麼這種情況怎麼解決呢?很簡單,我們只需要對源串做一次遍歷,並不需要像固定串匹配那樣利用KMP算法。即子串在匹配源串的過程中,當遇見?字符時,我們直接認爲子串中的該字符和源串中對應位置的字符匹配成功。舉個例子,假設源串爲helloSuperfc,子串爲S*per,那麼當源串和子串中的S匹配時,分別做++,這個時候源串中的u就會和子串中的*相比較,此時我們就認爲*就是字符u,匹配成功,然後進行下一個字符的匹配。

好,給個圖示:

 

      單字符動態匹配就是這樣,我們來看看它的實現代碼:

void StringMatchOfAsk(char *Source, int SourceLength, char *Pattern, 						int P_Length, char *Buffer) {     int i = 0, j = 0;     int index = 0;     while(i < SourceLength)     {     	if((Pattern[j] == Source[i]) || (Pattern[j] == '?'))     	{ 			/*匹配完成*/ 			if((j + 1) == P_Length) 			{ 				memcpy((Buffer + index * P_Length), (Source + i - P_Length + 1), P_Length); 				index++; 				j = 0; 			} 			else 			{ 				j++; 				i++; 			} 		} 		else 		{ 			i = i - j + 1;//相當於i++ 			j = 0; 		}     } }


  第三章 多字符動態匹配

 

       所謂多字符動態匹配,就是:當子串中出現*字符時,該字符表示可以匹配至少0個以上的字符(因爲它可以匹配多個字符,所以說它是多字符動態匹配)。那麼這種情況怎麼解決呢?我認爲它比單字符動態匹配還要簡單,即當子串中的*與源串中的對應字符比較時,我就認爲*字符包含從源串中的當前位置開始,直到當子串中的下一個字符與源串中的字符相等時,匹配結束。這麼說太拗口了,我用一個圖例來說明一下:

 

我們來看看代碼的實現:

void StringMatchOfStar(char *Source, int SourceLength, char *Pattern, 						int P_Length, char *Buffer) { 	int i = 0, j = 0; 	int index = 0;  	while(i < SourceLength) 	{ 		if(Source[i] == Pattern[j]) 		{ 			/*匹配完成*/ 			if(P_Length == ++j) 			{ 				memcpy(Buffer, (Source + index), i - index + 1); 				break; 			}  			/*保存起始匹配的下標*/ 			if(0 == j - 1) 			{ 				index = i; 			} 		} 		else if(Pattern[j] == '*') 		{ 			j++; 		} 		i++; 	} }

第四章 結束語

 

想想、寫寫、畫畫......

 

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