零零散散學算法之再敘字符串匹配
正文
字符串匹配問題這是個老話題了,而我們也熱衷於學習和探討這個問題,並且我們也經常會用到它。比如說,我們用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++; } }
第四章 結束語
想想、寫寫、畫畫......