在对字符串的操作中,我们经常要用到子串的查找功能,我们称子串为模式串,模式串在主串中的查找过程我们成为模式匹配,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. 蓝牙门禁Android客户端

    先来了解下Android传统蓝牙连接的大致简单的流程: 其中涉及到几个类依次来介绍,废话不多说,下面是从Android4.4开发指南蓝牙所用到的类的截图: 第一个类BluetoothAdapter: ...

  2. oracle中的net manager 无法配置

    今天遇到配置oracle net manager时无法配置,点击服务命名下面没有节点,绿色加号也没有反应 后在网上查到是因为tnsnames文件中的有空格存在导致 文件路径:oracle/produc ...

  3. CentOS下WDCP下的MYSQL开启远程连接

    1.首先要在防火墙开启3306端口访问 2.然后做如下操作 如何开启MySQL的远程帐号-1)首先以 root 帐户登陆 MySQL 在 Windows 主机中点击开始菜单,运行,输入"cm ...

  4. 从源码看集合ArrayList

    可能大家都知道,java中的ArrayList类,是一个泛型集合类,可以存储指定类型的数据集合,也知道可以使用get(index)方法通过索引来获取数据,或者使用for each 遍历输出集合中的内容 ...

  5. PrefixHeader.pch' file not found 以及 Xcode 中如何添加pch文件

    在开发的过程中,有时候接触到旧项目,会报: 'PrefixHeader.pch' file not found 的错误! 在Xcode6之前,新建一个工程的时候,系统会帮我们自动新建一个以工程名为名字 ...

  6. Dijkstra算法——单源最短路径问题

    学习一个点到其余各个顶点的最短路径--单源最短路径 Dijkstra算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶点到其余各顶点的最短路径算法,解决的是有向 ...

  7. 存储可靠性技术之 --RAID

    云计算项目交付时,不可避免的需要考虑存储磁盘采用何种RAID.例如:我们的项目工程师可能会建议大家连接克隆虚拟机系统盘组RAID 10,完整复制虚拟机数据盘 使用RAID5或者RAID6等,那么RAI ...

  8. Nginx rewrite(重读)

    Nginx Rewrite规则相关指令  Nginx Rewrite规则相关指令有if.rewrite.set.return.break等,其中rewrite是最关键的指令.一个简单的Nginx Re ...

  9. [LeetCode]House Robber II (二次dp)

    213. House Robber II     Total Accepted: 24216 Total Submissions: 80632 Difficulty: Medium Note: Thi ...

  10. 我的第一个webapiDemo

    最近在面试,总是被问到有没有接触过webapi 或者问webapi和MVC 的区别,今天看了看,原来如此,自己理解感觉webapi和webservice一样像是实现soa 的一种形式,也可以理解为服务 ...