kmp算法简明教程

在字符串s中寻找模式串p的位置,这是一个字符串匹配问题。

举例说明:

 i = 0   1   2   3   4   5   6  7   8   9  10  11  12  13
 s = a   b   a   a   c   a   b  a   a   a   b   a   a   b
 p = a   b   a   a   b
 j = 0   1   2   3   4

在kmp算法发明之前。人们使用这种算法:

‘‘‘
原始的求子串位置算法,O( m * n )
‘‘‘
def string_index_of( pstr, pattern, pos = 0 ):
    str_index = pos
    pattern_index = 0
    pattern_len = len( pattern )
    while str_index < len( pstr ) and pattern_index < pattern_len:
        if pstr[ str_index ] == pattern[ pattern_index ]:
            str_index += 1
            pattern_index += 1
        else:
            str_index = str_index - pattern_index + 1
            pattern_index = 0
    if pattern_index == pattern_len:
        return str_index - pattern_index
    return -1

pstr = ‘i am caochao, i love coding!‘
pattern = ‘ao‘
print( string_index_of( pstr, pattern, 7 ) )
print( pstr.find( pattern ) )

当s[4]与p[4]失配时,主串s回溯到i=1,模式串回溯到j=0,然后从此位置继续匹配。显然这种做法效率低下,假设s长度为n,p长度为m,则其时间复杂度为O(m*n)。

现考虑这样一个问题,当s与p匹配到位置i,j处,s[j]不等于p[j],如果此时保持i不变,p串中从k(0<k<j)处继续匹配,这样无需回溯i的做法这就是我们要讲到的kmp算法。那么应当如何取得这个k值?可以预先求出p中每个失配的j处需要跳转到的位置k(next[j]),这就是kmp算法中的next数组。

kmp算法步骤如下:

1,初始化i,j均为0,

2,依次往后比较s[i]与p[j],若相等则i,j各自加1,否则保持i不变,j=k(next[j])。若某时刻求得j值为-1,i,j也各自加1然后继续匹配

3,重复步骤2

依据上述分析可知,kmp算法中最关键之处在于k值的取法。在匹配进行到s[i]不等于p[j]时,假设应当让s[i]与p[k]继续比较(0<k<j),那么p中前k个字符必须满足,且不能存在k‘>k满足等式1:

等式1,p[0,k-1]=s[i-k,i-1]

而在i,j之前已经匹配的字符里存在等式2:

等式2,p[j-k,j-1]=s[i-k,i-1]

由此,可以推出等式3:

等式3,p[0,k-1]=p[j-k,j-1]

至此,k值的取法已经非常明显,即在p串中取最大的k(0<k<j),使得p中开始的k个字符与p[j]之前的k个字符相等。这样就可在s[i]不等于p[j]时,尽可能在距离p[0]远的位置处继续匹配,从而提高匹配效率。

从上面的分析中可以给出k,即next[j]的定义:

1,j=0时,next[j]=-1

2,next[j] = max{k|0<k<j且p[0,k-1]=p[j-k,j-1]}

3,其它情况,next[j]=1

递推求next数组:

从next[j]的定义出发,可以采用递推的方式求得next[j]:

首先,next[0]=-1

令next[j]=k(0<k<j),则表明在p中存在k,且不存在k‘>k满足下列关系:

p[0,k-1]=p[j-k,j-1]

那么next[j+1]的取值有3种情况,

1,若p[k]=p[j],则表明在p中存k,且不存在k‘>k满足关系p[0,k]=p[j-k,j],那么next[j+1]=k+1,即

next[j+1]=next[j]+1

2,若p[k]不等于p[j],此时可把求next函数的过程看成模式匹配的过程,即p既是主串又是模式串。而在模式匹配过种中,此时应当让p[j]与p[next[k]]继续比较。

为了便于理解,这里令next[k]=k‘。

若p[j]=p[k‘]时,next[j+1]=k‘+1,即next[j+1]=next[k]+1,也即

next[j+1]=next[next[j]]+1

同理若p[j]不等于p[k‘],那么继续让p[j]与next[k‘]比较,依次类推,直至k‘=-1时:

next[j+1]=0

代码实现如下:

‘‘‘
kmp求next[j]数组
‘‘‘
def kmp_get_next( pattern ):
    i = 0
    j = -1
    _next = [ 0 ] * len( pattern )
    _next[ 0 ] = -1
    while i < len( pattern ) - 1:
        if j == -1 or pattern[ i ] == pattern[ j ]:
            i += 1
            j += 1
            _next[ i ] = j
        else:
            j = _next[ j ]
    return _next

优化的求next数组方法:

考虑如下模式串:

j       =   0    1    2    3    4
p       =   a    a    a    a    b
next[j] =  -1    0    1    2    3

若某时刻s[i]与p[3]不相等,依据next[3]指示应当让s[i]与p[2]继续比较,因p[3]与p[2]相等,这一步明显是多余的。推广到普遍情况,在求next数组过程中,如果next[i]=j且p[i]=p[j],那么令next[i]=next[j]。代码如下:

