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

KMP演算法淺顯理解

說明:轉載

KMP演算法看懂了覺得特別簡單,思路很簡單,看不懂之前,查各種資料,看的稀裡糊塗,即使網上最簡單的解釋,依然看的稀裡糊塗。
我花了半天時間,爭取用最短的篇幅大致搞明白這玩意到底是啥。
這裡不扯概念,只講演算法過程和程式碼理解:
KMP演算法求解什麼型別問題

字串匹配。給你兩個字串,尋找其中一個字串是否包含另一個字串,如果包含,返回包含的起始位置。
如下面兩個字串:

char *str = "bacbababadababacambabacaddababacasdsd";
char *ptr = "ababaca";

str有兩處包含ptr
分別在str的下標10,26處包含ptr。

“bacbababadababacambabacaddababacasdsd”;\

問題型別很簡單,下面直接介紹演算法
演算法說明

一般匹配字串時,我們從目標字串str(假設長度為n)的第一個下標選取和ptr長度(長度為m)一樣的子字串進行比較,如果一樣,就返回開始處的下標值,不一樣,選取str下一個下標,同樣選取長度為n的字串進行比較,直到str的末尾(實際比較時,下標移動到n-m)。這樣的時間複雜度是O(n*m)。

KMP演算法:可以實現複雜度為O(m+n)

為何簡化了時間複雜度:
充分利用了目標字串ptr的性質(比如裡面部分字串的重複性,即使不存在重複欄位,在比較時,實現最大的移動量)。
上面理不理解無所謂,我說的其實也沒有深刻剖析裡面的內部原因。

考察目標字串ptr:
ababaca
這裡我們要計算一個長度為m的轉移函式next。

next陣列的含義就是一個固定字串的最長字首和最長字尾相同的長度。

比如:abcjkdabc,那麼這個陣列的最長字首和最長字尾相同必然是abc。
cbcbc,最長字首和最長字尾相同是cbc。
abcbc,最長字首和最長字尾相同是不存在的。

**注意最長字首:是說以第一個字元開始,但是不包含最後一個字元。
比如aaaa相同的最長字首和最長字尾是aaa。**
對於目標字串ptr,ababaca,長度是7,所以next[0],next[1],next[2],next[3],next[4],next[5],next[6]分別計算的是
a,ab,aba,abab,ababa,ababac,ababaca的相同的最長字首和最長字尾的長度。由於a,ab,aba,abab,ababa,ababac,ababaca的相同的最長字首和最長字尾是“”,“”,“a”,“ab”,“aba”,“”,“a”,所以next陣列的值是[-1,-1,0,1,2,-1,0],這裡-1表示不存在,0表示存在長度為1,2表示存在長度為3。這是為了和程式碼相對應。

下圖中的1,2,3,4是一樣的。1-2之間的和3-4之間的也是一樣的,我們發現A和B不一樣;之前的演算法是我把下面的字串往前移動一個距離,重新從頭開始比較,那必然存在很多重複的比較。現在的做法是,我把下面的字串往前移動,使3和2對其,直接比較C和A是否一樣。

程式碼解析:


void cal_next(char *str, int *next, int len)
{
    next[0] = -1;//next[0]初始化為-1,-1表示不存在相同的最大字首和最大字尾
    int k = -1;//k初始化為-1
    for (int q = 1; q <= len-1; q++)
    {
        while (k > -1 && str[k + 1] != str[q])//如果下一個不同,那麼k就變成next[k],注意next[k]是小於k的,無論k取任何值。
        {
            k = next[k];//往前回溯
        }
        if (str[k + 1] == str[q])//如果相同,k++
        {
            k = k + 1;
        }
        next[q] = k;//這個是把算的k的值(就是相同的最大字首和最大字尾長)賦給next[q]
    }
}

 

KMP

這個和next很像,具體就看程式碼,其實上面已經大概說完了整個匹配過程。

int KMP(char *str, int slen, char *ptr, int plen)
{
    int *next = new int[plen];
    cal_next(ptr, next, plen);//計算next陣列
    int k = -1;
    for (int i = 0; i < slen; i++)
    {
        while (k >-1&& ptr[k + 1] != str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配)
            k = next[k];//往前回溯
        if (ptr[k + 1] == str[i])
            k = k + 1;
        if (k == plen-1)//說明k移動到ptr的最末端
        {
            //cout << "在位置" << i-plen+1<< endl;
            //k = -1;//重新初始化,尋找下一個
            //i = i - plen + 1;//i定位到該位置,外層for迴圈i++可以繼續找下一個(這裡預設存在兩個匹配字串可以部分重疊),感謝評論中同學指出錯誤。
            return i-plen+1;//返回相應的位置
        }
    }
    return -1;  
}

 

KMP演算法程式碼:



#include "iostream"
#include "cstring"
using namespace std;

void cal_next(char *str,int *next,int len)
{
	next[0]=-1;
	int k=-1;
	for(int i=1;i<=len-1;i++)
	{
		while(k>-1&&str[i]!=str[k+1])
		k=next[k];
		if(str[k+1]==str[i]);
		k++;
		next[i]=k;
	}
}
int KMP(char *str,int slen,char *ptr,int plen)
{
	int *next=new int [plen];
	cal_next(ptr,next,plen);
	int k=-1;
	for(int i=0;i<=slen-1;i++)
	{
		while(k>-1&&ptr[k+1]!=str[i])
		k=next[k];
		if(ptr[k+1]==str[i])
		k++;
		if(k==plen-1)
		{
			return i-plen+1;
		}
	}
	return -1;
}
int main()
{
	char str[1000],ptr[1000];
	cin>>str;
	cin>>ptr;
	int a=KMP(str,strlen(str),ptr,strlen(ptr));
	if(a==-1)
	{
		cout<<"找不到"<<endl;
	}
	else
	{
		cout<<a<<endl;
	}
	return 0;
}

 

無關筆記:

 

class Calor
{
    public:
        Calor(double a,double b)
        {
            weight=a;
            worth=b;
        }
        void Getin(const Calor &a);
        void Getout(const Calor &a);
        void Objshow();
        static void init();
    static void Allshow() ;//靜態函式不能含有cv限定符 const voletile
    static void remind();
    private:
        double weight;
        double worth;
        static double Aweight;//static不可以重複使用
        static double Aworth;    
};


double Calor::Aweight=1000;
double Calor::Aworth=1000;//再類裡的靜態變數,定義如左邊

void Calor::Allshow()
{
//    int a=5;靜態函式裡面使用變數和靜態變數都沒關係
//    static int b=2;
//    int c=a+b;
    cout<<"*******************"<<endl;
    cout<<"所剩餘貨物重量為:"<<Aweight<<"千克"<<endl;
    cout<<"所剩餘貨物價值為:"<<Aworth<<"元"<<endl;
    cout<<"*******************"<<endl;    
}

class A
{
    public:
        A(int x,int y):a(x)//靜態變數不能成員初始化法
        {//const函式不可以與靜態函式一起使用,只能是成員函式,而且函式內部只能實現輸入輸出
            cout<<a<<' '<<b;
        }
    private:
        int a;
        static int b;
};
int A::b=1;