KMP算法演繹之路

前言

模式匹配是數(shù)據(jù)結(jié)構(gòu)需要解決的經(jīng)典問題之一缀辩,由此衍生出許多算法。本文介紹模式匹配算法的復(fù)雜度從o(mn)逐步演化到o(m+n)的過程萤厅。

模式匹配

模式匹配描述了這樣的一個(gè)問題:

字符串T[1..n]P[1..m]由字符集中的字符組成(m<=n),要求字符串T中和模式P所匹配的索引號(hào)焚刚。

樸素模式匹配算法

這個(gè)問題最簡(jiǎn)單的解決辦法是:

從T的每一個(gè)字符T[i]{1≤i≤n}開始依次向后比對(duì)P的每一個(gè)字符,如果全部匹配净薛,則輸出i汪榔;重復(fù)之前的操作,直到找到所有符合條件的i肃拜。

代碼如下:

object KMPv1 {
  def kmp(source:String,sequence: String):scala.collection.mutable.Seq[Int] = {
    val sourceLen = source.length
    val sequenceLen = sequence.length
    if(sourceLen<sequenceLen)
      return scala.collection.mutable.Seq.empty[Int]

    var cursor = 0
    val cursorMax = sourceLen-sequenceLen+1
    var res:scala.collection.mutable.Seq[Int] = scala.collection.mutable.Seq[Int]()
    while(cursor<cursorMax){//m-n+1
      var isMatch = true
      var cursorInner = 0
      while(isMatch && cursorInner<sequenceLen){//m
        isMatch = source.charAt(cursor+cursorInner) == sequence.charAt(cursorInner)
        cursorInner+=1
      }
      if(isMatch){
        res = res.:+(cursor)
      }
      cursor+=1
    }
    res
  }
}

這個(gè)算法的復(fù)雜度是o(m*(n-m+1))=o(mn),它并沒有利用到曾經(jīng)匹配過的信息雌团。

有限自動(dòng)機(jī)的思想

有限自動(dòng)機(jī)在接受到一個(gè)信號(hào)之后燃领,會(huì)從一個(gè)狀態(tài)轉(zhuǎn)移到另外一種狀態(tài)。

引入有限自動(dòng)機(jī)的概念之后可以對(duì)模式匹配問題作如下變形:

將字符串T[1..n]中的字符T[i]{1≤i≤n}依次輸入有限自動(dòng)機(jī)锦援,自動(dòng)機(jī)的狀態(tài)q記錄了信號(hào)輸入之后與P[1..m]相匹配的字符數(shù),當(dāng)自動(dòng)機(jī)的狀態(tài)轉(zhuǎn)移到m時(shí)我們接受此時(shí)的索引i-m為匹配索引猛蔽。

舉例來說:

假設(shè)P=ababaca∑={a,b,c}中的字符組成,有限自動(dòng)機(jī)的初始狀態(tài)是0灵寺,在這個(gè)時(shí)候接受輸入a則轉(zhuǎn)移到狀態(tài)1(因?yàn)檩斎?code>a之后和P有一個(gè)匹配的字符)曼库;在狀態(tài)為1的時(shí)候接受輸入a則狀態(tài)仍然是1,接受b則狀態(tài)轉(zhuǎn)移為2略板,接受c則狀態(tài)轉(zhuǎn)移為0(因?yàn)楦?code>P沒有匹配的字符)毁枯。

有限自動(dòng)機(jī)
有限自動(dòng)機(jī)

顯然這個(gè)思想的關(guān)鍵在于構(gòu)造出有限自動(dòng)機(jī)的轉(zhuǎn)移函數(shù)?(q,signal)。從上圖b可知叮称,該函數(shù)只與m|∑|(全集的字符個(gè)數(shù))有關(guān)种玛,該表共有(m+1)|∑|個(gè)狀態(tài)值,計(jì)算每個(gè)狀態(tài)值的復(fù)雜度最多是o(m^2)瓤檐,所以構(gòu)造轉(zhuǎn)移函數(shù)的復(fù)雜度最多達(dá)到o(|∑|m^3)赂韵。之后與T來匹配只需要o(n)的復(fù)雜度。整個(gè)算法的復(fù)雜度最多是o(n+|∑|m^3)挠蛉。

此算法的復(fù)雜度遠(yuǎn)遠(yuǎn)大于樸素模式匹配算法的o(mn)祭示,但是它充分利用曾經(jīng)匹配過的信息(存儲(chǔ)在有限自動(dòng)機(jī)的狀態(tài)中),給了我們另外一種思路來思考這個(gè)問題谴古,如果能將轉(zhuǎn)移函數(shù)的復(fù)雜度優(yōu)化到o(m)质涛,就可以大大降低整體的復(fù)雜度悄窃。

KMP算法

KMP算法在有限自動(dòng)機(jī)的思想上做了一些調(diào)整,同樣引入了狀態(tài)這一概念蹂窖,但是狀態(tài)并不是由狀態(tài)轉(zhuǎn)移函數(shù)計(jì)算得到轧抗,而是通過前綴函數(shù)進(jìn)行邏輯運(yùn)算之后計(jì)算出來的。

舉例來說:

如下圖a瞬测,在輸入T[9]=b之前横媚,整個(gè)匹配過程處于狀態(tài)q=5,即T[4..8]=P[0..4]月趟,此時(shí)輸入T[9]=b灯蝴,發(fā)現(xiàn)和P[5]=c不匹配,此時(shí)通過觀察發(fā)現(xiàn):

P向右移動(dòng)2個(gè)位置剛好又有P[0..2]T[6..8]匹配孝宗,此時(shí)狀態(tài)是q=3穷躁。

輸入T[9]=b之后發(fā)現(xiàn)匹配則狀態(tài)變成q=4