‘‘‘
kmp求next[j]数组
‘‘‘
def kmp_get_next( pattern ):
    i = 0
    j = -1
    _next = [ 0 ] * len( pattern )
    _next[ 0 ] = -1
    while i < len( pattern ) - 1:
        if j == -1 or pattern[ i ] == pattern[ j ]:
            i += 1
            j += 1
            if pattern[ i ] == pattern[ j ]:
                _next[ i ] = _next[ j ]
            else:
                _next[ i ] = j
        else:
            j = _next[ j ]
    return _next

预先求得next[j]数组后,kmp算法代码实现如下:

‘‘‘
kmp求子串位置
‘‘‘
def kmp_index_of( pstr, pattern, pos = 0 ):
    _next = kmp_get_next( pattern )
    str_index = pos
    pattern_index = 0
    pattern_len = len( pattern )
    while str_index < len( pstr ) and pattern_index < pattern_len:
        if pattern_index == -1 or pstr[ str_index ] == pattern[ pattern_index ]:
            str_index += 1
            pattern_index += 1
        else:
            pattern_index = _next[ pattern_index ]
    if pattern_index == pattern_len:
        return str_index - pattern_index
    return -1

pstr = ‘i am caochao, i love coding!‘
pattern = ‘ao‘
print( kmp_index_of( pstr, pattern, 7 ) )
print( pstr.find( pattern ) )

最后,对比下普通算法与kmp算法解决本文开始提出的问题匹配过程:

普通算法:

 i = 0   1   2   3   4   5   6   7   8   9  10  11  12  13
 s = a   b   a   a   c   a   b   a   a   a   b   a   a   b
 p = a   b   a   a   b
 j = 0   1   2   3   4

s[4]不等于p[4],令i=1,j=0

 i = 0   1   2   3   4   5   6   7   8   9  10  11  12  13
 s = a   b   a   a   c   a   b   a   a   a   b   a   a   b
 p =     a   b   a   a   b
 j =     0   1   2   3   4

s[1]不等于p[0],令i=2,j=0

 i = 0   1   2   3   4   5   6   7   8   9  10  11  12  13
 s = a   b   a   a   c   a   b   a   a   a   b   a   a   b
 p =         a   b   a   a   b
 j =         0   1   2   3   4

s[3]不等于p[1],令i=3,j=0

 i = 0   1   2   3   4   5   6   7   8   9  10  11  12  13
 s = a   b   a   a   c   a   b   a   a   a   b   a   a   b
 p =             a   b   a   a   b
 j =             0   1   2   3   4

限于篇幅,略过中间n步,跳至i=9,j=0

 i = 0   1   2   3   4   5   6   7   8   9  10  11  12  13
 s = a   b   a   a   c   a   b   a   a   a   b   a   a   b
 p =                                     a   b   a   a   b
 j =                                     0   1   2   3   4

一路i++,j++,直到i=14,跳出循环结束匹配,并返回9。

kmp算法:

p串next数组为:

j       =   0    1    2    3    4
p       =   a    b    a    a    b
next[j] =  -1    0    0    1    1

next数组优化过后变为:

j       =   0    1    2    3    4
p       =   a    b    a    a    b
next[j] =  -1    0   -1    1    0

下面开始匹配:

 i = 0   1   2   3   4   5   6   7   8   9  10  11  12  13
 s = a   b   a   a   c   a   b   a   a   a   b   a   a   b
 p = a   b   a   a   b
 j = 0   1   2   3   4

s[4]不等于p[4],令j=0

 i = 0   1   2   3   4   5   6   7   8   9  10  11  12  13
 s = a   b   a   a   c   a   b   a   a   a   b   a   a   b
 p =                 a   b   a   a   b
 j =                 0   1   2   3   4

s[4]不等于p[0],next[0]=-1,因此i,j各自加1。i=5,j=0

 i = 0   1   2   3   4   5   6   7   8   9  10  11  12  13
 s = a   b   a   a   c   a   b   a   a   a   b   a   a   b
 p =                     a   b   a   a   b
 j =                     0   1   2   3   4

i++,j++,直到s[9]不等于p[4],令j=0

 i = 0   1   2   3   4   5   6   7   8   9  10  11  12  13
 s = a   b   a   a   c   a   b   a   a   a   b   a   a   b
 p =                                     a   b   a   a   b
 j =                                     0   1   2   3   4

一路i++,j++,直到i=14,跳出循环结束匹配,并返回9。

通过对比可以看出,kmp算法比普通算法快得多,只要预先求出模式串next数组,则整个匹配过程中i无需回溯,时间复杂度亦由普通算法O(m*n)提升为O(m+n)。

时间: 2024-10-10 21:54:19

kmp算法简明教程的相关文章

Lisp简明教程

