字符串樸素匹配算法和KMP算法

字符串的匹配在平常的編碼過(guò)程中非常常用湿酸,在編程語(yǔ)言中通常是調(diào)用一個(gè)內(nèi)置函數(shù)就可以實(shí)現(xiàn)字符串的匹配辜御,當(dāng)不讓使用內(nèi)置的函數(shù)伍派,而是自己編寫(xiě)一個(gè)函數(shù)來(lái)實(shí)現(xiàn)匹配的功能江耀,應(yīng)該如何來(lái)寫(xiě)呢?

今天介紹兩個(gè)算法诉植,樸素匹配算法祥国,和無(wú)回溯匹配算法中的KMP算法。

樸素匹配算法

樸素匹配算法就是按照常識(shí)來(lái)晾腔,最容易理解的逐個(gè)字符匹配舌稀。
從待匹配字符串中的某個(gè)下標(biāo)i開(kāi)始,匹配字符串從0開(kāi)始灼擂,逐個(gè)匹配壁查。當(dāng)有不匹配的字符時(shí),重新從i+1下標(biāo)開(kāi)始重復(fù)上次的匹配過(guò)程剔应。

圖(1) 樸素匹配算法

下面用代碼實(shí)現(xiàn)一下:
t表示待匹配字符串睡腿,p表示用來(lái)匹配的字符串。

p_i 表示p字符串的第i個(gè)下標(biāo)的字符峻贮。

def naive_match(t, p):
    """
    t 目標(biāo)字符串
    p 匹配字符串
    """
    m, n = len(p), len(t)
    i, j = 0, 0
    while i < m and j < n:
        if p[i] == t[j]: # 字符相同席怪,考慮下一字符
            i, j = i + 1, j + 1
        else:
            i, j = 0, j - i + 1  # 字符不同,查找字符串重置月洛,目標(biāo)字符串回溯

    if i == m:
        return j - i
    return -1

樸素匹配算法非常簡(jiǎn)單何恶,容易理解。當(dāng)然嚼黔,它的效率也是很低的细层,造成效率低的原因是在執(zhí)行過(guò)程中會(huì)有回溯惜辑。當(dāng)遇到p[i] != t[j]時(shí),匹配字符串下標(biāo)置0疫赎,待匹配字符串的下標(biāo)回到上一次檢查的下一個(gè)位置盛撑,往回退了j - i + 1個(gè)位置。

這種操作造成的效率很低捧搞。最壞的情況是抵卫,每次匹配都是到最后一個(gè)字符的時(shí)候不匹配。例如:
??待匹配字符串: 0000000000001
??匹配字符串: 00001
這樣需要 n-m+1次比較胎撇,總的比較次數(shù)就是(n-m+1) * m介粘,這樣它的復(fù)雜度就是:O(n*m)

KMP算法

樸素算法的效率低,根源上是把每次匹配都看成的單獨(dú)的事件晚树,沒(méi)有利用到之前的匹配信息姻采。而其他改進(jìn)算法就是利用了之前的匹配信息。

KMP算法的基本思想就是在匹配中不回溯爵憎。

圖(2) KMP算法圖解

描述KMP算法慨亲,需要借助上圖。
待匹配字符串t宝鼓,和匹配字符串p刑棵。
在匹配過(guò)程中,p的第i個(gè)字符在和t的第j個(gè)字符比較愚铡,這時(shí)蛉签,t_{j-i}t_{j-1}p_0p_{i-1}相等,匹配完成茂附。下面要做的步驟可以分為兩個(gè):

  • 當(dāng)t_j = p_i 時(shí)正蛙,繼續(xù)進(jìn)行下一個(gè)字符的比較。
  • 當(dāng)t_j \neq p_i 時(shí)营曼,這是不需要重置p,而是找到一個(gè)位置k(0\leq k < i)愚隧,使得t_{j-k}t_{j-1} 等于 p_0p_{k-1}蒂阱,繼續(xù)匹配t_{j}p_{k},重復(fù)上面的步驟狂塘。這樣待匹配字符串也不需要回溯到前面去重新匹配录煤。

KMP算法中的關(guān)鍵認(rèn)識(shí)是:在p_i匹配失敗時(shí),所有的p_k(0\leq k < i)都已經(jīng)匹配成功荞胡。也就是說(shuō)妈踊,待匹配字符串中的t_j之前的i個(gè)字符,與匹配字符串中的前i個(gè)字符p_0,p_1,...,p_{i-1}泪漂。
通過(guò)上面的分析廊营,要找到k歪泳,完全可以先不管待匹配字符串,而是研究一下匹配字符串p露筒,通過(guò)p找到它前移的位置k呐伞。

得出一個(gè)結(jié)論:在p中,其中的每一個(gè)字符的i都會(huì)有其對(duì)應(yīng)的下標(biāo)k慎式,與待匹配的字符串無(wú)關(guān)伶氢。

因此,我們可以為匹配字符串設(shè)計(jì)一個(gè)列表來(lái)存儲(chǔ)每一個(gè)i的下標(biāo)k瘪吏。假設(shè)p的長(zhǎng)度為m癣防,用一個(gè)長(zhǎng)度為m的列表pnext來(lái)存儲(chǔ),用表元素pnext[i]來(lái)表示i個(gè)元素的下標(biāo)k值掌眠。
有一種特殊情況:p_i匹配失敗后蕾盯,之前所做的匹配都沒(méi)有用,需要從頭開(kāi)始匹配扇救,用p_0t_{j+1}比較刑枝。在這種特殊情況下可以在pnext[i]中存入-1來(lái)表示。顯然迅腔,p_0一直為-1装畅。

