字符串匹配-KMP算法

KMP算法目的:盡快解決字符串匹配問題,時間復雜度為O(m+n),而常規(guī)的簡單匹配算法時間復雜度:O(m*n)

這個算法不太容易理解,而且網(wǎng)上很多關于KMP算法的文章讀起來很費勁娱节,以下,我按照自己的理解祭示,試著寫一篇易懂的算法解釋肄满。

1 關于模式的前后綴函數(shù)(next數(shù)組獲取)

首先,為了方便后面的描述质涛,先定義下:S表示原字符串稠歉,T表示目標字符串(模式串),關于字符串匹配蹂窖,就是在S中尋找T轧抗。

關于尋找字符串的前后綴,舉個例子:
字符串:abcab
前綴:a瞬测,ab横媚,abc纠炮,abca
后綴:bcab,cab灯蝴,ab恢口,b

“前綴”指除了最后一個字符以外,一個字符串全部頭部組合穷躁。
“后綴”指除了第一個字符以外耕肩,一個字符串全部尾部組合。

模式前后綴函數(shù)问潭,就是產生一個長度等于模式串T長度的數(shù)組猿诸,每個值為相應“部分匹配值”的數(shù)組。
“部分匹配值”就是“前綴”和“后綴”的最長的共有元素字符串的長度狡忙。以“ABCDABD”為例:

模式串 A B C D A B D
部分匹配值(next) 0 0 0 0 1 2 0
  • "A"的前綴和后綴都為空集梳虽,共有元素的長度為__ 0 __;
  • "AB"的前綴為[A],后綴為[B],共有元素的長度為__ 0 __夏块;
  • "ABC"的前綴為[A, AB],后綴為[BC, C]禀挫,共有元素的長度__ 0 __;
  • "ABCD"的前綴為[A, AB, ABC]拓颓,后綴為[BCD, CD, D]语婴,共有元素的長度為__ 0 __;
  • "ABCDA"的前綴為[A, AB, ABC, ABCD]录粱,后綴為[BCDA, CDA, DA, A]腻格,共有元素為"A",長度為__ 1 __啥繁;
  • "ABCDAB"的前綴為[A, AB, ABC, ABCD, ABCDA]菜职,后綴為[BCDAB, CDAB, DAB, AB, B],共有元素為"AB"旗闽,長度為__ 2 __酬核;
  • "ABCDABD"的前綴為[A, AB, ABC, ABCD, ABCDA, ABCDAB],后綴為[BCDABD, CDABD, DABD, ABD, BD, D]适室,共有元素的長度為__ 0 __嫡意。

所以,對于字符串"ABCDABD"捣辆,會相應地產生一個數(shù)組array(0, 0, 0, 0, 1, 2, 0)蔬螟。這就是KMP算法關于next數(shù)組(也就是計算前后綴函數(shù))原理,它記錄的是模式串T子串(T[0 ... j] 0 < j < n)的最長前后綴元素長度的信息汽畴。

接下來旧巾,介紹KMP算法思想耸序。

2 KMP算法思想

第一次:

0 1 2 3 4 5 6 7 8 9
S a b c a b c a b d a
T a b c a b d
0 1 2 3 4 5

第二次:

0 1 2 3 4 5 6 7 8 9
S a b c a b c a b d a
T a b c a b d
0 1 2 3 4 5

傳統(tǒng)匹配算法中,每一輪匹配過后鲁猩,都會回溯到T[0]和S[i+1]的狀態(tài)位置開始下一輪的匹配坎怪。而上面的表圖運用了KMP算法,顯然兩次就能得出匹配信息:
這里先給出模式串T("abcabd")的next數(shù)組參照:

模式串 a b c a b d
部分匹配值(next) 0 0 0 1 2 0
0 1 2 3 4 5

第一個表格中廓握,S[5]與T[5]匹配失敗時搅窿,T[0 ... 4]字符串最長“前-后綴”是"ab",它在T[0 ... 4]中對應的前綴是T[0 ... 1]隙券,后綴是T[3 ... 4] (前后綴相等)男应,既然T[0 ... 4]與S[0 ... 4]匹配成功,那么T[0 ... 1]必然與S[3 ... 4]完全匹配是尔。(先結合next獲取那段好好理解)

由此可以想到殉了,當S[i]與T[j]匹配失敗時开仰,如果我們知道T[0 ... j-1]最長“前-后綴”在T[0 ... j - 1]對應的前綴是T[0 ... m] (m = next[j-1] - 1)拟枚,那么我們可以直接將S[i]與T[m+1]對齊,開始下次匹配众弓,因為T[0 ... m]必然已經(jīng)與S[0 ... i - 1]的后綴匹配成功恩溅。避免了不必要的回溯

