数据结构- 串的模式匹配算法:BF和 KMP算法 

Brute-Force算法的思想

1.BF(Brute-Force)算法

Brute-Force算法的基本思想是:

1) 从目标串s 的第一个字符起和模式串t的第一个字符进行比较,若相等,则继续逐个比较后续字符,否则从串s 的第二个字符起再重新和串t进行比较。

2) 依此类推,直至串t 中的每个字符依次和串s的一个连续的字符序列相等,则称模式匹配成功,此时串t的第一个字符在串s 中的位置就是t 在s中的位置,否则模式匹配不成功。

Brute-Force算法的实现

c语言实现:

  1. // Test.cpp : Defines the entry point for the console application.
  2. //
  3. #include "stdafx.h"
  4. #include <stdio.h>
  5. #include "stdlib.h"
  6. #include <iostream>
  7. using namespace std;
  8. //宏定义
  9. #define TRUE   1
  10. #define FALSE   0
  11. #define OK    1
  12. #define ERROR   0
  13. #define  MAXSTRLEN 100
  14. typedef char    SString[MAXSTRLEN + 1];
  15. /************************************************************************/
  16. /*
  17. 返回子串T在主串S中第pos位置之后的位置,若不存在,返回0
  18. */
  19. /************************************************************************/
  20. int BFindex(SString S, SString T, int pos)
  21. {
  22. if (pos <1 ||  pos > S[0] ) exit(ERROR);
  23. int i = pos, j =1;
  24. while (i<= S[0] && j <= T[0])
  25. {
  26. if (S[i] == T[j])
  27. {
  28. ++i; ++j;
  29. } else {
  30. i = i- j+ 2;
  31. j = 1;
  32. }
  33. }
  34. if(j > T[0]) return i - T[0];
  35. return ERROR;
  36. }
  37. void main(){
  38. SString S = {13,'a','b','a','b','c','a','b','c','a','c','b','a','b'};
  39. SString T = {5,'a','b','c','a','c'};
  40. int pos;
  41. pos = BFindex( S,  T, 1);
  42. cout<<"Pos:"<<pos;
  43. }

2.KMP算法

2.1 算法思想:

每当一趟匹配过程中出现字符比较不等时,不需要回溯I指针,而是利用已经的带的“部分匹配”的结果将模式向右滑动尽可能远的一段距离后,继续进行比较。

即尽量利用已经部分匹配的结果信息,尽量让i不要回溯,加快模式串的滑动速度。

需要讨论两个问题:
①如何由当前部分匹配结果确定模式向右滑动的新比较起点k?
② 模式应该向右滑多远才是高效率的?

现在讨论一般情况:

假设 主串:s: ‘s(1)  s(2) s(3) ……s(n)’ ;  模式串 :p: ‘p(1)  p(2) p(3)…..p(m)’

现在我们假设 主串第i个字符与模式串的第j(j<=m)个字符‘失配’后,主串第i个字符与模式串的第k(k<j)个字符继续比较。

此时,s(i)≠p(j):

由此,我们得到关系式:即得到到1 到  j -1 "部分匹配"结果:

 ‘P(1)  P(2) P(3)…..P(j-1)’   =    ’ S(i-j+1)……S(i-1)’

从而推导出k 到 j- 1位的“部分匹配”:即Pj-1j-k=S前i-1~i- (k -1))位             

  ‘P(j - k + 1) …..P(j-1)’  =  ’S(i-k+1)S(i-k+2)……S(i-1)’

由于s(i)≠p(j),接下来s(i)将与p(k)继续比较,则模式串中的前(k-1)个字符的子串必须满足下列关系式,并且不可能存在  k’>k  满足下列关系式:(k<j)

有关系式: 即(P的前k- 1 ~ 1位= S前i-1~i-(k-1) )位 ) ,:

‘P(1) P(2)  P(3)…..P(k-1)’ = ’S(i-k+1)S(i-k+2)……S(i-1)’

现在我们把前面总结的关系综合一下,有:

由上,我们得到关系:

‘p(1)  p(2)  p(3)…..p(k-1)’  =   ‘p(j - k + 1) …..p(j-1)’ 

