算法——字符串匹配之BM算法

前言

Boyer-Moore算法是一种基于后缀匹配的模式串匹配算法(简称BM算法),后缀匹配就是模式串从右到左开始比较,但模式串的移动依然是从左到右的。在实践中,BM算法效率高于前面介绍的《KMP算法》,算法分为两个阶段:预处理阶段和搜索阶段;预处理阶段时间和空间复杂度都是是O(m+sigma),sigma是字符集大小,一般为256;在最坏的情况下算法时间复杂度是O(m*n);在最好的情况下达到O(n/m)。

BM算法实现

BM算法预处理过程

BM算法有两个规则分别为坏字符规则(Bad
Character Heuristic)和好后缀规则(Good Suffix Heuristic);这两种规则目的就是让模式串每次向右移动尽可能大的距离。BM算法是每次向右移动模式串的距离是,按照好后缀算法和坏字符算法计算得到的最大值。以下给出基本概念:

    坏字符:输入文本字符串中的字符与模式串当前字符不匹配时,则文本字符串的该字符称为坏字符;

    好后缀:是指在遇到坏字符之前,文本串和模式串已匹配成功的字符子串;

下面是坏字符和好后缀的图示:

坏字符规则:当输入文本字符串中的某个字符跟模式串的某个字符不匹配时,模式串需要向右移动以便进行下一次匹配,移动的位数
= 坏字符在模式串中对应的位置 - 坏字符在模式串中最右出现的位置。此外,如果模式串中不存在"坏字符",则最右出现位置为-1;所以坏字符规则必然有两种情况,下面会进行讨论。

好后缀规则:当字符失配时,后移位数 = 好后缀在模式串中对应的位置 - 好后缀在模式串上一次出现的位置,且如果好后缀在模式串中没有再次出现,则为-1。根据模式串是否存在好后缀或部分好后缀,可以分为三种情况,下面会逐一讨论。

坏字符规则

坏字符规则有两种情况,如下图所示:

好后缀规则

若文本字符串和模式串匹配了一个好后缀u, 下面根据模式串其他位置是否存在好后缀进行不同的移动;假如,模式串pat的后u个字符和文本串txt都已经匹配了,但是下一个字符是坏字符,则需要移动模式串重新匹配。若在模式中依然存在相同的后缀或部分后缀,
那把最长的后缀或部分后缀移动到当前后缀位置;若模式串pat不存在其他的好后缀,则直接右移整个pat。因此好后缀规则有三种情况,如下图所示:

好后缀规则和坏字符规则的大小通过模式串的预处理数组的简单计算得到。坏字符算法的预处理数组是bmBc[],好后缀算法的预处理数组是bmGs[]。

计算坏字符数组bmBc[]

Case1:若模式串存在坏字符,若模式串存在多个坏字符时,选取最右边的那个字符。bmBc[‘b‘]表示字符b在模式串中最右出现的位置。

例如下面模式串中出现坏字符b的位置分别为j,k,i;则选取最右位置i作为bmBc[‘b‘]的值;

Case2:字符在模式串中没有出现,如模式串中没有字符b,则bmBc[‘b‘]
= -1。

坏字符数组bmBc[]源码实现如下:

void PreBmBc(const string &pat, int m, int bmBc[])
{
    int i = 0;
	// Initialize all occurrences as -1, include case2
    for(i = 0; i < MAX_CHAR; i++)
        bmBc[i] = -1;
   // case1:Fill the actual value of last occurrence of a character
    for(i = 0; i < m; i++)
        bmBc[pat[i]] = i;

}

计算好后缀数组bmGs[]

求解好后缀数组之前先求解好后缀数组长度的辅助数组suff[];表示以i为边界,与模式串后缀匹配的最大长度,如下图所示:

suff[i]就是求pat中以i位置字符为后缀和以最后一个字符为后缀的公共后缀串的长度(包括当前位置字符);下面举例说明:

i  : 0 1 2 3 4 5 6 7
	 | | | | | | | |
pat: b c a b a b a b
/*
当i=m-1=7时,则suff[7]=8;
当i=6时,以pat[6]为后缀的后缀字符串为bcababa,以最后一字符b为后缀的后缀字符串为bcababab
	     则不存在公共最长子串,即suff[6]=0;
当i=5时,以pat[5]为后缀的后缀字符串为bcabab,以最后一字符b为后缀的后缀字符串为bcababab
	     则公共最长子串abab,即suff[5]=4;
当i=4时,以pat[4]为后缀的后缀字符串为bcaba,以最后一字符b为后缀的后缀字符串为bcababab
	     则不存在公共最长子串,即suff[4]=0;
.......
当i=0时,以pat[0]为后缀的后缀字符串为b,以最后一字符b为后缀的后缀字符串为bcababab
	     则公共最长子串b,即suff[0]=1;
*/

        suff数组的定义:引用自《Boyer-Moore
algorithm

对于;

其中m为模式串的长度,;所以很容易源码实现如下:

