string 之 strchr函數 和 strstr函數(BF算法和KMP算法的應用)


Author: bakari  Date: 2012/8/9

繼上篇。。。。。

下面是我寫的代碼與源碼作的一些比較,均已嚴格測試通過,分別以“string 之”系列述之。

strchr函數:求字符在字符串中所在的位置

strstr函數:求子串在主串中的起始位置(用的字符串的模式匹配算法)

1 char * Mystrchr(const char *str, char c);        //c第一次出現的位置
2 //BF algorithm
3 int Mystrstr_BF(char *mainStr, char *subStr);    //子串第一次出現的位置
4 //KMP algorithm
5 int  Mystrstr_KMP(char *mainStr, char *subStr);
 1 /*******************************************************
 2  *  strchr
 3  *******************************************************/
 4 char * Mystrchr(const char *str, char c){          //c第一次出現的位置
 5     assert(NULL != str);
 6     for(; *str != c; str ++){
 7         if(*str == '\0')
 8             return NULL;
 9     }
10     return (char * )str;
11 }

 

下面着重講解BF算法和KMP算法,要真正懂一個算法並將它吃透,一定要懂這個算法的歷史,回到最初去了解這個算法是怎樣被發現的。對於相對感性的東西不用追本溯源,咬文嚼字,但是對於理性(換個詞叫抽象)的東西一定不能急躁,即使短時間內能夠清楚了解之,但過了一陣之后毫無疑問會忘記,本人上學期上DS的時候已經學過這個算法,當時就是為了坑爹的考試,沒有好好吃透,導致現在需要又要重新去復習回味。所以,為了做個高效的人士,還是那句老話,欲速則不達,好的算法就應該慢下心來慢慢品味,從它的根抓起。將之吃透!

本文是不會跟你去講歷史的,So,想知道歷史的就Google,百度吧,在下也幫不了,本文只做簡單的總結,加深印象。

一 、BF算法

  這個算法符合人的思維過程,不用轉彎,一看便知.為了顯示清晰,用途代替文字

看着圖就可以寫代碼了:

 1 int  BF(const char * str1, const char * str2){
 2     assert(NULL != str1 && NULL != str2);
 3     int i = 0, j = 0;
 4 
 5     while(*(str1 + i) && *(str2 + j)){
 6         if(*(str1 + i) == *(str2 + j)){
 7             ++ i;
 8             ++ j;
 9         }
10         else{
11             i = i - j + 1;   //i 回溯到上一輪的下一個
12             j = 0;           //j 從第一個開始比較
13         }
14     }
15     if(*(str2 + j) == '\0')
16         return  i -  j;
17     else
18         return -1;
19 }

缺點:低效,復雜度O(M*N)

  但在某些場合,如文本編輯啊,效率也較高,但對於計算機的二進制文件就顯得蒼白無力。

 

二、KMP算法

所以這個時候KMP算法誕生了,由於是三個人提出了的,所以用了三個人的名字的開頭字母作為名稱,我只記得Knuth,這個人實在太有名了,計算機科學的鼻祖,計算機所有獎項都拿過。

  KMP算法是對BF算法的改進,當匹配失效是指針不回溯,根據失效函數(即Next[n]的值)進行下一輪的匹配。

E.g:   主串  “a b a b c a b c a c b a b”    模式串  “a b c a c”

第一趟匹配: a b a b c a b c a c b a b  i = 2 i 不回溯

                  a b c      j = 2

第二趟匹配: a b a b c a b c a c b a b  i= 6 

                       a b c a c   j = 4

第三趟匹配:a b a b c a b c a c b a b   i= 10

            a b c a c  j = 5

依上所得:用數學的語言表述:

假設主串:S1S2.....Sn      模式串:P1P2......Pm

當主串中第 i 個字符與模式串第 k 個字符不匹配,前提是前面的字符皆已匹配,則有下面的關系:

P1P2......P(k-1) = S(i - k + 1)S(i - k + 2)......S(i - 1);  ...................................(1)

