模式比對與KMP演算法

來源:互聯網
上載者:User

模式比對的KMP演算法詳解

這種由D.E.Knuth,J.H.Morris和V.R.Pratt同時發現的改進的模式比對演算法簡稱為KMP演算法。大概學過資訊學的都知道,是個比較難理解的演算法,今天特把它搞個徹徹底底明明白白。

注意到這是一個改進的演算法,所以有必要把原來的模式比對演算法拿出來,其實理解的關鍵就在這裡,一般的匹配演算法:

int Index(String S,String T,int pos)//參考《資料結構》中的程式
{
  i=pos;j=1;//這裡的串的第1個元素下標是1
  while(i<=S.Length && j<=T.Length)
  {
    if(S[i]==T[j]){++i;++j;}
    else{i=i-j+2;j=1;}//**************(1)
  }
  if(j>T.Length) return i-T.Length;//匹配成功
  else return 0;
}

匹配的過程非常清晰,關鍵是當‘失配’的時候程式是如何處理的?回溯,沒錯,注意到(1)句,為什麼要回溯,看下面的例子:

S:aaaaabababcaaa  T:ababc

aaaaabababcaaa
    ababc.(.表示前一個已經失配)
回溯的結果就是
aaaaabababcaaa
     a.(babc)
如果不回溯就是
aaaaabababcaaa
        aba.bc
這樣就漏了一個可能匹配成功的情況
aaaaabababcaaa
      ababc

為什麼會發生這樣的情況?這是由T串本身的性質決定的,是因為T串本身有前後'部分匹配'的性質。如果T為abcdef這樣的,大沒有回溯的必要。

改進的地方也就是這裡,我們從T串本身出發,事先就找准了T自身前後部分匹配的位置,那就可以改進演算法。

如果不用回溯,那T串下一個位置從哪裡開始呢?

還是上面那個例子,T為ababc,如果c失配,那就可以往前移到aba最後一個a的位置,像這樣:
...ababd...
   ababc
   ->ababc

這樣i不用回溯,j跳到前2個位置,繼續匹配的過程,這就是KMP演算法所在。這個當T[j]失配後,j應該往前跳的值就是j的next值,它是由T串本身固有決定的,與S串無關。

《資料結構》上給了next值的定義:
          0   如果j=1
next[j]={Max{k|1<k<j且'p1...pk-1'='pj-k+1...pj-1'
          1   其它情況

我當初看到這個頭就暈了,其實它就是描述的我前面表述的情況,關於next[1]=0是規定的,這樣規定可以使程式簡單一些,如果非要定為其它的值只要不和後面的值衝突也是可以的;而那個Max是什麼意思,舉個例子:

T:aaab

...aaaab...
   aaab
  ->aaab
   ->aaab
    ->aaab

像這樣的T,前面自身部分匹配的部分不止兩個,那應該往前跳到第幾個呢?最近的一個,也就是說儘可能的向右滑移最短的長度。

OK,瞭解到這裡,就看清了KMP的大部分內容,然後關鍵的問題是如何求next值?先不管它,先看如何用它來進行匹配操作,也就是說先假設已經有了next值。

將最前面的程式改寫成:

int Index_KMP(String S,String T,int pos)
{
  i=pos;j=1;//這裡的串的第1個元素下標是1
  while(i<=S.Length && j<=T.Length)
  {
    if(j==0 || S[i]==T[j]){++i;++j;} //注意到這裡的j==0,和++j的作用就知道為什麼規定next[1]=0的好處了
    else j=next[j];//i不變(不回溯),j跳動
  }
  if(j>T.Length) return i-T.Length;//匹配成功
  else return 0;
}

OK,是不是非常簡單?還有更簡單的,求next值,這也是整個演算法成功的關鍵,從next值的定義來求太恐怖了,怎麼求?前面說過了,next值 表達的就是T串的自身部分匹配的性質,那麼,我只要將T串和T串自身來一次匹配就可以求出來了,這裡的匹配過程不是從頭一個一個匹配,而是從T[1]和T [2]開始匹配,給出演算法如下:

void get_next(String T,int &next[])
{
  i=1;j=0;next[1]=0;
  while(i<=T.Length)
  {
    if(j==0 || T[i]==T[j]){++i;++j; next[i]=j;/**********(2)*/}
    else j=next[j];
  }
}

看這個函數是不是非常像KMP匹配的函數,沒錯,它就是這麼乾的!注意到(2)語句邏輯覆蓋的時候是T[i]==T[j]以及i前面的、j前面的都 匹配的情況下,於是先自增,然後記下來next[i]=j,這樣每當i有自增就會求得一個next[i],而j一定會小於等於i,於是對於已經求出來的 next,可以繼續求後面的next,而next[1]=0是已知,所以整個就這樣遞推的求出來了,方法非常巧妙。

這樣的改進已經是很不錯了,但演算法還可以改進,注意到下面的匹配情況:

...aaac...
   aaaa.
T串中的'a'和S串中的'c'失配,而'a'的next值指的還是'a',那同樣的比較還 是會失配,而這樣的比較是多餘的,如果我事Crowdsourced Security Testing道,當T[i]==T[j],那next[i]就設為next[j],在求next值的時候就已經比較了, 這樣就可以去掉這樣的多餘的比較。於是稍加改進得到:

void get_nextval(String T,int &next[])
{
  i=1;j=0;next[1]=0;
  while(i<=T.Length)
  {
    if(j==0 || T[i]==T[j])
    { ++i;++j;
      if(T[i]!=T[j]) next[i]=j;
      else next[i]=next[j];//消去多餘的可能的比較,next再向前跳
    }
    else j=next[j];
  }
}

匹配演算法不變。

到此就完全弄清楚了,以前老覺得KMP演算法好神秘,真不是人想出來的,其實不然,它只不過是對原有的演算法進行了改進。可見基礎的經典的東西還是很重要,你有本事‘廢’了經典,就創造了進步。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.