KMP算法,Boyer-Moore算法

http://www-igm.univ-mlv.fr/~lecroq/string/node19.html
首先看普通的串的模式匹配算法开始讲起,才能更深入的了解KMP算法及其优点。
1. 普通的串的模式匹配算法是怎么进行比较:
主串 (S) a b a b c a b c a c b a b
子串 (T) a b c a c (子串又被称为模式串)
第一趟(详细过程)
ab a b c a b c a c b a b
ab c a caba b c a b c a c b a b
abc a ca bab c a b c a c b a b
a bca c
遇到不匹配的地方时指针回朔,子串向前移动一位(下同),变成如下形式
aba b c a b c a c b a b
ab c a c重复上述步骤
假如串的长度为m,子串的长度为n的话,那么这个算法在最坏的情况下的时间复杂度为 O(m*n)
1.模式匹配的Brute-Force算法
Brute-Force算法实现模式匹配的思想是:从主串s=”s0s1…sn-1”的第一个字符开始和模式串t=”t0t1…tn-1”的第一个字符比较,若相等,则继续比较后续字符;否则从主串s的第二个字符开始重新与模式串t的第一个字符比较。如此不断继续,若存在模式串中的每个字符依次和主串中的一个连续字符序列相等,则匹配成功,返回模式串t的第一个字符在主串中的下标;否则匹配失败,返回-1。
int Index_BF ( char S [ ], char T [ ], int pos )
{
/* 若串 S 中从第pos(S 的下标0≤pos<StrLength(S))个字符
起存在和串 T 相同的子串,则称匹配成功,返回第一个
这样的子串在串 S 中的下标,否则返回 -1 */ int i = pos, j = ; while ( S[i+j] != '\0'&& T[j] != '\0')
if ( S[i+j] == T[j] )
j ++; // 继续比较后一字符
else
{
i ++; j = ; // 重新开始新的一轮匹配
} if ( T[j] == '\0')
return i; // 匹配成功 返回下标
else
return -; // 串S中(第pos个字符起)不存在和串T相同的子串
} // Index_BF
2. D.E.Knuth 和 J.H.Morris 和 V.R.Pratt给了我们一种时间复杂度为O(m+n)的算法 KMP
主要思想让子串向右移动尽可能远的一段距离来达到降低时间复杂度。
逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。

逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。

