1. 程式人生 > >KMP淺顯理解

KMP淺顯理解

KMP演算法解釋

1.好的部落格

從頭到尾徹底理解KMP演算法,這是我在搜尋各種部落格之後,解釋的最為詳盡的一篇關於介紹KMP演算法的一篇部落格。自己的理解也算是建立在這篇部落格上。很早很早之前就已經學過了,不過還是忘了好多。現在再拾起來按照自己的理解疏解一遍。

2.KMP緣何而起

目標問題,給你一個文字串S,文字串T求出S中與T相匹配的第一個位置。
我們有樸素的演算法是:

int j=1;
int i=1;
int s0=strlen(S);
int tp=strlen(T);
while(i<=s0&&j<=t0)
{
		if(S[i]==T[j])
		{
			i++;
			j++;
		}
		else
		{
			i++;
			j=1;
		}
}

這是最為簡單的暴力演算法,同時我們也會發現,每一次匹配不成功的時候我們都會要去從頭開始匹配。

a b c d e
a b b d e

如上圖所示當abc與abb失配的時候我們會將a再與b去匹配,但是實際上我們早已經知道了第二個字元是b。所以如此顯而效率會較為低下.

3.KMP演算法解釋

Knuth-Morris-Pratt 字串查詢演算法,簡稱為 “KMP演算法”,常用於在一個文字串S內查詢一個模式串P 的出現位置,這個演算法由Donald Knuth、Vaughan Pratt、James H. Morris三人於1977年聯合發表,故取這3人的姓氏命名此演算法。


簡要思路:當我們匹配到S字串中的第i個位置的時候,此時匹配到T字串的第j個字元,此時有i-j+1位置到i位置與T字串中的字元相匹配。

如果第j+1個字元相匹配,接著向下進行匹配即可。
如果第j+1個字元失配,我們就控制S中字元位置不發生改變,將T向後滑動一段位置。(next[j])

我們先來看KMP演算法的程式碼

int j=0;
int s0=strlen(S);
int t0=strlrn(T);
while(i<s0&&j<t0)
{
	if(j==0||S[i]==T[j])
	{
		i++;
		j++;
	}
	else
	{
		j=next[j];
	}
	if(j>t0)
	return i-t0;
	else
	 return 0;
}

程式碼中next即為向右滑動的值。那麼如何去理解這個next值則成了關鍵,自己也被折磨了好久好久。

	int j=0;
	int i=1;
	next[1]=0;
	int t0=strlen(T);
	while(i<t0)
	{
		if(j==0||T[j]==T[j])
		{
			i++;
			j++;
			next[i]=j;
		}
		else
		{
			j=next[j];
		}
	}

理解如下

A B C D A B C
A B C D A B D

A B C D A B D
0 1 1 1 1 1 2

在這裡j所表示的含義代表著第j個字元位置前面與T字串從頭開始最大匹配長度。拿上面表格舉例來說,在最後一個位置我們發生了失配問題,D之前與字串T從頭匹配最大長度為2,截至到C為止。所以我們就將字串的位置滑到了C處。 next的值所表示的東西就是j處字元前面的字元與T字串從頭開始匹配的最大長度!!!

但其實截至到現在這個演算法還不算完美,因為我們還會有一種極特殊的情況。

A A A A A B
0 1 2 3 4 5

比如上面表格所列處的這種結構當B失配的時候我們右移到了左邊的A的位置,A失配後我們右移到了第四個A的位置,但顯然我們之前已經知道了肯定會失配的結果。

a b a d e
a b a b e
0 1 1 2 1

b失配移到了第二個位置但這個時候還是b,所以所得結果依然會是失配,明知故作?

實際上是我們求next的過程中有點小疏漏,這個時候應該要能夠滿足如果j==T[next[j]]那麼j=next[next[j]],因為如果T[j]=T[next[j]]則肯定失配,則應該儘量避免這種狀況。


int j=0;
int i=1;
int t0=strlen(T);
while(i<=t0)
{
	if(j==0||T[j]==T[i})
	{
		i++;
		j++;
		if(T[i]!=T[j])
		next[i]=j;
		else
		{
			next[i]=next[j];
		}
	}
	else
	{
		j=next[j];
	}
}

自己目前也理解的不是特別充分,也只能將自己想出來的,大概寫出來成這個樣子了,實在理解不了的話可以採取強制記憶措施,只不過實在不是太好的樣子。
up!up!