KMP算法

KMP算法主要包括两个过程,一个是针对子串生成相应的“索引表”,用来保存部分匹配值,第二个步骤是子串匹配。

部分匹配值是指字符串的“前缀”和“后缀”的最长的共有元素的长度。以“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},共有元素长度为1;
  • “ABCDAB”的前缀为{A, AB, ABC, ABCD, ABCDA},后缀为{BCDAB, CDAB, DAB, AB, B},共有元素长度为2;
  • “ABCDABD”的前缀为{A, AB, ABC, ABCD, ABCDA, ABCDAB},后缀为{BCDABD, CDABD, DABD, ABD, BD, D},共有元素长度为0;

所以,生成了如下的部分匹配值表:

搜索词 A B C D A B D
部分匹配值 0 0 0 0 1 2 0

生成部分匹配值表的意义在于匹配时,父串的搜索位置可以向后跳动,而不是每次都只向后移动一位。可以用下面的公式计算向后跳动的位数:

移动位数=已匹配的字符数-对应的部分匹配值

以上是KMP的主要思想,下面是KMP的Java实现:

//生成next数组,表示部分匹配值
public static int[] next (String sub) {
int[] a = new int [sub.length()];
char[] c = sub.toCharArray();
int i = 0, j = 1;
a[0] = -1;
for(; j<sub.length(); j++) {
i = a[j-1];
while (i>=0 && c[j]!=c[i+1]) {
i = a[i];
}
if (c[j] == c[i+1]) {
a[j] = i+1;
} else {
a[j] = -1;
}
}
return a;
} //匹配过程
public static void pattern (String str, String sub, int[] next) {
char[] ch1 = str.toCharArray();
char[] ch2 = sub.toCharArray();
int i = 0, j = 0;
while (i<ch1.length) {
if (ch1[i] == ch2[j]) {
i ++;
j ++;
} else if (j==0) {
i ++;
} else {
j = next[j-1] + 1;
}
}
}

BM算法

  BM算法的基本流程: 设文本串T,模式串为P。首先将T与P进行左对齐,然后进行从右向左比较 ,若是某趟比较不匹配时,BM算法就采用坏字符规则和好后缀规则来计算模式串向右移动的距离,直到整个匹配过程的结束。

首先介绍坏字符规则和好后缀规则。

坏字符规则:

  在从右向左扫描的过程中,若发现某个字符x不匹配,则按下面两种情况讨论:

  • 如果字符x在模式P中没有出现,那么从字符x开始的m个文本显然不可能与P匹配成功,直接跳过该区域即可
  • 如果x在模式P中出现,则以该字符进行对齐。

  可以用下面公式表示:(skip(x)是P右移的距离,m为模式串P的长度,max(x)为字符x在P中最有位置)

  skip(x) = m;  x在P中未出现
      = m-max(x);  x在P中出现

好后缀规则:

  若发现某个字符串不匹配的同时,已有部分字符匹配成功,同样分情况讨论:

  • 模式串中有子串匹配上好后缀,此时移动模式串,让该子串和好后缀对齐即可,如果超过一个子串匹配上好后缀,则选择最靠靠近好后缀的子串对齐

  • 模式串中没有子串匹配上后后缀,此时需要寻找模式串的一个最长前缀,并让该前缀等于好后缀的后缀,寻找到该前缀后,让该前缀和好后缀对齐即可
  • 模式串中没有子串匹配上后后缀,并且在模式串中找不到最长前缀,让该前缀等于好后缀的后缀。此时,直接移动模式到好后缀的下一个字符。

  用数学公式表示:

  shift(j) = min{s|(P[j+1..m]=P[j-s+1..m-s]) && (P[j]!=P[j-s])(j>s), P[s+1..m]=P[1..m](j<=s)}

在BM算法匹配过程中,取skip和shift中较大者作为跳跃的距离。

下面是BM算法的C实现:

//根据坏字符规则做预处理,建立坏字符表
int* MakeSkip (char* ptrn, int plen) {
int i;
//申请256个int的空间,一个字符8位,总共有256中不同的字符
int *skip = (int*) malloc( * sizeof(int));
assert (skip != NULL);
//初始化坏字符表
for (i=; i<; i++) {
*(skip + i) = plen;
}
//给表中需要赋值的单元赋值
while (plen != ) {
*(skip + (unsigned char)*ptrn++) = plen--;
}
return skip;
} //根据好后缀规则做预处理,建立好后缀表
int *MakeShift (char* ptrn, int plen) {
//为好后缀表申请空间
int *shift = (int*) malloc(plen * sizeof(int));
//给好后缀表进行赋值的指针
int *sptr = shift + plen - ;
//记录字符串边界的指针
int *pptr = ptrn + plen - ; assert (shift != NULL);
char c = *pptr;
*sptr = ; while (sptr-- != shift) {
char *p1 = ptrn + plen -, *p2, *p3;
do {
while (p1 >= ptrn && *p1-- != c);
p2 = ptrn + plen -;
p3 = p1;
while (p3 = ptrn && *p3-- == *p2-- && p2 >= pptr);
} while (p3 >= ptrn && p2 >= pptr); *sptr = shift _ plen - sptr + p2 - p3;
pptr--;
}
return shift;
} int BMSearch ( char* buf, int blen, char* ptrn, int plen, int* skip, int* shift) {
int b_idx = plen;
if (plen == )
return ;
//计算字符串是否匹配到了尽头
while (b_idx <= blen) {
int p_idx = plen, skip_stride, shift_stride;
while (buf[--b_idx] == ptrn[--p_idx]) {
if (b_idx < ) {
return ;
}
if (p_idx == ) {
return ;
}
}
skip_stride = skip[(unsigned char)buf[b_idx]];
shift_stride = shift[p_idx];
b_idx += (skip_stride > shift_stride) ? skip_stride : shift_stride;
}
return ;
}

