[基于子串搜索的方法] BNDM算法
BNDM算法的搜索方法与BDM算法相同,但它使用了位并行来识别子串。
与原始的BDM相比,BNDM更简单,内存用量更少,具有更好的引用局部性,并且易于扩展到更复杂的模式串的情形。
在当前搜索窗口内,设已读入的字符串为u,BNDM算法维护一个集合,记录u在prv中的所有出现位置。同shift—and算法一样,该集合可以用一个位向量D来表示。如果子串pj...pj+|u|-1等于u,那么D的第m-j+1位是1,表示p的位置j是一个活动状态。
当读入一个新的文本字符σ时,要从D更新到D'。D‘的一个活动状态j对应于σu在模式串中的一个起始位置,也就是说:
* u出现在模式串的位置j+1,即D的第j+1位是活动的;
*σ在模式串的位置j处出现。
同Shift-And算法一样,BNDM算法预先计算一张表B,表B用一个位掩码记录了该字符在p的出现位置。那么利用如下公式,就可以从D更新到D':
D' <- (D<<1) & B[σ] ;
然而,初始化的时候有些问题需要注意。在初始化D的时候,可能会将D初始化为1m ,这表示模式串的每个位置都能与空串相匹配。如果这样的话,第一次位移将会得到(D<<1) = 1m-10,这样会丢失一个子串。最简单的方法是将D的大小设为m+1,并初始化为1m+1 。然而,这样会将搜索的最大模式串长度限制在w-1以为。为了解决这个问题,可以将以上的公式拆分为两个部分。
第一部分进行操作 D1’ <- D & B[σ]并且验证可能的匹配;第二部分进行位移 D‘ <- D’1 <<1。这里使用的初始化是D = 1m 。如果D’1的位置dm 是活动的,那么已读入的文本字符串也是p的前缀。
BNDM与BDM使用的搜索方法是一样的,区别在于前者用为并行技术进行子串搜索,而后者使用后缀自动机。每当比特位dm 是活动的时候,窗口中的当前位置就用变量last保存。
我的BNDM算法c++版实现如下:
1 void BNDM(char *p,char * t) 2 { 3 int Pstr_len,Tstr_len,i,last,pos,D; 4 int B[128]; 5 Pstr_len = strlen(p); 6 Tstr_len = strlen(t); 7 8 //Preprocessing 9 for(i=0;i<128;i++) 10 { 11 B[i] = 0; 12 } 13 for(i=0;i<Pstr_len;i++) 14 { 15 B[p[i]] = B[p[i]] | (1<<(Pstr_len-i-1)); 16 } 17 18 //Searching 19 pos = -1; 20 cout<<Tstr_len-Pstr_len<<endl; 21 while(pos<=(Tstr_len-Pstr_len)) 22 { 23 i = Pstr_len; 24 last = Pstr_len; 25 D = ~0; 26 27 while(D != 0) 28 { 29 D = D & B[t[pos+i]]; 30 i--; 31 32 if((D&(1<<(Pstr_len-1))) != 0 ) 33 { 34 35 if(i>0) 36 { 37 last = i; 38 } 39 else 40 { 41 cout<<" match at "<<pos+1<<endl; 42 } 43 } 44 D = D << 1; 45 } 46 pos = pos +last; 47 } 48 }
由实验给出,当字母表小于英文字母表且机器字长大于8位时BNDM算法体现出较高的效率。但并不代表其在处理扩展字符串时的能力。