比較BF、KMP和BM演算法的效能(純C語言實現,而且……VC6.0編譯的程式跑得比codeblocks 17.12編譯的快)

u25th_engineer發表於2018-08-14

       首先宣告:BF和KMP演算法是刁肥宅自己實現的,BM演算法源自此處,刁肥宅未曾妄加改動,只是作測試用。作業系統及硬體配置資訊如圖6所示,刁肥宅所用編譯環境為:Code::Blocks 17.12、VC6.0(完整綠色版)。所用原始碼與測試資料都已上傳到百度雲盤(提取密碼:dhu0)與CSDN“我的資源-下載”上,各位看官可以免費下載親測。

       話不多說,先貼出三個演算法的C語言實現:

一、演算法實現

1.BF模式匹配演算法

       標頭檔案BF.c:

/*Bf.h*/

#ifndef BF_H_INCLUDED
#define BF_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <malloc.h>

int Find( char *T, int T_Length,char *P, int P_Length );

#endif /* BF_H_INCLUDED*/

       原始檔BF.c:

/*BF.c*/

#include "BF.h"

int Find( char *T, int T_Length,char *P, int P_Length )
{
/*在目標串T中從第0個字元開始尋找模式串P在T中匹配的位置。若在T中找不到與
串P匹配的子串, 則函式返回-1, 否則返回P在T中第一次匹配的位置。*/
		int i, j, k;							/*last為在T中最後可比對位置*/
        /*printf( "In Find,T_Length = %d, P_Length = %d\n", T_Length, P_Length );*/
        /*for ( i = 0; i <= strlen(T) - strlen(P); i++)*/
        for ( i = 0; i <= T_Length - P_Length; i++  )/*逐趟比對*/
        {
			for ( k = i, j = 0; j < P_Length; k++, j++ )	/*從T.ch[i]開始與P.ch進行比對*/
          		if ( *( T + k ) != *( P + j ) )
                    break;	/*比對不等跳出迴圈*/
            if ( j == P_Length )
                return i + 1;			/*P已掃描完, 匹配成功*/
	   	}
	   	return -1;							/*匹配失敗*/
}

       原始檔test.c:

/*test.c*/

#include "BF.h"

int main()
{
    /*freopen("x1.out","w",stdout);*/
    srand( time(NULL) );
    char *T = NULL,*P = NULL;
    FILE *fp1 = fopen("x1.in","r");
    if( fp1 == NULL )
    {
        printf("Opening Error.\n");
        exit(EXIT_FAILURE);
    }
    int i,d,ChoosingKey = 0,Value = 0,T_Length,P_Length,RandomSize = 1000001430;
    T = ( char * )malloc( sizeof(char) * RandomSize );
    P = ( char * )malloc( sizeof(char) * 34 );
    /*strcpy(P,"wOvT5jN9154gQ751sK9eH1I1l0Hgf9d");*/

    strcpy(P,"fsf8QQCNlL80s1ouGx2TeANoH2jxx9SYQ");

    for( i = 0;!feof(fp1);i ++ )
		fscanf( fp1,"%c",(T + i) );

     P_Length = ( strlen(P) );
     T_Length = ( strlen(T) );

    /*
    for( i = 0;i < RandomSize;i ++ )
    {
        ChoosingKey = rand() % 3 + 1;
        switch(ChoosingKey)
        {
        case 1:
            Value = rand() % ( 90 - 65 + 1 ) + 65;
            *( T + i ) = (char)Value;
            break;
        case 2:
            Value = rand() % ( 122 - 97 + 1 ) + 97;
            *( T + i ) = (char)Value;
            break;
        case 3:

            Value = rand() % ( 57 - 48 + 1 ) + 48;
            *( T + i ) = (char)Value;
            break;
        default:
            *( T + i ) = 'Q';
        }
    }
    i = 0;
    while( *(T + i) != '\0' )
    {
        printf("%c",*(T + i));
        i ++;
    }
    */
    d = Find ( T,T_Length,P,P_Length );
    if( d == -1 )
        printf("匹配位置失敗!\n");
    else
        printf("匹配位置為%d\n",d);

    printf("Time used = %.2f seconds.\n",(double)clock() / CLOCKS_PER_SEC);
    system("pause");
    //return 0;
}

2.KMP模式匹配演算法

       標頭檔案KMP.h:

/*KMP.h*/

#ifndef KMP_H_INCLUDED
#define KMP_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <time.h>
#define DefaultSize 40


void GetNext ( char *P, int P_Length, int next[] );
int FastFind ( char *T, int T_Length,char *P, int P_Length,int next[ ] );

#endif /* KMP_H_INCLUDED*/

       原始檔KMP.c:

/*KMP.c*/

#include "KMP.h"

