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