日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

KMP、BM、Sunday、Horspool、strstr字符串匹配算法的性能比较

發布時間:2024/3/26 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 KMP、BM、Sunday、Horspool、strstr字符串匹配算法的性能比较 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

KMP、BM、Sunday、Horspool、strstr字符串匹配算法的性能比較

一、簡介

簡介:字符串匹配算法,顧名思義,在一個給定的字符文本內搜尋出自己想要找的一個字符串,平常所用的各種文本編輯器里的ctrl+F大多就是使用的這些字符匹配算法。本文通過用c語言實現4種比較受歡迎的字符匹配算法,在同一文本下搜尋同一關鍵字符串,用來和c庫函數strstr作時間上的性能比較,同時也對實現的4種算法做對應比較。各個算法名稱如下:(對各個算法詳細原理感興趣的伙伴自行去查詢,下面只做簡要介紹,文中的T都代表文本串,即要搜索的范圍,P代表要搜索的目標串,紅色代表失配,黃色代表一些P串和T串中相同的字符)

1、strstr():c語言的庫函數

函數原型:char* strstr(char* str1,char* str2);//包含在<string.h>中

原理簡述:暴力匹配,從左到右依次匹配。

文本串T

A

B

C

F

D

L

K

???????

模式串P

K

L

F

C

F

?????????

第一次移動

K

L

F

C

F

????????

第二次移動

K

L

F

C

F

???????

2、KMP(Knuth-Morris-Pratt)算法:

原理簡述:KMP相對strstr做了一些改進,它和strstr一樣,都是從左到右依次匹配,當出現失配時,比如,文本串T是“ABCGHABDHKLOEM,”,模式串P是“ABCGHABCF”,當匹配到第八個字符“C”時失配,因為第一、二字符是“AB”,與當前失配字符“C”(第八個)的前兩個字符剛好是一樣的,于是將整個P串向右移動5個位置,使得第一、二個字符“AB”與文本串T中的"AB"相對,然后繼續從P串的第三個字符“C”往后匹配(注意,與當前失配字符的前1個以上字符相等的字符必須是從P串第一個字符開始的,否則不算是滿足規則,當不存在時,與strstr一樣,只往前移動一個位置)。

文本串T

A

B

C

G

H

A

B

D

H

K

L

O

E

M

?

模式串P

A

B

C

G

H

A

B

C

F

??????

第一次移動

開頭有相同前綴,

A

B

C

G

H

A

B

C

F

?

第二次移動

開頭無相同前綴

A

B

C

G

H

A

B

C

F

實現代碼如下:

