字符串匹配KMP算法的C語言實現


字符串匹配是計算機的基本任務之一。

舉例來說,有一個字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一個字符串"ABCDABD"?

下面的的KMP算法的解釋步驟,引用於http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

1.

首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一個字符與搜索詞"ABCDABD"的第一個字符,進行比較。因為B與A不匹配,所以搜索詞后移一位。

2.

因為B與A不匹配,搜索詞再往后移。

3.

就這樣,直到字符串有一個字符,與搜索詞的第一個字符相同為止。

4.

接着比較字符串和搜索詞的下一個字符,還是相同。

5.

直到字符串有一個字符,與搜索詞對應的字符不相同為止。

6.

這時,最自然的反應是,將搜索詞整個后移一位,再從頭逐個比較。這樣做雖然可行,但是效率很差,因為你要把"搜索位置"移到已經比較過的位置,重比一遍。

7.

一個基本事實是,當空格與D不匹配時,你其實知道前面六個字符是"ABCDAB"。KMP算法的想法是,設法利用這個已知信息,不要把"搜索位置"移回已經比較過的位置,繼續把它向后移,這樣就提高了效率。

8.

怎么做到這一點呢?可以針對搜索詞,算出一張《部分匹配表》(Partial Match Table)。這張表是如何產生的,后面再介紹,這里只要會用就可以了。

9.

已知空格與D不匹配時,前面六個字符"ABCDAB"是匹配的。查表可知,最后一個匹配字符B對應的"部分匹配值"為2,因此按照下面的公式算出向后移動的位數:

  移動位數 = 已匹配的字符數 - 對應的部分匹配值

因為 6 - 2 等於4,所以將搜索詞向后移動4位。

10.

因為空格與C不匹配,搜索詞還要繼續往后移。這時,已匹配的字符數為2("AB"),對應的"部分匹配值"為0。所以,移動位數 = 2 - 0,結果為 2,於是將搜索詞向后移2位。

11.

因為空格與A不匹配,繼續后移一位。

12.

逐位比較,直到發現C與D不匹配。於是,移動位數 = 6 - 2,繼續將搜索詞向后移動4位。

13.

逐位比較,直到搜索詞的最后一位,發現完全匹配,於是搜索完成。如果還要繼續搜索(即找出全部匹配),移動位數 = 7 - 0,再將搜索詞向后移動7位,這里就不再重復了。

14.

下面介紹《部分匹配表》是如何產生的。

首先,要了解兩個概念:"前綴"和"后綴"。 "前綴"指除了最后一個字符以外,一個字符串的全部頭部組合;"后綴"指除了第一個字符以外,一個字符串的全部尾部組合。

15.

