【数据结构与算法】常用十大算法:KMP算法
应用场景
字符串匹配问题:
有一个字符串 BBC ABCDAB ABABCDABD
,另一个字符串为 ABCDABD
现在要判断 str1 中是否含有 str2,如果有就返回 str1 中首个匹配字母的索引,如果没有,则返回 1
暴力匹配算法
思路
遍历两个字符串:
- 若
str1[i] == str2[j]
,则i++
、j++
,继续匹配下一个字符 - 若
str1[i] != str2[j]
,则i = i - j + 1
、j = 0
,继续进行匹配 - 如此循环直至
i > str1.length && j > str2.length
图解
- 红色代表匹配失败
- 绿色代表匹配成功
- 。。。表示以此类推
代码实现
1 | public int violenceMatch(String str1, String str2) { |
分析
使用暴力匹配法匹配字符串,每次匹配失败都需要回溯到开始位置的后一位,其间花费了大量的时间,仔细思考,其实这些开销是不必要的。
看图,字符串一直从 A 匹配到 D ,D 处不匹配,但是前面的 ABCDAB 都已经一一匹配了,故此时将 str2 后移一位结果可想而知也不会匹配
如图,中间后移的五步结果不算都可以知道是不匹配的,那我们干嘛还要去浪费时间一次一次移动呢?
直接从移动到 str1 中的下一个 A 不香嘛!
那我们可以可以设计某种算法来进行优化呢?
我觉得不行
但是有三位聪明的大师做到了!
这种算法被命名为 KMP算法 ,也就是今天的主角了!
KMP算法
简介
KMP是三位大牛:D.E.Knuth、J.H.Morris 和 V.R.Pratt 同时发现的。其中第一位就是《计算机程序设计艺术》的作者!
KMP算法要解决的问题就是在字符串(也叫主串)中的模式(pattern)定位问题。说简单点就是我们平时常说的关键字搜索。模式串就是关键字,如果它在一个主串中出现,就返回它的具体位置,否则返回-1(常用手段)。
思路
- KMP算法如何实现呢,这里我们需要先引入一些概念
举例:“ABCDABD”
- ”A”的前缀和后缀都为空集,共有元素的长度为 0
- ”AB”的前缀为[A],后缀为[B], 共有元素的长度为 0
- “ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度 0
- ”ABCD” 的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为 0
- ”ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA,A],共有元素为”A”,长度为 1
- ”ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,
长度为 2 - ”ABCDABD”的前缀为[A, AB, ABC,ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD,
D],共有元素的长度为 0
通过上述分析,我们可以得到一张部分匹配表
A B C D A B D 0 0 0 0 1 2 0 这张部分匹配表就是 KMP算法的核心,接下来我们就要用它了!
接着暴力匹配算法走:
当 D 与 “ ” 不匹配时,前面六个字符 “ABCDAB” 是匹配的,查 部分匹配表 可知最后一个匹配的字母 B 对应的部分匹配值为 2
由公式:
$$
移动位数 = 已知匹配字符数 - 对应匹配值
$$
可得 移动位数 = 6 - 2 = 4
故将搜索词向后移动 4 位
C 的部分匹配值为 0,
由公式得 位移位数 = 2 - 0 = 2
故将搜索词后移两位
以此类推,可高效得到答案!
至此,KMP算法 思路分析全部结束。
至于原理,emmm,比较玄学的东西还没有学习,可以参考 https://zhuanlan.zhihu.com/p/83334559
代码实现
1 | public int kmpSearch(String str1, String str2) { |
mvn install:install-file -DgroupId=com.google.code.kaptcha -DartifactId=kaptcha -Dversion=2.3 -Dfile=E:\CS\Java\Project\ruoyi\kaptcha-2.3.2.jar -Dpackaging=jar -DgeneratePom=true
参考
尚硅谷:https://www.bilibili.com/video/BV1E4411H73v?p=161
labuladong:https://zhuanlan.zhihu.com/p/83334559