题目:

7-1 串的模式匹配 (30 分)

给定一个主串S(长度<=10^6)和一个模式T(长度<=10^5),要求在主串S中找出与模式T相匹配的子串,返回相匹配的子串中的第一个字符在主串S中出现的位置。

输入格式:

输入有两行: 第一行是主串S; 第二行是模式T.

输出格式:

输出相匹配的子串中的第一个字符在主串S中出现的位置。若匹配失败,输出0.

输入样例:

在这里给出一组输入。例如:

aaaaaba
ba

输出样例:

在这里给出相应的输出。例如:

6

分析:

这里就是在主串里面找是否存在和模式串相等的子串啦,
如果存在就输出该子串在主串中第一个字符的位置,否则输出0。 主要有两种方法: 1.BF算法(在数据量大的时候可能会导致运行超时) 2.KMP算法 这里将采用KMP算法

代码:
#include<iostream>
#include<string.h>
using namespace std; /*在本题中有如例题般屈指可数的字符串,也有10^6如此庞大的数据,
为了避免不必要的空间浪费,笔者决定在输入字符串string s后
化为字符数组 char *s[s.length()] 为此,我还特地查找了string的最大容量:
堆开得足够大,数组的最大长度是可以不断增大的(推测最长的长度为 2^32,也就是4G。)
但编码时有需要注意的地方,采用明文的方式,如果超过65534个字节,可能报编译错误。
*/ char* trans(string str)
{
int size = str.length();
char *s;
s= new char[size];
strcpy(s, str.c_str());
return s;
} void calc_next(string pstring, int *next)
{
next[] = -;//-1表示模式串开头
int j = , k = -;
int p_len = pstring.length();//模式串长度
char *p = trans(pstring);//模式串字符数组 while(j<p_len){//当j尚未指向模式串尾端时
if(k == - || p[j] == p[k]) {
k++;//k指前缀开始下标
j++;//j指后缀开始下标
next[j] = k;//next[]存放已匹配子串中最长前后缀长度
//其中,next[j]表示 p[0]-p[j-1]子串中最长前后缀长度
}else{
k = next[k]; //k回溯到模式串开头
}
}
} int kmp(string sstring, string pstring)
{
int *next = new int[pstring.length()];
calc_next(pstring, next);//得到 next[]数组
char *s = trans(sstring), *p = trans(pstring); //转字符串为字符数组
int i=, j=;
int pos = ; while( i<=sstring.length() || j<=pstring.length()){
if( j == - || s[i] == p[j]){
i++;
j++;
}else{
j = next[j];
/*
①有最长前后缀时:
当主串和模式串在主串s[j]位置(即模式串最长后缀后一位)
不匹配时, s[j]将和 模式串最长前缀后一位比较
②冇最长前后缀时:
j = next[0] == -1;
整个模式串向后移一位
*/
} if(j == pstring.length()){//匹配成功
pos = i-j+;
break;
}
} return pos; } int main()
{
string sstring, pstring;//定义字符串
getline(cin, sstring);//输入字符串
getline(cin, pstring); cout<<kmp(sstring, pstring);
return ; }

2019.04.07  22:08更新

继续查阅资料的时候发现了一个可以优化原始KMP算法的判断条件:

以上图为例

我们的模式串 p[j] 是与主串中 s[i] 不匹配时才开始第一次移位,但是我们发现有一种情况:

模式串中的 p[k] == p[j] ,p[j] != s[i] ;由此我们可以知道 p[k] != s[i] ,那么这时我们需要进行第二次移位。

针对上述情况,不妨在第一次移位前增加一个判断条件,即当  p[j] == p[k] 时令 next[j] = next[k] ,如此便可一步到位,优化算法。

部分代码更改如下:

    while(j<p_len){//当j尚未指向模式串尾端时
if(k == - || p[j] == p[k]) {
k++;//k指前缀开始下标
j++;//j指后缀开始下标
if(p[j] == p[k]){
next[j] = next[k];
}else{
next[j] = k;//next[]存放已匹配子串中最长前后缀长度
//其中,next[j]表示 p[0]-p[j-1]子串中最长前后缀长度
} }else{
k = next[k]; //k回溯到模式串开头
}
}

编程中遇到的困难:

1.KMP算法相对于BF算法或者时其他算法来说更为抽象,没有办法很好地从利用该算法的目的、优势、所需操作方法等内容正向理解。
需要从KMP核心原理入手才能够感悟到数据结构之美。 2.值得注意的一点是(部分博客没有说清楚)我们所求的最长前后缀是在已经匹配的模式串子串中,而非整个模式串,更非主串。

总结:

  在实际的应用上,BF算法相对于KMP算法来说使用可能更为方便、简洁,实操性更强。但是KMP算法的意义在于,这是人类第一次发现
串的模式匹配问题能够以线性模式来简化。在KMP的学习上,与其说是学会了一种数据结构,不妨说是与自己来了一场头脑风暴。当然,
生命不息,进步不止,在查阅KMP资料的时候,后人对于一开始的版本也进行了更多的优化,不断完善。 时间复杂度 空间复杂度
BF算法 O(n*m) O(1)
KMP算法 O(n+m) O(m)
注:n为主串长度,m为模式串长度。KMP算法牺牲了一定的空间换取时间上的简化。
解析:KMP算法的时间复杂度由匹配的时间复杂度O(n)+求next数组的时间复杂度O(m)求得
空间复杂度为辅助数组next[m]求得

参考资料:

1.百度百科 https://baike.baidu.com/item/kmp%E7%AE%97%E6%B3%95/10951804?fr=aladdin

2.https://blog.csdn.net/x__1998/article/details/79951598

