KMP算法及其改进

KMP算法及其改进

字符串匹配算法也就是从一个很长的字符串里面找出与我们手中的字符串相匹配的字符串(是这个大字符串的第几个字符开始),对于这个问题我们有很简单的解法,叫BF算法,Brute Force也就是蛮力的意思,充分依靠计算能力来解决问题的方法,对于这种解法可以用下面的图片来表述:

上面的算法就是BF算法,不好之处是效率太低了,因为就像第三趟比较中那样,我们只有最后一个元素没有匹配上就要从头再来,主串的对应元素竟然要回头从第四个元素开始比较,我们明明比较到了主串的第七个元素,前面的工作全部白费了。

KMP算法的想法减少我们比较时主串又回溯回去的这种现象,在KMP算法里,比较过的主串的元素(匹配成功)不会再进行比较,我们只是在失败的时候选中字串的某一个元素来和主串所对应的元素进行比较。最核心的就是next数组了,next数组对于比较失败时的情况给出了明确指引下一步我们应该拿子串的那一个元素再进行比较,这种指引是建立在对字串充分解析的基础之上的。

上面就是一个字串,下面的一行就是我们给出的next数组,用法也很简单,譬如说我们子串的第七个元素a与主串对应的元素进相比较失败了,由这一点可以说明,子串前面的六个元素都与主串匹配成功了,我们下一步就依照next数组的指示,用子串的第四个元素来与主串当中刚才没有匹配成功的那个元素比较,这样选择的依据是,我们把第四个元素与主串中刚才失败的那个元素对齐后,子串的第四个元素前面的元素都是匹配的。

next数组的产生,对于第一个元素a来说,next[1]永远都是0,意味着我们要用第0号元素与主串元素对其,但没有0号元素,也就是说我们要用1号元素与主串的下一个元素对齐,对于第二个元素b来说,我们假想他与主串匹配失败,用子串的第一个元素来对齐,这种情况直到第五个元素发生了变化,我们用第二个元素来对齐,可以看见子串的第一个元素a与主串的失败元素的前一个元素是完全匹配的都是a,这样相比较BF算法我们就少比较了一次,第六个元素C如果失败了,我们就用第三个元素C来与主串中的元素对齐,发现前面的ab两个元素不用比较了,再说子串第八个元素失败的情况,我们用子串的第五个元素与主串元素对齐,可以发现前面的abca四个元素是完全匹配的。

通过充分解析字串的内容,我们可以给出next数组来指引我们加快匹配过程,具体的怎么填写next数组有具体的程序,但这里还是要说一下我的思路,假设第一个元素a比较失败,没办法,我们只能将子串后移一下与主串在进行比较,填写0意味着我们放弃与主串的这个元素比较转而与主串的下一个元素进行比较,如果第二个元素b比较失败,我们发现它的前面只有一个a,我们就填1了,这里说的太牵强了,那第六个元素来说,当它失败的时候,我们发现子串中他前面的元素中 后缀ab(4,5)与前缀ab(1,2)是一样的,我们如果把第三个元素移到此处对齐时就会发现前两个元素已经完全匹配了,这就是基于我们对子串中失败元素前面(所有元素成为一个整体)的前缀和后缀相不相同分析而来的,因为我们如果依照next数组移动了,肯定就是原来子串中失败元素的前缀和后缀对齐在一起。前缀后缀都是来年各个元素,这里我们+1,因为对齐的话主串中失败元素正好对的是子串的第三个元素。

分析失败元素的前缀和后缀得到了next数组,前缀为从前开始的前几个元素的组合,后缀是左侧紧贴失败元素的几个元素的组合,例如     next[8]==5,是因为1234和4567都是abca,4+1==5;

