<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"><strong>BF算法 </strong></span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"><span style="white-space:pre"> </span>在字符串匹配中,最简单、最原始的算法就是BF(Brute Force)算法。BF算法的基本思想就是:将目标串的第一个字符和模式串的第一个字符比较,若相等,则依次比较目标串和模式串的下一个字符,直到模式串的结束位置;否则,将模式串的第一个字符和目标串上次开始比较位置的下一个位置的字符进行比较。重复以上过程,一直到在目标串中找到模式串或者到目标串结束。</span>
源码如下:
char* BFMatch(const char* str,const char* pattern) { char* result = 0; if(!str || !pattern) return result; unsigned int strLen = strlen(str); unsigned int patLen = strlen(pattern); strLen -= patLen; unsigned int j; for(unsigned int i = 0; i <= strLen; ++i) { for(j = 0; j < patLen; ++j) { if(pattern[j] != str[i + j]) break; } if(j == patLen) { result = (char*)str + i; } } return result; }很明显,BF算法的时间复杂度为0(strlen(str) * strlen(pattern)),空间复杂度O(1)。同时在上面比较过程中重复比较次数较多,没有用上历史的比较信息。为了改进上面的缺陷,就有了下面的KMP算法。
KMP算法
在S=”abcabcabdabba”中查找T=”abcabd”,如果使用KMP匹配算法,当第一次搜索到S[5] 和T[5]不等后,S下标不是回溯到1,T下标也不是回溯到开始,而是根据T中T[5]==’d’的模式函数值,直接比较S[5] 和T[2]是否相等,因为相等,S和T的下标同时增加;因为又相等,S和T的下标又同时增加,最终在S中找到了T。如图:
第一趟匹配:
第二趟匹配:
此时又该如何确定当失配时,模式串的移动距离呢?下面将说明如何求next数组,以及为什么目标串不需要回溯。
假设目标串str从位置i开始和模式串pattern比较,在位置j(j >= i)处,模式串和目标串不匹配,即pattern[j - i - 1] != str[j]。此时,str[i,j-1] = pattern[0,j - i-1]成立。
假设存在k使pattern[0,j - i - k - 1] = pattern[k ,j-i-1]成立,并且j - i - k最大,则此时可以将pattern[k]和str[j]进行比较(为什么目标串不需要回溯呢?反证法证明)假设从目标串第i + x(x < k)处开始可能存在着和模式串完全匹配的子串,则str[i + x,j - 1] = pattern[0, j - i - x - 1] = str[i,j - i - x] = pattern[x ,j - i - 1],由于j - i - k > j - i - x,所以k < x,这与假设矛盾。注:这里不考虑k < x的原因是因为处于x位置的情况后面将要考虑,但处于k前面的位置将要被我们跳过,但要证明被跳过的位置是正确的,所以只考虑k前面的位置。
以上证明说明了我们只要找到最大的k,使pattern[0.k - 1] = pattern[j - k,j - 1]即可,此时k值为子串pattern[0,j-1]最长的相等前缀和后缀的长度。如何计算next呢?这里采用了一个技巧,根据前一次的的结果来计算。当i - 1位置的最大k值为j,即next[i - 1] = j,那么next[i]的计算就可以根据next[i - 1]来计算,当pattern[i - 1] = pattern[j]时,由next[i - 1] = j,可知pattern[0,j - 1] = pattern[i - j ,i - 2],所以pattern[0,j] = pattern[i - j - 1,i - 1],因此next[i] = j + 1;当pattern[i - 1] != pattern[j]时,此时需要算pattern[next[i - 1]]与pattern[i-1]的关系。代码如下:
void GetNext(const char* pattern,int len,int *next) { if(!pattern || !next) return ; next[0] = -1; int j = -1; int i = 0; while(i < len) { if(j == -1 || pattern[i] == pattern[j]) { ++i; ++j; next[i] = j; } else j = next[j]; } }当求出next数组时,那就可以进行字符串匹配了,
<pre name="code" class="cpp">char* GetKMPMatch(const char* str,const char* pattern) { char* result = 0; if(!str || !pattern) return result; int patLen = strlen(pattern); int strLen = strlen(str); int* next = (int*)malloc(sizeof(int) * (patLen + 1)); if(!next) return result; int i = 0; int j = -1; while(i < strLen && j < patLen) { if(j == -1 || str[i] == pattern[j]) { ++i; ++j; } else { j = next[j]; } } if( j >= patLen) result = (char*)str + i - patLen; free(next); return result; }
求next数组的改进我们再看一种情况,str = "aaabaaaab",pattern = "aaaab",利用上述方法则求的next数组为next[] = {-1,0,1,2,3},当i = 3,j = 3时,此时匹配失效,并且按照next数组此时需要三次比较,而实际情况只需i = 4,j = 0比较即可。当pattern[i] = pattern[j]时,当匹配时,pattern[i]失效,则j = next[i]时也会失效。
void GetNext(const char* pattern,int len,int *next) { if(!pattern || !next) return ; next[0] = -1; int j = -1; int i = 0; while(i < len) { if(j == -1 || pattern[i] == pattern[j]) { ++i; ++j; if(pattern[i] != pattern[j]) next[i] = j; else next[i] = next[j]; } else j = next[j]; } }