Sunday算法

  从头开始匹配,当发现失配的时候就判断子串的后一位在父串的字符是否在子串中存在。如果存在则将该位置和子串中的该字符对齐,在从头开始匹配。如果不存在就将子串向后移动,和父串k+1处的字符对齐,再进行匹配。重复上面的操作直到找到,或父串被找完。

  下面是Sunday算法的C实现:

int SundayMatch(byte* pSrc, int nSrcSize, byte* pSubSrc, int nSubSrcSize)
{
int skip[];
for (int i = ; i < ; i++)
{
skip[i] = nSubSrcSize + ;
} for (int i = ; i < nSubSrcSize; i++)
{
skip[pSubSrc[i]] = nSubSrcSize - i;
} int nPos = ;
while(nPos <= nSrcSize - nSubSrcSize)
{
int j = nSubSrcSize - ;
while(j >= && pSrc[nPos + j] == pSubSrc[j])
{
j--;
}
if (j < )
{
break;
}
nPos = nPos + skip[pSrc[nPos + nSubSrcSize]];
}
return nPos;
}

字符串匹配算法——KMP、BM、Sunday的更多相关文章

  1. 字符串匹配算法--KMP字符串搜索(Knuth–Morris–Pratt string-searching)C语言实现与讲解

    一.前言   在计算机科学中,Knuth-Morris-Pratt字符串查找算法(简称为KMP算法)可在一个主文本字符串S内查找一个词W的出现位置.此算法通过运用对这个词在不匹配时本身就包含足够的信息 ...

  2. 字符串匹配算法之BM算法

    BM算法,全称是Boyer-Moore算法,1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹配算法. BM算法定义了两个规则: ...

  3. 字符串匹配 扩展KMP BM&Sunday

    复杂度都是O(n) 扩展1:BM算法 KMP的匹配是从模式串的开头开始匹配的,而1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹 ...

  4. leetcode28 strstr kmp bm sunday

    字符串匹配有KMP,BM,SUNDAY算法. 可见(https://leetcode-cn.com/problems/implement-strstr/solution/c5chong-jie-fa- ...

  5. 字符串匹配算法 - KMP

    前几日在微博上看到一则微博是说面试的时候让面试者写一个很简单的字符串匹配都写不出来,于是我就自己去试了一把.结果写出来的是一个最简单粗暴的算法.这里重新学习了一下几个经典的字符串匹配算法,写篇文章以巩 ...

  6. 字符串匹配算法——KMP算法学习

    KMP算法是用来解决字符串的匹配问题的,即在字符串S中寻找字符串P.形式定义:假设存在长度为n的字符数组S[0...n-1],长度为m的字符数组P[0...m-1],是否存在i,使得SiSi+1... ...

  7. 4种字符串匹配算法:KMP(下)

    回顾:4种字符串匹配算法:BS朴素 Rabin-karp(上) 4种字符串匹配算法:有限自动机(中) 1.图解 KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R ...

  8. 字符串匹配算法KMP算法

    数据结构中讲到关于字符串匹配算法时,提到朴素匹配算法,和KMP匹配算法. 朴素匹配算法就是简单的一个一个匹配字符,如果遇到不匹配字符那么就在源字符串中迭代下一个位置一个一个的匹配,这样计算起来会有很多 ...

  9. 字符串匹配算法(二)-BM算法详解

    我们在字符串匹配算法(一)学习了BF算法和RK算法,那有没更加高效的字符串匹配算法呢.我们今天就来聊一聊BM算法. BM算法 我们把模式串和主串的匹配过程,可以看做是固定主串,然后模式串不断在往后滑动 ...

随机推荐

  1. HDU2639Bone Collector II(01背包变形)

    01背包,求第k大. 以前看k短路的时候看过代码以为懂了 = =结果还是跑去看了别人的代码才会.果然要自己写一遍才行啊 0.0难得1A.. 每次把可能的2k种求出来,求前k个.注意要不一样的k个数.. ...

  2. A Tour of Go Basic types

    Go's basic types are bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr ...

  3. MySQL 5.7 for Windows 解压缩版 MySQL 服务无法启动

    MySQL 5.7 for Windows 解压缩版配置安装 http://jingyan.baidu.com/article/f3ad7d0ffc061a09c3345bf0.html basedi ...

  4. java web知识点总结

    创建与销毁 ServletContext HttpRequest HttpSession 1.ServletContext 创建:启动服务器时就创建,服务为每个web应用创建该项目的ServleCon ...

  5. C# 算速表达式

              public object ComputeExpression(string expression)         {             var result = new  ...

  6. Android-ViewPagerIndicator

    https://github.com/JakeWharton/Android-ViewPagerIndicator

  7. stack计算表达式的值

    9.52 使用stack对象处理带圆括号的表达式.遇到左圆括号时,将其标记下来.当你在一个左括号之后遇到右圆括号时,弹出stack对象中这两边括号之间的元素,直到遇到左括号,将左括号也一起弹出栈. 接 ...

  8. 由 Windows 向 Linux 迁移字体 和 Linux 等宽字体

    1. From Windows Windows下字体库的位置为C:\Windows\fonts,这里面包含所有windows下可用的字体.2. To Linux linux的字体库是 /usr/sha ...

  9. Android(java)学习笔记140:SpannableString类的使用

    我们之前说过了我们想实现在TextView组件之中,可以显示URL.Email等特殊信息,这些信息点击可以实现跳转,真正意义上的超链接 要实现上面的需求就要SpannableString这个类. 因为 ...

  10. how to forget about delta cycles for RTL design

    A delta cycle is a VHDL construct used to makeVHDL, a concurrent language, executable on asequential ...