百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

「图示+代码」一看就懂的字符串匹配算法KMP、BM、Sunday

wxin55 2024-11-17 16:47 9 浏览 0 评论

数据与智能 本公众号关注大数据与人工智能技术。由一批具备多年实战经验的技术极客参与运营管理,持续输出大数据、数据分析、推荐系统、机器学习、人工智能等方向的原创文章,每周至少输出7篇精品原创。同时,我们会关注和分享大数据与人工智能行业动态。欢迎关注。

作者 | yu

校对 | gongyouliu

编辑 | auroral-L


全文共1839字,预计阅读时间20分钟。


字符串模式匹配是NLP领域的基础任务,可以在大量的文本内容中快速找到需要的文本信息,比如在文章中搜索关键词的位置和数量,在现实生活中有较高的使用频率,本文主要介绍几种常见的字符串模式匹配算法:KMPBMSunday



1. KMP (Kunth-Morris-Pratt-string-searching algorithm)


给定字符串 S:mnmmomnmnomo

待匹配字符 P:mnmno


步骤


1). 待匹配字符 P的前缀表(prefix table)的生成


step 1 写出该字符串所有的前缀

m

m n

m n m

m n m n

m n m n o


step 2 对于每一个字符串 找出其最长公共前缀后缀长度 注:公共前缀后缀长度小于原始字符串长度


字符串

最长公共前缀后缀长度

''

-1

m

0

m n

0

m n m

1 前缀(m) 后缀(m)

m n m n

2 前缀(m n) 后缀(m n)

m n m n o

0


得到到前缀表 (注:最长公共前缀后缀长度右移一位)


m

n

m

n

o


-1

0

0

1

2

0


2)字符串匹配


step 1 将字符串S,P头部对齐,从左到右依次比较字符


step 2 遇到不同的字符时,获取字符串P对应最长公共前后缀的长度,找到其对应的索引值,将字符串P的索引值与字符串S失配位置对齐,从未匹配位置开始从左到右依次比较。若字符串P对应的索引值为-1,则将字符串P右移一位,比较字符。



step 3 重复step 2 ,直到匹配成功




匹配成功,找到第一个匹配位置!



step 4 若想找到S串中全部的P串则继续重复step2



code


def genPreTable(P):
    '''
    :param P:
    :return: 前缀表
    '''
    preTable = [-1] * (len(P) + 1)
    if len(P) > 1:
        preTable[1] = 0
        i, j = 0, 1
        while j < len(P):
            if i == -1 or P[i] == P[j]:
                i += 1
                j += 1
                preTable[j] = i
            else:
                i = preTable[i]
    return preTable


def kmp(S, P):
    '''
    :param S:
    :param P:
    :return: 返回所有匹配成功的index
    '''
    indices = []
    if not P: return [0]


    preTable = genPreTable(P)


    s = p = 0
    while s < len(S):
        if p == -1 or S[s] == P[p]:
            s += 1
            p += 1
        else:
            p = preTable[p]
        if p == len(P):
            indices.append(s - p)
            p = preTable[p]
    return indices


S = 'mnmmomnmnomo'
P = 'mnmno'
result = kmp(S, P)
print(result)


2. BM (Boyer-Moore)


给定字符串 S:mnommnomn

待匹配字符 P:mnomn

(注:从右向左匹配字符串)


1)坏字符规则 (bad character Heuristics)


字符串S跟字符串P中某个字符不匹配时,称S中的这个失配字符为坏字符,此时P需右移。


右移位数 = 坏字符对应P中的位置 - 坏字符在P中最右出现的位置。若P中不存在坏字符,则最右出现位置置-1。



上图所示字符串S,P头部对齐。从右向左比较字符,若字符不同,则右移字符串P,使P中右侧第一个字符‘m‘与S中失配的'm'对齐(字符串P右移一位)


2) 好后缀规则(Good-Suffix Heuristics)


字符失配时,右移位数 = 好后缀在P中的索引 - 好后缀在P上一次出现的索引。若好后缀在模式串中没有再次出现,则为-1。



如上图所示,好后缀为mn, 字符串P右移3位(如下图所示)



步骤


step1. 字符串S,P头部对齐。

step2. 从右到左比较字符,根据上述规则分别计算移动距离,选择较大的距离进行移动,直至匹配成功。





code


def getBadChar(P):
    '''
    :param P:
    :return: 坏字符表
    '''
    badChar = {}
    for i in range(len(P) - 1):
        char = P[i]
        badChar[char] = i + 1
    return badChar


def getGoodSuffix(P):
    # 预生成好后缀表
    goodSuffix = {}
    goodSuffix[''] = 0


    for i in range(len(P)):
        subString = P[len(P) - i - 1:]
        for j in range(len(P) - i - 1):
            if subString == P[j:j + i + 1]:
                goodSuffix[subString] = len(P) - j - i - 1
    return goodSuffix


def BM(S, P):
    '''
    :param S:
    :param P:
    :return: 返回所有匹配成功的index
    '''
    p, s = len(P), len(S)
    if not P: return [0]
    i, j = 0, p
    indices = []


    badChar = getBadChar(P)
    goodSuffix = getGoodSuffix(P)


    while i < s:
        while (j > 0):
            if i + j > s:
                return indices


            strOfS = S[i + j - 1:i + p]
            strOfP = P[j - 1:]


            if strOfS == strOfP:
                j = j - 1
            else:
                i = i + max(goodSuffix.setdefault(strOfP[1:], p), j - badChar.setdefault(S[i + j - 1], 0))
                j = p
            if j == 0:
                indices.append(i)
                i += 1
                j = len(P)
    return indices


S = "mnommnomn"
P = "mnomn"
result = BM(S, P)
print(result)


3. Sunday


给定字符串 S:mnommpomnomn

待匹配字符 P:mnomn


步骤


step1. 字符串S,P头部对齐。

step2. 从左到右比较字符,在匹配失败时获取字符串S中失败字符的下一位字符,在字符串P中获取该字符。若该字符没有在P中出现则对应的移动位数 = P的长度 + 1;否则,移动位数 = P的长度 - 该字符在P中最右出现的索引 。






code


def move(P, p, char):
    index = p + 1
    if char not in P: return index
    for i in range(p):
        if P[i] == char: index = p - i
    return index


def sunday(S, P):
    if not P: return [0]
    p, s = len(P), len(S)
    temp_index = 0
    indices = []


    while temp_index + p <= s:
        p_head_index = 0
        s_head_index = temp_index
        print(s_head_index, p_head_index)


        while s_head_index < s and p_head_index < p and S[s_head_index] == P[p_head_index]:
            s_head_index += 1
            p_head_index += 1


            if p_head_index == p:
                indices.append(temp_index)
                temp_index += 1


        if temp_index + p >= s: break
        temp_index += move(P, p, S[temp_index + p])
    return indices
S = "mnommpomnomn"
P = "mnomn"
result = sunday(S, P)
print(result)

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: