当前位置: 首页 > news >正文

算法学习----Python数据结构--kmp字符串

来自acwing

核心优化思想在于通过一个lps数组来反映部分重复匹配的位置信息,能够从头前进好几位达到快捷搜索,减少传统暴力从头只前进一位遍历带来的冗余。难点主要在于lps数组的构建思路

当我们要在ababa中找aba时

当下标遍历到2,找到第一个匹配点,再寻找第二个匹配点时暴力方法是将母串的下标进一位,也就是在babca中再遍历查找aba,此时发现第一个字母就不匹配,再将母串下标进一位。这只是一个便于理解的情况,倘若数据量巨大,这种遍历的时间复杂度是相当高的。

对此,我需要在第一次匹配成功,直接定位到母串的第二个a,也就是穿越到下标为2进行开头遍历,对比暴力方法这一方式无疑是更快得多的

对此我需要记录这些完全相同的类似于‘a’这种重复出现的部分

也就来到了构建lps数组的环节,它的出现是为了记录子串中每个位置的最长公共前后缀长度

对于aba,我们发现第一个a和自己能匹配但是作为开头无意义,对于b,因为a!=b,匹配不到,对于第三个a,能够和开头a匹配,我们记录为1,同时引入长度概念,对于子串的这种单个字符的匹配,长度length记录为1,此时我们的lps数组为[0,0,1]

当我们通过这个数组和长度信息再进行遍历时,如何将子串的开头定位到母串的2下标位置呢,而且这里存在一个问题就是我们已经找到第一个能够匹配的位置了,我们下一次匹配又要怎么设置呢

当我们找到其中一个字串位置时,母串下标j是需要返回的,我们如何将j直接移动到a第二次出现的位置呢。

事实上kmp并不是这样的返回思路,而是通过lps了解到子串的前后缀的相同性,不通过重新遍历母串下标而是将子串下标回退进行继续检索。

具体分析 "ababa" 中查找 "aba"

让我们仔细分析这个例子:

  1. 第一次匹配完成后

    • 母串指针 i = 3 (指向第4个字符 'b')

    • 子串指针 j = 3 (表示已完全匹配)

    • 记录匹配位置 0

  2. KMP的智能移动

    • KMP通过LPS数组知道子串"aba"的前缀和后缀有重叠

      • 前缀"a"和后缀"a"相同

      • LPS[2] = 1 表示最长公共前后缀长度为1

    • 所以算法将子串向右移动 3 - 1 = 2 位:(也就是将子串从b开始与母串进行比对)

      文本: a b a b a
      模式:     a b a
      

KMP算法的移动量是由LPS数组决定的:

  • 移动量 = 已匹配长度 - LPS[已匹配长度-1]

  • 这确保了不会错过任何可能的匹配

在"aba"的例子中:

  • 已匹配长度 = 3

  • LPS[2] = 1

  • 移动量 = 3 - 1 = 2

KMP算法的精妙之处就在于它利用模式串本身的信息(LPS数组)来做出最优的移动决策,既保证了正确性(不会错过任何匹配),又保证了效率(移动尽可能远但不会太远)。

def lpsestablish(parttern):m=len(parttern)lps=[0]*mlength=0i=1while i<m:if parttern[i]==parttern[length]:length+=1lps[i]=lengthi+=1else:if length!=0:length=lps[length-1]else:lps[i]=0i+=1return lpsdef kmp_search(text,parttern):n=len(text)m=len(parttern)if m==0:return []if n==0 or m>n:return []lps=lpsestablish(parttern)matches=[]i=0j=0while i<n:if parttern[j]==text[i]:i+=1j+=1if j==m:matches.append(i-j)j=lps[j-1]else:if j!=0:j=lps[j-1]else:i+=1return matchesdef main():import sysdata=sys.stdin.read().splitlines()if not data:return n=int(data[0].strip())parttern=data[1].strip()m=int(data[2].strip())text=data[3].strip()matches=kmp_search(text,parttern)print(' '.join(map(str,matches)))if __name__=='__main__':main()

http://www.dtcms.com/a/340439.html

相关文章:

  • gcc 与 g++ 的区别:本身不是编译器而是编译器驱动
  • Day23 双向链表
  • STL模板库——string容器
  • DPO,PPO,GRPO
  • 【Linux基础知识系列:第一百零四篇】使用apt-cache管理软件包信息
  • 【数据结构】直接选择排序
  • 跨域问题解决方法
  • 链表-24.两两交换链表中的结点-力扣(LeetCode)
  • Spring Boot 3整合Nacos,配置namespace
  • 云计算学习100天-第26天
  • linux的sysctl系统以及systemd系统。
  • Linux + arm 内存属性
  • 静/动态库 IIC(arm) day58
  • 机器学习——网格搜索(GridSearchCV)超参数优化
  • Linux + arm 内存屏障
  • 商用厨房物联网智能化解决方案——打造环保、高效、安全的智慧餐饮新生态
  • C语言基础:(二十)自定义类型:结构体
  • 领码方案:通用物联网数据采集低代码集成平台——万物智联时代的黄金钥匙
  • 【Grafana】grafana-image-renderer配合python脚本实现仪表盘导出pdf
  • 车载软件架构 --- 赢得汽车软件开发竞赛
  • MySQL事务及原理详解
  • YAML格式笔记
  • SQL面试题及详细答案150道(41-60) --- 条件查询与分组篇
  • 【自记】Power BI 中 ALL、ALLSELECTED、ALLEXCEPT、ALLNOBLANKROW 的区别说明
  • 自学嵌入式第二十三天:数据结构(3)-双链表
  • SQL四大类命令(DQL、DML、DDL、DCL)
  • 第1课_Rust基础入门
  • Rust系统编程:从入门到实战的蜕变之旅
  • MySQL 数据与表结构导出 Excel 技术文档
  • 基础笔记8.20