当然这种方法还有值得改进的地方,如果我们按照next数组的指示换子串的另一个元素来比较,但这个元素与原来的子串中的那个元素是相等的,这样和主串比较不还是失败么!!!如子串中第六个元素是C,它比较失败了,我们按照next数组指示选择第三个元素来接他的班,但第三个元素与第六个元素相等都是C,不用想,依旧会比较失败,这里我们怎么办???我们要尽享以下判断,如果第六个元素与第三个元素不相等我们就用第三个元素,如果相等了,我们就用第三个元素对应的next成员来接替(也就是第一个元素a)

假如上面的不好理解的话,我们可以假设我们傻啦吧唧的用了第三个元素C来对齐比较,结果不出所料,失败了,我们又会用第三个元素对应的next元素来与之对其进行比较——————我们上面做的只不过是提前了一步,投石问路,少走了一步弯路而已。

因为next数组是从前往后依次建立的,上面的做法最终使得第i个元素与它对应的next[i]总不会是相同的,这样避免了可预料的重复失败,进一步加快了匹配速度,按照这种思想建立如下:

注意上面的next数组,其实少了一个元素,我们没有看见next[0]但是在实际的next数组中这个元素肯定是存在的,这样的话我们的next数组要比子串长度大一,因为next[0]永远都不会被用到,所以我们也就不对他进行赋值了,但他还是存在的。

下面贴一下改进后的kmp_next数组产生程序:

void get_next(HString S, int next[])
{
    int i = 1, j = 0;
    HString subs1, subs2;
    next[1] = 0;//这是肯定的,next[0]我们没有处理
    while (i < S.StrLength())//如果后缀i还没有到达末尾
    {
        S.SubString(subs1, i, 1);//这就是取S的第i个字符的意思
        S.SubString(subs2, j, 1);
        //因为j等于0,所以就不会返回什么东西了
        /* 下面的处理函数当进入if的时候也就是说前缀和后缀字符相等,通常情况下我们进行的操作就是 i++,j++,next[i]=j;意味着当我们在后缀i这个地方发生不匹配的时候,我们可以使用第j个元素进行匹配,但是呢我们没有考虑到的一个问题就是,如果当前的(增加后的)后缀i(潜在发生不匹配的元素)与要接替他的元素(增加后的J)是相等的该怎么办呢?如果相等,也就是说还是会不匹配,假设我们就真的写了 next[i]=j;因为第i个元素与要接替他的第j个元素是相等的,我们很快就会用第j个元素所对应的next元素来接替第j个元素,所以呢,我们提前考虑一步,如果当前的i与要接替他的元素j是相等的,那我们就跨过一步,直接让要接替j的元素来接替i,如果不想等,那么我们可以放心的接替也就是直接让 next[i]=j;

        */
//看看上面取到的字符相不相等,至于j==0这是实际真的会发生的,此时进入if的原因就不//是后面的相等了,而是0==j这一条件。

        if (0 == j || subs1.StrCompare(subs2) == 0)
        {
            ++i;
            ++j;
            S.SubString(subs1, i, 1);
            S.SubString(subs2, j, 1);

            if (subs1.StrCompare(subs2) != 0)
                next[i] = j;
            else next[i] = next[j];
        }
        else //发生不匹配,那我们就用j所对应的next元素来接替他接着进行比较
        {
            j = next[j];
        }

    }
}

