• 字符串匹配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!
    ");
     75                 return EXIT_FAILURE;
     76             }
     77             strncpy(dst,str,(i+1));   //匹配str的前(i+1)个字符
     78             dst[i+1] = '';    //注意字符串要以'/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 != '')
     92     {
     93         printf("%-4c",c);        //左对齐输出搜索字符串中的字符
     94         c = *++str;
     95     }
     96     printf("
    ");
     97     for(i=0;i<keyword_length;i++)
     98     {
     99         printf("%-4d",table[i]); //左对齐输出每个字符对应的部分匹配值
    100     }
    101     printf("
    ");
    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 != '')   //直到搜索到目标串的最后一个字符为止
    119     {
    120         temp = p;
    121         while(*q != '')
    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
    ");
    157     if(search_str == NULL)
    158     {
    159         printf("Please input search string
    ");
    160         return EXIT_FAILURE;
    161     }
    162 
    163     if(dst_str == NULL)
    164     {
    165         printf("Please input dst_string
    ");
    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
    ",dst_str);         //输出待搜索的目标串
    172     if(result == 0)
    173     {
    174         printf("Sorry!Don't find the string %s
    ",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("
    ");
    189     }
    190     
    191     return EXIT_SUCCESS;
    192 }

      测试的结果:

      

  • 相关阅读:
    原生js ajax与jquery ajax的区别
    ajax的五大步骤
    js中setTimeout()时间参数设置为0的探讨
    js数组与字符串的相互转换方法
    javascript的三个组成部分
    linq 获取不重复数据,重复数据 var unique = arr.GroupBy(o => o).Where(g => g.Count() == 1) .Select(g => g.ElementAt(0));
    C# 随机 抽奖 50个随机码 不重复
    聚集索引和非聚集索引 聚集索引的叶节点就是最终的数据节点,而非聚集索引的叶节仍然是索引节点,但它有一个指向最终数据的指针。
    WPF ControlTemplate,DataTemplate
    C# 实现 奇数偶数排序,奇数在前,偶数在后
  • 原文地址:https://www.cnblogs.com/woshijpf/p/3926599.html
Copyright © 2020-2023  润新知