/*
/*P和T的長度必須以引數的形式從主函式傳遞給被呼叫的 GEtNext 和 FastFind 函式,
否則當T長度較大時匹配結果必然錯誤!!!或者d的返回值為 -1 (預設匹配失敗時的值)
在這兩個函式中,直接用 strlen( P ) 、strlen( T ) 求P、T的長度,且似乎主要是因為 strlen( T )
最終匹配結果錯誤。為什麼?!
*/

/*void GetNext ( char *P, int next[] )*/
void GetNext ( char *P, int P_Length, int next[] )
{
/*對模式串P, 計算next失配函式*/
	int j = 0, k = -1;
	next[0] = -1;	/*j已經在最左邊了,不可能再移動了,這時候要應該是i指標後移*/
	/*while ( j < strlen( P ) )
	直接用 strlen( P ) 求P的長度,最終匹配結果錯誤。為什麼?!
	*/
    while ( j < P_Length )/*計算next[j]*/
    {
		/*while ( k >= 0 && P->ch[j] != P->ch[k] )*/
		while ( k >= 0 && *(P + j) != *(P + k) )
            k = next[k];
		j++;
        k++;
		if ( *(P + j) == *(P + k) )
            next[j] = next[k];
		else
			next[j] = k;
	}
	/*printf( "In GetNext,strlen( P ) = %d\n",strlen( P ) );*/
}

/*int FastFind ( char *T, char *P, int next[ ] )*/
int FastFind ( char *T, int T_Length,char *P, int P_Length,int next[ ] )
{
/*在目標串T中尋找模式串P的匹配位置。若找到,則函式返回P在T中開始字元
下標,否則函式返回-1。陣列nex存放P的失配函式next[j]值。*/
	int j = 0, i = 0;				/*串P與串T的掃描指標*/
	/*while ( j < strlen( P ) && i < strlen( T ) )
	直接用 strlen( P ) 、strlen( T ) 求P、T的長度,最終匹配結果錯誤。為什麼?!
	*/
	while ( j < P_Length && i < T_Length )   /*對兩串掃描*/
    /*while ( j < 31 && i < 1000050 )*/
    {
		/*printf ( "i=%d, j=%d\n", i, j );*/
 		if ( j == -1 || *(P + j) == *(T + i) )/*對應字元匹配,比對位置加一*/
 		{
 		    j++;
            i++;
        }
 		else
            j = next[j];								/*第j位失配,找下一對齊位置*/
	}
    /*printf( "In FastFind,strlen( T ) = %d,strlen( P ) = %d\n",strlen( T ),strlen( P ) );*/
	/*if ( j < strlen( P ) )
	直接用 strlen( P ) 求P的長度,最終匹配結果錯誤。為什麼?!
	*/
	if ( j < P_Length )   /*j未比完失配,匹配失敗*/
    /*if ( j < 31 )*/
        return -1;
	else
        /*return i - strlen( P );		*/
        return i - P_Length + 1;   /*匹配成功*/
}

     原始檔test.c:

/*test.c*/

#include "KMP.h"

int main()
{
    /*freopen( "x1.out","w",stdout );*/
    srand( time(NULL) );
    char *T = NULL,*P = NULL;
    FILE *fp1 = fopen("x1.in","r");
    if( fp1 == NULL )
    {
        printf("Opening Error.\n");
        exit(EXIT_FAILURE);
    }
    int i,T_Length = 0,P_Length = 0,d = 0,ChoosingKey = 0,Value = 0,RandomSize = 1000001430;
    /*int Fail[10000120];*/
    int *Fail = (int *)malloc( sizeof(int) * DefaultSize );

    T = ( char * )malloc( sizeof(char) * RandomSize );
    P = ( char * )malloc( sizeof(char) * 34 );
    /*strcpy(P,"wOvT5jN9154gQ751sK9eH1I1l0Hgf9d");*/
    strcpy(P,"fsf8QQCNlL80s1ouGx2TeANoH2jxx9SYQ");


    P_Length = ( strlen(P) );



    for( i = 0;!feof(fp1);i ++ )
		fscanf( fp1,"%c",(T + i) );
    T_Length = ( strlen(T) );

    /*printf( "In main,T_Length = %d, P_Length = %d\n", T_Length, P_Length );
    system("pause");*/
    /*for( i = 0;i < 10000120;i ++ )
        Fail[i] = 0;*/
    memset( Fail,0,sizeof(Fail) );

    /*GetNext ( P, Fail );*/
    GetNext ( P, P_Length, Fail );
    /*d = FastFind ( T, P, Fail );*/
    d = FastFind ( T, T_Length, P, P_Length, Fail );

    if( d == -1 )
        printf("匹配位置失敗!\n");

    else
        printf("匹配位置為%d\n",d);/*-(d + 45) - 2*/

    /*Sleep(1000 * 60);*/
    printf("Time used = %.2f seconds.\n",(double)clock() / CLOCKS_PER_SEC);
    system("pause");

    /*return 0;*/
}