反之,若模式串中满足该等式的两个子串,则当匹配过程中,主串中的第i 个字符与模式中的第j个字符等时,仅需要将模式向右滑动至模式中的第k个字符和主串中的第i个字符对齐。此时,模式中头k-1个字符的子串‘p(1)  p(2)  p(3)…..p(k-1)’  必定与主串中的第i 个字符之前长度为k-1 的子串  ’s(j-k+1)s(j-k+2)……s(j-1)’相等,由此,匹配仅需要从模式中的第 k 个字符与主串中的第 i 个字符比较起 继续进行。      若令 next[j] = k ,则next[j] 表明当模式中第j个字符与主串中相应字符“失配”时,在模式中需要重新和主串中该字符进行的比较的位置。由此可引出模式串的next函数:

根据模式串P的规律:  ‘p(1)  p(2)  p(3)…..p(k-1)’  =   ‘p(j - k + 1) …..p(j-1)’ 

由当前失配位置j(已知) ,可以归纳计算新起点k的表达式。

由此定义可推出下列模式串next函数值:

模式匹配过程:

KMP算法的实现:

第一步,先把模式T所有可能的失配点j所对应的next[j]计算出来;

第二步:执行定位函数Index_kmp(与BF算法模块非常相似)

  1. int KMPindex(SString S, SString T, int pos)
  2. {
  3. if (pos <1 ||  pos > S[0] ) exit(ERROR);
  4. int i = pos, j =1;
  5. while (i<= S[0] && j <= T[0])
  6. {
  7. if (S[i] == T[j]) {
  8. ++i; ++j;
  9. } else {
  10. j = next[j+1];
  11. }
  12. }
  13. if(j > T[0]) return i - T[0];
  14. return ERROR;
  15. }

完整实现代码:

  1. // Test.cpp : Defines the entry point for the console application.
  2. //
  3. #include "stdafx.h"
  4. #include <stdio.h>
  5. #include "stdlib.h"
  6. #include <iostream>
  7. using namespace std;
  8. //宏定义
  9. #define TRUE   1
  10. #define FALSE   0
  11. #define OK    1
  12. #define ERROR   0
  13. #define  MAXSTRLEN 100
  14. typedef char    SString[MAXSTRLEN + 1];
  15. void GetNext(SString T, int next[]);
  16. int KMPindex(SString S, SString T, int pos);
  17. /************************************************************************/
  18. /*
  19. 返回子串T在主串S中第pos位置之后的位置,若不存在,返回0
  20. */
  21. /************************************************************************/
  22. int KMPindex(SString S, SString T, int pos)
  23. {
  24. if (pos <1 ||  pos > S[0] ) exit(ERROR);
  25. int i = pos, j =1;
  26. int next[MAXSTRLEN];
  27. GetNext( T, next);
  28. while (i<= S[0] && j <= T[0])
  29. {
  30. if (S[i] == T[j]) {
  31. ++i; ++j;
  32. } else {
  33. j = next[j];
  34. }
  35. }
  36. if(j > T[0]) return i - T[0];
  37. return ERROR;
  38. }
  39. /************************************************************************/
  40. /*      求子串next[i]值的算法
  41. */
  42. /************************************************************************/
  43. void GetNext(SString T, int next[])
  44. {   int j = 1, k = 0;
  45. next[1] = 0;
  46. while(j < T[0]){
  47. if(k == 0 || T[j]==T[k]) {
  48. ++j;  ++k;   next[j] = k;
  49. } else {
  50. k = next[k];
  51. }
  52. }
  53. }
  54. void main(){
  55. SString S = {13,'a','b','a','b','c','a','b','c','a','c','b','a','b'};
  56. SString T = {5,'a','b','c','a','c'};
  57. int pos;
  58. pos = KMPindex( S,  T, 1);
  59. cout<<"Pos:"<<pos;
  60. }

2.2  求串的模式值next[n]

k值仅取决于模式串本身而与相匹配的主串无关。

我们使用递推到方式求next函数:
1)由定义可知:
     next[1] = 0;
