csdn_export_md

kmp算法和BM算法在字符串匹配中的性能比较

算法特点介绍

kmp算法:因为在bf蛮力算法中,需要嵌套两层循环。但是在匹配过程中,如果遇到失配字符是在模式字符串的中部,且失配前的一部分字符串刚好和模式字符串最前面的字符串相同,那么就可以利用失配前的匹配部分,直接将模式字符串后移,省掉模式字符串最前面部分的匹配工作。

例如:
asdfghuiyiuhljkyuiasdfghuuuuuu
当模式匹配进行到右侧的"asdfghuu"时,若发生失配,也可以确定原始字符串中有"asdfghu",这样模式字符串的前七个字符就不需要进行比较,原始字符串必定与之匹配。
通过记录模式字符串中与前缀字符串相同的关联关系,就可以在模式匹配中利用之前比较的结果,从而减少比较的次数。
建立与模式字符串等长的next数组,每次失配后访问对应的next中存储的位置。

  void GetNext(char* t, int Next[])
{
int j= 1,k= 0; Next[1]= 0; 
while ( j < t[0] )
if (k==0||T[j]==T[k]){ 
   j++;
  k++;
  Next[j]= k;
}
else    k=Next[k];
}
//时间复杂性:O(m)
也可以看出,KMP算法只适合模式字符串中容易出现重复的情况。也就是字符集比较小,模式字符串较长的情况。
BM算法:在KMP算法中,如果总是在模式字符串的后半部分失配,即使每次失败后能右移多位,但是每次匹配仍然耗时较长。

BM算法有两种策略,BC(bad character)和GS(good suffix-shift)。

  • BC策略是判断在失配时失配字符下一个可能进行匹配的位置。
  1. 若模式字符串的前部分没有该失配字符,则可以直接将模式字符串整体右移到失配字符右侧;
  2. 如果模式字符串中有该字符,

如果失配字符在模式字符串中出现多次,将模式字符串右移,则取其中最靠右者。

如果模式字符串中失配字符的最右侧的位置过于靠右,使得位移值为负,则将模式字符串向右移动一位。

  • GS策略是指,以后缀中多个字符为单位进行跳跃,因为如果匹配了多个字符后发生失配,所有已匹配的部分都可以最大化利用,这样即使失配,原始字符串中已匹配的部分也会被下一次匹配利用。避免了失配字符在匹配后半段造成的时间浪费。
    例如:
    “sdjfgjashdgfasdfgaksdgfuiweasdfasdfg
    当后半部分的"fasdfg"匹配结束后发生了失配,原始字符串的”fasdfg"部分若想要匹配成功,最可能的就是出现在模式字符串的"fasdfg"部分。

BM算法结合BC策略和GS策略从后向前进行匹配,如果出现失配字符的最右侧过于靠右,则优先使用GS表进行右移;如果出现失配字符的最右侧在左边,则选取BC表和GS表中的最大值进行右移。

测试用例生成

使用rand32()来进行随机数的生成,分别生成长度为100、1000、10000的字符串。
随机字符串取三种取值,(0,1)串,(0-9)串和(33-125)ASCII码字符串。
代码如下

int  seed;
int rand1() { return(((seed = seed * 214013L + 2531011L) >> 16) & 0x7fff); }
int rand32() {
    return ((rand1() << 16) + (rand1() << 1) + rand1() % 2);
}
void getRand(int length,int rangeStart,int rangeEnd)
{
    
    ofstream out("rand.txt", ios::app);//app表示每次操作前均定位到文件末尾
    for (int i = 0; i < length; i++)
    {
        out<<rand32() % (rangeEnd - rangeStart) + rangeStart;
    }
    out<<endl;
    out.close();
 
}
void getRand(int length,int rangeStart,int rangeEnd,bool character)
{
     
    ofstream out("rand.txt", ios::app);//app表示每次操作前均定位到文件末尾
    for (int i = 0; i < length; i++)
    {
        out<<char(rand32() % (rangeEnd - rangeStart) + rangeStart);
    }
    out<<endl;
    out.close();
 
}


