python-字符串算法

1.朴素 算法

"""
朴素算法:
    一种字符串匹配算法, 
    在一个较长的原字符串中寻找某个子串并返回其首次出现的索引

思路:
   使用子串中的第0个字符去匹配原串中的每一个字符, 
   直到找到和子串第0个字符相同的字符,
   再一一匹配后面的字符,若后面所有字符都相同,则匹配成功;
   若有一个不相同则子串从第0个字符继续在原串向后匹配, 
   直到找到匹配的子串或搜索结结束
"""

def isMatch(src, dst):
    """
    朴素算法
    :param src: 原字符串
    :param dst: 目标子串
    :return: 子串首次出现的索引
    """
    # 分别获取长度
    src_len, dst_len = len(src), len(dst)
    # 遍历下标到 m-n+1,后面不用遍历了,因为长度不够了
    for i in range(src_len - dst_len + 1):
        # 若相同
        if src[i:i + dst_len] == dst:
            # 返回下标
            return i
    # 不匹配 返回-1
    return -1

if __name__ == '__main__':

    src = 'byte bye-by bye-bye'
    dst = 'bye-bye'
    print(isMatch(src, dst))

2. KMP 算法


"""
KMP 算法
    优化了朴素算法的比较次数,大大提高了匹配效率
思路:
    KMP算法优化的比较次数需要通过被搜索的子串来计算,
    计算的依据是子串的前缀和后缀的最长共有长度, 也被称为部分匹配表
"""

部分分配表-详
部分分配表
部分分配表-简
部分分配表-简

"""
举例:
    要在 byte bye-by bye-bye 中 搜索bye-bye,需要通过子串bye-bye 生成部分匹配表
    然后使用子串bye-bye去原字符串匹配字符,
    第一次匹配到部分字符相同时,字符b和字符y都匹配,但是t与e不匹配,
    如果用朴素算法,下一次使用子串中的b去匹配元字符串中的y,即向后移动一位 
    KMP算法移动位数通过公示计算: 移动位数 = 已比较长度 - 对应的部分匹配表值
    此时已比较长度为2,对应的部分匹配表的值为0,所以此时向后移动位数 = 2 - 0 = 2
"""

def getSteps(dst):
    # index 初始值为0 用于记录最长匹配长度
    index, dst_len = 0, len(dst)
    # 用于存储部分匹配表, 默认值为0
    steps = [0] * dst_len
    # 从第一个字符开始向后遍历
    for i in range(1, dst_len):
        # 若当前遍历字符和index的字符相同
        if dst[i] == dst[index]:
            # index + 1 为当前的长度
            steps[i] = index + 1
            # 然后 index + 1
            index += 1
        # 若当前遍历字符和index的字符不相同,且index不为0
        elif index != 0:
            index = steps[index - 1]
        # 字符不匹配,且index为0则当前匹配长度为0
        else:
            steps[i] = 0
    return steps


def kmp(src, dst):
    """
    KMP 算法
    :param src: src原字符串
    :param dst: dst子串
    :return: 子串在原串中首次出现的索引下标
    """
    steps = getSteps(dst)
    # 获取两个串的长度
    src_len, dst_len = len(src), len(dst)
    # 初始下标
    i, j = 0, 0
    while i < src_len and j < dst_len:
        # 若相同, 继续向后匹配
        if src[i] == dst[j]:
            i += 1
            j += 1
        # 根据部分匹配表决定j向后移动的位数
        elif j != 0:
            j = steps[j - 1]
        else:
            i += 1
    # 若长度相同,返回下标
    if j == dst_len:
        return i - j
    else:
        return -1


if __name__ == '__main__':

    src = 'byte bye-by bye-bye'
    dst = 'bye-bye'
    print(kmp(src, dst))

3. Boyer-Moore 算法

