4种字符串匹配算法:KMP(下)
回顾:4种字符串匹配算法:BS朴素 Rabin-karp(上)
1、图解
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。我不喜欢叫他“看毛片”算法。但我不得不说,能联想到这个的人,确实很有才。
原理如果文字理解起来非常复杂,而且有点难懂。因此,画图来讲解是最好的方式啦,下图非常容易理解算法的执行原理。
我之前也是看了这幅图理解的。所以我觉得把这个图用来讲解最好不过(抱歉,我搬了这图,但是这个图是我至今觉得讲的最好的图,不得不搬),当然我已经全部重新画过。网络上KMP的讲解,讲的好的寥寥无几,发现一些博客,都是转载,或者讲的不够清楚,很难理解。我觉得有必要重新整理整理,自己来梳理一下知识点,为了让自己更理解深刻一些。
首先模式串逐一对比文本串,如上图,直到遇到相同的元素,如下图:
模式串,逐一对比,直到发现蓝色框框内的字符不相同,下图。这时候怎么办?
BS算法,就是把模式串向前移动一位,从头继续比较,所以他的时间复杂度最差才是o(m*n)。而KMP呢,不再从头比较啦,这样大大减少了时间复杂度。我们即将引出next数组概念。
既然,不保存,那他是怎么跳的呢?
我们发现,ABCDAB,AB**AB, 这个字符串首尾相同,因此直接跳4格,如下图。
也就是说,next数组保存的数和跳几格是有关系的呗。那我们怎么来看呢?这个字符串的匹配值有关。我们只要数,字符串首尾有几个是匹配的即可,通过这样来初始化。我们来看一下这个表格。
A = 0 AB = 0 ABC = 0 ABCD = 0 ABCDA = 1 ABCDAB = 2 ABCDABD = 0
公式:
移动位数 = 已匹配的字符数 - 表格内的匹配值
我们继续看,即使跳转了4格,还是有蓝色的部分不匹配,又因为AB = 0 所以移动位数 = 已匹配的字符数(2) - 表格内的匹配值(0) = 2,依次类推,直到匹配到下图,则成功。
该算法,最重要的是next数组上。理解这个,我们觉得其他就迎刃而解了。
2、代码实现
主要代码(c++版):
std::map<int,int> compute_prefix(const std::string &pattern)
{
int i = ;
int p = ;
std::map<int, int> pi;
int length = pattern.length();
pi.insert(std::make_pair(, ));
while (i < length)
{
if (p > && pattern[i] != pattern[p])
{
p = 0;
}
if (pattern[i] == pattern[p])
{
++p;
}
pi.insert(std::make_pair(i + , p));
i++;
}
return pi;
} bool kmp_match(const std::string &text,const std::string &pattern)
{
std::map<int, int> pos;
pos = compute_prefix(pattern);
int q = ;
for (int i = ; i < text.length(); i++)
{
if (q > && text[i] != pattern[q])
{
q = pos.at(q);
}
if (text[i] == pattern[q])
{
q++;
}
if (q == pattern.length())
{
return true;
}
}
}
测试代码:
int main()
{
char a[] = "bbc abcdab abcdabcdabde";
char b[] = "abcdabd"; bool iftrue = kmp_match(a, b);;
if (iftrue == true)
{
std::cout << "找到了" << std::endl;
}
else
{
std::cout << "没有" << std::endl;
}
}
注:你也可以返回文本串的地址下标,稍加改动即可。
int kmp_match(const std::string &text,const std::string &pattern)
{
std::map<int, int> pos;
pos = compute_prefix(pattern);
int q = ;
for (int i = ; i < text.length(); i++)
{
if (q > && text[i] != pattern[q])
{
q = pos.at(q);
}
if (text[i] == pattern[q])
{
q++;
}
if (q == pattern.length())
{
return (i+)-q+;
}
}
return -;
}
返回数据下标 稍加改动后的代码 点击打开
另外,从代码中可以看出,他的时间复杂度为o(n),预处理时间o(m)
资料:
特别感谢:阮一峰的网络日志
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <stdlib.h> std::map<int,int> compute_prefix(const std::string &pattern)
{
int i = ;
int p = ;
std::map<int, int> pi;
int length = pattern.length();
pi.insert(std::make_pair(, ));
while (i < length)
{
if (p > && pattern[i] != pattern[p])
{
p = ;
}
if (pattern[i] == pattern[p])
{
++p;
}
pi.insert(std::make_pair(i + , p));
i++;
}
return pi;
} int kmp_match(const std::string &text,const std::string &pattern)
{
std::map<int, int> pos;
pos = compute_prefix(pattern);
int q = ;
for (int i = ; i < text.length(); i++)
{
if (q > && text[i] != pattern[q])
{
q = pos.at(q);
}
if (text[i] == pattern[q])
{
q++;
}
if (q == pattern.length())
{
return (i+)-q+;
}
}
return -;
} int main()
{
char a[] = "bbc abcdab abcdabcdabde";
char b[] = "abcdabd"; int iftrue = kmp_match(a, b);;
if (iftrue >=)
{
std::cout << "找到了" << " "<< iftrue <<std::endl;
}
else
{
std::cout << "没有" << std::endl;
}
system("pause");
}
完整代码
4种字符串匹配算法:KMP(下)的更多相关文章
- 4种字符串匹配算法:BS朴素 Rabin-karp(上)
字符串的匹配的算法一直都是比较基础的算法,我们本科数据结构就学过了严蔚敏的KMP算法.KMP算法应该是最高效的一种算法,但是确实稍微有点难理解.所以打算,开这个博客,一步步的介绍4种匹配的算法.也是& ...
- 字符串匹配算法--KMP字符串搜索(Knuth–Morris–Pratt string-searching)C语言实现与讲解
一.前言 在计算机科学中,Knuth-Morris-Pratt字符串查找算法(简称为KMP算法)可在一个主文本字符串S内查找一个词W的出现位置.此算法通过运用对这个词在不匹配时本身就包含足够的信息 ...
- 字符串匹配算法 - KMP
前几日在微博上看到一则微博是说面试的时候让面试者写一个很简单的字符串匹配都写不出来,于是我就自己去试了一把.结果写出来的是一个最简单粗暴的算法.这里重新学习了一下几个经典的字符串匹配算法,写篇文章以巩 ...
- 字符串匹配算法——KMP算法学习
KMP算法是用来解决字符串的匹配问题的,即在字符串S中寻找字符串P.形式定义:假设存在长度为n的字符数组S[0...n-1],长度为m的字符数组P[0...m-1],是否存在i,使得SiSi+1... ...
- 字符串匹配算法KMP算法
数据结构中讲到关于字符串匹配算法时,提到朴素匹配算法,和KMP匹配算法. 朴素匹配算法就是简单的一个一个匹配字符,如果遇到不匹配字符那么就在源字符串中迭代下一个位置一个一个的匹配,这样计算起来会有很多 ...
- 字符串匹配算法——KMP、BM、Sunday
KMP算法 KMP算法主要包括两个过程,一个是针对子串生成相应的“索引表”,用来保存部分匹配值,第二个步骤是子串匹配. 部分匹配值是指字符串的“前缀”和“后缀”的最长的共有元素的长度.以“ABCDAB ...
- KMP Algorithm 字符串匹配算法KMP小结
这篇小结主要是参考这篇帖子从头到尾彻底理解KMP,不得不佩服原作者,写的真是太详尽了,让博主产生了一种读学术论文的错觉.后来发现原作者是写书的,不由得更加敬佩了.博主不才,尝试着简化一些原帖子的内容, ...
- [Algorithm] 字符串匹配算法——KMP算法
1 字符串匹配 字符串匹配是计算机的基本任务之一. 字符串匹配是什么?举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串& ...
- 字符串匹配算法——KMP算法
处理字符串的过程中,难免会遇到字符匹配的问题.常用的字符匹配方法 1. 朴素模式匹配算法(Brute-Force算法) 求子串位置的定位函数Index( S, T, pos). 模式匹配:子串的定位操 ...
随机推荐
- DBA词典:数据库设计常用词汇中英文对照表
1. Access method(访问方法):此步骤包括从文件中存储和检索记录. 2. Alias(别名):某属性的另一个名字.在SQL中,可以用别名替换表名. 3. Alternate keys(备 ...
- hdoj(3790) 最短路径
最短路径问题 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Subm ...
- 为什么喜欢Kindle
为什么喜欢Kindle 有朋友问为什么那么多人喜欢用Kindle,作为刚入手一台Kindle PaperWhite 2 的人, 我想说:这个东西,应该算今年我买的最值得的设备了.它的好处太多了:1:轻 ...
- CSS3圆角(border-radius)
CSS3中的border-radius支持IE9+,chrome,firefox 2.3+,以及safari3.2+浏览器. border-radius可直接使用,无需加前缀(注意:firefox13 ...
- pushState与replaceState区别
history.pushState(state, title, url) 将当前URL和history.state加入到history中,并用新的state和URL替换当前.不会造成页面刷新. sta ...
- mysql 时间戳与日期格式的相互转换
1.UNIX时间戳转换为日期用函数: FROM_UNIXTIME() ); 输出:2006-08-22 12:11:10 2.日期转换为UNIX时间戳用函数: UNIX_TIMESTAMP() Sel ...
- 【python】原始字符创
原始字符串语法在字符串前加"r" 如 tem=r"h\c\d\n"print(tem) 结果:h\c\d\n 注意:字符串结尾不能是\,否则报错,应对措施将\单 ...
- awk基础 [马哥视频]
awk基础 1.1 print print的使用格式: print item1,item2, …. 要点: 各项目自己使用逗号隔开,而输出时则以空白字符分隔: 输出的item可以为字符串或者数值,当前 ...
- java printf与println的区别
Java中的println和printf的区别在于:println是用于输出参数内容,然后换行,其参数个数固定为一个.printf是用于输出带各种数据类型的占位符的参数,其参数个数是不定的.
- Encoding 类别
Encoding 類別 .NET Framework 4.5 表示字元編碼方式. 繼承階層架構 System.Object System.Text.Encoding System.Tex ...