2)  设 next[j] = k ,这个表面在模式串中存在下列关系:
    ‘P(1)  ….. P(k-1)’  =   ‘P(j - k + 1) ….. P(j-1)’ 
    其中k为满足1< k <j的某个值,并且不可能存在k` > 满足:
    ‘P(1)  ….. P(k`-1)’  =   ‘P(j - k` + 1) ….. P(j-1)’ 
    此时next[j+1] = ?可能有两种情况:
   (1) 若Pk = Pj,则表明在模式串中:

‘P(1) ….. P(k)’  =   ‘P(j - k + 1) ….. P(j)’ 
          并且不可能存在k` > 满足:  ‘P(1) ….. P(k`)’  =   ‘P(j - k` + 1) ….. P(j)’ 
          即next[j+1] = k + 1 推到=》:

next[j+1] = next[j] + 1;

(2)  若PkPj 则表明在模式串中:

‘P(1) ….. P(k)’     ‘P(j - k + 1) ….. P(j)’ 
     此时可把next函数值的问题看成是一个模式匹配的问题,整个模式串即是主串又是模式串,
     而当前匹配的过程中,已有:

Pj-k+1 = P1, Pj-k+2 = P2,... Pj-1 = Pk-1.
     则当PkPj时应将模式向右滑动至以模式中的第next[k]个字符和主串中的第 j 个字符相比较。
     若next[k] = k`,且Pj= Pk`, 则说明在主串中的第j+1 个字符之前存在一个长度为k` (即next[k])的最长子串,和模式串
     从首字符其长度为看k`的子串箱等。即
       ‘P(1) ….. P(k`)’  =  ‘P(j - k` + 1) ….. P(j)’ 
     也就是说next[j+1] = k` +1 即
     next[j+1] = next[k] + 1
     同理,若Pj Pk` ,则将模式继续向右滑动直至将模式串中的第next[k`]个字符和Pj对齐,
     ... ,一次类推,直至Pj和模式中某个字符匹配成功或者不存在k`(1< k` < j)满足,则:
     next[j+1] =1;

  1. /************************************************************************/
  2. /*      求子串next[i]值的算法
  3. */
  4. /************************************************************************/
  5. void GetNext(SString T, int next[])
  6. {   int j = 1, k = 0;
  7. next[1] = 0;
  8. while(j < T[0]){
  9. if(k == 0 || T[j]==T[k]) {
  10. ++j;  ++k;   next[j] = k;
  11. } else {
  12. k = next[k];
  13. }
  14. }
  15. }
next 函数值究竟是什么含义,前面说过一些,这里总结。
设在字符串S中查找模式串T,若S[m]!=T[n],那么,取T[n]的模式函数值next[n],
1.       next[n] = 0 表示S[m]和T[1]间接比较过了,不相等,下一次比较 S[m+1] 和T[1]
2.       next[n] =1 表示比较过程中产生了不相等,下一次比较 S[m] 和T[1]。
3.       next[n] = k >1 但k<n, 表示,S[m]的前k个字符与T中的开始k个字符已经间接比较相等了,下一次比较S[m]和T[k]相等吗?
4.       其他值,不可能。

注意:

(1)k值仅取决于模式串本身而与相匹配的主串无关。

(2)k值为模式串从头向后及从j向前的两部分的最大相同子串的长度。

(3)这里的两部分子串可以有部分重叠的字符,但不可以全部重叠。

next[j]函数表征着模式P中最大相同前缀子串和后缀子串(真子串)的长度。

可见,模式中相似部分越多,则next[j]函数越大,它既表示模式T字符之间的相关度越高,也表示j位置以前与主串部分匹配的字符数越多。

即:next[j]越大,模式串向右滑动得越远,与主串进行比较的次数越少,时间复杂度就越低(时间效率)。

数据结构- 串的模式匹配算法:BF和 KMP算法的更多相关文章

  1. 字符串模式匹配算法--BF和KMP详解

    1,问题描述 字符串模式匹配:串的模式匹配 ,是求第一个字符串(模式串:str2)在第二个字符串(主串:str1)中的起始位置. 注意区分: 子串:要求连续   (如:abc 是abcdef的子串) ...

  2. 串、串的模式匹配算法(子串查找)BF算法、KMP算法

    串的定长顺序存储#define MAXSTRLEN 255,//超出这个长度则超出部分被舍去,称为截断 串的模式匹配: 串的定义:0个或多个字符组成的有限序列S = 'a1a2a3…….an ' n ...

  3. 【Java】 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)

    本文根据<大话数据结构>一书,实现了Java版的串的朴素模式匹配算法.KMP模式匹配算法.KMP模式匹配算法的改进算法. 1.朴素的模式匹配算法 为主串和子串分别定义指针i,j. (1)当 ...

  4. 《数据结构》之串的模式匹配算法——KMP算法

    //串的模式匹配算法 //KMP算法,时间复杂度为O(n+m) #include <iostream> #include <string> #include <cstri ...

  5. 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)

    --喜欢记得关注我哟[shoshana]-- 目录 1.朴素的模式匹配算法2.KMP模式匹配算法 2.1 KMP模式匹配算法的主体思路 2.2 next[]的定义与求解 2.3 KMP完整代码 2.4 ...

  6. 数据结构学习之字符串匹配算法(BF||KMP)

    数据结构学习之字符串匹配算法(BF||KMP) 0x1 实验目的 ​ 通过实验深入了解字符串常用的匹配算法(BF暴力匹配.KMP.优化KMP算法)思想. 0x2 实验要求 ​ 编写出BF暴力匹配.KM ...

  7. 【算法】串的模式匹配算法(KMP)

    串的模式匹配算法     问题:         求子串位置的定位函数如何写? int index(SString S,SString T,int pos);         给定串S,子串T,问T在 ...

  8. 字符串模式匹配算法1 - BF和KMP算法

    在字符串S中定位/查找某个子字符串P的操作,通常称为字符串的模式匹配,其中P称为模式串.模式匹配有多种算法,这里先总结一下BF算法和KMP算法. 注意:本文在讨论字符位置/指针/下标时,全部使用C语法 ...

  9. 串的模式匹配算法 ------ KMP算法

    //KMP串的模式匹配算法 #include <stdio.h> #include <stdlib.h> #include <string.h> int* get_ ...

随机推荐

  1. English - consist of 和 compose of 的区别

    comprise,compose,consist,constitute,include 这一组动词都有"组成,包含"的意思. comprise v.包含,包括,由……组成(整体): ...

  2. jquery ajax 在ie7不能正常使用

    jquery ajax结构不规范到时再ie8以下的用户不能正常使用.比如[1,2,].{1,2,},结构内部的最后不能有“,”.

  3. The c programming language第一章节所有程序的实现

    //打印第一个程序hello, word #include<stdio.h> int main() { printf("hello, world\n"); ; } // ...

  4. 在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be closed first”

    在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be ...

  5. 剑指offer——stack与queue的互相实现

    我们知道,stack和queue是C++中常见的container.下面,我们来探究下如何以stack来实现queue,以及如何用queue来实现stack. 首先,先了解下stack与queue的基 ...

  6. python进阶3--文件系统

    文件系统 python的标准库中包括大量工具,可以处理文件系统中的文件,构造和解析文件名,也可以检查文件内容. pyhton表文件名表示为简单的字符串,另外还提供了一些工具,用来由os.path中平台 ...

  7. 调用Android自带日历功能(日历列表单、添加一个日历事件)

    调用Android自带日历功能  觉得这篇文章不错,转载过来. 转载:http://blog.csdn.net/djy1992/article/details/9948393 Android手机配备有 ...

  8. 夜未央Test1题解

    T1 积木游戏              树状数组的一个简单应用,建立一个维护左节点的树状数组和一个维护右节点的树状数组,对于add操作,只要在维护左节点的树状数组l处加1,维护右节点的树状数组r处加 ...

  9. DrawerLayout、CoordinatorLayout、CollapsingToolbarLayout的使用--AndroidSupportDesign练手

    先po一张效果图 PS:原谅题主的懒惰吧.. 看着是不是很酷炫,那是因为5.0的动画做得好,代码其实没有多少,搞清楚这个布局的层次关系很重要. 废话不多说了,先来看布局文件 最外层是一个DrawerL ...

  10. Java的接口及实例

    一.定义 Java接口(Interface),是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为( ...