嘗試將上述的觀察過程理論化因妇,發(fā)現(xiàn)因?yàn)?code>P[0..4]的所有前綴中(除開P[0..4]本身)问潭,與T[4..8]的后綴所匹配的最大長(zhǎng)度是31,所以嘗試將P向右移動(dòng)[當(dāng)前狀態(tài)q=5減去3等于2]個(gè)位置婚被;又由于有T[4..8]=P[0..4]狡忙,所以1處與T[4..8]作匹配等價(jià)于與P[0..4]作匹配。

圖解kmp
圖解kmp

由此址芯,下一個(gè)狀態(tài)可以通過當(dāng)前狀態(tài)q和與P[1..q]的后綴匹配的最長(zhǎng)前綴(除開自身)的函數(shù)來計(jì)算出灾茁。

計(jì)算前綴函數(shù)的偽代碼如下:

前綴函數(shù)
前綴函數(shù)

匹配函數(shù)的偽代碼如下:

匹配函數(shù)
匹配函數(shù)

scala實(shí)現(xiàn)如下:

/**
  * Created by studyz on 17/3/16.
  */
object KMPv2 {

  def kmpMatch(source:String, pattern: String):scala.collection.mutable.Seq[Int] = {

    var res = scala.collection.mutable.Seq[Int]()

    val statusArr = kmpPrefixFunc(pattern)

    var k =0//表示已經(jīng)匹配的個(gè)數(shù)
    val n = source.length
    val m = pattern.length

    for(q <- 0 until n){//n次
      while(k>0 && source.charAt(q) != pattern.charAt(k)){
        k = statusArr(k-1)
      }
      if(source.charAt(q) == pattern.charAt(k)){
        k+=1
      }
      if(k == m){
        res = res.:+(q-m+1)
        k = statusArr(k-1)
      }
    }
    res
  }

  //pattern字符串第k位前綴的與自身匹配的最長(zhǎng)后綴
  //P[1..m],1≤q≤m,0≤k<q,求k使得P[1..k]是P[1..q]的最長(zhǎng)后綴谷炸,kmpPrefixFunc(q)=k
  def kmpPrefixFunc(pattern:String):Array[Int]={
    val m = pattern.length
    val res = new Array[Int](m)
    res(0) = 0
    var k = res(0)
    for(q <- 1 until m){
      while(k>0 && pattern.charAt(k) != pattern.charAt(q)){
        k = res(k)
      }
      if(pattern.charAt(k) == pattern.charAt(q)){
        k+=1
      }
      res(q) = k
    }
    res
  }
}

使用平攤分析法可知此算法的復(fù)雜度是o(m+n)北专,其中前綴函數(shù)kmpPrefixFunc的復(fù)雜度是o(m),匹配函數(shù)kmpMatch的復(fù)雜度是o(n)旬陡。(//TODO)


參考文獻(xiàn):

  • 算法導(dǎo)論第三版
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拓颓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子季惩,更是在濱河造成了極大的恐慌录粱,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件画拾,死亡現(xiàn)場(chǎng)離奇詭異啥繁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)青抛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門旗闽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事适室〉找猓” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵捣辆,是天一觀的道長(zhǎng)蔬螟。 經(jīng)常有香客問我,道長(zhǎng)汽畴,這世上最難降的妖魔是什么旧巾? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮忍些,結(jié)果婚禮上鲁猩,老公的妹妹穿的比我還像新娘。我一直安慰自己罢坝,他們只是感情好廓握,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嘁酿,像睡著了一般隙券。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上痹仙,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天是尔,我揣著相機(jī)與錄音,去河邊找鬼开仰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛薪铜,可吹牛的內(nèi)容都是我干的众弓。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼隔箍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼谓娃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜒滩,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤滨达,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后俯艰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捡遍,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年竹握,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了画株。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谓传,靈堂內(nèi)的尸體忽然破棺而出蜈项,到底是詐尸還是另有隱情,我是刑警寧澤续挟,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布紧卒,位于F島的核電站,受9級(jí)特大地震影響诗祸,放射性物質(zhì)發(fā)生泄漏跑芳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一贬媒、第九天 我趴在偏房一處隱蔽的房頂上張望聋亡。 院中可真熱鬧,春花似錦际乘、人聲如沸坡倔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罪塔。三九已至,卻和暖如春养葵,著一層夾襖步出監(jiān)牢的瞬間征堪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工关拒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佃蚜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓着绊,卻偏偏與公主長(zhǎng)得像谐算,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子归露,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,238評(píng)論 0 4
  • 9.3.3 快速排序 ??快速排序?qū)⒃瓟?shù)組劃分為兩個(gè)子數(shù)組洲脂,第一個(gè)子數(shù)組中元素小于等于某個(gè)邊界值,第二個(gè)子數(shù)組中的...
    RichardJieChen閱讀 1,847評(píng)論 0 3
  • 本系列第三篇剧包,承接前面的《淺談機(jī)器學(xué)習(xí)基礎(chǔ)》和《淺談深度學(xué)習(xí)基礎(chǔ)》恐锦。 自然語言處理緒論 什么是自然語言處理? 自然...
    我偏笑_NSNirvana閱讀 17,620評(píng)論 2 68
  • 寒雪獨(dú)立枝頭 外柔香飄十里 錚錚傲骨孤寂 誰曾知己難求
    男城神才閱讀 222評(píng)論 4 2
  • 1.我怎么如此幸運(yùn)疆液,上午查完房一铅,很早就回家了,外面冷枚粘,家里也冷馅闽,身體生理期飘蚯,什么活也不想干,就想鉆到昨晚換好的厚被...
    史真如閱讀 150評(píng)論 0 0