字符串匹配算法综述

 引言 

什么是字符串匹配,字符串匹配的概念:是指一个字符串是否为另一个字符串的子串。我们将短的字符串称为模式串,将长的字符串称为文本串(图1-1):

图 1-1 字符串匹配

字符串A为主串,字符串B是字串,字符串B在字符串A中出现的位置为2,最终返回2。

我们再看另一个例子(图1-2):

图 1-2 字符串匹配示列

在这个例子中,字符串A中并不包含字符串B,因此返回 -1。

基于此,本文利用多个字符串匹配算法,依据时空复杂度(时间复杂度/空间复杂度)O(n),对不同的字符串匹配算法进行分析。

2  BF算法(Brute Force暴力算法

2.1  BF算法简介

BF(Brute Force,暴力检索)算法是最直接的暴力匹配。暴力算法的主要思路是从主串第一个字符位置出发并与字串进行匹配,然后假设主串的第一个字符位置与子串的第一个字符位置一致,那么继续比较后续字符。若前二个字符都不相同,则子串由主串的第二个字符起,以此类推。相等则继续匹配,不相等则子串之间又再次进行匹配,直至子串之间全部匹配成功,若主串中含有子串,则匹配成功,若不含有子串,则匹配失败。

2.2  Brute Force暴力算法的基本原理

首先,从主串的第一位开始,把主串和子串的每个字符一一比较(图2-1):

图 2-1 BF算法一一比较

主串和子串的第一个字符明显不匹配,因此,从第二个字符开始比对图2-2) 

图2-2 BF算法匹配字符

二者匹配,继续比较图2-3)

图2-3 BF算法再次匹配字符

主串的第三个字符位置两个字符不匹配,因此,将子串整体往后移一位,从主串的第三个字符串开始比较图2-4):

图2-4 BF算法匹配字符后移

主串的第三个字符是b,子串的第三个字符也是b,两者相等,继续比较图2-5)

图2-5 BF算法匹配字符比较[1]

主串的第四个字符是c,子串的第二个字符也是c,两者相等,继续比较图2-6)

图2-6 BF算法匹配字符比较[2]

主串的第五个字符是e,子串的第五个字符也是e,两个字符串一样,字符串匹配完成,主串包含子串,子串在主串中出现的位置是2,返回2图2-7):

图2-7 BF算法返回子串位置

2.3  利用python实现BF算法:

def BF(s, p):

    """

   BF算法字符串匹配

    """

    indies = []

    n = len(s)

    m = len(p)

    for i in range(n - m + 1):

        index = i  

        for j in range(m):

            if s[index] == p[j]:

                index += 1

            else:

                break

        if index - i == m:

            indies.append(i)

        

    return indies

2.4  BF算法总结:

BF(Brute Force,暴力检索)算法是初学者最容易理解的算法,也是传说中的暴力检索算法,假设主串和子串的长度分别为A,B,则它时间复杂度很容易达到O(A*B)。时间复杂度较大。虽然时间复杂度较高,但很容易理解,很适合初学者进行学习。有时候它的时间复杂度也能达到O(A+B),所以,此算法依旧被大量使用。

3 KMP算法(Knuth-Morris-Pratt算法)

3.1  KMP算法简介

Knuth-Morris-Pratt算法是一种比较高效的字符串匹配算法,它也是找到子串出现在主串中第一次出现的位置比如 mnmnmnp,那么nmn在其位置1处(字符串从0开始计数),np在其位置5处,我们第一时间想到的是暴力匹配,即BF算法。但利用BF算法会导致时间复杂度为O(m*n),而KMP算法保证了时间复杂度为O(m+n)

3.2  KMP算法的基本原理:

KMP算法是一种字符串匹配算法,它是对朴素模式算法(暴力匹配)的改进。 我们先看朴素模式下是怎么进行字符串匹配的。假设我们有两个字符串,被匹配的大串称为 主串,要匹配的小串称为模式串(图3-1):

图3-1 KMP算法基本原理

发现x与c不同后,进行移动(图3-2):

图3-2 KMP算法开始匹配

a与x不同,再次移动(图3-3):

图3-3 KMP算法匹配判断