3.BM模式匹配演算法

       標頭檔案BM.h:

/*BM.h*/

#ifndef BM_H_INCLUDED
#define BM_H_INCLUDED

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>

int *MakeSkip(char* ptrn,int pLen);
int *MakeShift(char *ptrn,int pLen);
int BMSearch(char *buf,int blen,char *ptrn,int plen,int *skip,int *shift);


#endif // BM_H_INCLUDED

       原始檔BM.c:

/*BM.c*/

#include "BM.h"

/*
    函式:int* MakeSkip(char*,int)
	目的:根據壞字元規則做預處理,建立一張壞字元表
	      表的長度由字元的規模而定,
		  如果只有字母則長度只有26,
		  如果是字母加數字長度就是26+10
	引數:
		  ptrn=>模式串P
		  pLen=>模式串P長度
    返回:
	      int* - 壞字元表
*/
int *MakeSkip(char* ptrn,int pLen)
{
	int i;
	int len=pLen;
	char *p=ptrn;
	//為建立壞字元表,申請256個int的空間
	int *skip=(int*)malloc(256*sizeof(int));
	if(skip==NULL)
	{
		printf("malloc failed!");
		return 0;
	}
	//初始化壞字元表,256個單元全部初始化為pLen
	for(i=0;i<256;i++)
	{
		*(skip+i)=pLen;
	}
	//賦值,從左到右遍歷ptrn,這樣如果一個字元出現兩次,後面的覆蓋前面的,
	//不在模式中出現的字元不用再賦值,它們使用預設值pLen。
	while(pLen!=0)
	{
		*(skip+(int)*ptrn++)=--pLen;
	}
	return skip;
}
/*
	函式:int *MakeShift(char*,int)
	目的:根據好字尾原則做預處理,建立一張好字尾表
	引數:
		ptrn=>模式串P
		pLen=>模式串P的長度
	返回:
		int* :好字尾表
*/
int *MakeShift(char *ptrn,int pLen)
{
	//為好字尾表申請pLen個int的空間
	//這樣,第一個位置放置長度為1的字尾
	int *shift=(int *)malloc(pLen*sizeof(int));
	int *sptr=shift+pLen-1;//方便為好字尾表進行賦值的指標
	char *pptr=ptrn+pLen-1;//記錄好字尾表邊界位置的指標
	char c;
	//int i;
	if(shift==NULL)
	{
		fprintf(stderr,"malloc failed!");
		return 0;
	}
	c=*(ptrn+pLen-1);//儲存模式串中最後一個字元,因為要反覆用到它
	*sptr=1;//以最後一個字元為邊界時,移動距離設為1(因為要與壞字元規則比較,所以這個是個假設,1也是最小的移動距離)
	pptr--;//邊界移動到倒數第二個字元
	while(--sptr>=shift)//該最外層迴圈完成給好字尾表中的每一個單元進行賦值的工作
	{
		char *p1=ptrn+pLen-2,*p2,*p3;
		//該do...while迴圈完成以當前pptr所指向的字元為邊界時,要移動的距離
		do
		{
			while(p1>=ptrn&&*p1--!=c);//該空迴圈,尋找與最後一個字元c匹配的字元所指向的位置
			if(p1<ptrn)
			{
				*sptr=pLen;
				break;
			}
			p2=ptrn+pLen-2;
			p3=p1;
			while(p3>=ptrn&&*p3--==*p2--&&p2>=pptr);//該空迴圈,判斷在邊界內字串匹配到什麼位置
			if(p2<pptr)
			{
				if(*++p3==*++p2)
				{
					*sptr=p2-p3;
					break;
				}
				if(*p2==*p3)
				{
					continue;
				}
			}
			if(p3<ptrn)
			{
				*sptr=p2-p3;
				break;
			}
		}while(p3>=ptrn);
		pptr--;//邊界繼續向前移動
	}
	return shift;
}
/*
	函式:int* BMSearch(char*,int ,char*,int,int*,int*)
	目的:判斷文字串是否包含模式串P
	引數:
		buf->文字串T
		blen->文字串T長度
		ptrn->模式串P長度
		plen->模式串P長度
		skip->壞字元表
		shift->好字尾表
	返回:
		int->1表示成功(文字串包含模式串),0表示失敗(文字串不包含模式串)
*/
int BMSearch(char *buf,int blen,char *ptrn,int plen,int *skip,int *shift)
{
	int b_idx=plen;
	if(plen==0)
	{
		return 1;
	}
	while(b_idx<=blen)//計算字串是否匹配到了盡頭
	{
		int p_idx=plen,skip_stride,shift_stride;
		int i=0;
		int temp=b_idx;//是為了不改動b_idx的值,b_idx將來用於計算移動的距離
		while(buf[--temp]==ptrn[--p_idx])//開始匹配
		{
			i++;
			if(p_idx==0)
			{
				fprintf(stderr,"match at %d!\n",b_idx - plen + 1);
				return 1;
			}
		}
		/*printf("i:%d\tbad:%c\n",i,buf[temp]);*/
		skip_stride=skip[(unsigned char)buf[temp]]-i;//根據壞字元規則計算跳躍的距離
		shift_stride=shift[p_idx];
		/*printf("b_idx:%d   1:%d   2:%d\n",b_idx,skip_stride,shift_stride);*/
		b_idx+=(skip_stride>shift_stride)?skip_stride:shift_stride;//取最大者
	}
	return 0;
}

       原始檔test.c:

