最近在写一个搜索字符串的用例,刚好使用到这个算法,在网络上学习了多篇文章,在这里记录一下自己的理解。

首先理清楚算法的原理:

1.算法是从尾部开始比较字符串的,如果最后一个字符不匹配,且不匹配的字符不在模式串中,那么可以直接忽略这段比较内容, 这种情况下效率最高。如下所示:

ABBDD
CCGGXABBDDTUUY
D != X 因此可以直接将指针移动到
----------ABBDD
CCGGXABBDDTUUY。

如果不匹配的字符在模式串中,如下:
AXBDD
CCGGXABBDDTUUY 则移动如下:

------AXBDD
CCGGXABBDDTUUY

这些就是算法中所谓的“坏字符“的情况。

2.当算法匹配一部分,但是遇到不等的情况时,如下所示:

ABBDD
CXBDDABBDD
B != X ,这时有两种方式移动指针。第一种就是按照“坏字符”的方式,忽略CX,即指针向后移动两位。但是这样,效率好像不太高,因为我们知道BDD这三个字符是比较过的,如何运用这些已知信息呢。

这就是算法的精髓所在了。我们假设ABBDD能与CX之后的字符串匹配上,那么AB必须等于BD。但这明显是错误的,因此如果我们能事先对模式串进行预处理,即记录它自身的字符串的匹配信息,那么我们就可以利用这些信息,判断是否需要进一步比较的必要性了。

怎么理解呢,如在ABBDD中以BDD为后缀的串,仅仅出现了一次,这样我们就知道,如果某个字符串如CXBDD中与BDD部分匹配,如果仅仅把模式串移动两位那就会导致AB需要和BDD比较,但是我们已经预先知道BDD之出现一次,这样就冗余了,因此我们可以把指针向后移动5个字节,效率就比“坏字符”高多了。这也被称为“好后缀”。

再比如
ADDBBDD 与
CAFBADDBBDD

A!=B 但是DD在模式串中出现两次,我们把指针移动到最近一个上就变成,

--------ADDBBDD
CAFBADDBBDD

为什么要这么移动呢?就是因为DD是匹配的,那么移动到这个位置上,就有可能匹配上。当然例子中是故意写出可以找到的,实际情况不一定能马上匹配上。但是这样做,确实可以大大提升查找的效率。

明白了算法的出发点,那么代码理解起来就容易了。具体实现不在赘述。

下面是官方代码经过包装,变为C++的风格,自写自测可用,效果不错哦!

#ifndef BM_SEARCH_H
#define BM_SEARCH_H

#include<vector>
#include<string>
#include<stdio.h>
#include<string.h>

using namespace std;

class CBMSearch
{
public:
    CBMSearch(const char*);//参数为模式串
    int    searchPattern(const unsigned char*,int,vector<int>&);//寻找匹配串位置
private:
    void    calSuffixes();//用于辅助计算好后缀
    void    preGoodSuffixes();//计算好后缀的移动字节数
    void    preBadCode();//计算坏字符的移动字节数
private:
    string        m_strPattern;//模式串
    const    char*    m_pSource;//需要查找的内容
    vector<int>    m_vecSuff;
    vector<int>    m_vecGoodSuff;
    vector<int>    m_vecBadCode;
};

#endif
#include"CBMSearch.h"

CBMSearch::CBMSearch(const char* pszPattern):m_strPattern(pszPattern),
                         m_vecSuff(m_strPattern.length()),
                         m_vecGoodSuff(m_strPattern.length()),
                         m_vecBadCode(256)
{
    preGoodSuffixes();
    preBadCode();
}

void    CBMSearch::calSuffixes()
{
    int    nLastMatch =0,nFailedIndex =0;
    int    nPatternLen = m_strPattern.length();
    const char* x  = m_strPattern.c_str();

    m_vecSuff[nPatternLen -1] = nPatternLen;
    nFailedIndex = nPatternLen -1;

    for(int i= nPatternLen -2;i >= 0;--i)
    {
        if(i > nFailedIndex && m_vecSuff[nPatternLen-1-(nLastMatch-i)] < i - nFailedIndex)
            m_vecSuff[i] = m_vecSuff[nPatternLen-1-(nLastMatch-i)];
        else
        {
            if(i < nFailedIndex)
                nFailedIndex = i;

            nLastMatch = i;

            while( nFailedIndex >= 0 && 
                x[nFailedIndex] ==x[nPatternLen -1 -(nLastMatch - nFailedIndex)])
                nFailedIndex --;

            m_vecSuff[i] = nLastMatch - nFailedIndex;
        }
    }
}

void    CBMSearch::preGoodSuffixes()
{
    calSuffixes();

    int nPatternLen = m_strPattern.length();

    for(int i=0;i <nPatternLen;i++)
        m_vecGoodSuff[i] = nPatternLen;

    for(int i = nPatternLen -1;i >= 0;i--)
    {
        if(m_vecSuff[i] != i+1)
            continue;
        
        for(int j=0;j<nPatternLen -1-i;j++)
        {
            if(m_vecGoodSuff[j] == nPatternLen)
                m_vecGoodSuff[j] = nPatternLen -1 -i;
        }
    }

    for(int i = 0;i <= nPatternLen -2;i++)
        m_vecGoodSuff[nPatternLen - 1 - m_vecSuff[i]] = nPatternLen -(i+1);
}

void    CBMSearch::preBadCode()
{
    int    nPatternLen = m_strPattern.length();
    const char* x = m_strPattern.c_str();

    for(int i =0;i<256;i++)
        m_vecBadCode[i] = nPatternLen;

    for(int i=0;i< nPatternLen -1;i++)
        m_vecBadCode[x[i]] = nPatternLen - i -1;
}

int    CBMSearch::searchPattern(const unsigned char* pContent,int nTotalLen,vector<int>& vecResult)
{
    int    nCurrent =0;
    int    nPatternLen = m_strPattern.length();
    int    i = 0;
    const char* x = m_strPattern.c_str();

    while(nCurrent <= nTotalLen - nPatternLen)
    {
        for(i = nPatternLen -1; i >=0 && x[i] == pContent[i+nCurrent];i--);

        if(i < 0)
        {
            vecResult.push_back(nCurrent);
            nCurrent+= nPatternLen;
        }
        else
        {
            int    nTmp = m_vecBadCode[pContent[i+nCurrent]] -(nPatternLen -i -1);
            
            if(nTmp > m_vecGoodSuff[i])
                nCurrent+=nTmp;
            else
                nCurrent+=m_vecGoodSuff[i];
        }
    }
    
    return 0;
}

使用方式如下:
vector<int> ovecResult;//记录找到的位置
CBMSearch otest("ABBDD");
otest.searchPattern("CAFBADDBBDD",strlen("CAFBADDBBDD"),ovecResult);


宇恒斯
4 声望1 粉丝