"""
Boyer_Moore算法 也称为BM算法
    目前使用最广泛的字符串匹配算法, 大多数文本编辑器的查找功能使用了此算法

举例:
    here is a simple example 中 匹配 example
    使用子串比较时,每次都是从后向前比较,找到第一个不相同的字符,
    然后使用坏字符规则计算一个向后移动的位数
    坏字符规则公式 移动位数 = 坏字符的位置 - 子串中上一次出现坏字符的位置(未出现过时-1)

    当不匹配的字符并不是最后一个时,还需要根据 好后缀规则 计算一个移动的位数, 
    取两种规则中最大数 作为移动位数
    好字后缀规则公式 移动位数 = 好后缀的位置 - 后缀在子串中上一次出现位置
"""

def index(dst, start, c):
    """
    在src中从start处向前找上一次字符出现的位置
    :param dst: 子串
    :param start: 字符不相同的位置
    :param c: 原串中不相同的字符
    :return:
    """
    start -= 1
    while start >= 0:
        if dst[start] == c:
            return start
        start -= 1
    return -1


def compare(src, dst):
    """
    比较两个字符串
    :param src: 原串中部分子串
    :param dst: 对比子串
    :return:
    """
    # 记录下标, 从后向前比
    src_len = len(src) - 1
    dst_len = len(dst) - 1
    # 若最后的下标不同, 说明长度不同 返回 -2
    if src_len != dst_len:
        return -2, None
    # 循环比较, 从后向前比较
    while src_len >= 0:
        # 若某个字符串不同, 返回该字符的下标级原字符串的不相同的字符
        if src[src_len] != dst[src_len]:
            return src_len, src[src_len]
        src_len -= 1
    # 完全相同 返回-3
    return -3, None

def searchSuffix(dst, suffix):
    """
    获取好后缀位置
    :param dst:
    :param suffix:
    :return:
    """
    i, l = 0, len(suffix)
    # 寻找最长后缀
    while i + 1 < len(dst) - 1:
        # 找到最长后缀
        if dst[i:i + 1] == suffix:
            return i
        i += 1
    # 若while循环未找到最长后缀
    for i in range(l):
        # 判断是否以其他后缀开始
        if src.startswith(suffix[i:]):
            return 0
    # 未找到任何后缀
    return -1


def bm(src, dst):
    """
    bm算法
    :param src: src 原字符串
    :param dst: 子串
    :return:
    """
    # 目标串长度
    l = len(dst) - 1
    # 原字符串长度
    start, end = 0, 0
    # 每次循环从原字符串中取出子串长度得字符串进行比较
    while True:
        # 计算取子串时得结束位置
        end = start + len(dst)
        print(f'查看原串中 子串长度的一段字符串 进行比较 {src[start:end]}')
        # 从原串中取出子串并比较
        cr = compare(src[start:end], dst)
        # 最后的下标不同, 长度不同
        if cr[0] == -2:
            break
        # 比较的两个字符串相同,直接返回下标
        if cr[0] == -3:
            return end - 1
        else:
            # 获取不相同字符上一次出现的位置
            pos = index(dst, cr[0], cr[1])
            # 最后一个字符不相同
            if cr[0] == len(dst) - 1:
                # 不相同的字符在子串中
                if pos != -1:
                    # 向后移动位数 = 坏字符串位置 - 不相同字符在子串中的位置
                    start += cr[0] - pos
                # 不相同的字符不在子串中
                else:
                    # 开始下标直接向后移动子串长度位
                    start += len(dst)
            # 中间某个字符不同
            else:
                # 坏字符规则
                step_bad = cr[0] - pos
                # 获取后缀
                suffix = dst[cr[0] + 1:]
                # 好后缀规则 获取后缀上次出现的位置
                suffix_index = searchSuffix(dst, suffix)
                # 好后缀规则 计算移动的位数
                step_good = l - suffix_index
                start += step_bad if step_bad > step_good else step_good

    return -1


if __name__ == '__main__':

    src = 'here is a simple example'
    dst = 'example'
    print(bm(src, dst))

控制台输出

查看原串中 子串长度的一段字符串 进行比较 here is
查看原串中 子串长度的一段字符串 进行比较  a simp
查看原串中 子串长度的一段字符串 进行比较  simple
查看原串中 子串长度的一段字符串 进行比较  exampl
查看原串中 子串长度的一段字符串 进行比较 example
23
  • 14
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值