当前位置: 首页 > news >正文

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

 

 

题目:

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[0] = -1;//-1表示模式串开头 
    int j = 0, k = -1;
    int p_len = pstring.length();//模式串长度 
    char *p = trans(pstring);//模式串字符数组
    
    while(j<p_len){//当j尚未指向模式串尾端时 
        if(k == -1 || 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=0, j=0;
    int pos = 0;
    
    while( i<=sstring.length() || j<=pstring.length()){
        if( j == -1 || 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+1;
            break; 
        }
    } 
    
    return pos;
    
} 

int main()
{
    string sstring, pstring;//定义字符串 
    getline(cin, sstring);//输入字符串 
    getline(cin, pstring);
    
    cout<<kmp(sstring, pstring);
    return 0;
    
}

 

 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 == -1 || 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语言版)》李云清等编著




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



转载于:https://www.cnblogs.com/yi2105/p/10665769.html

相关文章:

  • 高级排序算法之归并排序
  • C#winform程序安装时自动卸载新版本覆盖旧版本
  • 第二次实验报告
  • 数据库原理 知识点总结
  • Express初识
  • MyBatis Dynamic SQL 1.1.1 发布,生成动态 SQL 的框架
  • 21个值得收藏的Javascript技巧
  • 43. Multiply Strings字符串相乘
  • displayport-2
  • 他山之石——运维平台哪家强?
  • 曾刷新两项世界纪录,腾讯优图人脸检测算法 DSFD 正式开源 ...
  • String Boot中@Controller和@RestController的区别?
  • 加入lib
  • form 表单中input 使用disable属性
  • Android 设置按钮为透明
  • .pyc 想到的一些问题
  • 【347天】每日项目总结系列085(2018.01.18)
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • 【个人向】《HTTP图解》阅后小结
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • exif信息对照
  • MySQL主从复制读写分离及奇怪的问题
  • npx命令介绍
  • rc-form之最单纯情况
  • Shell编程
  • underscore源码剖析之整体架构
  • 缓存与缓冲
  • 技术胖1-4季视频复习— (看视频笔记)
  • 深度学习入门:10门免费线上课程推荐
  • 什么软件可以提取视频中的音频制作成手机铃声
  • 学习笔记DL002:AI、机器学习、表示学习、深度学习,第一次大衰退
  • ​Java并发新构件之Exchanger
  • $.proxy和$.extend
  • (3)llvm ir转换过程
  • (PWM呼吸灯)合泰开发板HT66F2390-----点灯大师
  • (多级缓存)缓存同步
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (南京观海微电子)——COF介绍
  • (三分钟)速览传统边缘检测算子
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • (转)c++ std::pair 与 std::make
  • ****Linux下Mysql的安装和配置
  • .net oracle 连接超时_Mysql连接数据库异常汇总【必收藏】
  • .NET 使用 XPath 来读写 XML 文件
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .net6 webapi log4net完整配置使用流程
  • /etc/X11/xorg.conf 文件被误改后进不了图形化界面
  • ?php echo ?,?php echo Hello world!;?
  • @31省区市高考时间表来了,祝考试成功
  • @RequestBody详解:用于获取请求体中的Json格式参数
  • [145] 二叉树的后序遍历 js
  • [2008][note]腔内级联拉曼发射的,二极管泵浦多频调Q laser——
  • [20140403]查询是否产生日志
  • [2016.7 day.5] T2