3.《数据结构(c语言版)》李云清等编著


以上仅为自己的一些拙见,如有不正确的地方欢迎指出。

利用KMP算法解决串的模式匹配问题(c++) -- 数据结构的更多相关文章

  1. (原创)数据结构之利用KMP算法解决串的模式匹配问题

      给定一个主串S(长度<=10^6)和一个模式T(长度<=10^5),要求在主串S中找出与模式T相匹配的子串,返回相匹配的子串中的第一个字符在主串S中出现的位置. 输入格式: 输入有两行 ...

  2. SA:利用SA算法解决TSP(数据是14个虚拟城市的横纵坐标)问题——Jason niu

    %SA:利用SA算法解决TSP(数据是14个虚拟城市的横纵坐标)问题——Jason niu X = [16.4700 96.1000 16.4700 94.4400 20.0900 92.5400 2 ...

  3. KMP 解决串的模式匹配问题

    初学KMP的时候,一直不得要领.后来学习AC自动机的时候,一下子明白了KMP实际上是AC自动机的特殊情况. 首先贴三段代码,一组是回溯法,暴力求解,另外两个是KMP串模式匹配 /* 回溯法字符串匹配算 ...

  4. KMP算法解决字符串匹配问题

    要解决的问题 假设字符串str长度为N,字符串match长度为M,M <= N, 想确定str中是否有某个子串是等于match的.返回和match匹配的字符串的首字母在str的位置,如果不匹配, ...

  5. 运用kmp算法解决的一些问题的简单题解

    学习kmp算法我最后是看的数据结构书上的一本教材学会的..我认为kmp相对于普通的BF算法就是避免了非常多不必要的匹配.而kmp算法的精髓自然就在于next数组的运用...而next数组简而言之就是存 ...

  6. 利用Tarjan算法解决(LCA)二叉搜索树的最近公共祖先问题——数据结构

    相关知识:(来自百度百科)  LCA(Least Common Ancestors) 即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先. 例如: 1和7的最近公共祖先为5: 1和5的 ...

  7. 全局匹配KMP算法

    KMP算法是通过分析模式字符串,预先计算每个位置发生不匹配的时候,所需GOTO的下一个比较位置,整理出来一个next数组,然后在上面的算法中使用. 本全局匹配KMP算法针对串的堆式存储数据结构 # d ...

  8. 串的两种模式匹配方式(BF/KMP算法)

    前言 串,又称作字符串,它是由0个或者多个字符所组成的有限序列,串同样可以采用顺序存储和链式存储两种方式进行存储,在主串中查找定位子串问题(模式匹配)是串中最重要的操作之一,而不同的算法实现有着不同的 ...

  9. 串和KMP算法

    一.串 串是由零个或多个字符串组成的有限序列 (一).串的定义 定长顺序存储 特点:每个串变量分配一个固定长度的存储区,即定长数组 定义: #define MAXLEN 255 typedef str ...

随机推荐

  1. MySQL基础之 如何删除主键

    我们在一个表中设置了主键之后,那么如何删除主键呢? 删除主键的语法是: ALTER TABLE TABLE_NAME DROP PRIMARY KEY; 在这里我们要考虑两种情况: 1.可以直接使用d ...

  2. FZU Monthly-201901 获奖名单

    FZU Monthly-201901 获奖名单 冠军: S031702338 郑学贵 一等奖: S031702524 罗继鸿 S031702647 黄海东 二等奖: S031702413 韩洪威 S0 ...

  3. C++课堂作业_02_PAT1025.反转链表

    The 1st classwork of the C++ program 题目:PAT.1025.反转链表 github链接:Click Here mdzz,做完题目的第一感受= = 这道题的题意就是 ...

  4. JAVA基础|从Class.forName初始化数据库到SPI破坏双亲委托机制

    代码托管在:https://github.com/fabe2ry/classloaderDemo 初始化数据库 如果你写过操作数据库的程序的话,可能会注意,有的代码会在程序的开头,有Class.for ...

  5. Lombok快速上手(安装、使用与注解参数)

    目录 Lombok插件安装与使用说明 常见参数 lombok的依赖于安装 依赖管理 IDEA插件的安装 @Data小例子 扩展@ToString 构造器注解扩展 @Log及其他日志注解 资料链接 Lo ...

  6. File API

    ES5 推出了一系列的 API: BLOB (二进制大对象) File (文件接口,基于 BLOB,但是增加了文件相关的方法,比如路径,大小) FileList (借助 <input type= ...

  7. ntp时间服务器--Linux配置

       时间服务器作用: 大数据产生与处理系统是各种计算设备集群的,计算设备将统一.同步的标准时间用于记录各种事件发生时序, 如E-MAIL信息.文件创建和访问时间.数据库处理时间等. 大数据系统内不同 ...

  8. Netty入门(八)构建Netty HTTP/HTTPS应用

    HTTP/HTTPS 是最常见的一种协议,这节主要是看一下 Netty 提供的 ChannelHaandler. 一.HTTP Decoder,Encoder 和 Codec HTTP 是请求-响应模 ...

  9. PAT B1035 插入与归并 (25 分)

    根据维基百科的定义: 插入排序是迭代算法,逐一获得输入数据,逐步产生有序的输出序列.每步迭代中,算法从输入序列中取出一元素,将之插入有序序列中正确的位置.如此迭代直到全部元素有序. 归并排序进行如下迭 ...

  10. Leetcode——64. 最小路径和

    题目描述:题目链接 同样对于这个问题,我们可以考虑用动态规划来解决. 解决动态规划常见的三个步骤: 1:问题的归纳.对于 i,j 位置上的最短路径可以用d[ i ][ j ]表示. 2:归纳递推式:d ...