KMP算法詳解

        剛剛在微易碼學習了KMP算法,深深的被這裏面的邏輯吸引了,也不得不佩服微易碼的主講以及顏值擔當“鐵血教主”(他自己這麼稱呼自己鄙視)的縝密的邏輯性,讓我的思路一直保持在很清晰的狀態,才能較爲輕鬆的基本掌握了這個算法的實現原理。爲了以後不至於忘記裏面的邏輯和自己的一些感悟,所以把KMP算法的實現較爲系統的做一次分析,有說的不合理的地方還請多多指正。


        KMP算法是一個時間複雜度極爲優秀的串匹配算法,它是由D.E.Knuth,J.H.Morris和V.R.Pratt三個人發現的。

        我們可以把KMP算法比作一匹好馬,因爲它有兩個優秀的品質:1、好馬不吃回頭草; 2、每次向前行走都是有目標的走,不會走一步冤枉路。想要掌握KMP算法,一定要銘記這兩個特點。

        衆所周知,所謂串匹配算法就是在一個源字符串中找到想要找到的目標字符串。如果只是單純的考慮能否實現,我們很容易完成該算法。只要用一個雙重循環,用一層循環控制遍歷整個源字符串,用第二層循環控制遍歷子字符串,當找到了返回當前子字符串相對於源字符串的位置。若當前情況下不匹配,則子串和源串的下一個位置進行比較。由於該算法比較簡單,這裏不做過多的介紹,僅對其時間複雜度進行分析。

        該算法把整個源串遍歷一遍,這是毋庸置疑的,無論什麼算法都涉及到遍歷整個源串,但是由於每次都要和整個子串進行比較,因此時間複雜度爲(m * n)。該算法的手工過程如圖所示:

        從圖中可以看出,該算法存在着大量的控制源字符串的下標的往回移動,比如源字符串比較到下標爲6的時候,由於在下標爲7的位置失配,則,不得不將下標從7移動到2繼續進行下一次比較,在對一個長度很長的字符串進行遍歷的時候,這樣的不必要的移動大量存在。因此該算法無法應用到更加大型的字符串差找中去。因此我們就要想辦法減少這樣的移動,KMP算法的精髓就在於能夠不進行這樣的移動,並且要做到每一次移動都是有的放矢,移動到應該移動到的地方去。下面我們就開始介紹KMP算法。

        我們在讓子串跟源串的一部分進行比較時,當遇到不匹配的字符了,自然說明源串的這一段字符串跟我們要找的目標串不匹配,我們自然要移動目標串,使它跟下一段比較,這時,普通算法的做法是同時改變控制源串和子串的下標進行下一次比較,而KMP算法則保持源串的當前下標不變,只移動子串到相應的位置進行下一次的比較。那麼,究竟移動多少,通過什麼得到應該移動多少都是KMP算法的一部分,我們來觀察這樣一組字符串:


源串:   abababaaaabbbabab

目標串:abababb


        我們觀察到,該目標串在下標爲6的位置失配,那麼則說明前六個位置的字符都是匹配的,我們想直接在適配的位置進行下一次比較。這是,該串截止到失配的位置都已經比較過了,而失配字符的後面還沒有比較過,而此時本次比較已經結束了。而此時,我們就可以得到截至失配字符,前面的字符都是匹配的結論。即:“ababab”;這時如果將目標串向後移動一個單位,兩者的對應關係就爲:


源串:    abababa ... ...

目標串:  ababab ... ...


        此時,我們明顯地看出前面的字符串都沒有匹配,因此,後面的比較都是沒有意義的。而我們向後移動兩個單位的時候對應關係爲:


源串:    abababa ... ...

