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

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

 

正文

 

       字符串匹配問題這是個老話題了,而我們也熱衷於學習和探討這個問題,並且我們也經常會用到它。比如說,我們用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++;
	}
}

第四章 結束語

 

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

 

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