我们按照上面给出的S再推一遍,首先i指向a,j==0指向第0个元素(S没有第0号元素),接下来就是next[1]==0;这是永远都成立的,也就是说要放弃与主串中的当前比较失败元素的继续比较转而与主串的下一个元素进行比较,然后我们看到了一个if条件问相不相等,怎么会相等?S都没有第0号元素我们怎么取它?所以subs2什么也没有取回来,好在if在j==0的时候也可以进入,我们将i和j都加加,i现在是2,j现在是1,再取出对应的第X个字符,两者不相等,我们应该next【i】=j;这也就是普通的KMP算法的做法,甚至都不比较相不相等,如果两者相等的话我们就执行next【i】=next【j】这里有点递归的意思,因为上面已经说明了为什么这样所以就不再解释了,这样next【2】=1了,然后再次看看能不能进入if,结果进不去了,这个时候就执行else的 j=next[j]了,j也就变成了next【1】也就是0而i却没有发生变化i还是2,j变成了0,在试试if果断进去了因为j==0,然后加加,i是3,j是1,取字符,结果不相等,next【i】=next【j】,next[3]==1,然后出去发现依然不相等,j=next[j],j变成了0,进入大if,i为4,j是1,二者相等,相等的话next【i】=next【j】也就是0,然后出去,在进入if,i是5,j是2,也相等next【i】=next【j】这样next【5】=1,又进入if,i为6,j是3,相等,next【6】=1,又进入,i为7,j是4,又相等,next【7】=0,再一次进入,i为8,j是5,不相等,next【8】=j也就是5,然后进不去了,j=next【j】也就是1,i为8,j是1,不相等,j变成了next[J]==0,成功进入,i为9,j是1,相等,next【9】=next【1】==0然后判断  i < S.StrLength() 此时是相等的,所以跳出while,next数组产生完毕。

然后下面再贴一下怎么使用next数组:

//S是模式串,T是主串,pos是说在T中第几个元素之后开始搜索,next就是我们要传入的next数组
//其中S非空,POS大于等于1,小于主串的长度
int Index_KMP(HString T, HString S, int pos, int next[])
{
    int i = pos,j = 1;//
    HString subs1, subs2;
    while (i <= T.StrLength() && j <= S.StrLength())//如果主串和子串都没有比较完毕,我们还需要继续比较。
    {
        T.SubString(subs1, i, 1);//取主串的第i个字符
        S.SubString(subs2, j, 1);//取子串的第j个字符
        if (j == 0 || subs1.StrCompare(subs2) == 0)
        {
            ++i; ++j;//相等的话就都加1,在比较下一对字符,j==0的进入条件是为了下面else产生的j=next[j]==0,这样i增加了j却没有增加

        }
        else
            j = next[j];//再从子串的第next[j]个元素开始比较,也就是当j是0的时候,我们放弃与主串的当前元素比较,转而与主串下一个元素进行比较,所以                        //上面有i++,j++,i加加意味着主串比较元素后移一位,而j++却变成了1,我们要用子串的第一个元素和主串元素比较,这不就是开始新一轮的比较么?
    }

    if (j > S.StrLength())
    {
        return i - S.StrLength();//就是说从主串的这个元素开始我们发现了匹配的子串
    }
    else
        return 0;
}

我们使用的时候,要制定从主串的第几个元素开始进行寻找,也就是设置的pos值,T和S分别是主串和子串,next是我们根据子串T已经产生好的next数组,实际上穿的是数组的名字,也就是地址。void get_next(HString S, int next[])是得到next数组的函数,我们传进去子串S以及我们自己动态分配好了的next数组,例如可以这样写 char*p=new char[S.StrLength()+1],我们传进去的实际上就是p,至于为什么p数组的长度比S的长度大1,上面已经解释过了就不再说了。

顺便说一句KMP算法的时间复杂度是:O(m+n)

时间: 2024-10-10 21:20:42

KMP算法及其改进的相关文章

模式串匹配、KMP算法及其改进(代码)

#include "string.h" #include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 100 /* 存

字符串模式匹配之KMP算法图解与 next 数组原理和实现方案

之前说到,朴素的匹配,每趟比较,都要回溯主串的指针,费事.则 KMP 就是对朴素匹配的一种改进.正好复习一下. KMP 算法其改进思想在于: 每当一趟匹配过程中出现字符比较不相等时,不需要回溯主串的 i指针,而是利用已经得到的“部分匹配”的结果将模式子串向右“滑动”尽可能远的一段距离后,继续进行比较.如果 ok,那么主串的指示指针不回溯!算法的时间复杂度只和子串有关!很好. KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的,很自然的,需要一个函数来存储匹

字符串的模式匹配——Brute-Force算法和KMP算法

