在对字符串的操作中,我们经常要用到子串的查找功能,我们称子串为模式串,模式串在主串中的查找过程我们成为模式匹配,KMP算法就是一个高效的模式匹配算法。KMP算法是蛮力算法的一种改进,下面我们先来介绍蛮力算法。

  蛮力算法使用两个int型变量当做当前匹配位置的指针,我们假设主串的位置指针为i,模式串的位置指针为j。蛮力算法的策略便是在i和j所指的位置的字符相等时,继续向后匹配,当发生失配时,便将i回溯到本次匹配前位置的后一个位置,而将j设置为0,从而对所有位置完成逐一比对,通过观察i和j是否越界判断整体匹配是否成功,若模式串位置指针j越界,显然此前所有位置都已完全匹配,那么也就可以返回i-j也即完全匹配时主串中匹配子串的下标位置。

int brute(char * mString ,char * subString) {
int i = ,j = ;
int m = strlen(mString),
n = strlen(subString);
while ( i < m &&j < n) {
if (mString[i] == subString[j]) {
i++;
j++;
}
else {
i -= j - ;
j = ;
}
}if (j >= n)
return i - j;
     if(i>=m)
return -1;
}

  通过观察,我们不难发现,如果主串中有大量与模式串相似的字符,从而每次比对都要比较到模式串的最后一个字符才发生失配,从而在每个比较位置都与模式串进行模式串长度n次的比较,而一共有主串长度m次的比较位置。从而时间复杂度达到了O(m*n)。这种情况在串中字符的种类较少时尤其容易发生,如主串为“00000000000001”,模式串为“00001”。

  显然,在每次发生失配时,i指针都要回溯到原来位置的下一个位置,而j指针则是复位至0,一切又从下一个位置从新开始。然而这种做法其实浪费了大量之前比较时所获得的有用信息。在发生失配时,可以分为两种情况:主串失配字符位置i之前的若干字符如果和模式串中的某个真前缀相同(为了避免丢失信息,必须选择最长的一种情况),那么显然只需将模式串与主串中对应字符对齐,而在当前位置与模式串的这个真前缀后的那个字符进行比较即可;而如果不能找到这样的情况,显然主串失配位置前的字符都无法派上用场,从而直接将模式串的开头与当前位置对齐并进行比较即可(注意,如果这种情况中开头位置依然失配,只需让i自增,从下一个位置开始和整个模式串的匹配即可)。这两种情况中的i指针始终没有回溯。因此在最坏情况下的时间复杂度也不会超过O(m)。

int KMP(char *mString ,char* subString) {
int i = ,j = ;
int mlen = strlen(mString);
int slen = strlen(subString);
int* next = (int*)malloc(sizeof(int)*strlen(subString));
getNext(subString, next);
while (i < mlen && j < slen) {
if (mString[i] == subString[j]) i++, j++;
else {
if (j == )i++;
j = next[j];//子串指针前移至最长公共前后缀的下标处
}
}
if(j>=slen)
return i - j;
if (i >= mlen)
return -;
}

  细心的读者可能发现,这里与蛮力算法不同的是多了一次主串适配位置之前字符与模式串前缀字符的比较操作,这样看来似乎复杂度没有改善,其实不然,由于发生失配时主串当前位置i和模式串当前位置j之前的所有字符必然相等,所以这种比较操作只取决于模式串,也就是说我们只需找出模式串每个位置的最长公共前后缀即可。我们只需在比较之前对模式串进行分析处理,将对应信息存入next[]数组来制表以供查询即可将这种操作简化为O(1)的复杂度。而这种预处理操作实际上只需O(n)的复杂度。

  next[]数组的获取我们可以使用递推的策略完成,由于next[]数组中存放的数值为当前位置最长公共前后缀的长度,也即最长公共前缀之后一个字符的下标位置,显然如果之前位置字符与之前位置的最长公共前缀的下一个字符相同,那么当前位置的最长公共前后缀的长度只需增加一即可(特别的,如果当前位置和当前位置最长公共前后缀的后一个字符相等,说明这次比较必然失败,于是应该取其此处不相等的最长公共前后缀);而如果这两个字符不同,我们希望找到之前字符稍短的一个公共前后缀,也即在之前字符的next[]值上再取一次next[]的值,再比较这个值处的字符和之前位置处字符,如果相等取此值加一即可,如果不相等,则继续循环,由于next[]数组中的值必然比数组内值小,所以循环一直继续下去必然收敛于0。当值为0时。取当前值为0即可。

void getNext(char * string, int * next) {
int len = strlen(string);
int i = ,j = ;
next[] = ;
while (j < len) {
if (i == )
next[++j] = ;
if (string[j] == string[i]) {
i++; j++;
         next[j] = string[j]!=string[i]?i:next[i];//避免出现重复比较
}
else
i = next[i]; }
}

