字符串模式匹配BF、KMP和Boyer-Moore,Sunday算法

    這幾天總結了一下總結了一下字符串匹配的幾種算法,BF、KMP和Boyer-Moore,Sunday算法,覺得就KMP算法難於理解,其餘三種都非常容易理解掌握。  串匹配:給一個目標串(源串)和模式串(子串),在目標串中找出模式串第一次出現的位置,或者目標串中找不到這樣一個模式串。

暴力匹配法(BF):就是挨個比較,產生失配了就把模式串往後移動一個位置接着和目標串比較。直到模式串所有字符匹配上了,或者目標串裏面不存在這樣的模式串。沒找到這樣的模式串。

int BF_StrMatching(char *libraryStr, char *subStr) {
	int i;
	int j;
	int libraryStr_len = strlen(libraryStr);
	int subStr_len = strlen(subStr);

	for(i = 0; i < (libraryStr_len- subStr_len); i++) {
		for(j = 0; subStr[j] && libraryStr[i+j] == subStr[j]; j++) {
		}

		if(j == subStr_len) {
			return i;
		}
	}
	return -1;
}

           KMP算法是模式匹配中的經典算法,理解起來很費勁,花了很長的時間去理解這個算法。和暴力匹配相比KMP的不用電是消除BF算法中目標串指針回溯的情況,不必每次從頭開始重新比較,也就是說在目標串移動的指針一直是從前往後走的。只需要每次失配的手,計算好下一次模式串從哪個位置繼續匹配,需要準備額外的一個數組next,開始匹配之前,需要對模式串進行處理,申請一個和模式串相同長度的數組,數組的作用是,當模式串進行匹配失配了的時候,下一次匹配從模式串哪個位置繼續開始匹配。kmp算法的難點就在於next數組的求解,以及理解next數組的作用。

            在求next數組前先求一下模式串中各個子串最大前綴後綴元素長度

有這個相同前綴後綴的表之後,按照這個就可以進行排序了。

next數組的作用在於每次失配之後,模式串從哪個位置繼續匹配,遞推也是根據求得的前後綴最大公共元素長度可以計算出來,既然每次失配時移動的位置是,已經匹配的字符數減去失配字符左側的串的前後綴最大公共元素長度。

每次失配的時候,關心的是失配字符左側的串的最大長度相同前綴後綴,也就是前一個字符的對應的前綴後綴最大公共元素長度,所以next數組的結果可以看成是把上面求出來的前後綴最大公共元素長度表後移一位,第一個元素爲0。

用next數組的時候,模式串T[j]失配了 那麼移動距離就是j-next[j]

#include<stdio.h>
#include<string.h>
#include<malloc.h>

#include"../../include/kwenarrayTools.h"

#define NOT_FOUND  -1

//KMP串匹配算法
int StrMatchKMP(char *libraryStr, char *subStr);

//得到一個數組根據目標串 在匹配的時候每次失配時 下次從哪個位置繼續匹配
void getNextLocal(char *subStr, int *array);


void getNextLocal(char *subStr, int *array) {
	int index = 2;
	int value = 0;

	if(strlen(subStr) <= 2) {
		return;
	}

	while(subStr[index]) {
		if(subStr[index - 1] == subStr[value]) {
			array[index++] = ++value;
		}else if(value == 0) {
			array[index++] = 0;
		}else {
			value = array[value];
		}
	}
}

int StrMatchKMP(char *libraryStr, char *subStr) {
	int *nextLocalArray;
	int libraryStr_len = strlen(libraryStr);
	int subStr_len = strlen(subStr);
	int libraryStr_index = 0;
	int subStr_index = 0;

	nextLocalArray = (int *)calloc(sizeof(int), subStr_len);
	getNextLocal(subStr, nextLocalArray);
	// showArray_1(nextLocalArray, subStr_len);
	while(libraryStr_len - subStr_len + subStr_index - libraryStr_index >= 0) {
		if(0 == subStr[subStr_index]) {
			free(nextLocalArray);

			return libraryStr_index - subStr_index;
		}else if(libraryStr[libraryStr_index] == subStr[subStr_index]) {
			subStr_index++;
			libraryStr_index++;
		}else if(subStr_index == 0) {
			libraryStr_index++;
		}else {
			subStr_index = nextLocalArray[subStr_index];
		}
	}

	free(nextLocalArray);

	return NOT_FOUND;
}