目標串:    ababa ... ...


        因此,我們得到如果在目標串下標爲7的位置失配,則應該將目標串向後移動兩個位置進行下一次的比較的結論。以此類推,每一個位置都應該有一個失配後應該移動多少個單位的值。如果得到了這樣的一組值,我們就能使每一次失配後的移動量達到最小。

        下面我們的工作就是得到每一個位置如果失配應該向後移動多少個單位的這樣一組數據。我們首先介紹這個算法的手工過程:

        KMP算法的實現方案是首先給出一個數組,我們先叫它“nextLocal”,元素個數爲目標字符串的長度。每一個元素的值“n”代表當以該元素下標爲目標字符串的下標的字符失配時,應該讓目標字符串的以“n”爲下標的元素繼續與源字符串失配的位置進行比較。該數組的實現過程如下:

        首先,如果在第一個元素就失配了,那麼肯定要用第一個元素繼續與源字符串失配的位置進行比較。因此,這個數組的第一個元素一定是“0”,這一點是容易想到的。之後的做法是:將目標字符串當前元素與nextLocal前一個元素的值作爲目標字符串下標的元素進行比較,若相等,則將當前nextLocal的元素爲上一個元素的值+1。若不相等,又分爲兩種情況:1、如果nextLocal數組當前元素的前一個元素的值爲零,那麼直接將當前元素賦值爲0;2、如果nextLocal數組當前元素的前一個元素不爲零,那麼將當前位置的字符與目標字符串的arrayLocal數組中上一個位置的元素的值爲下標的元素作比較,繼續執行上述判斷操作。具體代碼如下:

void getNextLocalArray(char *res, int *nextLocal) {
	int index = 2;
	int value = 0;

	if (strlen(res) <= 2) return;

	while (res[index]) {
		if (res[index-1] == res[value]) {
			nextLocal[index++] = ++value;
		}
		else if (value == 0) {
			nextLocal[index++] = 0;
		}
		else {
			value = nextLocal[value];
		}
	}
}
        以上就是KMP算法得到nextLocal數組的手工過程的文字性說明以及具體實現代碼。下面我們通過圖示跟蹤配合上面的文字描述以及代碼進行說明使我們理解起來更加清晰:

        上述的手工過程看似繁瑣,但是通過代碼實現其實並不困難,從上文給出的代碼可以知道,我們通過一個變量value記錄當前需要與目標字符串當前的字符進行比較的字符的下標,這樣,我們在圖示中最後一步看似繁瑣的步驟只要更改一下value的值卻不增加index的值就可以實現圖示手工過程中繼續用當前的字符與前面的字符作比較了。

        在能夠得到目標字符串相應位置失配後應有的偏移量這個信息後,我們就可以正式編寫KMP串匹配算法了,有了前面的基礎,KMP算法已經完成了百分之七八十了,真正實現KMP串匹配的函數並不是很困難。

        前面我們說過,KMP算法最重要的就是控制源字符串的下標的不向前移動,當在某一位置的字符失配後,將目標串的以當前失配字符的下標作爲nextLocal數組的下標的值所對應的位置移動到源字符串的失配字符的位置,然後繼續從失配字符的後一個字符進行比較。具體代碼如下:

int KMPStringMarth(char *resource, char *target) {
	int resLenth = strlen(resource);
	int tarLenth = strlen(target);
	int *nextLocal;
	int resIndex = 0;
	int tarIndex = 0;

	nextLocal = (int *)calloc(sizeof(int), tarLenth);
	getNextLocalArray(target, nextLocal);

	while (resource[resIndex]) {
		while (target[tarIndex] && target[tarIndex] == resource[resIndex]) {
			resIndex++;
			tarIndex++;
		}
		if (target[tarIndex] == 0) {
			free(nextLocal);

			return resIndex - tarIndex;
		}
		else{
			tarIndex = nextLocal[tarIndex];
			if (tarIndex == 0) {
				resIndex++;
			}
		}
	}

	free(nextLocal);
	return NOT_FOUND;
}
        對上述代碼的分析:1、我們根據target字符串的長度給出了一個數組,用我們之前編寫的getNextLocalArray函數得到相應字符失配後偏移量的信息;

                                        2、給出兩個變量resIndex、tarIndex分別用於跟蹤源字符串和目標字符串;

                                        3、用一個循環控制源字符串的遍歷,逐一進行比較,字符匹配則兩個字符串的下標均加一,若不匹配則保持跟蹤源字符串的下標不變,讓跟蹤目標

                                             字符串的下標等於nextLocal中該失配字符所對應的值,並繼續進行比較。

                                        4、如果某一次比較時,目標字符串一直到零結束標誌都沒有失配,則說明找到了目標字符串,返回當前的源字符串中目標字符串的開始位置所對應的

                                             字符的下標;若直到源字符串找到了零結束標誌都沒有返回,則返回“沒找到”。

        以上就是KMP算法的全部分析過程,以後有需要補充的還會繼續改善的大笑

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