《部分匹配表》是如何产生的?
首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合
"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,
移动位数 = 已匹配的字符数 - 对应的部分匹配值
-"A"的前缀和后缀都为空集,共有元素的长度为0;
-"AB"的前缀为[A],后缀为[B],共有元素的长度为0;
-"ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
-"ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
-"ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
-"ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
-"ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度0。
"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。
#include <iostream>
#include <string>
using namespace std; void get_nextval(const char *T, int next[])
{
// 求模式串T的next函数值并存入数组 next
int j = , k = -;
next[] = -; while ( T[j] != '\0' )
{
if (k == - || T[j] == T[k])
{
++j;
++k;
next[j] = k;
}// if
else
k = next[k];
}// while ////加的显示部分
//for(int i=0;i<j;i++)
//{
// cout<<next[i];
//}
//cout<<endl; }// get_nextval int KMP(const char *Text,const char* Pattern)
{
if( !Text||!Pattern|| Pattern[]=='\0' || Text[]=='\0' )
return -;//空指针或空串,返回-1 int len=;
const char * c=Pattern; while(*c++!='\0')//计算模式串长度
{
++len;
} int *next=new int[len+];
get_nextval(Pattern,next);//求Pattern的next函数值 int index=,i=,j=;
while(Text[i]!='\0' && Pattern[j]!='\0' )
{
if(Text[i]== Pattern[j])
{
++i;// 继续比较后继字符
++j;
}
else
{
index += j-next[j];
if(next[j]!=-)
j=next[j];// 模式串向右移动
else
{
j=;
++i;
}
}
}//while delete []next;
if(Pattern[j]=='\0')
return index;// 匹配成功
else
return -;
} int main()//abCabCad
{
char* text="bababCabCadcaabcaababcbaaaabaaacababcaabc";
char*pattern="abCabCad"; cout<<KMP(text,pattern)<<endl;
return ;
}
Boyer-Moore算法
Boyer-Moore算法的基本思想是,每次后移这两个规则之中的较大值("坏字符规则"只能移3位,"好后缀规则"可以移6位)。
1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了这种算法。

首先,"字符串"与"搜索词"头部对齐,从尾部开始比较。
这是一个很聪明的想法,因为如果尾部字符不匹配,那么只要一次比较,就可以知道前7个字符(整体上)肯定不是要找的结果。
我们看到,"S"与"E"不匹配。这时,"S"就被称为"坏字符"(bad character),即不匹配的字符。我们还发现,"S"不包含在搜索词"EXAMPLE"之中,这意味着可以把搜索词直接移到"S"的后一位。

依然从尾部开始比较,发现"P"与"E"不匹配,所以"P"是"坏字符"。但是,"P"包含在搜索词"EXAMPLE"之中。所以,将搜索词后移两位,两个"P"对齐。

我们由此总结出"坏字符规则":
后移位数 = 坏字符的位置 - 搜索词中的上一次出现位置
如果"坏字符"不包含在搜索词之中,则上一次出现位置为 -1。
以"P"为例,它作为"坏字符",出现在搜索词的第6位(从0开始编号),在搜索词中的上一次出现位置为4,所以后移 6 - 4 = 2位。再以前面第二步的"S"为例,它出现在第6位,上一次出现位置是 -1(即未出现),则整个搜索词后移 6 - (-1) = 7位。

我们知道,此时存在"好后缀"。所以,可以采用"好后缀规则":
后移位数 = 好后缀的位置 - 搜索词中的上一次出现位置
举例来说,如果字符串"ABCDAB"的后一个"AB"是"好后缀"。那么它的位置是5(从0开始计算,取最后的"B"的值),在"搜索词中的上一次出现位置"是1(第一个"B"的位置),所以后移 5 - 1 = 4位,前一个"AB"移到后一个"AB"的位置。
再举一个例子,如果字符串"ABCDEF"的"EF"是好后缀,则"EF"的位置是5 ,上一次出现的位置是 -1(即未出现),所以后移 5 - (-1) = 6位,即整个字符串移到"F"的后一位。
这个规则有三个注意点:
(1)"好后缀"的位置以最后一个字符为准。假定"ABCDEF"的"EF"是好后缀,则它的位置以"F"为准,即5(从0开始计算)。
(2)如果"好后缀"在搜索词中只出现一次,则它的上一次出现位置为 -1。比如,"EF"在"ABCDEF"之中只出现一次,则它的上一次出现位置为-1(即未出现)。
(3)如果"好后缀"有多个,则除了最长的那个"好后缀",其他"好后缀"的上一次出现位置必须在头部。比如,假定"BABCDAB"的"好后缀"是"DAB"、"AB"、"B",请问这时"好后缀"的上一次出现位置是什么?回答是,此时采用的好后缀是"B",它的上一次出现位置是头部,即第0位。这个规则也可以这样表达:如果最长的那个"好后缀"只出现一次,则可以把搜索词改写成如下形式进行位置计算"(DA)BABCDAB",即虚拟加入最前面的"DA"。
回到上文的这个例子。此时,所有的"好后缀"(MPLE、PLE、LE、E)之中,只有"E"在"EXAMPLE"还出现在头部,所以后移 6 - 0 = 6位。

继续从尾部开始比较,"P"与"E"不匹配,因此"P"是"坏字符"。根据"坏字符规则",后移 6 - 4 = 2位。
14.

从尾部开始逐位比较,发现全部匹配,于是搜索结束。如果还要继续查找(即找出全部匹配),则根据"好后缀规则",后移 6 - 0 = 6位,即头部的"E"移到尾部的"E"的位置。
KMP算法,Boyer-Moore算法的更多相关文章
- Boyer Moore算法(字符串匹配)
上一篇文章,我介绍了KMP算法. 但是,它并不是效率最高的算法,实际采用并不多.各种文本编辑器的"查找"功能(Ctrl+F),大多采用Boyer-Moore算法. Boyer-Mo ...
- 文本比较算法三——SUNDAY 算法
SUNDAY 算法描述: 字符串查找算法中,最著名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore).两个算法在最坏情况下均具有线性的查找时间.但是在实用上 ...
- Machine Learning读书会,面试&算法讲座,算法公开课,创业活动,算法班集锦
Machine Learning读书会,面试&算法讲座,算法公开课,创业活动,算法班集锦 近期活动: 2014年9月3日,第8次西安面试&算法讲座视频 + PPT 的下载地址:http ...
- Levenshtein Distance算法(编辑距离算法)
编辑距离 编辑距离(Edit Distance),又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数.许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符, ...
- javascript数据结构与算法--高级排序算法
javascript数据结构与算法--高级排序算法 高级排序算法是处理大型数据集的最高效排序算法,它是处理的数据集可以达到上百万个元素,而不仅仅是几百个或者几千个.现在我们来学习下2种高级排序算法-- ...
- ISAP算法对 Dinic算法的改进
ISAP算法对 Dinic算法的改进: 在刘汝佳图论的开头引言里面,就指出了,算法的本身细节优化,是比较复杂的,这些高质量的图论算法是无数优秀算法设计师的智慧结晶. 如果一时半会理解不清楚,也是正常的 ...
- 文本比较算法Ⅱ——Needleman/Wunsch算法
在"文本比较算法Ⅰ--LD算法"中介绍了基于编辑距离的文本比较算法--LD算法. 本文介绍基于最长公共子串的文本比较算法--Needleman/Wunsch算法. 还是以实例说明: ...
- [算法]检测空间三角形相交算法(Devillers & Guigue算法)
#pragma once //GYDevillersTriangle.h /* 快速检测空间三角形相交算法的代码实现(Devillers & Guigue算法) 博客原地址:http://bl ...
- 最短路径算法之Dijkstra算法(java实现)
前言 Dijkstra算法是最短路径算法中为人熟知的一种,是单起点全路径算法.该算法被称为是“贪心算法”的成功典范.本文接下来将尝试以最通俗的语言来介绍这个伟大的算法,并赋予java实现代码. 一.知 ...
- Dijkstra 算法、Kruskal 算法、Prim算法、floyd算法
1.dijkstra算法 算最短路径的,算法解决的是有向图中单个源点到其他顶点的最短路径问题. 初始化n*n的数组. 2.kruskal算法 算最小生成树的,按权值加入 3.Prim算法 类似dijk ...
随机推荐
- Oracle 左连接、右连接、全外连接、(+)号作用、inner join(等值连接) (转载)
Oracle 外连接 (1)左外连接 (左边的表不加限制) (2)右外连接(右边的表不加限制) (3)全外连接(左右两表都不加限制) 外连接(Outer Join) oute ...
- Linux命令:head命令详解
概述:head命令用于显示文件文字区块 1.格式 head [参数][文件] 2.参数 -q 隐藏文件名 -v 显示文件名 -c<字节> 显示字节数 -n<行数> 显示的行数 ...
- Elasticsearch学习2--Elasticsearch数据类型简介
1.Elasticsearch 是 面向文档型数据库,这意味着它存储的是整个对象或者 文档,它不但会存储它们,还会为他们建立索引,这样你就可以搜索他们了.你可以在 Elasticsearch 中索引. ...
- Linux下JDK环境变量配置
JDK官方下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 我的下载路 ...
- ETLLib库走读
以前从来也没想过要去看库里的代码,咱想,那怎么也是很底层的代码,居然发现库里的代码也可以这样写,那就先读懂这个ETLLib(跨windows与Linux平台,封装了一些基本的系统调用函数,然后实现定制 ...
- cocos2dx 实现华丽丽的滚动层.
前言 好久没写博客了. 前几周策划要求实现一个比较多功能的滚动层控件. 这个艰巨的任务就这样自然而然的落在了我这小身板上. 当然了, 只要我出手, 难度再高的需求也变得不堪一击. 哈哈哈哈 示例图 该 ...
- Problem 1183 - 排列
#include<iostream> #include<vector> #include<algorithm> using namespace std; int c ...
- Codeforces 193D Two Segments 解题报告
先是在蓝桥杯的网站上看到一道题: 给出1~n的一个排列,求出区间内所有数是连续自然数的区间的个数.n<=50000. 由于数据较弱,即使用O(N^2)的算法也能拿到满分. 于是在CF上发现了这一 ...
- linux根目录下各文件的作用
各文件详列: /bin 存放常用命令的目录(二进制可执行命令) /dev 设备特殊文件 /etc 存放配置相关的文件(系统管理和配置文件) /etc/rc.d 启动的配置文件和脚 ...
- Tomcat环境变量的配置
Tomcat web服务器 支持全部JSP以及Servlet规范 主要作用 是提供了一个可以让Jsp和Servlet运行的平台 tomcat环境变量设置 CATALINA_HOME : D:\bran ...