纯属个人理解,如有错误,欢迎指出

串的模式匹配和KMP算法的更多相关文章

  1. 串的模式匹配,KMP算法

    串的模式匹配 现考虑一个常用操作,在字符串s(我们称为主串)中的第pos开始处往后查找,看在主串s中有没有和子串p相匹配的的,如果有,则返回字串p第一次出现的位置. 暴力求解 int Index(ch ...

  2. 《数据结构》之串的模式匹配算法——KMP算法

    //串的模式匹配算法 //KMP算法,时间复杂度为O(n+m) #include <iostream> #include <string> #include <cstri ...

  3. 串的应用与kmp算法讲解--学习笔记

    串的应用与kmp算法讲解 1. 写作目的 平时学习总结的学习笔记,方便自己理解加深印象.同时希望可以帮到正在学习这方面知识的同学,可以相互学习.新手上路请多关照,如果问题还请不吝赐教. 2. 串的逻辑 ...

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

    之前说到,朴素的匹配,每趟比较,都要回溯主串的指针,费事.则 KMP 就是对朴素匹配的一种改进.正好复习一下. KMP 算法其改进思想在于: 每当一趟匹配过程中出现字符比较不相等时,不需要回溯主串的 ...

  5. 【模式匹配】KMP算法的来龙去脉

    1. 引言 字符串匹配是极为常见的一种模式匹配.简单地说,就是判断主串\(T\)中是否出现该模式串\(P\),即\(P\)为\(T\)的子串.特别地,定义主串为\(T[0 \dots n-1]\),模 ...

  6. 模式匹配的KMP算法详解

    这种由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现的改进的模式匹配算法简称为KMP算法.大概学过信息学的都知道,是个比较难理解的算法,今天特把它搞个彻彻底底明明白白. 注意到这 ...

  7. 字符串模式匹配之KMP算法的next数组详解与C++实现

    相信来看next数组如何求解的童鞋已经对KMP算法是怎么回事有了一定的了解,这里就不再赘述,附上一个链接吧:https://www.cnblogs.com/c-cloud/p/3224788.html ...

  8. 串的模式之kmp算法实践题

    给定两个由英文字母组成的字符串 String 和 Pattern,要求找到 Pattern 在 String 中第一次出现的位置,并将此位置后的 String 的子串输出.如果找不到,则输出“Not ...

  9. 模式匹配之Kmp算法

    Kmp: 算法定义借鉴wikipedia: http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm#KMP_ ...

随机推荐

  1. C#中的foreach语句与枚举器接口(IEnumerator)及其泛型 相关问题

    这个问题从<C#高级编程>数组一节中的foreach语句(6.7.2)发现的. 因为示例代码与之前的章节连贯,所以我修改了一下,把自定义类型改为了int int[] bs = { 2, 3 ...

  2. git文件夹下项目更改ip地址小结

    在我们开发的过程中,经常切换项目IP地址是很正常的,之前弄过一次,没有记住,现在简单的总结下: 找到要切换IP地址的项目,点击鼠标右键,弹出下图: 打开该项目的路径后,双击打开该项目,具体参考自己项目 ...

  3. CentOS7.0安装Nginx

    安装Nginx yum install nginx 正常情况下必定是: 已加载插件:fastestmirror, langpacks base | :: docker-main | :: extras ...

  4. Ceph部署(二)RGW搭建

    背景 Ceph RGW简介 Ceph RGW基于librados,是为应用提供RESTful类型的对象存储接口.RGW提供两种类型的接口: 1) S3:兼容Amazon S3RESTful API: ...

  5. Android系统--Binder系统具体框架分析(二)Binder驱动情景分析

    Android系统--Binder系统具体框架分析(二)Binder驱动情景分析 1. Binder驱动情景分析 1.1 进程间通信三要素 源 目的:handle表示"服务",即向 ...

  6. Javascript中关于作用域和闭包和域解释的面试题

    <script type="text/javascript"> function fn() { var i = 10; return function (n) { co ...

  7. mfc---添加背景图

    添加背景图: CDC m_dcMem CBitmap m_bmpMem CDC * pDC = GetDC(); m_dc.CreateComparableDC(pDC); m_bmpMem.Load ...

  8. Vue学习之路---No.1(分享心得,欢迎批评指正)

    首先为了打消大家对Vue.js存在的顾虑,先通过大家所熟知的JQ作为对比. 都知道JQ的语法相对简单.清楚.使用方便.功能齐全: 那么Vue.js呢,同样的,Vue.js与JQ在很多地方都是相同之处, ...

  9. Vue.js组件之同级之间的通信

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. 3408: [Usaco2009 Oct]Heat Wave 热浪

    3408: [Usaco2009 Oct]Heat Wave 热浪 Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 67  Solved: 55[Subm ...