字符串匹配-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ù),就是產(chǎn)生一個長度等于模式串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",會相應地產(chǎn)生一個數(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]完全匹配。(先結(jié)合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
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

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