/* function:KMP的next數組求解(預處理) Param: @p 需要匹配的字符串 @next 需要匹配的字符串對應的next數組 */ void KMPPre(char* p, int KMP_next[]) {int pLen=strlen(p);KMP_next[0]=-1;int k=-1;int j=0;while(j<pLen-1){//p[k]表示前綴,p[j]表示后綴 if(k==-1||p[j]==p[k]){++j;++k;if(p[j]!=p[k])KMP_next[j]=k;else if(KMP_next[k]!=-1)//若是k是0的話,它的next[0]是-1,KMP_next[j]=KMP_next[k];elseKMP_next[j]=0;}else{k=KMP_next[k];}} } /* function:KMP字符匹配算法 Param: @s 文本內容 @sLen 文本內容長度 @p 需要匹配的字符串 @pLen 需要匹配的字符串長度 @next[] 輔助數組 */ char* KmpSearch(char* s,int sLen,char* p,int pLen,int next[]) {int i=0;int j=0;while(i<sLen&&j<pLen){//①如果j=-1(代表又回到了P串的開頭第一個字符,因為next[0]=-1),或者當前字符匹配成功(即S[i]==P[j]),都令i++,j++ if(j==-1||s[i]==p[j]){i++;j++;}else{//②如果j!=-1,且當前字符匹配失敗,則令i不變(當前s串失配的地方),j=next[j] //next[j]即為j所對應的next值(其實就是和它含有相同前綴的地方,比如P為"AFHKAFOIU",則next[6]=2,即第7個字符“O”的前兩個字符"AF"(第五、六個字符)有相同前綴"AF"(第一、二個字符))j=next[j];}}if(j==pLen){return &s[i-j];}else{return NULL;} }

總結:原理不難理解,但是我們也發現了,KMP的匹配規則是和模式串P的內容有關系的,特別是對那種有大量重復字符的字符串有很大幫助,基于這個匹配規則,每次匹配一個模式串P時,都要相應生成一個輔助數組(人們都習慣成為next數組),這個數組記錄著與模式串P中每個字符失配時需要移動的位置數有關的值。具體計算在此不做詳細介紹,需要了解的伙伴自行查詢。所有的實現代碼在上面已給出。

3、BM(Boyer-Moore)算法:

原理簡述:BM算法有兩個匹配規則,一個是壞字符規則,另一個是好后綴規則。匹配順序是從右向左(即從模式串p的最后一個字符開始匹配,然后依次向左匹配)。

(1)、壞字符規則:當失配時,若模式串p中當前失配字符的左半部分存在文本串T當前失配字符時,則將模式串整體向右移動,使兩個串的相等字符對應匹配,然后又開始從右向左匹配,若模式串p中存在多個該字符時,使用最靠右的一個字符。

如下表a,從右向左匹配,即從P串的最后一個字符"D"開始匹配,由于P串的"D"和T串的"F"不相等,故在P串中找"F",剛好P中有"F",取最靠右的字符"F",所以P串整體向右移動兩個位置,使得P中的"F"和T中的"F"對上,然后又從最后一個字符開始匹配。

a、當失配字符在P串的最右端時

文本串T

A

B

C

F

O

U

P

K

M

模式串P

F

F

C

D

?

?

?

?

?

移動后

F

F

C

D

???

b、當失配字符在P串的中部位置時,

文本串T

A

B

C

F

O

U

P

K

M

模式串P

C?

F

D

F

?

?

?

?

?

移動后

C

F

D

F

???

c、若失配時,不存在相同字符,則P串向右移動strlen(P)個位置。

文本串T

A

B

C

F

O

U

P

K

M

模式串P

A

L

C

D

?

?

?

?

?

移動后

??

A

L

C

D

?

實現代碼如下:

/* function:求解壞字符數組 Param: @pattern 需要匹配的字符串 @bmBc 壞字符數組 @m 需要匹配的字符串長度 @ */ void PreBmBc(char *pattern,int m,int bmBc[]) {int i;for(i=0;i<256;i++){//一個字符占八位,共256個字符,把所有字符都覆蓋到,這里的初始化是將所有字符失配時的移動距離都賦值為mbmBc[i]=m;}for(i=0;i<m-1;i++){//針對模式串pattern中存在的每一個字符,計算出它們最靠右的(非最后一個字符)地方距離串末尾的距離,即它們失配時該移動的距離,這一操作更新了初始化中一些字符的移動距離bmBc[pattern[i]]=m-1-i;} }

(2)、好后綴規則:當已經有部分字符匹配通過,然后遇到失配時,處理方法將會有變化。

若P串中當前失配位置的左半部分(前綴)存在與右半部分相等的子串(即以當前失配字符為邊界的右半后綴)或者后綴的子串時,P串將整體向右移動,使得最靠右的該子串(可能存在多個)和T串的相應子串相對,然后重新從最右的字符開始匹配。若左半前綴不存與后綴相同的字符串或者后綴的子串時,P串整體向右移動strlen(P)個位置。(注:若只存在子串時必須是從最左第一個字符往后才算是后綴的子串,如AFCDAF中,第一、二個字符“AF”算是以C為分界的后綴“DAF”的子串,但是對于KAFCDAF來說,同樣以C為邊界時,前綴中不存在后綴DAF的子串,第二、三個字符AF并不算它的子串,因為第一個字符K和D都不相等了,自然和T串中的已經匹配的“D”也不等,故不用做多余的比較操作了,應該直接跳過)。

a、存在與后綴相同的前綴時;(可在最左)

文本串T

A

B

C

F

A

F

P

K

M

O

L

模式串P

A

F

C

D

A

F

?

?

?

?

?

“F”!=”D”,執行移動,按前綴往前移動

文本串T

A

B

C

F

A

F

P

K

M

O

L

模式串P

?

?

?

?

A

F

C

D

A

F

?

存在與后綴相同的前綴時;(也可不在最左)

文本串T

K

A

B

C

F

A

F

P

K

M

O

L

模式串P

A

A

F

C

D

A

F

?

?

?

?

?

“F”!=”D”,執行移動,按前綴往前移動

文本串T

K

A

B

C

F

A

F

P

K

M

O

L

模式串P

?

?

?

?

A

A

F

C

D

A

F

?

b、存在與后綴子串相同的前綴時;(下表中P串以"D"為邊界,后綴為"FAF","AF"屬于子串,必須從最左開始)

文本串T

A

B

C

F

A

F

P

K

M

O

L

模式串P

A

F

D

F

A

F

?

?

?

?

?

“C”!=”D”,執行移動,按前綴(后綴子串)往前移動

文本串T

A

B

C

F

A

F

P

K

M

O

L

模式串P

?

?

?

?

A

F

D

F

A

F

?

c、與后綴子串相同的前綴為啥要在最左;

文本串T

A

B

C

F

D

A

F

K

M

O

L

模式串P

K

A

F

C

D

A

?F

?

?

?

?

若是我們將KAF中的AF移到相應位置時,K和D注定不相等,那又何必多余比較

文本串T

A

B

C

F

D

A

F

K

M

O

L

模式串P

?

?

?

?

K

A

F

C

D

A

?F

代碼如下:

/* function:好后綴輔助數組(好后綴長度)求解前的預處理操作,即求出模式串中各個字符失配時相應的相同前綴長度 Param: @pattern 需要匹配的字符串 @suff 好后綴輔助數組的相同前綴長度數組 @m 需要匹配的字符串長度 */ void suffix(char *pattern,int m,int suff[]) {int f, g, i;suff[m-1]=m;g=m-1;for(i=m-2;i>=0;--i){if(i>g&&suff[i+m-1-f]<i-g)suff[i]=suff[i+m-1-f];else {if(i< g)g=i;f=i;while(g>=0&&pattern[g]==pattern[g+m-1-f])--g;suff[i]=f-g;}} } /* function:好后綴數組求解方法 Param: @pattern 需要匹配的字符串 @bmGs 好后綴數組 @m 需要匹配的字符串長度 */ void PreBmGs(char *pattern,int m,int bmGs[]) {int i, j;int suff[maxNum]; // 計算后綴數組suffix(pattern,m,suff);//看上一個函數// 先全部賦值為m,初始化for(i=0;i<m;i++){bmGs[i]=m;}// 當只存在后綴的相同子串時,如"ASDKGJOELHKSD"在"L"處失配時,第二、三個字符"SD"就是就是"L"的后綴"HKSD"的相同子串,子串必須是從第一個字符開始的j=0;for(i=m-1;i>=0;i--){if(suff[i]==i+1){for(;j<m-1-i;j++){if(bmGs[j]==m)bmGs[j]=m-1-i;}}}//當存在后綴的相同前綴時,如"HKSDKGJOELHKSD"在"L"處失配時,//第一、二、三、四個字符"HKSD"就是就是"L"的后綴"HKSD"的相同前綴,//相同前綴可以不從第一個字符開始,如"MHKSDKGJOELHKSD"在"L"處失配時,第二、三、四、五個字符"HKSD"就是就是"L"的后綴"HKSD"的相同前綴,for(i=0;i<=m-2;i++){bmGs[m-1-suff[i]]=m-1-i;} }

最終的匹配函數如下:

/* function:Boyer-Moore字符匹配算法 Param: @text 文本內容 @Tlen 文本內容長度 @pattern 需要匹配的字符串 @Plen 需要匹配的字符串長度 */ char* BoyerMoore(char *text,int Tlen ,char *pattern,int Plen,int bmBc[],int bmGs[]) {int i,pos;pos=0;while(pos<=Tlen-Plen){for(i=Plen-1;i>=0&&pattern[i]==text[i+pos];i--);if(i < 0){return &text[pos];}else{pos+=MAX(bmBc[text[i+pos]]-Plen+1+i,bmGs[i]);//MAX為求兩個數中最大的一個,使用了宏定義#define MAX(x, y) (x)>(y) ? (x):(y)}}return NULL; }

總結:壞字符規則和好后綴規則是獨立計算的,最后P串具體按那個規則走,是通過對比兩個規則計算出來需要往后移動的位置數的大小,取其中最大的。從原理可看出,BM每次匹配前也需要做預處理,需要針對模式串P分別生成一個壞字符輔助數組和好后綴輔助數組,它們分別存放著各自規則下模式串P的字符發生失配時,需要相應地向右移動的位置數,在此也不做介紹。實現代碼如上。

4、Sunday算法:

原理簡述:Sunday的處理方式與BM不同的是,它只關注文本串T中當前與模式串P最后一個字符相對應的字符的下一個字符(我說的清楚了嗎?),直接上圖吧。sunday只關心下圖標綠色的“L”。當P串和T串匹配過程中出現失配時,若在P串中當前的失配字符"K"的左半部分(無)不存在T中當前與模式串P最后一個字符"F"相對應的字符"D"的下一個字符"L"時,P串整體向右移動strlen(p)+1個位置。(匹配時從左向右依次匹配)

a、不存在時,整體向右移動strlen(p)+1;

文本串T

A

B

C

F

D

L

K

H

R

J

K

模式串P

K

A

F

C

F

?

?

????

移動后

K

A

F

C

F

b、當存在時,則和BM的壞字符規則一樣,將P串右移使得P串中最右的“L”和T中的“L”相對。

文本串T

A

B

C

F

D

L

K

H

R

J

K

???

模式串P

K

L

F

C

F

?????????

移動后

K

L

F

C

F

?????

代碼如下:

/* function:Sunday字符匹配算法預處理 Param: @sun_shift 最終存放每個字符失配時該右移的距離 @p 需要匹配的字符串 @lenP 需要匹配的字符串長度 */ void SundayPre(int sun_shift[],char* P,int lenP) {int i;for(i=0;i<maxNum;i++){sun_shift[i]=lenP+1;}for(i=0;i<lenP;i++){sun_shift[P[i]]=lenP-i;} }/* function:Sunday字符匹配算法 Param: @T 文本內容 @lenT文本內容長度 @p 需要匹配的字符串 @lenP 需要匹配的字符串長度 @sun_shift 最終存放每個字符失配時該右移的距離 */ char* Sunday(char* T,int lenT,char* P,int lenP,int shift[]){int i;int pos=0;int j;while(pos<=lenT-lenP) {j=0;while(T[pos+j]==P[j]&&j<lenP) j++;if(j>=lenP){return &T[pos];//匹配成功,返回地址}else{pos+=shift[T[pos+lenP]];}}return NULL; }

總結:sunday匹配前也需要做一個預處理,生成一個輔助數組,它也是存放著,當發生失配時,模式串P需要向右移動的位置數。

5、Horspool算法:

原理簡述:Horspool也是一種改進匹配的算法,它的匹配規則有點像BM的壞字符規則,但是卻也不一樣,BM的壞字符規則關注當前失配的字符(失配字符可能是在最后一個,也有可能是在中間,也可能在開頭),然而當發生失配時,Horspool都只關注T串中與P串最后一個字符相對應的字符(匹配時從右向左依次匹配),如圖:

a、失配時(最后一個字符失配),只關注最后一個字符;

文本串T

A

B

C

F

A

F

P

K

M

O

L

模式串P

A

F

C

D

A

C

?

?

?

?

?

“F”!=”C”,執行移動、P前半部分存在T當前的最后一個字符“F”

文本串T

A

B

C

F

A

F

P

K

M

O

L

模式串P

?

?

?

?

A

F

C

D

A

C

?

b、失配時(中間字符失配),只關注最后一個字符;

文本串T

A

B

C

E

A

F

P

K

M

O

L

模式串P

E

F

C

D

A

F

?

?

?

?

?

“D”!=”E”,執行移動、P前半部分存在T當前的最后一個字符“F”

文本串T

A

B

C

E

A

F

P

K

M

O

L

模式串P

?

?

?

?

E

F

C

D

A

C

?

代碼如下:

/* function:horspool字符匹配算預處理 Param: @P 需要匹配的字符串 @lenP 需要匹配的字符串長度 */ void horspoolPre(int hors_d[],char* P,int lenP) {int i;for(i=0;i<maxNum;i++){hors_d[i]=lenP;}for(i=0;i<(lenP-1);i++){hors_d[P[i]]=lenP-i-1;} } /* function:horspool字符匹配算法 Param: @T 文本內容 @lenT 文本內容長度 @P 需要匹配的字符串 @lenP 需要匹配的字符串長度 @hors_d[] 輔助數組 */ char* horspool(char *T, int lenT, char *P, int lenP,int hors_d[]) { int i,pos,j; pos=0; while(pos<=(lenT-lenP)){ j=lenP-1; while(j>=0&&T[pos+j]==P[j]) j--; if(j==-1){return &T[pos];} else{ pos+=hors_d[T[pos+lenP-1]];}} return NULL; }

總結:很明顯,Horspool也需要一個預處理操作,需要一個輔助數組。

二、測試

1、測試準備工作:

運行環境:

? ? ? ? ? ? 系統:centos6.4

? ? ? ? ? ?語言:Linux c

(1)、構建一個稍微大一點的文本串,本測試構造的文本串如下圖(在程序中賦值給char*型變量,下圖是用雙引號將文本串用多行顯示);

?

(2)、選一個關鍵字符串,本測試選了“MY_TEST_string”作為要匹配的關鍵字符串;

(3)、 4種自己實現的算法在匹配前都需要一些預處理工作,本文比較中不將這些預處理時間計入運行時間;

2、測試開始:

測試的關鍵代碼如下:(其余四種算法也是相同的處理方式,感興趣的看官可到GitHub上下載完整代碼,https://github.com/scu-igroup/string_match)

///KmpSearch///gettimeofday(&start,NULL);//起始時間for(i=0;i<times;i++){//為運行次數if(i==0){temp[0]=KmpSearch(My_TXT,len_txt,pattern,lenp,KMP_next);//返回結果,可用來驗證是否真的找到了模式串pattern所在位置}elseKmpSearch(My_TXT,len_txt,pattern,lenp,KMP_next);//循環調用}gettimeofday(&end,NULL);//結束時間dif_sec=end.tv_sec-start.tv_sec;//相差的秒數dif_usec=end.tv_usec-start.tv_usec;//相差的微秒數printf("KMP running time is %ld 秒 (%ld 微秒)\n",dif_sec, dif_sec*1000000+dif_usec);//最終時間換算,欲知詳情請自行查詢結構體struct timeval

(1)、將關鍵字符串放文本串開頭,然后測試各個算法程序匹配所花時間:

總結:結果很明顯,當我們要匹配的字符串在文本串開頭時,strstr()遠遠落后于其他四種算法,而sunday與horspool優于BM、KMP,BM匹配速度相當于KMP的三倍。

(2)、將關鍵字符串放文本串中間位置,然后測試各個算法程序匹配所花時間:

總結:當我們將匹配的字符串放在文本串中部時,sunday繼續保持著優勢,horspool也仍然優于BM、KMP,除開讓人很驚訝的KMP,strstr相對于其他三種算法來說仍然是墊底(講道理,KMP不應該表現得如此菜,它應該比strstr快速才對。所以可能是本人的實現代碼有問題,在上面已給出實現代碼,供各位看官找找錯,我實在是汗顏,未能發現問題)。

(3)將關鍵字符串放文本串末尾,然后測試各個算法程序匹配所花時間:

總結:當我們要匹配的字符串在文本串尾部時,sunday依然一路在秀,可謂是“一枝獨秀”,依然完勝其余四種算法。horspool顯然被strstr斃掉了,不過它仍然壓著BM和KMP。差點讓我掉下下巴的KMP,再次成功讓我懷疑自己的實現代碼有嚴重的BUG。strstr這次反而“翻身”做老二了,又讓小伙伴驚呆了(這時應該有很多看官開始嘲笑了,“Are you fucking kidding me!!!!!”,BM、KMP、Horspool怎么會比strstr慢呢?,這也是我疑惑的地方。。。)

三、總結

從測試結果來看,五種算法中,只有sunday沒讓我們失望,在三種情境下依然保持著自己的強勢匹配速度,而Horspool也不差,一直都領先于BM和KMP。strstr是很多人都在用的函數,畢竟c把它封裝好了,用起來方便,它的表現還是相對可以的,畢竟它是暴力匹配,總要多花時間。最讓人想不通的就是KMP的表現了,從原理上來說,最次也是和strstr一樣(本文測試都把KMP的nxet數組求解過程在預處理階段給單獨處理了,不計入運行時間),畢竟它每一次匹配的跳躍是大于等于1的,而strstr每次都只移動一位。當匹配串在文本串尾部時,BM也沒有比strstr快,這也是很出人意外。

四、不足之處

1、樣本串的選擇沒有盡可能覆蓋多種情景;

2、strstr是c語言的庫函數,它的實現方式應該經過了開發者無數次推敲優化,而自己實現的另外四種算法可能不夠優化,多了一些冗余計算,導致沒把算法優勢最大化,因為strstr是系統內部函數,運行多次可能也會有自動優化;

3、測試方式不夠全面,得出的結論有些偏差,僅供大家做參考;

所有完整代碼請移步到:https://github.com/scu-igroup/string_match

?

總結

以上是生活随笔為你收集整理的KMP、BM、Sunday、Horspool、strstr字符串匹配算法的性能比较的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。