void suffix(const string &pat, int m, int suff[])
{
    int i, j;

    suff[m - 1] = m;

    for(i = m - 2; i >= 0; i--)
    {
		j = i;
        while(j >= 0 && pat[j] == pat[m - 1 - i + j]) j--;

        suff[i] = i - j;
    }
}

有了上面求解的好后缀长度数组suff[],现在可以计算好后缀数组bmGs[],根据前面好后缀的三种情况,这里求解数组也对应三种情况:

则可以写出好后缀数组bmGs[]的源代码:

void PreBmGs(const string &pat, int m, int bmGs[])
{
    int i, j;
    int suff[SIZE];  

    // computed the suff[]
    suffix(pat, m, suff);

    // Initialize all occurrences as -1, include case3
    for(j = 0; j < m; j++)
    {
        bmGs[j] = -1;
    }

    // Case2
    j = 0;
    for(i = m - 1; i >= 0; i--)
    {
        if(suff[i] == i + 1)
        {
            for(; j < m - 1 - i; j++)
            {
                if(bmGs[j] == -1)
                    bmGs[j] = i;
            }
        }
    }

    // Case1
    for(i = 0; i <= m - 2; i++)
    {
        j = m - 1 - suff[i];
		bmGs[j] = i;
    }
}

BM算法匹配过程

到此为止已经讲解了BM算法的求解方法,以下给出BM算法的程序:

#include <iostream>
#include <string>

using namespace std;

const int MAX_CHAR = 256;
const int SIZE = 256;
static inline int MAX(int x, int y){return x < y ? y:x;}

void BoyerMoore(const string &pat, const string &txt);

int main()
{
    string txt = "abababaacbabaa";
	string pat = "babaa";

    BoyerMoore(pat,txt);
    system("pause");
    return 0;
}

void PreBmBc(const string &pat, int m, int bmBc[])
{
    int i = 0;
	// Initialize all occurrences as -1, include case2
    for(i = 0; i < MAX_CHAR; i++)
        bmBc[i] = -1;
   // case1:Fill the actual value of last occurrence of a character
    for(i = 0; i < m; i++)
        bmBc[pat[i]] = i;

}

void suffix(const string &pat, int m, int suff[])
{
    int i, j;

    suff[m - 1] = m;
    for(i = m - 2; i >= 0; i--)
    {
		j = i;
        while(j >= 0 && pat[j] == pat[m - 1 - i + j])
			j--;

        suff[i] = i - j;
    }
}

void PreBmGs(const string &pat, int m, int bmGs[])
{
    int i, j;
    int suff[SIZE];  

    // computed the suff[]
    suffix(pat, m, suff);
    // Initialize all occurrences as -1, include case3
    for(j = 0; j < m; j++)
          bmGs[j] = -1;
    // Case2
    j = 0;
    for(i = m - 1; i >= 0; i--)
    {
        if(suff[i] == i + 1)
        {
            for(; j < m - 1 - i; j++)
            {
                if(bmGs[j] == -1)
                    bmGs[j] = i;
            }
        }
    }

    // Case1
    for(i = 0; i <= m - 2; i++)
    {
        j = m - 1 - suff[i];
		bmGs[j] = i;
    }
}

void BoyerMoore(const string &pat, const string &txt)
{
    int j, bmBc[MAX_CHAR], bmGs[SIZE];

	int m = pat.length();
	int n = txt.length();

    // Preprocessing
    PreBmBc(pat, m, bmBc);
    PreBmGs(pat, m, bmGs);

    // Searching
    int s = 0;// s is shift of the pattern with respect to text
    while(s <= n - m)
    {
        j = m - 1;
		/* Keep reducing index j of pattern while characters of
           pattern and text are matching at this shift s */
		while(j >= 0 && pat[j] == txt[j + s])
			j--;

		/* If the pattern is present at current shift, then index j
           will become -1 after the above loop */
        if(j < 0)
        {
            cout<<"pattern occurs at shift :"<< s<<endl;
			/* Shift the pattern so that the next character in text
               aligns with the last occurrence of it in pattern.
               The condition s+m < n is necessary for the case when
               pattern occurs at the end of text */
             s += (s+m < n)? m-bmBc[txt[s+m]] : 1;
        }
        else
        {/* Shift the pattern with the Max value between bmBc[] and bmGs[]  */
            s += MAX(j - bmBc[txt[s+j]], j-bmGs[j]);
        }
    }
}

参考资料:

http://www-igm.univ-mlv.fr/~lecroq/string/node14.html

http://blog.csdn.net/v_july_v/article/details/7041827

http://blog.jobbole.com/52830/

http://www.searchtb.com/

http://www.geeksforgeeks.org/pattern-searching-set-7-boyer-moore-algorithm-bad-character-heuristic/

http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html

http://dsqiu.iteye.com/blog/1700312

时间: 2024-12-22 20:16:11

算法——字符串匹配之BM算法的相关文章

算法——字符串匹配之KMP算法

前言 本节介绍Knuth-Morris-Pratt字符串匹配算法(简称KMP算法).该算法最主要是构造出模式串pat的前缀和后缀的最大相同字符串长度数组next,和前面介绍的<朴素字符串匹配算法>不同,朴素算法是当遇到不匹配字符时,向后移动一位继续匹配,而KMP算法是当遇到不匹配字符时,不是简单的向后移一位字符,而是根据前面已匹配的字符数和模式串前缀和后缀的最大相同字符串长度数组next的元素来确定向后移动的位数,所以KMP算法的时间复杂度比朴素算法的要少,并且是线性时间复杂度,即预处理时间复

算法——字符串匹配之有限自动机算法

前言 上篇文章介绍<Rabin-Karp字符串匹配算法>,这里介绍有限自动机(Finite Automata)字符串匹配算法,有限自动机(Finite Automata)字符串匹配算法最主要的是计算出转移函数.即给定一个当前状态k和一个字符x,计算下一个状态:计算方法为:找出模式pat的最长前缀prefix,同时也是pat[0...k-1]x(注意:字符串下标是从0开始)的后缀,则prefix的长度即为下一个状态.匹配的过程是比较输入文本子串和模式串的状态值,若相等则存在,若不想等则不存在.有

算法——字符串匹配之Rabin-Karp算法

前言 Rabin-Karp字符串匹配算法和前面介绍的<朴素字符串匹配算法>类似,也是对应每一个字符进行比较,不同的是Rabin-Karp采用了把字符进行预处理,也就是对每个字符进行对应进制数并取模运算,类似于通过某种函数计算其函数值,比较的是每个字符的函数值.预处理时间O(m),匹配时间是O((n-m+1)m). Rabin-Karp算法实现 伪代码: Rabin_Karp_search(T, P, d, q) n = T.length; m = P.length; h = d^(m-1)mo

字符串匹配的sunday算法

sunday算法核心思想:启发式移动搜索步长! SUNDAY 算法描述: 字符串查找算法中,最著名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore).这里介绍一种比BM算法更快一些的sunday查找算法. 例如我们要在"substring searching algorithm"查找"search",刚开始时,把子串与文本左边对齐: substring searching algorithm search ^ 结果在第二个

字符串匹配的KMP算法

html, body { font-size: 15px; } body { font-family: Helvetica, "Hiragino Sans GB", 微软雅黑, "Microsoft YaHei UI", SimSun, SimHei, arial, sans-serif; line-height: 1.6; color: ; background-color: ; margin: 0; padding: 16px 20px; } h1, h2, h

[算法系列之二十六]字符串匹配之KMP算法

一 简介 KMP算法是一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特-莫里斯-普拉特操作(简称KMP算法).KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的. 二 基于部分匹配表的KMP算法 举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含搜索串"ABCDABD"? 步骤1:字符串"BBC ABC

字符串匹配(KMP 算法 含代码)

主要是针对字符串的匹配算法进行解说 有关字符串的基本知识 传统的串匹配法 模式匹配的一种改进算法KMP算法 网上一比較易懂的解说 小样例 1计算next 2计算nextval 代码 有关字符串的基本知识 串(string或字符串)是由零个或多个字符组成的有限序列,一般记为 当中s是串的名,用单引號括起来的字符序列是串的值:ai(1<=i<=n)能够是字母.数值或其它字符.串中字符的数组 n称为串的长度.零个字符的串称为空串,它的长度为0 串中随意个连续的字符组成的子序列称为该串的子串. 包括子

字符串匹配(BF,BM,Sunday,KMP算法解析)

字符串匹配一直是计算机领域热门的研究问题之一,多种算法层出不穷.字符串匹配算法有着很强的实用价值,应用于信息搜索,拼写检查,生物信息学等多个领域. 今天介绍几种比较有名的算法: 1. BF 2. BM 3. Sunday 4. KMP -,BF算法 BF(Brute Force)算法又称为暴力匹配算法,是普通模式匹配算法. 其算法思想很简单,从主串S的第pos个字符开始,和模式串T的第一个字符进行比较,若相等,则主串和模式串都后移一个字符继续比较:若不相同,则回溯到主串S的第pos+1个字符重新

字符串匹配之horspool算法(简化的BM算法)

前面介绍在BF,KMP这些算法的时候老是提到BM这个东西,究竟这什么东西,有啥高深的,这些问题我们如今不去考虑.不知道,认真读前几篇文章的读者有没有发现前面的算法都是从模式串的前面開始匹配的,那我们就想能不能从模式串的后面開始匹配了? 答案肯定是能够的.所以这就有了我们今天的这篇文章Horspool算法,这个算法是基于字符串后缀的匹配算法. 在上一篇文章中,我们学习了一个概念叫好字符(又叫好后缀),大家都知道有好必有坏吧,所以我们今天再来学习一个概念-----坏字符. 一.坏字符与模式串滑动 坏