/*test.c*/

#include "BM.h"

int main()
{
    FILE *fp1 = fopen( "E:/document/²âÊÔÊý¾Ý/100503.in","r" );
    if( !fp1 )
    {
        printf( "Opening data failed.\n" );
        Sleep( 1000 * 60 );
        exit( EXIT_FAILURE );
    }
    int BLength = 100503,PLength = 60,i;
	char *buf = ( char * )malloc( sizeof(char) * BLength );
	char *ptrn = ( char * )malloc( sizeof(char) * PLength );

	for( i = 0;!feof(fp1);i ++ )
        fscanf( fp1,"%c",(buf + i) );
    /*strcpy(ptrn,"6qc48TlVIvb482XM07Y4isP6X89a7WYi948579f1HR1Avsp2Qok5n2T0z9I");*/
    strcpy(ptrn,"fsf8QQCNlL80s1ouGx2TeANoH2jxx9SYQ");

	int *skip = NULL;
	int *shift = NULL;


	fprintf(stderr,"plen=%d!\n",strlen(ptrn));

	skip=MakeSkip(ptrn,strlen(ptrn));
	shift=MakeShift(ptrn,strlen(ptrn));
	BMSearch(buf,strlen(buf),ptrn,strlen(ptrn),skip,shift);
	/*printf( "%d\n",strlen("6qc48TlVIvb482XM07Y4isP6X89a7WYi948579f1HR1Avsp2Qok5n2T0z9I") );*/
	/*return 0;*/
	printf("Time used = %.2f seconds.\n",(double)clock() / CLOCKS_PER_SEC);
	system( "pause" );
}

4.生成測試用隨機字串C程式碼

/*生成測試用隨機字串*/

#include <stdio.h>
#include <stdlib.h>

int main()
{
    freopen("10000000050.out","w",stdout);
    srand( time(NULL) );
    char *T = NULL,*P = NULL;

    int i,d,ChoosingKey = 0,Value = 0,RandomSize = 10000000050;
    T = ( char * )malloc( sizeof(char) * RandomSize );
    /*P = ( char * )malloc( sizeof(char) * 32 );
    strcpy(P,"wOvT5jN9154gQ751sK9eH1I1l0Hgf9d");*/

    for( i = 0;i < RandomSize;i ++ )
    {
        ChoosingKey = rand() % 3 + 1;
        switch(ChoosingKey)
        {
        case 1:
            Value = rand() % ( 90 - 65 + 1 ) + 65;
            *( T + i ) = (char)Value;
            break;
        case 2:
            Value = rand() % ( 122 - 97 + 1 ) + 97;
            *( T + i ) = (char)Value;
            break;
        case 3:

            Value = rand() % ( 57 - 48 + 1 ) + 48;
            *( T + i ) = (char)Value;
            break;
        default:
            *( T + i ) = 'Q';
            break;
        }
    }
    i = 0;
    while( *(T + i) != '\0' )
    {
        printf("%c",*(T + i));
        i ++;
    }
    /*printf("\n%s",P);*/
    return 0;
}

二、測試結果

       一言難盡,暫且放下結論,改天詳談,演算法執行效率:BM>BF>KMP。而且刁肥宅發現,VC6.0編譯的程式跑的結果耗時永遠比CodeBlocks 17.12編譯的短

       有圖有真相,各位可以去下載刁肥宅上傳的原始碼與測試資料,自行驗證。

圖1 CodeBlocks編譯BF、KMP與BM對比結果

        

圖2 VC6.0編譯BF、KMP與BM對比結果
圖3 CodeBlocks與VC6.0編譯BF對比結果
圖4 CodeBlocks與VC6.0編譯KMP對比結果
圖5 CodeBlocks與VC6.0編譯BM對比結果
圖6 作業系統及硬體配置

 

相關文章