int main()
{
    seed = 354;
    getRand(100,0,2);
    getRand(1000,0,2);
    getRand(10000,0,2);

    getRand(100,0,10);
    getRand(1000,0,10);
    getRand(10000,0,10);
    
    getRand(100,33,126,true);
    getRand(1000,33,126,true);
    getRand(10000,33,126,true);

    return 0;
}

生成的rand.txt文件中有9条符合条件的字符串,对于每个字符串,手动挑选前、中、后三个子字符串作为模式字符串,进行模式匹配。每次读取一个原始字符串和一个子字符串进入循环。
具体测试性能代码如下:

ifstream MyInput("rand.txt");
    string a, b;    
    while (!MyInput.eof()){

        MyInput >> a >> b;

#ifdef SHOW_INFO
        //#include <windows.h>  //记录时长
        LARGE_INTEGER Bmt1, Bmt2,Kmpt1,Kmpt2, tc;
        QueryPerformanceFrequency(&tc);
        QueryPerformanceCounter(&Bmt1);

#endif // SHOW_INFO
//BM执行!!!
        int bm = BM(a, b);

#ifdef SHOW_INFO
        QueryPerformanceCounter(&Bmt2);     //程序结束用时
        BM_TIME = (double)(Bmt2.QuadPart - Bmt1.QuadPart) * 1.0 / tc.QuadPart;
#endif // SHOW_INFO

#ifdef SHOW_INFO
        QueryPerformanceCounter(&Kmpt1);
#endif // SHOW_INFO
//KMP执行!!!
        int kmp = indexKMP(b, a);
        
#ifdef SHOW_INFO
        QueryPerformanceCounter(&Kmpt2);        //程序结束用时
        KMP_TIME = (double)(Kmpt2.QuadPart - Kmpt1.QuadPart) * 1.0 / tc.QuadPart;
#endif // SHOW_INFO

//输出结果
        printf("BM算法的结果为:%d\nKMP算法的结果为:%d\n", bm,kmp);
#ifdef SHOW_INFO
        cout << "BM比较次数为:" << BM_COMPARE << endl<< "BM花费时间为:" << BM_TIME << endl;
        cout << "KMP比较次数为:" << KMP_COMPARE <<endl<< "KMP花费时间为:" << KMP_TIME << endl;
        KMP_COMPARE = 0;
        BM_COMPARE = 0;
#endif // SHOW_INFO

        
    }
    MyInput.close();

全部代码见BM.cpp和随机数生成.cpp

测试

字符串种类字符串长度匹配结果KMP耗时(单位0.1ms)BM耗时(单位0.1ms)KMP比较次数BM比较次数
0,1串10011.3996e-52.14605e-51111
0,1串100484.99198e-56.11157e-56163
0,1串100857.74443e-57.04463e-59767
0,1串1000260.0002202030.000170284130141
0,1串10004960.0003181750.000612556595879
0,1串10008160.0006475460.0009186019001225
0,1串1000013520.001386070.002525822551626
0,1串1000050610.003139290.0058633759808416
0,1串1000085820.005284870.0048985893727777
0-9串10062.00609e-53.63895e-5205
0-9串100373.91887e-53.40568e-55020
0-9串10001310.0005495740.00032190721088
0-9串10003950.000254260.000152556474125
0-9串10007110.0004110140.000197809847212
0-9串1000018720.00115280.0007543822425777
0-9串1000048520.003160290.0016977155881481
0-9串1000095050.004657850.0014868498211034
33-126的符号串10048.8641e-61.21298e-51411
33-126的符号串100397.83773e-57.46451e-5484
33-126的符号串100803.63895e-52.00609e-58818
33-126的符号串1000768.30426e-58.67749e-514773
33-126的符号串10004470.0003923530.0001250350975
33-126的符号串10008760.0006447470.00013249593179
33-126的符号串100002970.0001968760.000123164399108
33-126的符号串1000040340.004025240.0010744250201043
33-126的符号串1000089270.006693330.0007119279070313

结论

  1. 当模式字符串中字符种类较少时,KMP算法略微优于BM算法,两种算法比较次数都较多。
  2. 当原始字符串较长时,BM算法明显优于KMP算法,KMP算法比较次数随原始字符串长度增长较快。
  3. 当模式字符串中字符种类比较多时,BM算法明显优于KMP算法,比较次数增长较慢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值