算法学习集合之一:字符串匹配算法

本文深入解析KMP算法,一种高效字符串匹配算法。通过对比普通暴力算法,KMP算法利用匹配失败后的信息,避免不必要的比较,实现快速匹配。文章详细解释了KMP算法的工作原理,包括next函数的计算和实际算法流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.普通暴力算法

两个for循环,逐字对比,时间复杂度O(m*n);

2.KMP算法

理解步骤:

1.基于暴力算法。

string1和string2进行匹配,假如没匹配到,于x点被中断。

abadex

abadef

按照暴力算法的步骤,string1的指针i,应该退回到string1的第二个位置(第一个位置匹配失败了)。而string2的指针j,应该退回到string2第一个位置,重新匹配。

2.大胆假设优化方法

第一次匹配于x点被中断的时候,string1的指针i,我能不能直接跳到x点。

然后string2的指针j,跳到第1个位置,再继续匹配。

答案是大部分情况下是可以的,不过有一种情况下是会出问题的:

abcdabcdabce

abcdabce

这种情况下,string1的i在第一次匹配失败的时候,直接跳到d的地方,会错过中间abc开头的一串正确的匹配。

如果解决了这个特殊情况问题,KMP算法就完成了。

3.分析特殊情况出现条件

abcdabcdabce

abcdabce

这种特殊情况出现的条件,就是:

条件1:匹配失败,断点为x。

条件2:string2中,断点x前的字符串string2_before拥有重复开头的序列。

什么意思呢,就是string2中断点e之前的字符串abcdabc可以显示为abcdabc,后面的字符串会出现重复开头的字符串。

这两个条件,其他博客里有严格的数学论证,这里不作更多讲解。

4.如何处理特殊情况

abcdabcdabce

abcdabce

string1和string2进行匹配,出现匹配失败,断点为string1的d的,string2的e。

而这次匹配失败是一次上文的特殊情况,下一次匹配,string1中的i指针以及string2中的j指针接下来应该往哪放?

答案是:

string1中的i指针,直接跳到上一次匹配失败的中断点,string2中的j指针,根据条件来跳转。

                i

abcdabcdabce

        abcdabce

                j

红色的字母i,j就是i和j匹配失败后,分别该跳转后的地方。

string1中的i的跳转和我们第二步骤的大胆假设中的跳转,没有很大区别,直接从第一个位置,跳转到中断点的位置。

重点就在于string2中的j的跳转位置,j跳转之后,需要满足我们的要求,string1的i点前和string2的j点前能够匹配。

这样碰到上述特殊情况,我们就转换成上面所示的情况,就能够继续完成我们第二步骤的大胆假设中的算法了。

而string2的j的跳转位置可以根据 string2中重复开头的子串的位置,推算出来。(其他博客有详细数学论证)

就能推算出一个getNext()函数,专门计算string2匹配失败的下次跳转位置。

5.getNext()函数

输入:string2字符串

输出:长度为string2.length的一个数组nums[];

数组nums[]的用法:

假设匹配,在j=5处匹配失败,j的下一次跳转位置就是 nums[5];

这个跳转的位置,是根据 string2中重复开头的子串的位置推算出来的。

6.实际算法

在大胆假设,string中的i可以在匹配失败后跳转到中断点x之后,出现了不符合假设的特殊情况。

在对特殊情况进行过处理之后,大胆假设就可以得到实现了.

Javascript实现:

let string1= "abcabcabdllabcabd";
let string2 = "abcabcabc";
console.log(getNext(string2 ));
console.log(KMP(string1,string2 ));


/**
 * 28题
 */
//如果找不到重复开头的后续字串,会把nums[x]设置为-1,代表大胆假设的普通情况。
function getNext(ps) {
    let p = ps;
    let next = [];
    next[0] = -1;
    let j = 0;
    let k = -1;
    while (j < p.length - 1) {
       if (k == -1 || p[j] == p[k]) {
           next[++j] = ++k;
       } else {
           k = next[k];
       }
    }
    return next;
}

function KMP(ts,ps) {
    let t = ts;
    let p = ps
    let i = 0; // 主串的位置
    let j = 0; // 模式串的位置
    let next = getNext(ps);
    while (i < t.length && j < p.length) {
       if (j == -1 || t[i] == p[j]) { //j==-1其实就是大胆假设的普通情况,i留在断点处后一位,j从0开始
           i++;
           j++;
       } else {
           // 大胆假设的特殊情况,i留在匹配失败的断点不用移动
           j = next[j]; // j跳转到getNext指定的位置
       }
    }
    if (j == p.length) {
       return i - j;
    } else {
       return -1;
    }
}

然后代码用chrome的断点调试多跑跑,就会理解更透彻了。

标准说明

KMP算法是一种改进的字符串匹配算法,KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。

在简单的一次匹配失败后,我们会想将模式串尽量的右移和主串进行匹配。右移的距离在KMP算法中是如此计算的:在已经匹配的模式串子串中,找出最长的相同的前缀后缀,然后移动使它们重叠。

用next()函数提前计算出每个匹配失败的位置应该移动的距离。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值