此时比较到了c与y,于是下一步移动成了下面这样(图3-4):

图3-4 KMP算法匹配结果

      这次移动和前二次移动有什么差别呢这一次的移动和前二次的移动有所不同,前二次移动时,直接把子串的首字符和前一次主串比对在不同的位置对齐,而这次不是,原因是在这次移动前,y和c已经对齐,而y之前的ab又和子串的前缀ab相同,所以ab不需重新比较,而是直接在第三个位置进行比较(图3-5):

图3-5 KMP算法再次匹配

Kmp对于这种情况就直接使用当前所比较的字符串前面的最长相同前后缀。再将前缀与主串对齐,然后再比较后面的字符串的字符。这里,就到了KMP的核心思想了,如何确定子串中每个字符之前的最长相同前后缀(图3-6):

图3-6 KMP算法确定前后缀

在主串的每个字符下记录一个数字用来记录以这个字符结尾的最长前后缀相同子串长度。开始都记录为0,并且第一个字符确定为0,开始比对(图3-7):

图3-7 KMP算法进行对比

A与b和c都不同,所以b和c下数字都为0,a和a对齐(图3-8): 

图3-8 KMP算法对比结果

此此时a与a相同,所以a底下的数值为1,也就是说比对相同的第一个字符下为1,b和b相同,因此b底下的数值为2,而c和a不同,此时上边的文字串不动,下边的文字串移动到当前比对地址即c的前一个的下方的数值的地方(图3-9):

图3-9 KMP算法规则位移

对于主串来说c的前一位是b,且下方所对应的数字为0,所以将子串的第0位与之前比对不匹配的位置对齐,即(图3-10):

图3-10 KMP算法再次匹配

子串位置如图所示,继续进行比对,比对到c与a时,发现主串与子串字符不匹配(图3-11):

图3-11 KMP算法出现不匹配

这时候,c和a不匹配,则比较子串所对应位置主串字符下的数字的位置(图3-12): 

图3-12 KMP算法比较数字位置

也就是位置为2的地方与上面比对位置对齐(图3-13):

图3-13 KMP算法位置对齐

此时c与c相同,整个字符串自比对完成(图3-14):

图3-14 KMP算法自对比完成

如果匹配不成功,则对比主串前一字符下的数字的位置,这么做是为了要找到前一个字符位置下相同前缀中的最长相同前缀,比如刚才的例子(图3-15):

图3-15 KMP算法查找前缀

此时a前边是“abcab“,所以要找到”abcab“的最长相同前缀,就是ab(图3-16):

图3-16 KMP算法查找成功

然后再移动到ab与ab对其的位置继续比较即可。

3.3  利用python实现KMP算法,如下:

def compute_prefix_function(p):

    m = len(p)

    pi = [0] * m

    k = 0

    for q in range(1, m):

        while k > 0 and p[k] != p[q]:

            k = pi[k - 1]

        if p[k] == p[q]:

            k = k + 1

        pi[q] = k

    return pi



def kmp_matcher(t, p):

    n = len(t)

    m = len(p)

    pi = compute_prefix_function(p)

    q = 0

    for i in range(n):

        while q > 0 and p[q] != t[i]:

            q = pi[q - 1]

        if p[q] == t[i]:

            q = q + 1

        if q == m:

            return i - m + 1

    return -1

3.4  KMP算法总结

总的来说,就是找到子串中的每个字符的最长相同前后缀,如果子串的长度为a,那么主串的指向是一直向前移动,子串的指向也是向前的,所以最后的结果是长度为2a,时间复杂度则为O(a),KMP算法中运用了最长相同前后缀的方法进行比对,如果子串的长度为b,则KMP算法的时间复杂度为O(a+b),提高了算法效率。

4  BM算法(Boyer和Moore算法)

4.1  BM算法简介

BM (Boyer和Moore算法) 算法的执行效率较高,而且比较容易理解。常用一些软件中的快捷查找方法都是利用此算法。

4.2 BM算法的基本原理   

我们用一个例子来了解BM算法的基本原理图4-1):

图4-1 BM算法基本原理

