最近在写一个搜索字符串的用例,刚好使用到这个算法,在网络上学习了多篇文章,在这里记录一下自己的理解。
首先理清楚算法的原理:
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);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。