KMP主算法

當(dāng)假設(shè)pnext已經(jīng)獲得了,KMP的算法實(shí)現(xiàn)為:

def kmp_match(t, p, pnext):
    """
    KMP匹配沧烈,主函數(shù)
    """
    i, j = 0, 0
    m, n = len(p), len(t)
    while i < m and j < n:
        if i == -1: # -1 匹配下一隊(duì)字符
            i, j = i + 1, j + 1
        elif p[i] == t[j]: # 相等掠兄,匹配下一對(duì)字符
            i, j = i + 1, j + 1
        else:
            i = pnext[i] # 從pnext中拿出下一個(gè)字符應(yīng)該的位置

    if i == m:
        return j - i
    return -1

該算法的時(shí)間復(fù)雜度為O(n),因?yàn)閖的循環(huán)次數(shù)不會(huì)超過(guò)n锌雀,i = pnext[i]的次數(shù)不會(huì)超過(guò)m蚂夕。

pnext的實(shí)現(xiàn)

最長(zhǎng)相等前后綴

已知pnext[0]=-1,并且pnext[0]到pnext[i-1]已知的情況下腋逆,求解pnext[i]:

  1. 假設(shè)pnext[i-1]=k-1婿牍,如果p_i=p_k,則p_0,p_1,...,p_i的最長(zhǎng)的匹配相等長(zhǎng)度為k惩歉,記下pnext[i]=k等脂。
  2. 如果p_i \neq p_k,就將k的值設(shè)為pnext[k]撑蚌,即考慮前一個(gè)保證匹配的字符串上遥,且更短。
  3. 假如k的值為-1(由第二步造成争涌,一直到不到可以匹配的字符串)粉楚,那么就將pnext[i]設(shè)置為0,將i遞增后繼續(xù)檢查。

構(gòu)造方法如下:

def gen_pnext(p):
    i, k, m = 0, -1, len(p)

    pnext = [-1] * m

    while i < m - 1:
        if k == -1 or p[i] == p[k]:
            i, k = i + 1, k + 1
            pnext[i] = k
        else:
            k = pnext[k]

    return pnext

舉例:匹配字符串為 abbcabcaabbcaa模软,得到的pnext為:
[-1, 0, 0, 0, 0, 1, 2, 0, 1, 1, 2, 3, 4, 5]

pnext生成算法的改進(jìn)

在pnext的生成中伟骨,對(duì)pnext[i]的設(shè)置可以有些優(yōu)化。
在圖(2)中撵摆,p_i \neq t_j匹配失敗底靠,假設(shè)pnext[i]=k,如果發(fā)現(xiàn)p_i=p_k特铝,那么也一定有p_k \neq t_j暑中,所以,這種情況下pnext[i]的位置移動(dòng)到pnext[k]鲫剿,這一修改減少了一個(gè)比較步驟鳄逾,有可能提高效率。修改后:

def gen_pnext(p):
    i, k, m = 0, -1, len(p)

    pnext = [-1] * m

    while i < m - 1:
        if k == -1 or p[i] == p[k]:
            i, k = i + 1, k + 1
            if p[i] == p[k]:
                pnext[i] = pnext[k]
            else:
                pnext[i] = k
        else:
            k = pnext[k]

    return pnext

舉例:匹配字符串為 abbcabcaabbcaa灵莲,得到的pnext為:
[-1, 0, 0, 0, -1, 0, 2, -1, 1, 0, 0, 0, -1, 5]

pnext的復(fù)雜度為O(m)雕凹,所以整個(gè)KMP算法的復(fù)雜度O(m+n),由于m小于n政冻,可以認(rèn)為復(fù)雜度為O(n)枚抵,優(yōu)于樸素算法。

轉(zhuǎn)載自:
https://codeeper.com/2020/05/23/tech/python/structure/str-search-and-kmp.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末明场,一起剝皮案震驚了整個(gè)濱河市汽摹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苦锨,老刑警劉巖逼泣,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異舟舒,居然都是意外死亡拉庶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)秃励,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)氏仗,“玉大人,你說(shuō)我怎么就攤上這事夺鲜±希” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵谣旁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我滋早,道長(zhǎng)榄审,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任杆麸,我火速辦了婚禮搁进,結(jié)果婚禮上浪感,老公的妹妹穿的比我還像新娘。我一直安慰自己饼问,他們只是感情好影兽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著莱革,像睡著了一般峻堰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盅视,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天捐名,我揣著相機(jī)與錄音,去河邊找鬼闹击。 笑死镶蹋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赏半。 我是一名探鬼主播贺归,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼断箫!你這毒婦竟也來(lái)了拂酣?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瑰枫,失蹤者是張志新(化名)和其女友劉穎踱葛,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體光坝,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尸诽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盯另。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片性含。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鸳惯,靈堂內(nèi)的尸體忽然破棺而出商蕴,到底是詐尸還是另有隱情,我是刑警寧澤芝发,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布绪商,位于F島的核電站,受9級(jí)特大地震影響辅鲸,放射性物質(zhì)發(fā)生泄漏格郁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望例书。 院中可真熱鬧锣尉,春花似錦、人聲如沸决采。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)树瞭。三九已至拇厢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間移迫,已是汗流浹背旺嬉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厨埋,地道東北人邪媳。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像荡陷,于是被迫代替她去往敵國(guó)和親雨效。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354