通用算法 - [字符串] - KMP算法

1、KMP算法介紹

KMP算法是一種改進的字符串匹配算法。KMP算法主要是通過消除主串指針的回溯以達到快速匹配的目的。具體實現就是生成一個next數組,next數組本身包含了模式串的局部匹配信息。時間複雜度O(m+n)。

2、KMP算法流程

2.1 算法整體流程

忽略next數組的具體生成過程,我們先來看kmp算法的整體流程:

文本串S和模式串P進行匹配:
假設現在文本串S匹配到 i 位置,模式串P匹配到 j 位置 ;
(1)如果j == -1,或者當前字符匹配成功(即S[i]
== P[j]),都令i++,j++;繼續匹配下一個字符;
(2)如果j != -1,且當前字符匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]。
如果j == S.length,則說明匹配成功,S中與P匹配的子串的起始位置爲i - S.length;
否則未匹配成功。

注意,步驟(2)意味着失敗時,模式串P相對於文本串S向右移動了j - next [j] 位。 換言之,當匹配失敗時,模式串向右移動的位數爲:失敗字符所在位置 - 失敗字符對應的next 值,即移動的實際位數爲:j - next[j],且此值大於等於1。

2.2 next數組的意義

上一小節我們提到了next數組,那麼next數組中各值的含義究竟是什麼呢?

實際上,next 數組各值的含義是:
k=next[j],代表模式串P第j個字符之前的子串p = P[0,…,j-1]中,最開頭的k個字符和結尾的k個字符是一樣的,即p[0,…,k-1] == p[j-k,…,j-1]。

舉個例子,假設P=“ABCDABD”,next[6] = 2,則表示子串p=“ABCDAB”中,開頭的兩個字符和結尾的兩個字符是一樣的,都是“AB”。

next數組表示,在某個字符匹配失敗時,該字符對應位置的next 值會告訴你下一步匹配中,模式串應該跳到哪個位置(跳到next [j] 的位置)。如果next [j] 等於0或-1,則跳到模式串的開頭字符,若next [j] = k 且 k > 0,代表下次匹配跳到j 之前的某個字符,而不是跳到開頭,且具體跳過了k 個字符。

繼續拿之前的例子來說,當S[10]跟P[6]匹配失敗時,KMP不是跟暴力匹配那樣簡單的把模式串右移一位,而是執行步驟(2):“如果j != -1,且當前字符匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]”,即j 從6變到2(後面我們將求得P[6],即字符D對應的next 值爲2),所以相當於模式串向右移動的位數爲j - next[j](j - next[j] =6-2 = 4)。
在這裏插入圖片描述
向右移動4位後,S[10]跟P[2]繼續匹配。爲什麼要向右移動4位呢,因爲移動4位後,模式串中又有個“AB”可以繼續跟S[8]S[9]對應着,從而不用讓i 回溯。相當於在除去字符D的模式串子串中尋找相同的前綴和後綴,然後根據前綴後綴求出next 數組,最後基於next 數組進行匹配。
在這裏插入圖片描述
瞭解了next的具體含義之後,對於給定的模式串“ABCDABD”,可求得它的next 數組如下:
在這裏插入圖片描述

3、KMP算法的代碼實現

(1)next的求解方法:

void getnext(int next[], string p){
	int k = -1;
	int j = 0;
	next[0] = -1;
	int plen = p.length();
	while(j < plen - 1){
		if(k == -1 || p[j] == p[k]){
			k++;
			j++;
			next[j] = k;
		}
		else{
			k = next[k];
		}
	}
}

(2) KMP算法:

int KMP(string s,string p){
	int plen = p.length();
	int slen = s.length();
	int next[plen];
	getnext(next,p);
	int i = 0,j = 0;
	while(i < slen && j < plen){
		if(j == -1 || s[i] == p[j]){
			i++;
			j++;
		}
		else{
			j = next[j];
		}
	}
	if(j >= plen) {
		return i - plen;
	}
	else{
		return -1;
	}	
}

(3) 改進後的 next 求解方法
先來看一下上面算法存在的缺陷:
在這裏插入圖片描述
顯然,當我們上邊的算法得到的next數組應該是[ -1,0,0,1 ]

所以下一步我們應該是把j移動到第1個元素咯:
在這裏插入圖片描述

不難發現,這一步是完全沒有意義的。因爲後面的B已經不匹配了,那前面的B也一定是不匹配的,同樣的情況其實還發生在第2個元素A上。

顯然,發生問題的原因在於p[j] == p[next[j]]。

所以我們需要添加一個判斷:

void getnext(int next[], string p){
	int k = -1;
	int j = 0;
	next[0] = -1;
	int plen = p.length();
	while(j < plen - 1){
		if(k == -1 || p[j] == p[k]){
			k++;
			j++;
			if(p[j] == p[k]){
				next[j]= next[k];
			}
			else{
				next[j] = k;
			}
		}
		else{
			k = next[k];
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章