KMP + 通配符

_

prerequisite

  • mergesort思想, 单调指针(monotonic pointer)
  • 笛卡尔积dfs的非递归计算方法 或者 非递归实现 itertools.combinations
  • python 编程语言相关知识, 尤其是generator
  • 不需要知道KMP算法以及相关概念

   

这篇文章用形式化语言讲会特别特别长, 所以只能用接近意会的方式了

难度 codeforce 1800分

没有相关OJ题目, 正确性靠自测

~

这篇文章介绍如何用带有通配符的字符串进行搜索

例如

'*'符号可以用来匹配单个的任意字符

P="ab*c", T="abec"

P在T出现过一次

或者'*'符号可以用来匹配任意长度子串

P="ab*c", T="abeec"

P可以匹配T

引用的代码

以下代码不需要看懂

以下代码在字符串T中搜索P的所有出现的位置,

时间复杂度O(|T|+|P|), 空间复杂度O(|P|)

def border(s):

    n = len(s)

    B = [None] * n

    B[0] = ml = 0

    for i in range(1, n):

        # loop inv: ml is B[i-1]

        while ml > 0 and s[i] != s[ml]:

            ml = B[ml - 1]

        if s[i] == s[ml]:

            ml += 1

        B[i] = ml

    return B

 

def search(T, P):

    """ yield all index j that T[j:j+np]==P """

    B = border(P) # B is border array of P

    ml = 0

    np, nt = len(P), len(T)

    for i in range(nt):

        while ml > 0 and T[i] != P[ml]:

            ml = B[ml - 1]

        if T[i] == P[ml]:

            ml += 1

            if ml == np:

                yield i + 1 - np

                ml = B[ml - 1]

notation

P

np

用来搜索的字符串

len(P)

T

nt

被搜索的字符串

len(T)

'*'可以匹配单个字符

让P在'*'处分裂, 例如

`P="abc*def*gh"`, 分裂为 `["abc","def","gh"]`

这些P子串, 记作Psub[i]

用KMP算法, 在T中搜索"abc"和"def"和"gh",

得到的三个结果是有序的,

接下来枚举T中所有可能位置 i, T[i:i+np] == P

由 T[i:i+np] == P 这个条件, 可以推出Ps[i]在T中应具有的位置

(类似于mergesort的思想)创建len(Ps)个单调递增的索引即可

节省空间的措施

Ps[i]在T中的搜索结果可以不用都存起来, 每次只获取一个结果

python中可以用`yield`关键字实现

代码

def search_wild(T, P):

    np = len(P)

    nt = len(T)

 

    Ps = []

 

    prv = 0

    for cur in range(np + 1):

        if cur == np or P[cur] == '*':

            if cur - prv > 0:

                Ps.append([prv, search(T, P[prv:cur]), None])

            prv = cur + 1

    nk = len(Ps)

    for iT in range(nt - np + 1):

        for ik in range(nk):

            while Ps[ik][2] == None or (Ps[ik][2] < iT + Ps[ik][0]):

                try:

                    Ps[ik][2] = next(Ps[ik][1])

                except StopIteration:

                    return

 

            if Ps[ik][2] != iT + Ps[ik][0]:

                break

 

        else:

            # the length of matched substring is determined

            yield iT

时间,空间复杂度

O( nk * nt + np ), O( nk )

'*'可以匹配任意长度子串

同样让P在'*'处分裂,

不过P不能以`*`开头, 不能以`*`结尾, 本来是搜索T中子串, 加个'*'没有意义, 还会导致程序运行时间复杂度过高

如代码所示, 在'*'处分裂P, 得到 nk 个P的子串

把这些子串在T中的搜索结果存起来, 得到一个 nk 列的表

对所有表进行笛卡尔积(类似) 得到结果

代码

def search_wild_match2(T, P):

    P = P.strip('*')

    np = len(P)

    if np == 0:

        # this is meaningless and time complexity become O(2^t)

        raise Exception("not valid")

    

    Ps = []

 

    prv = 0

    for cur in range(np + 1):

        if cur == np or P[cur] == '*':

            if cur - prv > 0:

                indices = list(search(T, P[prv:cur]))

                if len(indices)==0: return

                Ps.append([cur - prv, indices, 0])

                # Ps[i][2] is index on Ps[i][1]

                # Ps[i][1][ Ps[i][2] ] is index on T

            prv = cur + 1

 

    # similar to pylib.itertools.combinations

    nk = len(Ps)

    def iToiT(i):

        return Ps[i][1][ Ps[i][2] ]

    for i in range(1, nk):

        while Ps[i][2] < len(Ps[i][1]) and iToiT(i) < iToiT(i-1) + Ps[i - 1][0]:

            Ps[i][2] += 1

        if Ps[i][2] == len(Ps[i][1]):

            return

    

    while True:

        yield tuple(Ps[i][1][Ps[i][2]] for i in range(nk))

        last = None

        for i in reversed(range(nk)):

            if Ps[i][2] < len(Ps[i][1]) - 1:

                last = i

                break

        else:

            break

 

        Ps[last][2] += 1

        for i in range(last + 1, nk):

            while Ps[i][2] < len(Ps[i][1]) and iToiT(i) < iToiT(i-1) + Ps[i - 1][0]:

                Ps[i][2] += 1

            if Ps[i][2] == len(Ps[i][1]):

                return

时间,空间复杂度

O(( nt / nk )^ nk + np ) , O( nt + np )

上一篇:SQLyog连接mysql8报2058错误


下一篇:C++串的模式匹配(BF,KMP详解)