【数据结构-查找】2.字符串(逐步演绎过程,超级详解KMP算法)
串的定义
串(string)是有0~n个字符组成的有限序列,一般记为
S=′a1a2…an′(n≥0)S = 'a_1a_2…a_n'(n≥0) S=′a1a2…an′(n≥0)
S
是字符串的名称,aia_iai 可以是字母数字或其他字符。
其中,串中任意连续的字符组成的子序列称为该串的 子串。
字符在串中的位置 通常是该字符在序列中的序号
子串在主串中的位置 指的是子串第一个字符在主串中的位置
串的模式匹配
子串的定位操作通常称之为串的模式匹配,它的目的是要获取子串在主串的位置。
一般的,我们想到的是一种暴力破解的方法
也就是说,从主串 S
的第 pos
个字符起,与模式串的一个字符比较,若相等,则继续逐个比较主串和模式的后续字符;否则,从主串的下一个字符起,重新和模式的字符比较,以此类推,直至成功或失败。
int IndexSubstring(string s, string sub, int pos) {int i = pos;int j = 0;while(i < s.size() && j < sub.size()) {if(s[i]==sub[j]) {i++;j++;} else {i = i - j + 1;j = 0;}}if(j >= sub.size()) return i - sub.size() + 1; // 查找成功else return 0; // 查找失败
}
但这种暴力破解的算法的最坏时间复杂度为O(mn),m和n分别是主串和模式串的长度。
KMP算法——改进的模式匹配算法
在回顾上面暴力破解的算法,我们可以得到,暴力算法每一次回溯都是回到模式串最初的起点,事实上,这一步是可以改进的。
【KMP算法】利用比较过的信息,i
指针不需要回溯,仅将子串向后滑动一个合适的位置,并从这个位置开始和主串比较,这个合适的位置仅与 子串本身结构有关,与主串无关。
【KMP算法】实际上是一种【备忘录设计模式】,下面通过几个步骤或许可以让你了解这个算法的使用。
我们以主串 s='ababcabcacbab'
,模式串 sub=‘abcac’
1.获取模式串的 next 数组(备忘录)
子串 | 前缀 | 后缀 | 前后缀相同的最长 |
---|---|---|---|
a | - | - | 0 |
ab | a | b | 0 |
abc | a,ab | c,bc | 0 |
abca | a,ab,abc | a,ca,bca | 1 |
abcac | a,ab,abc,abca | c,ac,cac,bcac | 0 |
所以 模式串sub
的 next数组
为
s | a | b | c | a | c |
---|---|---|---|---|---|
next | 0 | 0 | 0 | 1 | 0 |
2.匹配过程
要满足一个公式:移动位数 = 已经匹配的位数 - 对应的部分匹配值
第一趟:发现 a
与 c
不配
a | b | a | b | c | a | b | c | a | c | b | a | b |
---|---|---|---|---|---|---|---|---|---|---|---|---|
a | b | c |
但是前面两个字符 'ab'
是匹配的,查表可知,最后一个匹配字符 b
对应匹配部分值为 0
,按公式,计算 $ 2-0=2 $,所以,模式串向后移动 2
位。
第2趟:模式串向后移动 2
位后,发现 b
与 c
不配
a | b | a | b | c | a | b | c | a | c | b | a | b |
---|---|---|---|---|---|---|---|---|---|---|---|---|
a | b | c | a | c |
但是前面两个字符 'abca'
是匹配的,查表可知,最后一个匹配字符 a
对应匹配部分值为 1
,按公式,计算 4−1=34-1=34−1=3,所以,模式串向后移动 3
位。
第3趟:模式串向后移动 3
位后,匹配成功
a | b | a | b | c | a | b | c | a | c | b | a | b |
---|---|---|---|---|---|---|---|---|---|---|---|---|
a | b | c | a | c |
使用【KMP算法】后,时间复杂度变为了O(m+n)
KMP原理
已知公式:移动位数 = 已经匹配的位数 - 对应的部分匹配值
改为代码:
move=(j−1)−next[j−1]move = (j -1) - next[j-1] move=(j−1)−next[j−1]
优化公式,如果我们将 next数组
向后挪一位,就不需要 next[j-1]
,只需要直接使用 next[j]
即可
s | a | b | c | a | c |
---|---|---|---|---|---|
next | 0 | 0 | 0 | 1 | 0 |
右移 | -1 | 0 | 0 | 0 | 1 |
于是上述公式(2)既可以改为
move=(j−1)−next[j]move = (j -1) - next[j] move=(j−1)−next[j]
此时,得到串移动的公式
j=j−move=j−((j−1)−next[j])=next[j]+1j = j - move =j -((j -1) - next[j])=next[j]+1 j=j−move=j−((j−1)−next[j])=next[j]+1
进一步优化,我们可以将右移后是 next数组
总体+1,那么,最后的数组就变成了
j=next[j]j = next[j] j=next[j]
s | a | b | c | a | c |
---|---|---|---|---|---|
next | 0 | 0 | 0 | 1 | 0 |
右移 | -1 | 0 | 0 | 0 | 1 |
右移+1 | 0 | 1 | 1 | 1 | 2 |
最后一行的意思是,把模式串的第 next[j]
个值,移动 j
这个位置。或者说,从模式串的第 next[j]
个数组开始查找,主串继续移动即可。
3.使用了改进后的next数组再匹配过程
第一趟:发现 a
与 c
不配
a | b | a | b | c | a | b | c | a | c | b | a | b |
---|---|---|---|---|---|---|---|---|---|---|---|---|
a | b | c |
在 c(pos=2)
这个位置匹配失败,查表得,c
的对应值是 1
,也就是将模式串的第一个字符 (a)
移到 c(pos=2)
这个位置
第2趟:模式串向后移动 2
位后,发现 b
与 c
不配
a | b | a | b | c | a | b | c | a | c | b | a | b |
---|---|---|---|---|---|---|---|---|---|---|---|---|
a | b | c | a | c |
在 c(pos=6)
这个位置匹配失败,查表得,c
的对应值是 2
,也就是将模式串的第二个字符 (b)
移到 c(pos=6)
这个位置
第3趟:模式串向后移动 3
位后,匹配成功
a | b | a | b | c | a | b | c | a | c | b | a | b |
---|---|---|---|---|---|---|---|---|---|---|---|---|
a | b | c | a | c |
参考代码
获取next数组
void getNext(string sub, int next) {int i = 0;int j = 0;next[0] = 0;while(i<sub.size()) {if(j == 0|| s[i]==s[j]){i++;j++;next[i] = j;} else {j = next[j];}}
}
【KMP算法】
int Index(string s, string sub, int next[], inr pos) {int i = pos;int j = 0;while(i<s.size()&&j<sub.size()) {if(j==0||s[i]==sub[j]){i++;j++; } else {j = next[j]; // 模式串右移}if(j >= sub.size()) {return i - sub.size() + 1;} eles {return 0;}}
}
【数据结构-查找】2.字符串(逐步演绎过程,超级详解KMP算法)相关推荐
- 【数据结构】详解KMP算法
字符串匹配算法:简单来说就是给你一个主串和一个子串,让你查找子串在主串中的位置,找到返回下标. 常见的两种算法:BF算法.KMP算法 这两种算法是怎样的思路呢,我们接着往下看: 目录 BF算法(暴力算 ...
- 数据结构:详解KMP算法,手工求解next、nextval数组,求模式串的比较次数例题
KMP 算法 手工求解 next 数组,nextval数组 例题:求模式串的比较次数 2019 年 408 统考真题 设主串 T="abaabaabcabaabc",模式串 S=& ...
- JavaScript:实现按字典顺序查找给定字符串的所有不同的非空子序列算法(附完整源码)
JavaScript:实现按字典顺序查找给定字符串的所有不同的非空子序列算法 /** Problem Statement: Find all distinct, non-empty subsequen ...
- 详解校招算法与数据结构
算法与数据结构(java)版 一,数据结构 1,数组和链表 (1)数组 数组是最常见的一种数据结构,它是相同类型的用一个标识符封装到一起的基本类型数据序列或者对象序列.数组使用一个统一的数组名和不同的 ...
- 【Big Data - Hadoop - MapReduce】通过腾讯shuffle部署对shuffle过程进行详解
摘要: 通过腾讯shuffle部署对shuffle过程进行详解 摘要:腾讯分布式数据仓库基于开源软件Hadoop和Hive进行构建,TDW计算引擎包括两部分:MapReduce和Spark,两者内部都 ...
- 【Python入门】Python字符串的45个方法详解
Python中字符串对象提供了很多方法来操作字符串,功能相当丰富.必须进行全面的了解与学习,后面的代码处理才能更得心应手,编程水平走向新台阶的坚实基础.目前一共有45个方法,给大家分类整理,可以收藏查 ...
- new Date将字符串转化成日期格式 兼容IE,ie8如何通过new Date将字符串转化成日期格式,js中如何进行字符串替换, replace() 方法详解
new Date将字符串转化成日期格式 兼容IE,ie8如何通过new Date将字符串转化成日期格式,js中如何进行字符串替换, replace() 方法详解 //获得年月日时分秒 //传入日期// ...
- 在VM虚拟机中 CentOS7安装VMware Tools(超级详解)
** 在VM虚拟机中的CentOS7的linux系统中安装VMware Tools(超级详解) ** 一.明白为什么这么做? 为什么要安装vmtools? vmtools 安装后,可以让我们在wind ...
- python怎么读文件内容-Python读取文件内容为字符串的方法(多种方法详解)
以下笔记是我在 xue.cn 学习群之数据分析小组所整理分享的心得.相关背景是:我选择中文词频统计案例作为考察大家python基础功掌握程度. 以小见大,下面是2个小技能的具体实战: 如何灵活地处理文 ...
最新文章
- 基于深度学习的文本分类应用!
- Linux下C语言的文件操作
- 七招制胜ASP.NET应用程序开发
- java 继承多态的一些理解和不理解
- 洛谷P1073 Tarjan + 拓扑排序 // 构造分层图
- java 拼图_功能项目拼图将Java 9引入
- 【CH - 1401】 兔子与兔子(字符串哈希)
- linux c 封装redis,封装hiredis——C++与redis对接(一)(string的SET与GET操作)
- 使用frp进行内网穿透的实例
- StringBuffer append整数0001的问题
- Android Serializable与Parcelable原理与区别
- 限制UITextView中的字数
- CouchDB 1.3.0的新特性以及算法的强化
- 让ProgressPar动起来
- C/C++[codeup 5901]回文串
- kuangbin专题一——Fliptile
- 【023】翼辉信息于南京召开国产嵌入式信息产业前沿技术交流会暨SylixOS新版发布会
- 使用 Sublime开发 Jade
- POI合并单元格,赋值
- openlayers中比例尺的计算原理
热门文章
- BloomFilter–大规模数据处理利器(转)
- 见鬼了,VS2005发布站点不会把Global.asax复上。
- CentOS7使用yum安装Nginx
- python调用电脑蜂鸣器一直响_电脑开机蜂鸣器一直响,9声滴~,什么问题?怎么处理?...
- 建立新冠病毒群体免疫屏障——数学建模
- xpath解析库的语法及使用
- windows平台下安装Mysql8.0.20版本
- 2020-11-11(aidl)
- IDA Pro7.0使用技巧总结使用
- PWN学习总结(四)—— BROP