而對於模式串,有如下關系:

P(j - k + 1)P(j - k + 2)......P(j - 1) = S(i - k + 1)S(i - k + 2)......S(i - 1);..........(2)

根據(1)(2),得:

P1P2......P(k-1) = P(j - k + 1)P(j - k + 2)......P(j - 1);

即最終只需要在模式串中進行比較,這個比較就是計算Next[j]的值,用此作為模式串的指針回溯點。下面會介紹到。

 

根據以上的推導就可以宏觀地寫出KMP的算法的實現:

 1 int KMP(const char * str1, const char * str2){
 2     assert(NULL != str1 && NULL != str2);
 3     int i = 0, j = 0;
 4     int next[SIZE];
 5     get_next(str2,next);      //得到next[j]的值
 6 
 7     while(*(str1 + i) && *(str2 + j)){
 8         if(*(str1 + i) == *(str2 + j)){
 9             ++ i;
10             ++ j;
11         }
12         else{
13             j = next[j];      //i 不用回溯,j 取得next[j],進行下一輪比較
14         }
15     }
16 
17     if(*(str2 + j) == '\0')
18         return i - j;
19     else
20         return 0;
21 }

 

所以,這個算法最終化為小問題,即求Next[j] 的值。

對於Next[j]的數學推導:

令Next[j] = k,則Next[j] 表明當模式中第 j 個字符與主串 中相應字符失效時,在模式中需重新和主串中該字符進行比較的字符的位置,由此可引出Next[j]函數的定義:

Next[j] = -1 , 當 j = 1時;

           = Max{k | 1 < k < j && P1P2......P(k - 1) = P(j - k + 1)......P(j - 1) }

         = 0 其他情況;

 

E.g:        j :    0 1 2 3 4 5 6 7 

          模式串:   a b a a  b c a c

   Next[j]: -1  0 0 1 1 2 0 1 0 

 

很好計算,關鍵是不單要知其為,更要知其所以為。所以理解本質很重要。

下面,看一個例子就懂了:

假設:主串: “a c a b a a b a a b c a c a a b c”      模式串:“a b a a b c a c”

第一趟:a c a b a a b a a b c a c a a b c      i = 1;

    a b                                             j = 1;  Next[j] = 0;

第二趟:a c a b a a b a a b c a c a a b c      i = 1;

       a                                             j = 0; Next[j] = -1   //模式串中的第一個元素不想等,i 后移;

第三趟:a c a b a a b a a b c a c a a b c      i = 7

         a b a a b c                             j = 5; Next[j] = 2;

第四趟:a c a b a a b a a b c a c a a b c      i = 13;

                 a b a a b c a c                j = 8; END!

 

了解到這里,就很容易寫出一份很好的代碼了:

 1 void get_next1(const char * str, int Next[]){
 2     assert(NULL != str);
 3     int j = Next[0] = -1;
 4     int i = 0;
 5     while(*(str + i)){
 6         if(-1 == j || *(str + i) == *(str + j)){      //當 j = -1時,有模式串的第一個元素開始比較
 7             ++ i;
 8             ++ j;
 9         }
10         else
11             j = Next[j];     //上一輪比較的next[j]和下一輪將要比較的呈遞增的關系,可以進行簡單的數學推導
12     }
13 }

 

Note:還未完,下面的很重要

前面定義的Next[]函數在某些情況下有缺陷。E.g:模式串“aaaab”在和主串“aaabaaaab”匹配時,當i = 3, j = 3 時,不等,但由Next[j]的指示還需進行 i = 3, j = 2 ; i = 3, j = 1; i = 3, j = 0這三次的比較,實際上,因為模式串中第1,2,3個字符和第四個字符都相等,因此不需要再和主串第4個字符相比較,而可以直接將模式串一氣像右滑動4個字符。這就是說,若按上述定義得到的Next[j] = k,而模式串中Pj = Pk ,則當主串中字符Si 和 Pj 比較不等時,不需要再和Pk進行比較,而直接和P(Next[k]) 進行比較,有點繞啊,那就 看一個實例吧:

 