子串的定位操作是要在主串S中找出一个与子串T相同的子串,通常把主串S称为目标,把子串T称为模式把从目标S中查找模式为T的子串的过程称为“模式匹配”. 1.Brute-Force算法的设计思想 Brute-Force是普通的模式匹配算法.将主串S的第1个字符和模式T的第1个字符比较,若相等,继续逐个比较后续字符:若不等,从主串的下一字符起,重新与模式的第一个字符比较,直到主串的一个连续子串字符序列与模式相等 ,返回值为S中与T匹配的子序列第一个字符的序号,即匹配成功:否则,匹配失败,返回值 0.

算法学习笔记 KMP算法之 next 数组详解

最近回顾了下字符串匹配 KMP 算法,相对于朴素匹配算法,KMP算法核心改进就在于:待匹配串指针 i 不发生回溯,模式串指针 j 跳转到 next[j],即变为了 j = next[j]. 由此时间复杂度由朴素匹配的 O(m*n) 降到了 O(m+n), 其中模式串长度 m, 待匹配文本串长 n. 其中,比较难理解的地方就是 next 数组的求法.next 数组的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀,也可看作有限状态自动机的状态,而且从自动机的角度反而更容易推导一些. "前

KMP算法详解

这几天学习kmp算法,解决字符串的匹配问题,开始的时候都是用到BF算法,(BF(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果.BF算法是一种蛮力算法.)虽然也能解决一些问题,但是这是常规思路,在内存大,数据量小,时间长的情况下,还能解决一些问题,但是如果遇到一些限制时间和内存的字符串问

串模式匹配之BF和KMP算法

本文简要谈一下串的模式匹配.主要阐述BF算法和KMP算法.力求讲的清楚又简洁. 一 BF算法 核心思想是:对于主串s和模式串t,长度令为len1,len2,   依次遍历主串s,即第一次从位置0开始len2个字符是否与t对应的字符相等,如果完全相等,匹配成功:否则,从下个位置1开始,再次比较从1开始len2个字符是否与t对应的字符相等.... BF算法思路清晰简单,但是每次匹配不成功时都要回溯. 下面直接贴代码: int BF_Match(char *s, char *t) { int i=0,

算法 - KMP算法

1 解决问题 从一个字符串中查找子串,如果存在返回字串在字符串中的位置. 示例: 字符串(T):"BBC ABCDAB ABCDABCDABDE" 子串( P):"ABCDABD" 通过算法查找字串P在字符串T中的位置为15(从0开始). 2 暴力算法 思路: 循环T,从T的每个字符开始子字串P匹配. 代码: int strstr(char iTarget[], int iTLen, char iPattern[], int iPLen) { for (int i

KMP算法简单回顾

前言 虽从事企业应用的设计与开发,闲暇之时,还是偶尔涉猎数学和算法的东西,本篇根据个人角度来写一点关于KMP串匹配的东西,一方面向伟人致敬,另一方面也是练练手,头脑风暴.我在自娱自乐,路过的朋友别太认真,嘿 背景 目标串: T(1…..n) 模式串: P(1…..m) 输出:搜索P在T中的位置 s,令 T(s…s+m-1) === P(1…m) 例如: a g c t a g c a g c t a g c t g中查找a g c t g 返回 12(从1计数) 资料 资料太多了,我在此不准备进

KMP算法解析(转自图灵社区)

KMP算法是一个很精妙的字符串算法,个人认为这个算法十分符合编程美学:十分简洁,而又极难理解.笔者算法学的很烂,所以接触到这个算法的时候也是一头雾水,去网上看各种帖子,发现写着各种KMP算法详解的转载帖子上面基本都会附上一句:“我也看的头晕”——这种诉苦声一片的错觉仿佛人生苦旅中找到知音,让我几乎放弃了这个算法的理解,准备把它直接记在脑海里了事. 但是后来在背了忘忘了背的反复过程中发现一个真理:任何对于算法的直接记忆都是徒劳无功的,基本上忘得比记的要快.后来看到刘未鹏先生的这篇文章:知其所以然(