第一步,将两个字符串左端对齐,从右端开始比对。S和E,通过此,便可以发现字符串不匹配。因此,我们便需要寻找一下子串中是否包含这个字符s,发现子串中并没有s,这时我们便需要将子串移动到主串s所对应的下一个位置图4-2):

图4-2 BM算法移动子串位置

然后我们再次从字符串的尾部开始比对,发现子串尾部位置p和E不匹配,在子串中看是否存在p,子串中包含p,因此,便将主串和子串中p的位置对齐图4-3): 

图4-3 BM算法主、子串对齐

继续从尾部进行比较,发现E匹配,L匹配,P匹配,M匹配,但I和A不匹配,再次寻找子串中是否有I,此时发现主串中的E可以和子串中的第一个字符E对应,可直接将子串移动到两个E对应的位置    (图4-4):

图4-4 BM算法字符匹配

然后继续从尾部开始比对,P和E不匹配,则看子串中是否包含P,存在,则移动子串的P和主串的P对应的位置对齐图4-5): 

图4-5 BM算法字符匹配对齐

再继续从尾部开始以一比对,匹配成功。

4.3  利用python实现BM算法:

def getBMBC(pattern):

    #预生成坏字符表

    BMBC = dict()

    for i in range(len(pattern) - 1):

        char = pattern[i]

        #记录坏字符最右位置 (不包括模式串最右侧字符)

        BMBC[char] = i + 1

    return BMBC



def getBMGS(pattern):

    #预生成好后缀表

    BMGS = dict()



    #无后缀仅限根据坏字移位符规则

    BMGS[''] = 0



    for i in range(len(pattern)):



        #好后缀

        GS = pattern[len(pattern) - i - 1:]



        for j in range(len(pattern) - i + 1):



            #匹配部分

            NGS = pattern[j:j + i + 1]



#记录模式串中好后缀最靠右位置(除结果处)

            if GS == NGS:

                BMGS[GS] = len(pattern) - j - i - 1

    return BMGS



def BM(string, pattern):

    """

    Boyer-Moore算法实现字符串查找

    """

    m = len(pattern)

    n = len(string)

    i = 0

    j = m

    indies = []

    BMBC = getBMBC(pattern=pattern)

#坏后缀

    BMGS = getBMGS(pattern=pattern)

#好后缀

    while i < n:

        while(j > 0):

            if i +j -1 >= n:

#当无法继续向下搜索就返回值

                return indies



            #主串判断匹配部分

            a = string[i +j - 1:i + m]



            #模式串判断匹配部分

            b = pattern[j - 1:]



          #当前位匹配成功则继续匹配

            if a == b:

                j = j - 1



        #当前位匹配失败根据规则位移

            else:

                i = i + max(BMGS.setdefault(b[1:],m),

                            j - BMBC.setdefault(string[i + j - 1], 0))

                j = m

            

            #匹配成功返回匹配位置

            if j == 0:

                indies.append(i)

                i += 1

                j = len(pattern)

4.4  BM算法总结:

BM (Boyer和Moore算法) 算法容易理解,且执行小路较高,是一种构思比较精巧的字符串匹配算法。BM算法与暴力算法和KMP算法不同,BM算法采用后缀比对法,其核心思想在于,通过坏字符算法和好后缀算法,找到子串每次后移的最大距离。BM算法开始将主串与子串头对齐,尾比对,如果尾部不同,则可一次确定比对失败,将子串进行位移。

5  总结

BF(Brute Force,暴力检索)算法是初学者最容易理解的算法,也是传说中的暴力检索算法,时间复杂度较大。虽然时间复杂度较高,但很容易理解,很适合初学者进行学习。有时候它的时间复杂度也能达到O(A+B),所以,此算法依旧被大量使用。

kmp算法中,找到子串中的每个字符的最长相同前后缀, KMP算法中运用了最长相同前后缀的方法进行比对,如果主串的长度为a,子串的长度为b,则KMP算法的时间复杂度可记为为O(a+b),相对于暴力法,提高了算法效率。

BM算法采用后缀比对法,其核心思想在于,通过坏字符算法和好后缀算法,找到子串每次后移的最大距离。BM算法开始将主串与子串头对齐,尾比对,如果尾部不同,则可一次确定比对失败,将子串进行位移。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值