int main(void) {
	char *libraryStr = "BABCBABCABCAABCABCACABC";
	char *subStr = "ABCABCACAB";
	
	int matchedIndex = indexOf(libraryStr, subStr);
	
	printf("matchedIndex = %d\n", matchedIndex);

	puts(libraryStr);
	while(matchedIndex-- > 0) {
		putchar(' ');
	}
	puts(subStr);


	return 0;
}

            對於kmp算法的理解還有些生疏,主要是對next數組的理解還需進一步認識。

 

           Boyer- Moore算法採取了不同的方法來更快速搜索匹配字符。這個算法核心的思想在於,想象模式串位於目標串的下面,也就是匹配可能發生的地方, 該算法從模式串最右邊的字符開始比較,如果產生了失配。該算法將模式串移動到下一個可能匹配正確的位置。

          不同於暴力匹配的是,比較字符的時候是從右向左比較的。失配了移動模式串還是正常往後移動,不過每一次移動的距離並不是1,取決於失配時字符的情況,簡單的說,匹配的過程中發生失配,失配位置目標串的字符x在模式串裏面沒有找到的話,失配,注意是在失配位置模式串的左側尋找,說明x沒有在模式串中的任何地方出現,直接把模式串按其全長度右移;如果在失配位置的左側,找到了失配時目標串的字符x,將模式串右移直到這兩個x對齊。

        還是用圖看起來清楚一點。

 

#include<stdio.h>
#include<string.h>


#define NOT_FOUND	-1

int BM_Matching(char *targetStr, char *PatStr);
int getRemoveDistance(char ch, char *PatStr, int place);

int getRemoveDiatance(char ch, char *PatStr, int place) {
	int i;

	for(i = place; i >= 0; i--) {
		if(ch == PatStr[i]) {
//在左側有這樣一個字符的話計算偏移距離			
			return place - i;
		}
	}
//沒有這樣一個字符就移動整個字符長度距離
	return strlen(PatStr);
}

int BM_Matching(char *targetStr, char *PatStr) {
	int targetStr_len = strlen(targetStr);
	int PatStr_len = strlen(PatStr);
	int Match_index;
	int j;

	for(Match_index = 0; Match_index <= targetStr_len - PatStr_len; ) {
		for(j = PatStr_len - 1; j >= 0 && targetStr[Match_index + j] == PatStr[j]; j--);
		
		if(j == -1) {
			return Match_index;
		}

		Match_index = Match_index + getRemoveDiatance(targetStr[Match_index + j], PatStr, j);
	}

	return NOT_FOUND;
}

int main(void) {
	char *targetStr = "BABCBABCABCAABCABCACABC"; //目標串
	char *PatStr = "ABCABCACAB";						//模式串
	int index = 0;

	index = BM_Matching(targetStr, PatStr);

	puts(targetStr);
	while(index-- > 0) {
		printf(" ");
	}
	puts(PatStr);
	

	return 0;
}

        BM匹配算法的效率是很高的,如果目標串更長,BM算法有着趨向更快的特性,因爲當它找到一個不匹配的字符時,可以進一步移向目標。大多數情況下BM算法性能是很不錯的,跳動的位移大,比起KMP算法關鍵是好理解。

        Sunday算法,Sunday算法是Daniel M.Sunday於1990年提出的,思路和BM差不多,也容易理解,可以掌握寫出來BM算法就一定可以看懂Sunday算法,不過Sunday算法比較的時候是從頭向尾比較的,和BM比較的順序相反,當發生失配的時候,在目標串中參與匹配的下一個字符,判斷這個字符是否存在於模式串中,存在的話就移動模式串對齊,不存在的話,模式串移動其全部長度+1。覺得和BM差不多,都是去判斷下一次匹配的時候字符是否存在,進行一個預判,從而可以跳躍很大的距離。

#include<stdio.h>
#include<string.h>

#define NOT_FOUND		-1

int Sunday_Matching(char *targetStr, char *PatStr);
int getRemoveDiatance(char *PatStr, char ch, int length);

int getRemoveDiatance(char *PatStr, char ch, int length) {
	int i;

	for(i = length - 1; i >= 0; i--) {
		if(ch == PatStr[i]) {
			return length - i;
		}
	}

	return length + 1;
}

int Sunday_Matching(char *targetStr, char *PatStr) {
	int matchedIndex = 0;
	int j;
	int targetStr_len = strlen(targetStr);
	int PatStr_len = strlen(PatStr);

	while(matchedIndex <= targetStr_len - PatStr_len) {
		for(j = 0; j < PatStr_len && targetStr[matchedIndex + j] == PatStr[j]; j++);
		if(j == PatStr_len) {
			return matchedIndex;
		}

		matchedIndex += getRemoveDiatance(PatStr, targetStr[matchedIndex + PatStr_len], PatStr_len);
	}

	return NOT_FOUND;

}

int main(void) {
	char *targetStr = "BABCBABCABCAABCABCACABC";
	char *PatStr = "ACABC";					//模式串
	int index = 0;

	puts(targetStr);
	index = Sunday_Matching(targetStr, PatStr);
	if(index != NOT_FOUND) {
		while(index-- > 0) {
			printf(" ");
		}
		puts(PatStr);
	}else {
		printf("[%s] in [%s] not found\n", PatStr, targetStr);
	}
	
	

	return 0;
}

        對於kmp算法的理解還是不夠深入有待提高。

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