Shift And/Or字符串匹配算法
Shift-And算法是一种基于前缀的单字符串匹配算法,采用位运算。其算法思想比KMP简单得多。
在最简单的brute force算法中,在文本串的每个位置都要进行m(模式串长度)次比较,而SHIFT AND算法则是利用位运算提高这个过程。现在计算机的字长一般为32,64位也开始流行了。一次比较的值为true or false,只需要一位即可存储,所以计算机可以在一次运算里完成位长次的比较。通过此思路可以把brute force的速度提高位长倍。
Shift-And算法思想:设模式字符串为P,文本为text。它主要通过维护一个字符串集合D(D中记录了P中所有与当前已读text的某个后缀相匹配的前缀),集合D中的每个字符串既是模式串p的前缀,同时也是已读入文本的后缀,每当从text中读入一个新的字符,算法立即利用位并行机制来更新集合D。
我们可以这么具体理解:
- 设P长度为m,则集合D可表示为D = dm…d1 而用D[j]代表dj;
- D[j]=1,当且仅当p1…pj 是 t1…ti 的某个后缀;
- 当D[m]=1时,就认为P已经于text匹配;
- 当读入下一个字符 ti+1, 需要计算新的集合 D′;
- 当且仅当D[j]=1并且 ti+1等于pj+1时D'[j+1]=1。这是因为D[j]=1时有 p1…pj是 t1…ti 的一个后缀,而当ti+1 等于 pj+1可推出p1…pj +1是 t1…ti+1 的一个后缀。这个集合可通过位运算来更新。
Shift-and算法首先建立一个数组B, 数组长度为字符集长度,B的第j位为1,表示则表示源串的i+1位与模式串的j位相同。设集合为D,则作位运算,((D<<1) + 1) & B即可得到新的D。
1 int ShiftAndMatch(byte* pSrc, int nSrcSize, byte* pSubSrc, int nSubSrcSize) 2 { 3 long skip[256]; 4 memset(skip, 0, sizeof(skip)); 5 for (int i = 0; i < nSubSrcSize; i++) 6 { 7 skip[ pSubSrc[i] ] |= (0x01 << i); 8 } 9 10 long mask = 0x01<<(nSubSrcSize-1); 11 long d = 0; 12 int nPos = 0; 13 while(nPos <= nSrcSize - nSubSrcSize) 14 { 15 d = ((d<<1)|0x01) & skip[pSrc[nPos]]; 16 if (d & mask) 17 { 18 break; 19 } 20 nPos++; 21 } 22 return nPos - (nSubSrcSize - 1); 23 }
Shift-Or算法是Shift-And的一种技巧性的改进实现,其算法思想跟Shift-And类似,只是在通过对位取反以去掉公式中的掩码0m-11,这样减少了位运算的次数,从而实现加速。Shift-Or作的修改是,使用反码表示B中的位掩码和位向量,即用0表示一个数在集合里,1表示不在,所以将
D = ((D<<1) | 1) & B[s[i]];
修改为
D=D<<1 | B[s[i]];
这样就省了一次位运算,当然B和D的初始化的时候,也要作相应的修改
1 int ShiftOrMatch(byte* pSrc, int nSrcSize, byte* pSubSrc, int nSubSrcSize) 2 { 3 long skip[256]; 4 memset(skip, -1, sizeof(skip)); 5 for (int i = 0; i < nSubSrcSize; i++) 6 { 7 skip[pSubSrc[i]] ^= (0x01 << i); 8 } 9 10 long mask = ~(0x01 << (nSubSrcSize - 1 )); 11 long d = -1; 12 int nPos = 0; 13 while(nPos <= nSrcSize - nSubSrcSize) 14 { 15 d = (d<<0x01) | skip[pSrc[nPos]]; 16 if (~(d|mask)) 17 { 18 break; 19 } 20 nPos++; 21 } 22 return nPos - (nSubSrcSize - 1); 23 24 }