字符串匹配kmp算法java_KMP字符串匹配算法

什么是KMP算法?

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。

暴力搜索算法实现

8fe13dea1cf316d89d17aee20de83ef9.png

请问,在字符串 T 中是否包含 P 的 "ababc"?

我们可以从第一个字符开始比对,如下图:

21a7fab8a627cb7f737a5b464f0a8a0d.png

在第四次比对的时候,我们发现 T 字符串和 P 字符串并不一致。

我们将字符串 P 整体后移一位重新进行对比。

97435e0dfcc30951efffd5dbd90db280.png

第一次比对我们就发现 T 字符串和 P 字符串比对不上。所以我们需要继续后移 P 字符串进行对比。

54dc1078408270713991f2a4227d63e7.png

以此类推,当所有字符能匹配上则说明匹配成功,如果匹配到 T 的末尾都没有成功的话则失败,此算法效率很低。

KMP算法实现

8fe13dea1cf316d89d17aee20de83ef9.png

请问,在字符串 T 中是否包含 P 的 "ababc"?

第一步:

在学习KMP算法之前,我们需要先了解前缀表(Prefix Table)。

我们先做的就是找到字符串长度较短的字符串,也就是字符串 P。

a

a b

a b a

a b a b

一共有四个前缀。

第二步:

我们需要找出最长公共前后缀并且比原始字符串短。

这句话有点不太好理解,我们使用a b a b来举例子。

242c92a1c1ed16f081577d9b64bf1966.png

我们可以看到 a b a b 最长前缀就是 a b a,最长后缀就是 b a b。

6c2f6d5a9582a449c5db504ddfba18e7.png

但是我们发现这两个字符串明显是不一样的。

34f3edbe56288c60c022108a6cf540b0.png

3个字符的前后缀明显不同,所以我们使用2个字符前后缀进行匹配看看。

我们发现 前缀是 a b 后缀也是 a b 。这样我们就知道最长公共前后缀是2。

62d874131ab5817b89392a0050a8acf0.png

然后我们继续往上看 a b a 很明显它的公共前后缀就是1。如果我们用2来看的话就是 ab 和 ba 很明显是不同的。

ok。再往上就是 a b 很明显,1是不匹配的。所以最长公共前后缀是0。 a 也一样。

我们看下最终是怎样的。

300f4114311a1a22fccd45b3159dc574.png

我们进行整体向右移动一位,然后初始值赋为 -1 可以得到如下结果:ababa-10012

这样我们去进行和 T 字符串进行比对,当比对到下标为3时,我们发现 a 和 b不同匹配失败。

44167925c39c57d1d55dd7531850a748.png

这个时候我们应该怎么做?

我们发现在 P 数组中下标为3的 b 匹配错误,而 b 的前缀表是 1,我们需要将 P 数组下标为 1 的位置 移动 T 数组匹配失败的位置。

486da14d4e557c8588150b3c33a5fe24.png

但是我们发现匹配还是错误的,我们可以知道 b 的 前缀表是 0 所以我们让下标为0的位置整体移动到 T 数组匹配失败的位置。

2269b7610e1d547749c96443067b49eb.png

我们重新进行匹配,发现第一个 a 相等,第二个 b 和 c 不相等。

8c553f5a56f2821cc953a79e9b33b3ae.png

我们继续移动,b 的前缀表是 0 所以我们让下标为0的位置整体移动到 T 数组匹配失败的位置。但是还是 a 和 c 匹配失败。

74039606ff4d207be5da9d2a807b8de8.png

但是我们知道 a 的前缀表是 -1 也就是一个空的字符串,我们将空的字符串对准匹配失败的位置,等同于-1就是整体右移一位

b1a3c29bbc3be3ec3c8fc20ed8185d25.png

这个时候我们继续进行匹配,发现已经匹配成功了!

22428f196a4ba5cb6bac3daec80d221b.png

暴力搜索和KMP区别

举一个例子:

f96a5fb856e3b98dbbd5e20681f0e8ba.png

如果我们使用暴力搜索的话我们会发现我们需要将 P 字符串一直后移一位,需要四次后移才可匹配成功。