此教程是我花了一点时间和功夫整理出来的,希望能够帮到喜欢Lisp(Common Lisp)的朋友们.本人排版很烂还望多多海涵! <Lisp简明教程>PDF格式下载 <Lisp简明教程>ODT格式下载 具体的内容我已经编辑好了,想下载的朋友可以用上面的链接.本人水平有限,如有疏漏还望之处(要是有谁帮我排排版就好了)还望指出!资料虽然是我整理的,但都是网友的智慧,如果有人需要转载,请至少保留其中的“鸣谢”页(如果能有我就更好了:-)). Lisp简明教程 整理人:Chaobs 邮箱:[

Vbs 脚本编程简明教程之一

-为什么要使用 Vbs ? 在 Windows 中,学习计算机操作也许很简单,但是很多计算机工作是重复性劳动,例如你每周也许需要对一些计算机文件进行复制.粘贴.改名.删除,也许你每天启动 计算机第一件事情就是打开 WORD ,切换到你喜爱的输入法进行文本编辑,同时还要播放优美的音乐给工作创造一个舒心的环境,当然也有可能你经常需要对文本中的某 些数据进行整理,把各式各样的数据按照某种规则排列起来--.这些事情重复.琐碎,使人容易疲劳. 第三方软件也许可以强化计算机的某些功能,但是解决这些重复劳动往

我也学算法 - KMP算法

一直以来,对算法都是理论大于实际,甚至没有实际. 最近由于项目需要.从新了解了一下KMP算法.唉,讨厌这种被动的学习过程. 不过KMP算法还是很有意思的,用了两天的时间才总算是弄懂了.期间参考了网上的博文和数据结构.下面分享一下KMP算法的心得. KMP的总体思想是利用模式串本身的特性来优化匹配的步骤.如何利用自身的特性呢,KMP借助一个数组来实现,也就是大多数教程中提到的next数组.后面我会介绍next数组是如何构建和使用的. 前面提到KMP算法需要模式串满足一定的条件,那么这个条件是什么呢

NGINX 简明教程

"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> NGINX 简明教程 - kkkloveyou - 博客频道 - CSDN.NET kkkloveyou 关注Web编程的那点事 目录视图 摘要视图 订阅 [活动]2017 CSDN博客专栏评选 &nbsp [5月书讯]流畅的Python,终于等到你!    &

关于KMP算法

http://www.cnblogs.com/maybe2030/p/4633153.html KMP算法,写的真好,非常的清楚明了,逻辑清晰,贯穿以例子,没有长篇大论却点明关键原理要害,比其他教程一堆教人半懂不懂的话什么不知高到哪里去了 因此,我认为写一个好的教程,必做到以下几点: 1. 始终以例子贯穿全文,通过例子具象化原理 2. 算法流程解释的清楚,逻辑清晰,陌生概念须定义 3. 最重要的,算法的关键之处,核心,即抛开所有细节,用三言两语点明算法要害,教程需紧紧围绕此主旨

KMP算法 学习笔记

kmp算法在很多人看来是如此的厉害,很早之前就学过了,但是各种看不懂把我拦住了,现在重新拾取,来写一下个人的学习总结. kmp看毛片算法(小甲鱼教的)(在这给小甲鱼做个广告,我个人看来小甲鱼讲的数据结构很好,很有趣.个人创业不容易,希望大家多多支持www.fishc.com小甲鱼,我跟小甲鱼素不相识,只是有用的东西大家分享) 好了言归正传. 如果你之前看过kmp算法没有看懂希望在这不要带着一种恐惧感,如果你没看过那是更好. 网上有很多详细教程,但是大部分都很啰嗦,容易把人看晕. kmp算法没有什

hiho 1015 KMP算法 &amp;&amp; CF 625 B. War of the Corporations

#1015 : KMP算法 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进. 这一天,他们遇到了一只河蟹,于是河蟹就向小Hi和小Ho提出了那个经典的问题:“小Hi和小Ho,你们能不能够判断一段文字(原串)里面是不是存在那么一些……特殊……的文字(模式串)?” 小Hi和小Ho仔细思考了一下,觉得只能想到很简单的做法,但是又觉得既然河蟹先生这么说了,就

KMP算法详解

这几天学习kmp算法,解决字符串的匹配问题,开始的时候都是用到BF算法,(BF(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果.BF算法是一种蛮力算法.)虽然也能解决一些问题,但是这是常规思路,在内存大,数据量小,时间长的情况下,还能解决一些问题,但是如果遇到一些限制时间和内存的字符串问

KMP算法

1 /* next数组是KMP算法的关键,next数组的作用是:当模式串T和主串S失配 2 * ,next数组对应的元素指导应该用T串中的哪一个元素进行下一轮的匹配 3 * next数组和T串相关,和S串无关.KMP的关键是next数组的求法. 4 * 5 * ——————————————————————————————————————————————————————————————————— 6 * | T | 9 | a | b | a | b | a | a | a | b | a | 7