下面是KMP算法代碼:

    def `kmp-matcher` (s: String, t: String): Int = {
        val next = `init-next`(t)

        val s_len = s.length
        val t_len = t.length

        var i = 0   /*記錄原字符串下標*/
        var j = 0   /*記錄模式串下標*/

        while (i < s_len && s_len - i > t_len - j) {
            while (j < t_len /*注意先檢查下標越界*/ && s.charAt(i) == t.charAt(j)) {
                i = i + 1
                j = j + 1
            }

            /*
             * 下面有兩種分支,完全匹配和匹配中斷
             * 完全匹配:函數(shù)直接返回匹配時的坐標
             * 匹配中斷:設置i谓娃,j下標脚乡,使其S[i]與T[NEXT[j-1]]對齊,進行下一次匹配
             */

            if (j == t_len /*完全匹配滨达,此處直接返回此次匹配首位下標*/)
                return i - t_len
            else /*匹配中斷*/
                j = next(j match {
                    case 0 => i = i + 1; j/*無“前-后綴”奶稠,直接將i下標加一匹配*/
                    case _ => j - 1
                })

        }
        -1   /*無匹配項*/
    }

上述代碼缺少\init-next` `函數(shù)的實現(xiàn),也就是next數(shù)組的獲燃癖椤:
實際上锌订,next數(shù)組記錄的是模式串T的各個字串C[0 ... j] (0 < j < T.length)的最長“前-后綴”長度信息。
傳統(tǒng)暴力求解next數(shù)組顯然很低效画株,這里也運用KMP匹配的方法獲取next數(shù)組辆飘。也就是在模式串T自身上使用KMP匹配:
用兩個變量i和j掃描T,i將模式串看做S谓传,j將模式串看做T蜈项。每次增加i時,賦予next[i]合適的值续挟,也就是最長“前-后綴”的長度紧卒。

    def `init-next` (s: String): Array[Int] = {
        val len = s.length
        val next = new Array[Int](len)

        next(0) = 0 //首個元素最大前后綴元素:無,所以此處設置為0
        var i = 1
        var j = 0

        while (i < len) {
            if (s.charAt(i) == s.charAt(j)) {
                j = j + 1
                next(i) = j
                i = i + 1
            } else if (j == 0) {
                next(i) = j
                i = i + 1
            } else {
                j = next(j - 1)
            }
        }
        next
    }

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末诗祸,一起剝皮案震驚了整個濱河市跑芳,隨后出現(xiàn)的幾起案子浇冰,更是在濱河造成了極大的恐慌,老刑警劉巖聋亡,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肘习,死亡現(xiàn)場離奇詭異,居然都是意外死亡坡倔,警方通過查閱死者的電腦和手機漂佩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來罪塔,“玉大人投蝉,你說我怎么就攤上這事≌骺埃” “怎么了瘩缆?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長佃蚜。 經(jīng)常有香客問我庸娱,道長,這世上最難降的妖魔是什么谐算? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任熟尉,我火速辦了婚禮,結果婚禮上洲脂,老公的妹妹穿的比我還像新娘斤儿。我一直安慰自己,他們只是感情好恐锦,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布往果。 她就那樣靜靜地躺著,像睡著了一般一铅。 火紅的嫁衣襯著肌膚如雪陕贮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天馅闽,我揣著相機與錄音飘蚯,去河邊找鬼。 笑死福也,一個胖子當著我的面吹牛局骤,可吹牛的內容都是我干的。 我是一名探鬼主播暴凑,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼峦甩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起凯傲,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤犬辰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后冰单,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幌缝,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年诫欠,在試婚紗的時候發(fā)現(xiàn)自己被綠了涵卵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡荒叼,死狀恐怖轿偎,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情被廓,我是刑警寧澤坏晦,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站嫁乘,受9級特大地震影響昆婿,放射性物質發(fā)生泄漏。R本人自食惡果不足惜亦渗,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一挖诸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧法精,春花似錦、人聲如沸痴突。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辽装。三九已至帮碰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拾积,已是汗流浹背殉挽。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拓巧,地道東北人斯碌。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像肛度,于是被迫代替她去往敵國和親傻唾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內容

  • 字符串匹配KMP算法詳解 1. 引言 以前看過很多次KMP算法承耿,一直覺得很有用冠骄,但都沒有搞明白伪煤,一方面是網(wǎng)上很少有...
    張晨輝Allen閱讀 2,402評論 0 3
  • 字符串匹配: KMP算法 學習于 從頭到尾徹底理解KMP 結合自己的理解, 本文致力于從簡介紹 先給出模板代碼v...
    Shadow0x70閱讀 437評論 0 0
  • 數(shù)據(jù)結構與算法--KMP算法查找子字符串 部分內容和圖片來自這三篇文章: 這篇文章、這篇文章凛辣、還有這篇他們寫得非常...
    sunhaiyu閱讀 1,737評論 1 21
  • 說明 kmp的一些概述不做解釋了, 請參考: kmp算法 (百度百科)參考了 阮一峰的: 字符串匹配的KMP算...
    一個什么都不會的菜鳥閱讀 294評論 0 0
  • “ ?抱既,又是一年春節(jié)到”馐模”大街小巷上有的賣鞭炮蝙砌,有的沾窗花,還有的掛燈籠跋理。處處充滿著春節(jié)的到來择克。 我和姐...
    陳德烜閱讀 221評論 0 1