关于KMP模式匹配的一些思考
算法简介
模式匹配
给定主串text和模式串pattern,在主串中查找,如果找到了模式串,返回模式串在主串中的起始位置,从1开始计数。
暴力求解求解模式匹配
算法的核心思想是:蛮力法。即使用两个指针i
和j
,其中i指针用来遍历text,j指针用来遍历pattern。当text[i]==text[j]的时候,继续比较;如果不相等,此时应当回退,i指针退到上次比较的位置,而j指针需要退至pattern起始位置,也就是0。从而展开新一轮比较。
使用C语言描述如下:
#include <string>
#include <cstdio>
int IndexViolent(string s, string p)
{
int i = 0, j = 0;
int count = 0;//记录比较的次数
while (i < s.length() && j < p.length())
{
count++;
if (s[i] == p[j])
{
i++;
j++;
}
else
{
// 注意细节
// j指针回到0重新进行匹配,i指针回到上次匹配位置
// 其中模式串[0,j-1]和主串[i-j,i-1]上面的字符是相匹配的
// 此时i指针应当回退到起始比较位置的后一个字符重新开始匹配
i = i - j+1;
j = 0;
}
}
printf("比较的次数为%d\n", count);
if (i < s.length())
return i - p.length()+1;
return 0;
}
kmp模式匹配推导
暴力求解可以解决问题,但是时间平均时间复杂度达到了O(m*n),其中m为模式串的长度,n为主串的长度。当主串是长文本时,算法运行时间比较慢。
回到查找,查找里面的核心操作为比较操作,因此为了降低时间复杂度,必须减少比较的次数。
那么如何减少比较的次数,既然暴力求解算法中每次失配的时候,模式串都是移动一位。那么能不能在失配的时候,模式串向后多移动几位?也就是说假设 text[i]!=pattern[j],下一次直接比较text[i]和pattern[x],其中x>=0
。这种情况下将减少比较次数,同时最重要的是i指针没有回退。现在当务之急就是找到x的值。
已知主串s和模式串p,假设:在s[i]和p[j]时发生失配,此时说明模式串p[0~j-1]和s[j-1,i-1]是相匹配的,接下来下一轮模式串的匹配位置记为next[j],其语义为当pattern[j]和主串不匹配的时候,下一轮模式串比较的起始位置为next[j],其中pattern[0,j-1]和text[i-j,i-1]相匹配,且next数组的长度和pattern数组长度相同。next[j]的语义看起来有一定的递归意味,因为当下一轮next[j]位置没有发生匹配时,此时模式串比较的起始位置应当为next[next[j]],依次类推,最差的情况应该是一直推到0,此时回到pattern起始位置比较。但是还有一种可能,那就是s[i]在和p[0]匹配时就失败,此时应当是s[i+1]和p[0]进行比较。
为了将这种特殊的情况包括在next数组的语义中,可以让next[0]=-1,而按照语义next[1]的值为0。
输入:主串s和模式串p
输出:匹配起始位置
int index(string s,string p){
int m=s.length();
int n=p.length();
int i=0;
int j=0;
while(j<n && i<m){
if(s[i]==p[j] || j==-1){
// 当前匹配继续向后进行
i++;
j++;
}else{
//不匹配的情况,下一轮模式串从next[j]开始比较
j=next[j];
}
// 还有一种情况,主串在模式串第一位比较时
}
if(j==m){
// 匹配成功
retuen i-j+1;
}else{
return -1;
}
}
接下来的问题便在于构建next数组,还是从next数组的语义出发
next[j]的值的含义:当pattern[j]和主串不匹配的时候,下一轮模式串比较的起始位置为next[j]
经过上述分析,知道next[0]的值为-1,next[1]的值为0
当j>1的时候,假设next[j]=x,x的最大值为x-1。则有p[0,x-1]和p[j-x,j-1],能不能求出next[j+1]的值?
next[j+1]最大值为x+1,此时p[0,x]和p[j-x,j]相匹配,结合上面的p[0,x-1]和p[j-x,j-1]相匹配,此时有p[x]=p[j],反过来也成立。
即:若p[x]=p[j],则有next[j+1]=next[j]+1
但是如果p[x]不等于p[j]呢?此时应当使用循环查看p[j]和p[next[x]]是不是相等,若相等,则next[j+1]=p[next[x]]+1。否则继续向后查看。一直查看到p[0],还不相等,此时说明next[j+1]的值应当为0
接下来使用代码进行描述
为此需要使用两个变量记录:使用变量i来遍历next数组,确定next[i]的值,【为了生成next数组,至少得遍历一遍数组】使用变量j记录next[i-1]的值。
void generateNext(string p){
next[0]=-1;
int i=0;
int j=-1;
while(i<p.length()){
if(j==-1 || p[i]==p[j]){
// j==-1处理p[i]和p[0]都不匹配得情况
i++;
j++;
next[i]=j;
// 上面三行代码实际上用一行代码更好理解
// next[++i]=++j;
}else{
j=next[j];
}
}
}
kmp模式匹配完整代码
#include <iostream>
#include <string>
#include <cstdio>
using namespace std;
const int MAXLENGTH = 100000;
int nextTable[MAXLENGTH];
/**
* @brief
*
* @param pattern
*/
void generateNext(string pattern)
{
nextTable[0] = -1;
int j = -1; // j 指针当模式失配的时候,此时应当重新进行匹配,如果使用next数组,重新匹配的位置 pattern[0],又回到起点,而使用 next 数组以后,位置变为 next[j],next[j]最大为j-1
int i=0;// i 指针用来遍历nextTable数组,是只增不减的
/*
a b a b d
-1 0
*/
while(i<pattern.length()){
// i的值至少始终比j的值大一
// next[j]的值最大为 j-1
// 这也是为什么i初始值为0而j的初始值为-1
if(j==-1 || pattern[i]==pattern[j]){
// 匹配
i++;
j++;
nextTable[i] = j;
}else{
// 失配的时候
// 此时应当找更短的后缀匹配
// j = nextTable[j];
// 代码优化,如果pattern[nextTable[j]]的位置和pattern[j]相等,此时也没有继续比较的必要
do{
j = nextTable[j];
} while (pattern[j] == pattern[nextTable[j]]);
// 循环结束,此时pattern[j] != pattern[nextTable[j]]
// 开启下一轮匹配
}
}
// print next array
for (int i = 0; i < pattern.length();i++){
printf("%d ", nextTable[i]);
}
printf("\n");
}
/**
* @brief kmp模式匹配
*
* @param text
* @param pattern
* @return int
*/
int kmp(string text, string pattern)
{
int n = text.length();
int m = pattern.length();
int i = 0, j = 0;
generateNext(pattern);
while (i < n && j < m)
{
// 匹配的情况,pattern[0]和主串不发生匹配
if (pattern[j] == text[i] || j == -1)
{
i++;
j++;
}
else
{
// 不匹配的情况
j = nextTable[j];
}
}
/*
aba
ba
*/
if(j==m){
return i - j + 1;
}else{
return -1;
}
}
测试代码
// main函数测试多组数据
/*
windows下的运行脚本
cd "d:\01.kaoyan\c_language_learning\" ;
if ($?) { g++ kmp2.cpp -o kmp2 } ;
if ($?) { .\kmp2 } ;
// 更改控制台编码格式为utf8编码
chcp 65001
*/
int main()
{
int caseNumber;
scanf("%d", &caseNumber);
while (caseNumber--)
{
string text, pattern;
cin >> text >> pattern;
printf("模式匹配的位置为%d\n", kmp(text, pattern));
}
return 0;
}
关于KMP模式匹配的一些思考的更多相关文章
- KMP模式匹配_2
http://blog.csdn.net/lin_bei/article/details/1252686 三. 怎么求串的模式值next[n] 定义: (1)next[0]= -1 意义:任何串的第一 ...
- YTU 2297: KMP模式匹配 三(串)
2297: KMP模式匹配 三(串) 时间限制: 1 Sec 内存限制: 128 MB 提交: 25 解决: 16 [提交][状态][讨论版] [Edit] [TestData] 题目描述 输入一 ...
- YTU 2296: KMP模式匹配 二(串)
2296: KMP模式匹配 二(串) 时间限制: 1 Sec 内存限制: 128 MB 提交: 29 解决: 17 题目描述 输入一个主串和一个子串,用KMP进行匹配,问进行几趟匹配才成功,若没成 ...
- YTU 2295: KMP模式匹配 一(串)
2295: KMP模式匹配 一(串) 时间限制: 1 Sec 内存限制: 128 MB 提交: 32 解决: 22 题目描述 求子串的next值,用next数组存放,全部输出 输入 输入一个字符串 ...
- KMP模式匹配 三(弦)
原文请訪问我的博客:xiaoshig.sinaapp.com KMP模式匹配 三(串) Time Limit:1000MS Memory Limit:131072KB 64bit IO ...
- KMP算法 KMP模式匹配 一(串)
A - KMP模式匹配 一(串) Crawling in process... Crawling failed Time Limit:1000MS Memory Limit:131072KB ...
- 2295: KMP模式匹配 一(串)
2295: KMP模式匹配 一(串) 时间限制: 1 Sec 内存限制: 128 MB提交: 210 解决: 97[提交][状态][讨论版][命题人:外部导入] 题目描述 求子串的next值,用n ...
- 字符串的朴素模式和KMP模式匹配
先复习一下字符串指针: #include <iostream> #include <string.h> using namespace std; int main() { ch ...
- KMP模式匹配
http://www.cnblogs.com/wangguchangqing/archive/2012/09/09/2677701.html nextal[j+1]=next[j]+1 KMP算法的实 ...
- KMP模式匹配练习题
使用KMP算法在文本串S中找模式串P是一种常见的方法.假设S=P={xyxyyxxyx},亦即将S对自己进行匹配,匹配过程中正确的next数组是____. 1.首先求最大相同前缀后缀长度 模式串的各个 ...
随机推荐
- zookeeper的Leader选举源码解析
作者:京东物流 梁吉超 zookeeper是一个分布式服务框架,主要解决分布式应用中常见的多种数据问题,例如集群管理,状态同步等.为解决这些问题zookeeper需要Leader选举进行保障数据的强一 ...
- 百度指数 Cipher-Text、百度翻译 Acs-Token 逆向分析
K 哥之前写过一篇关于百度翻译逆向的文章,也在 bilibili 上出过相应的视频,最近在 K 哥爬虫交流群中有群友提出,百度翻译新增了一个请求头参数 Acs-Token,如果不携带该参数,直接按照以 ...
- 【windows Server 2019系列】 构建IIS服务器
个人名片: 对人间的热爱与歌颂,可抵岁月冗长 Github:念舒_C.ying CSDN主页️:念舒_C.ying 个人博客 :念舒_C.ying Web服务器也称为WWW(World Wide W ...
- 使用svn.externals(外链)提升美术多个svn目录的svn up速度
svn up多个目录耗时大 svn上的美术资源项目,在打包机上对一个很久没有变化的目录进行svn up也是需要消耗不少时间的,特别打包时需要对多个目录进行svn up,比如空跑54个目录的svn up ...
- 微信小程序-页面跳转Tabbar
官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#tabBar 首先我们 ...
- SqlSugar入门
SqlSugar入门 创建对象 你可以使用 SqlSugarClient (new模式)或者 SqlSugarScope (单例) 对数据库进行增.删.查.改等功能 注意:除了名字和使用模式不同,功能 ...
- 14.6 Socket 应用结构体传输
当在套接字编程中传输结构体时,可以将结构体序列化为字符串(即把结构体的所有成员打包成一个字符串),然后将字符串通过套接字传输到对端,接收方可以将字符串解析为结构体,然后使用其中的成员数据.这种方法通常 ...
- 3.1 C/C++ 使用字符与指针
C/C++语言是一种通用的编程语言,具有高效.灵活和可移植等特点.C语言主要用于系统编程,如操作系统.编译器.数据库等:C语言是C语言的扩展,增加了面向对象编程的特性,适用于大型软件系统.图形用户界面 ...
- 驱动开发:DKOM 实现进程隐藏
DKOM 就是直接内核对象操作技术,我们所有的操作都会被系统记录在内存中,而驱动进程隐藏的做旧就是操作进程的EPROCESS结构与线程的ETHREAD结构.链表,要实现进程的隐藏我们只需要将某个进程中 ...
- 技嘉水雕II 360水冷散热器评测:稳压340W i9-14900K
一.前言:极简卡扣连锁风扇设计 再多风扇也只需2根线 如今这个年代,DIY主机几乎都会配大量的RGB风扇,然而"光污染"虽然带来了视觉感官享受,在理线方面却非常繁琐. 就拿360水 ...