"部分匹配值"就是"前綴"和"后綴"的最長的共有元素的長度。以"ABCDABD"為例,

  - "A"的前綴和后綴都為空集,共有元素的長度為0;

  - "AB"的前綴為[A],后綴為[B],共有元素的長度為0;

  - "ABC"的前綴為[A, AB],后綴為[BC, C],共有元素的長度0;

  - "ABCD"的前綴為[A, AB, ABC],后綴為[BCD, CD, D],共有元素的長度為0;

  - "ABCDA"的前綴為[A, AB, ABC, ABCD],后綴為[BCDA, CDA, DA, A],共有元素為"A",長度為1;

  - "ABCDAB"的前綴為[A, AB, ABC, ABCD, ABCDA],后綴為[BCDAB, CDAB, DAB, AB, B],共有元素為"AB",長度為2;

  - "ABCDABD"的前綴為[A, AB, ABC, ABCD, ABCDA, ABCDAB],后綴為[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的長度為0。

16.

"部分匹配"的實質是,有時候,字符串頭部和尾部會有重復。比如,"ABCDAB"之中有兩個"AB",那么它的"部分匹配值"就是2("AB"的長度)。搜索詞移動的時候,第一個"AB"向后移動4位(字符串長度-部分匹配值),就可以來到第二個"AB"的位置。

 

  接下來,就是我自己對KMP算法的實現了。

  這個算法的實現主要包括了三個方面:

  1) 求得我們用來搜索字符串的部分匹配值表

  2) 實現待搜索字符串在搜索過程中的指針的移動問題

  3) 如何定位我們搜索到的結果

  接下來我就貼上我實現的代碼

  

  1 /*
  2 *用KMP算法實現字符串匹配搜索方法
  3 *該程序實現的功能是搜索本目錄下的所有文件的內容是否與給定的
  4 *字符串匹配,如果匹配,則輸出文件名:包含該字符串的行
  5 *待搜索的目標串搜索指針移動位數 = 已匹配的字符數 - 對應部分匹配值
  6 */
  7 
  8 #include <stdio.h>
  9 #include <string.h>
 10 #include <stdlib.h>
 11 
 12 #define KEYWORD_MAX_LENGTH 100      //設定搜索串的最大長度
 13 
 14 int kmp_table[KEYWORD_MAX_LENGTH];  //為搜索串建立kmp表
 15 char prefix_stack[KEYWORD_MAX_LENGTH]; //前綴表達式棧
 16 char suffix_stack[KEYWORD_MAX_LENGTH]; //后綴表達式棧
 17 int keyword_length = 0;  //搜索串的長度
 18 int record_position[KEYWORD_MAX_LENGTH]; //記錄與關鍵字串匹配源串中的位置
 19 
 20 /*
 21 *GetMatchValue:獲得字符串src的部分匹配值
 22 */
 23 int GetMatchValue(char *src)
 24 {
 25     int value = 0;
 26     int src_len = strlen(src);
 27     char *begin = src;    //初始化指向字符串第一個字符
 28     char *end = src + (src_len - 1);  //初始化指向字符串最后一個字符
 29     int i = 0;
 30     for(i=0;i<(src_len-1);i++)
 31     {
 32         prefix_stack[i] = *begin;
 33         suffix_stack[i] = *end;
 34         begin++;
 35         end--;
 36     }
 37     char *p = prefix_stack;
 38     char *q = suffix_stack + (src_len - 2);  //指向棧中最后一個元素
 39     int flag = 0;   //用一個標志位來確定后綴棧中到最后一個元素都與前綴棧中的符號匹配
 40     while(q >= suffix_stack)
 41     {
 42         if(*p == *q)
 43         {
 44             value++;
 45             p++;
 46             flag=1;
 47         }
 48         else {
 49             flag = 0;
 50         }
 51         q--;
 52     }
 53     if(flag == 0) value = 0;
 54     return value;
 55 }
 56 
 57 /*
 58 *創建搜索字符串的KMP表
 59 */
 60 int Create_KMP_Table(char *str,int *table)
 61 {
 62     int i;
 63     char *dst;
 64     keyword_length = strlen(str);
 65     for(i=0;i<keyword_length;i++)
 66     {
 67         if(i == 0) {
 68             table[i] = 0;   //第一個字符無前綴和后綴,所以為0
 69         }
 70         else {
 71             dst = (char*)malloc((i+2));
 72             if(dst == NULL)
 73             {
 74                 printf("malloc space error!\n");
 75                 return EXIT_FAILURE;
 76             }
 77             strncpy(dst,str,(i+1));   //匹配str的前(i+1)個字符
 78             dst[i+1] = '\0';    //注意字符串要以'/0'結尾
 79             table[i] = GetMatchValue(dst); 
 80             free((void*)dst);    
 81         }
 82     }
 83     return EXIT_SUCCESS;
 84 }
 85 
 86 //打印搜索字符串對應的KMP表
 87 void Table_Print(char *str,int *table)
 88 {
 89     int i;
 90     char c = *str;
 91     while(c != '\0')
 92     {
 93         printf("%-4c",c);        //左對齊輸出搜索字符串中的字符
 94         c = *++str;
 95     }
 96     printf("\n");
 97     for(i=0;i<keyword_length;i++)
 98     {
 99         printf("%-4d",table[i]); //左對齊輸出每個字符對應的部分匹配值
100     }
101     printf("\n");
102 }
103 
104 //在目標串dst_str中搜索關鍵子串search_str,打印出關鍵字串的位置信息,返回與關鍵字串匹配的數目
105 int Search_Keyword(char *dst_str,char *search_str)
106 {
107     char *p = dst_str;
108     char *q = search_str;
109     char *temp;
110 
111     //創建關鍵字串的KMP表    
112     Create_KMP_Table(search_str,kmp_table);
113     
114     int count = 0;  //記錄現在已經匹配的數目
115     int k = 0;     //記錄與關鍵字串匹配的字串的數目
116     int move = 0;  //當字符串不匹配時,搜索指針移動的位數    
117 
118     while(*p != '\0')   //直到搜索到目標串的最后一個字符為止
119     {
120         temp = p;
121         while(*q != '\0')
122         {
123             if(*q == *temp)
124             {
125                 count++;
126                 temp++;
127                 q++;
128             }
129             else break;
130         }
131         
132         if(count == 0)
133             p++;
134         else {
135             if(count == keyword_length)
136             {
137                 record_position[k++] = (temp-dst_str)-(keyword_length);
138             }
139             move = count - kmp_table[count-1];
140             p += move;
141         }
142 
143         count = 0;
144         q = search_str;
145     }
146     return k;
147 }
148 
149 
150 int main(int argc,char **argv)
151 {
152     char *search_str = argv[1];
153     //char dst_str[] = "hello woshijpf woshijpf woshij woshijp woshijpf";
154     char dst_str[] = "BBC ABCDAB ABCDABCDABDE";
155     
156     printf("Please input serach string and dst_string\n");
157     if(search_str == NULL)
158     {
159         printf("Please input search string\n");
160         return EXIT_FAILURE;
161     }
162 
163     if(dst_str == NULL)
164     {
165         printf("Please input dst_string\n");
166         return EXIT_FAILURE;
167     }
168     
169     int result = Search_Keyword(dst_str,search_str);  //放回搜索到的結果的數目
170     Table_Print(search_str,kmp_table);
171     printf("%s\n",dst_str);         //輸出待搜索的目標串
172     if(result == 0)
173     {
174         printf("Sorry!Don't find the string %s\n",search_str);
175         return EXIT_SUCCESS;
176     }
177     else {
178         int i,j,num;
179         int before = 0;
180         for(i=0;i<result;i++)
181         {
182             num = record_position[i] - before;    //打印搜索串在目標串中的位置
183             before = record_position[i]+1;
184             for(j=1;j<=num;j++)
185                 printf(" ");
186             printf("*");
187         }
188         printf("\n");
189     }
190     
191     return EXIT_SUCCESS;
192 }

 

  測試的結果:

  

 


免責聲明!

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



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