E.g:         j :    0 1 2 3 4 

 

          模式串:   a a a a b

 

   Next[j]: -1  0 1 2 3 0

   Next2[j]: -1  0 0 0 3 0

 

換句話說就是:此時的Next[j] 應該和Next[k] 相同。由此可計算Next函數修正值的算法如下,此時匹配算法不變。

 

 1 void get_next(const char * str, int next[]){
 2     assert(NULL != str);
 3     int j = next[0] = -1;
 4     int i = 0;
 5 
 6     while(*(str + i)){
 7         if(-1 == j || *(str + i) == *(str + j)){
 8             ++ i;
 9             ++ j;
10             if(*(str + i) != *(str + j)){     //先判斷,不等才執行next[i] = j
11                 next[i] = j;
12             }
13             else{
14                 next[i] = next[j];          //把前一個next[j]賦給后面的
15             }
16         }
17         else
18             j = next[j];
19     }
20 }

 

 

綜合以上,匯總一下代碼,全部代碼已經嚴格測試通過。

 

 1 /*******************************************************
 2  *  strstr
 3  *  there are two algorithm : BF and KMP
 4  *  compare with the difference of two
 5  *******************************************************/
 6 
 7 const int SIZE = 10;
 8 int  KMP(const char * str1, const char * str2);
 9 int  BF(const char * str1, const char * str2) ;
10 void get_next(const char * str,int next[]);
11 void get_next1(const char * str,int next[]);
12 
13 int  BF(const char * str1, const char * str2){
14     assert(NULL != str1 && NULL != str2);
15     int i = 0, j = 0;
16 
17     while(*(str1 + i) && *(str2 + j)){
18         if(*(str1 + i) == *(str2 + j)){
19             ++ i;
20             ++ j;
21         }
22         else{
23             i = i - j + 1;   //i 回溯到上一輪的下一個
24             j = 0;           //j 從第一個開始比較
25         }
26     }
27     if(*(str2 + j) == '\0')
28         return  i -  j;
29     else
30         return -1;
31 }
32 
33 int KMP(const char * str1, const char * str2){
34     assert(NULL != str1 && NULL != str2);
35     int i = 0, j = 0;
36     int next[SIZE];
37     get_next(str2,next);      //得到next[j]的值
38 
39     while(*(str1 + i) && *(str2 + j)){
40         if(*(str1 + i) == *(str2 + j)){
41             ++ i;
42             ++ j;
43         }
44         else{
45             j = next[j];      //i 不用回溯,j 取得next[j],進行下一輪比較
46         }
47     }
48 
49     if(*(str2 + j) == '\0')
50         return i - j;
51     else
52         return 0;
53 }
54 
55 void get_next(const char * str, int next[]){
56     assert(NULL != str);
57     int j = next[0] = -1;
58     int i = 0;
59 
60     while(*(str + i)){
61         if(-1 == j || *(str + i) == *(str + j)){
62             ++ i;
63             ++ j;
64             if(*(str + i) != *(str + j)){     //先判斷,不等才執行next[i] = j
65                 next[i] = j;
66             }
67             else{
68                 next[i] = next[j];          //把前一個next[j]賦給后面的
69             }
70         }
71         else
72             j = next[j];
73     }
74 }
75 
76 void get_next1(const char * str, int Next[]){
77     assert(NULL != str);
78     int j = Next[0] = -1;
79     int i = 0;
80     while(*(str + i)){
81         if(-1 == j || *(str + i) == *(str + j)){      //當 j = -1時,有模式串的第一個元素開始比較
82             ++ i;
83             ++ j;
84         }
85         else
86             j = Next[j];     //上一輪比較的next[j]和下一輪將要比較的呈遞增的關系,可以進行簡單的數學推導
87     }
88 }

 

歡迎指正交流!

 

 


開了個公眾號「aCloudDeveloper」,專注技術干貨分享,期待與你相遇。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM