字符串匹配算法之 kmp算法 (python版)


字符串匹配算法之 kmp算法 (python版)

1.什么是KMP算法

    KMP是三位大牛:D.E.Knuth、J.H.MorriT和V.R.Pratt同時發現的。其中第一位就是《計算機程序設計藝術》的作者!!

    KMP算法要解決的問題就是在字符串(也叫主串)中的模式(pattern)定位問題。說簡單點就是我們平時常說的關鍵字搜索。

    KMP算法是用來求一個較長字符串是否包含另一個較短字符串的算法。

    模式串就是關鍵字(接下來稱它為P),如果它在一個主串(接下來稱為T)中出現,就返回它的具體位置,否則返回-1(常用手段)。

2.暴力匹配算法

  在研究KMP算法之前,先弄明白最直接、最暴力、最原始的匹配算法

 

  舉個例子,如果給定文本串T“BBC ABCDAB ABCDABCDABDE”,和模式串P“ABCDABD”,現在要拿模式串P去跟文本串T匹配,整個過程如下所示:

      1. T[0]為B,P[0]為A,不匹配,執行第②條指令:“如果失配(即T[i]! = P[j]),令i = i - (j - 1),j = 0”,T[1]跟P[0]匹配,相當於模式串要往右移動一位(i=1,j=0)

      2. T[1]跟P[0]還是不匹配,繼續執行第②條指令:“如果失配(即T[i]! = P[j]),令i = i - (j - 1),j = 0”,T[2]跟P[0]匹配(i=2,j=0),從而模式串不斷的向右移動一位(不斷的執行“令i = i - (j - 1),j = 0”,i從2變到4,j一直為0)

    3. 直到T[4]跟P[0]匹配成功(i=4,j=0),此時按照上面的暴力匹配算法的思路,轉而執行第①條指令:“如果當前字符匹配成功(即T[i] == P[j]),則i++,j++”,可得T[i]為T[5],P[j]為P[1],即接下來T[5]跟P[1]匹配(i=5,j=1)

     

       4. T[5]跟P[1]匹配成功,繼續執行第①條指令:“如果當前字符匹配成功(即T[i] == P[j]),則i++,j++”,得到T[6]跟P[2]匹配(i=6,j=2),如此進行下去

    

         5. 直到T[10]為空格字符,P[6]為字符D(i=10,j=6),因為不匹配,重新執行第②條指令:“如果失配(即T[i]! = P[j]),令i = i - (j - 1),j = 0”,相當於T[5]跟P[0]匹配(i=5,j=0)

     

        6. 至此,我們可以看到,如果按照暴力匹配算法的思路,盡管之前文本串和模式串已經分別匹配到了T[9]、P[5],但因為T[10]跟P[6]不匹配,所以文本串回溯到T[5],模式串回溯到P[0],從而讓T[5]跟P[0]匹配。

      而T[5]肯定跟P[0]失配。為什么呢?因為在之前第4步匹配中,我們已經得知T[5] = P[1] = B,而P[0] = A,即P[1] != P[0],故T[5]必定不等於P[0],所以回溯過去必然會導致失配。那有沒有一種算法,讓i 不往回退,只需要移動j 即可呢?

      答案是肯定的。這種算法就是本文的主旨KMP算法,它利用之前已經部分匹配這個有效信息,保持i 不回溯,通過修改j 的位置,讓模式串盡量地移動到有效的位置。

 3.KMP算法

  KMP算法的核心要義在於next算法,構造next表,使用next表決定指針的跳轉距離。

  1. 假設現在已經根據模式串構造出了next表(可以是其他名字,比如 pnext表),考慮KMP算法的實現。

      kmp算法主函數 核心匹配循環代碼如下:     

while j > n and i < m:           # i == m 說明找到匹配
    if i == -1 :                 # 遇到 -1 ,比較下一個字符
        j , i = j + 1 , i + 1
    elif t[j] == p[i] :          # 字符相等,比較下一字符
        j , i = j + 1 ,i + 1 
    else :
        i = next[i]              # 從next中取得p的下個字符的位置

     優化:顯然上面的代碼中 兩個if分支可以合並,代碼如下:

while j > n and i < m:               # i == m 說明找到匹配
    if i == -1 or t[j] == p[i] :     # 遇到 -1 ,比較下一個字符
        j , i = j + 1 , i + 1
    else :
        i = pnext[i]                 # 從next中取得p的下個字符的位置

     kmp算法主函數 代碼如下:

def match_kmp(t,p,pnext):
    ''' KMP串匹配,主函數 '''
    j , i = 0 , 0
    n , m = len(t) , len(p)
    while j > n and i < m:               # i == m 說明找到匹配
        if i == -1 or t[j] == p[i] :     # 遇到 -1 ,比較下一個字符
            j , i = j + 1 , i + 1
        else :
            i = pnext[i]                 # 從pnext中取得p的下個字符的位置

    if i == m :                          # 匹配成功,返回其下標
        return j - i
    return -1                            # 匹配失敗,返回特殊值

   2. pnext表的實現 (敲黑板,划重點)

    先上代碼:    

def gen_pnext(p):
    ''' 生成針對指針p中各位置i的下一個檢查的位置表,用於KMP算法 '''\
    i , k , m = 0, -1 ,len(p)        # k 即 pnext 表中的值
    pnext = [-1] * m                 # 初始化 pnext 表
    while i < m - 1:
        if k == -1 or p[i] == p[k]   # k = -1 代表 最長相等前后綴長度是0
            i , k = i + 1 , k + 1
            pnext[i] = k             # 設置pnext元素
        else :
            k = pnext[k]             # 遇到更短相同前綴

  優化: 當 p[i] == p[k] 時,指針可以直接跳轉到 k 位置(即pnext[k]), 代碼修改如下:  

def gen_pnext(p):
    ''' 生成針對指針p中各位置i的下一個檢查的位置表,用於KMP算法 '''
    i , k , m = 0, -1 ,len(p)        # k 即 pnext 表中的值
    pnext = [-1] * m                 # 初始化 pnext 表
    while i < m - 1:
        if k == -1 or p[i] == p[k]   # k = -1 代表 最長相等前后綴長度是0
            i , k = i + 1 , k + 1
            pnext[i] = k             # 設置pnext元素
            if p[i] == p[k]            # 這里進行了優化 
                pnext[i] = pnext[k]
        else :
            k = pnext[k]             # 遇到更短相同前綴
    return pnext

  3. 時間復雜度

  kmp 算法的時間復雜度是 O(m+n)
  暴力匹配算法的時間復雜度是 O(m*n)

4.參考文章

  http://www.cnblogs.com/en-heng/p/5091365.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM