主串 s:A B D A B C A B C

子串 t:  A B C A B

问题:在主串 s 中是否存在一段 t 的子串呢?

形如上述问题,就是串匹配类问题。【串匹配——百度百科】

串匹配问题是一项有着非常多应用的重要技术,KMP匹配算法就是其中一种高效的字符串匹配算法。

在KMP算法之前先介绍一下BF算法,BF算法又名暴力匹配算法,该算法在匹配的时候把子串依次从主串的起始位置开始匹配,若匹配失败再从主串的下一个位置开始,子串重新从头开始匹配……

BF算法

int BF(char *str, char *sub)
{//暴力匹配算法
int i = 0; //遍历主串
int j = 0; //遍历子串
int k = i; //记录每次从主串匹配的起始位置
while (i < strlen(str) && j < strlen(sub))
{
if (str[i] == sub[j]) //当前下标位置匹配
{
++i;
++j;
}
else //当前下标位置不匹配
{
if(strlen(str) - i <= strlen(sub) ) return -1;//优化,如果主串剩余的长度没有子串长,则肯定不匹配
j = 0; //子串从头开始匹配
i = ++k; //匹配失败,i从主串的下一位置开始
}
}
if (j >= strlen(sub)) //子串遍历完,说明找到了对应的位置
{
return k;
}
else //子串没有遍历完,说明无该子串
return -1;
}

可以看到,BF算法是一种非常笨的算法,执行效率不高,那么有没有更优化的算法呢?

当然有啦,就是本文所讲的 KMP 算法。

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。

KMP算法的核心在于求Next子串,Next子串也叫模式串的前缀表,也就是模式串的最长公共前后缀



GetNext

next 前缀表中存放着当前字符之前的串最大公共前后缀。这样做的好处就在于,当模式串与主串进行匹配时,如果出现失配情况可以根据保存在 next 中的信息迅速定位,无需从头开始匹配。

比如:下列匹配情况

X X X X X A B A X X X

     A B A B C

A 与 C 不匹配,如果按照BF算法就要重头开始匹配了。但是我们发现,在模式串 A B A B C 中,有 A B 和 A B 相同的部分,那么是不是可以这样呢?

X X X X X A B A X X X

     A B A B C

         A B A B C

我们发现此时主串中的 A 与 模式串中的 A 匹配上了,先不管后续匹配情况如何,单就这个过程我们已经可以看出 KMP 算法的一些细节了。那么怎么让计算机实现我们上述的算法呢,这就不得不利用我们的next子串了。在本例中使用的以 -1,0 开头的next串格式,此格式便于在失配时(求next时失配或串匹配时失配) 快速回退。即 i = next(i) 这种形式。如下为求 next 的源码:

void GetNext(const char *sub, int *next)
{
int len = strlen(sub);
next[0] = -1; //此前缀表从-1开始,一些书籍上是从0开始
next[1] = 0;
int i = 0; //前缀
int j = 1; //后缀
while(j < len-1) //len-1 模式串中最后一个字符无需求前缀表
{
if (i == -1 || sub[i] == sub[j]) //匹配结束 || 匹配成功 ,写入next
{
next[++j] = ++i;
//++i;
//++j;
}
else //匹配失败,i回退
{
i = next[i];
}
}
}

KMP匹配过程演示

//从主串pos位置开始查找,默认从头开始
int KMP(const char* str, const char* sub, int pos = 0 )
{
int len_str = strlen(str);
int len_sub = strlen(sub); if (pos < 0 || pos >= lenstr) return -1;//位置非法,查找失败
char* next = (char*)malloc(sizeof(char)*len_sub); //构建前缀表
GetNext(sub, next); int i = pos; //主串 //从pos位置开始查找
int k = 0; //模式串/next
while(i < len_str && k < len_sub)
{
if(k == -1 || str[i] == sub[k]) //k==-1匹配结束(失败)|| 匹配成功,向后移动
{
++k;++i;
}
else //匹配失败,查询next表,移动模式串重新比较
{
k = next[k];
} } free(next); if(k == len_sub) //模式串遍历完,找到位置
{
return i-k;
}
else //主串已结束,未找到子串
{
return -1;
} }

KMP算法的确是非常的智能,但是还有一种情况我们没有考虑到,比如一个 aaaaaaab 的模式串,在该模式串与主串匹配的时候,KMP算法也不是那么好用了。真是聪明难得糊涂一回,不过我们也有改进的方法,只需要去除next子串中多余重复的前缀并对其进行相应的优化即可,如下图所示:

优化:GetNextVal

void GetNextVal(const char* str, int* next)
{
int len = strlen(str);
next[0] = -1;
next[1] = 0;
int j = 1;
int k = 0; while (j + 1 < len)
{
if (k == -1 || str[j] == str[k])
{
next[++j] = ++k;
}
else
{
k = next[k];
}
} int* nextval = (int*)malloc(len * sizeof(int));
nextval[0] = -1;
for (int i = 1; i < len; i++)
{
if (str[i] == str[next[i]])
{
nextval[i] = nextval[next[i]];
}
else
{
nextval[i] = next[i];
}
} for (int i = 0; i < len; i++)
{
next[i] = nextval[i];
}
free(nextval);
}

算法 | 串匹配算法之KMP算法及其优化的更多相关文章

  1. 模式字符串匹配问题(KMP算法)

    这两天又看了一遍<算法导论>上面的字符串匹配那一节,下面是实现的几个程序,可能有错误,仅供参考和交流. 关于详细的讲解,网上有很多,大多数算法及数据结构书中都应该有涉及,由于时间限制,在这 ...

  2. 单模式串匹配----浅谈kmp算法

    模式串匹配,顾名思义,就是看一个串是否在另一个串中出现,出现了几次,在哪个位置出现: p.s.  模式串是前者,并且,我们称后一个 (也就是被匹配的串)为文本串: 在这篇博客的代码里,s1均为文本串, ...

  3. 串匹配算法讲解 -----BF、KMP算法

      参考文章: http://www.matrix67.com/blog/archives/115     KMP算法详解 http://blog.csdn.net/yaochunnian/artic ...

  4. 神奇的字符串匹配:扩展KMP算法

    引言 一个算是冷门的算法(在竞赛上),不过其算法思想值得深究. 前置知识 kmp的算法思想,具体可以参考 → Click here trie树(字典树). 正文 问题定义:给定两个字符串 S 和 T( ...

  5. 【数据结构&算法】10-串基础&KMP算法源码

    目录 前言 串的定义 串的比较 串的抽象类型数据 串与线性表的比较 串的数据 串的存储结构 串的顺序存储结构 串的链式存储结构 朴素的模式匹配算法 模式匹配的定义 朴素的匹配方法(BRUTE FORC ...

  6. 算法-最通俗易懂的KMP算法详解

    有些算法,适合从它产生的动机,如何设计与解决问题这样正向地去介绍.但KMP算法真的不适合这样去学.最好的办法是先搞清楚它所用的数据结构是什么,再搞清楚怎么用,最后为什么的问题就会有恍然大悟的感觉.我试 ...

  7. c算法:字符串查找-KMP算法

    /* *用KMP算法实现字符串匹配搜索方法 *该程序实现的功能是搜索本目录下的所有文件的内容是否与给定的 *字符串匹配,如果匹配,则输出文件名:包含该字符串的行 *待搜索的目标串搜索指针移动位数 = ...

  8. 大话数据结构(十二)java程序——KMP算法及改进的KMP算法实现

    1.朴素的模式匹配算法 朴素的模式匹配算法:就是对主串的每个字符作为子串开头,与要连接的字符串进行匹配.对主串做大循环,每个字符开头做T的长度的小循环,直到成功匹配或全部遍历完成为止. 又称BF算法 ...

  9. KMP算法的优化与详解

    文章开头,我首先抄录一些阮一峰先生关于KMP算法的一些讲解. 下面,我用自己的语言,试图写一篇比较好懂的 KMP 算法解释. 1. 首先,字符串"BBC ABCDAB ABCDABCDABD ...

  10. 串匹配模式中的BF算法和KMP算法

    考研的专业课以及找工作的笔试题,对于串匹配模式都会有一定的考察,写这篇博客的目的在于进行知识的回顾与复习,方便遇见类似的题目不会纠结太多. 传统的BF算法 传统算法讲的是串与串依次一对一的比较,举例设 ...

随机推荐

  1. POJ3368题解

    题目大意:一个非降序序列,有若干查询,每次查询一个区间中重复次数最多的数字的个数. 思路:因为是非降序的,所以可以从头遍历把每个相同的数字划为一个块,用p[i]表示ai划分到了哪个块里面,同时还可以记 ...

  2. Python:List

    1.List相关的操作符 操作符 说明 例子 * 重复,将List重复若干遍放到同一个List中 ['hi'] * 3 ['hi' , 'hi' , 'hi'] + 合并两个List(作用和appen ...

  3. C语言刷数组题记录

    讲解:https://mp.weixin.qq.com/s/weyitJcVHBgFtSc19cbPdw 二分法: 704. 二分查找 int search(int* nums, int numsSi ...

  4. JAVA——转义字符

    目录 1.Java转义字符 2.Java中的注释 2.1Java 中的注释类型 2.2文档注释 3.Java代码规范 4.Java开发注意事项和细节说明 1.Java转义字符 在控制台,输入 tab ...

  5. 微信小程序如何测试?

    不需要安装,只要在微信里找到这个小程序打开即可使用,由于小程序的便捷,如今越来越多的平台开发方都纷纷推出自身的小程序应用. 那我们该如何进行微信小程序测试呢? 1.功能测试 功能测试以需求文档和交互视 ...

  6. java基础复习记录

    java基础复习记录(数组.对象.异常) 数组 数组的定义 数组是相同类型数据的有序集合.如:篮球队就是一个数组,队员球服上有号码,相当于索引.通过某一个的某一个号码来确认是某一个队员.数组中索引从0 ...

  7. Linux内存泄漏

    0 什么是内存泄漏? 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果. 1 常见的造成内 ...

  8. pandas连接数据库

    项目中使用pandas方法读取数据库数据可能用到的方法 使用pandas连接数据库 例如 mysql_conn = pymysql.connect(host='172.28.*.***', port= ...

  9. 机器学习实战 | SKLearn最全应用指南

    作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/41 本文地址:http://www.showmeai.tech/article-det ...

  10. CVE-2012-1823(PHP-CGI远程代码执行)

    基于vulhub漏洞环境: 安装vulhub漏洞环境 https://blog.csdn.net/qq_36374896/article/details/84102101 CGI模式下的参数: -c ...