如果我们使用KMP来解决的话,还是首先我们需要知道前缀表是多少,如下图:

44f1eb13cb44a4439f0cef21f306d752.pngaaaab-10123

然后我们进行比对。

765b596fefe92b1ec0f06ce2c92bbc79.png

前缀表为3匹配失败,将数组下标为3移动至此,继续进行对比。

656b2489515a33e7849e36a2dd196625.png

需要注意的是,我们不需要再重头开始对比,只需要从下标为3的位置开始匹配即可

后面其实就是大同小异,继续后移匹配即可。

总结

由此我们可以知道,暴力搜索每次移动后都要从第一位开始重新匹配,而我们用KMP的话我们不需要再重头开始对比可以省去大量时间。

Java代码实现KMP算法

7880b47ad4cb5ca2475e932414d90ee5.png

使用Java代码如何先得到前缀表。private static void buildPrefixTable(char[] pattern, int[] prefix) {

// 对比指针下标

int len = 0;

// 第一位不需要进行比对因为肯定是0

prefix[0] = 0;

//因为第一位不需要进行比对,我们从1开始

int i = 1;

//最后一位不需要比较因为我们需要的前缀表是比原始字符串短

int length = prefix.length -1;

while (i < length) {

if (pattern[i] == pattern[len]) {

//匹配成功,自增继续匹配下一位字符

len++;

prefix[i] = len;

i++;

} else {

if (len > 0) {

//获得前一位的前缀

len = prefix[len - 1];

} else {

//没有找到只能是0,i++继续匹配下一位

prefix[i] = len;

i++;

}

}

}

//整体后移一位,将第一位修改为-1

for (int j = prefix.length - 1; j > 0; j--) {

prefix[j] = prefix[j - 1];

}

prefix[0] = -1;

}

整体逻辑代码如下:public static void main(String[] args) {

String T = "ABABABCABAABABABAB";

String P = "ABABCABAA";

char[] text = T.toCharArray();

char[] pattern = P.toCharArray();

kmpSearch(text, pattern);

}

private static void kmpSearch(char[] text, char[] pattern) {

int m = text.length;

int n = pattern.length;

int[] prefix = new int[n];

buildPrefixTable(pattern, prefix);

// 字符串 text 的指针下标

int i = 0;

// 字符串 pattern 的指针下标

int j = 0;

while (i < m) {

if (j == n - 1 && text[i] == pattern[j]) {

System.out.println("找到了!开始下标为:" + (i - j));

j = prefix[j];

//如果未比较的pattern长度已超出text剩下的长度则提前结束

if (n - j > m - i) {

break;

}

}

if (text[i] == pattern[j]) {

//相同的话自增匹配下一位

i++;

j++;

} else {

//不同的话就将前缀值当下标进行整体移动

j = prefix[j];

if (j == -1) {

//如果值为-1则说明需要整体后移一位进行匹配

i++;

j++;

}

}

}

}

/**

* 构建前缀表

*/

private static void buildPrefixTable(char[] pattern, int[] prefix) {

// 对比指针下标

int len = 0;

// 第一位不需要进行比对因为肯定是0

prefix[0] = 0;

//因为第一位不需要进行比对,我们从1开始

int i = 1;

//最后一位不需要比较因为我们需要的前缀表是比原始字符串短

int length = prefix.length -1;

while (i < length) {

if (pattern[i] == pattern[len]) {

//匹配成功,自增继续匹配下一位字符

len++;

prefix[i] = len;

i++;

} else {

if (len > 0) {

//获得前一位的前缀

len = prefix[len - 1];

} else {

//没有找到只能是0,i++继续匹配下一位

prefix[i] = len;

i++;

}

}

}

//整体后移一位,将第一位修改为-1

for (int j = length; j > 0; j--) {

prefix[j] = prefix[j - 1];

}

prefix[0] = -1;

System.out.println("前缀表为:"+Arrays.toString(prefix));

}

输出结果:前缀表为:[-1, 0, 0, 1, 2, 0, 1, 2, 3]

找到了!开始下标为:2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值