通配符的字符串匹配算法

1. 简述

    题目描述:
    Str1中可能包含的字符:除了'*'和'?'以外的任意字符。
    Str2中可能包含的字符:任意字符。其中,'?'表示匹配任意一个字符,'*'表示匹配任意字符0或者多次。
    给出这样两个字符串,判断Str2是否是Str1的子串,如果是输出第一个匹配到的子串,如果不是,输出"不是子串"。

2. 分析

    对于'?'的处理,只要在匹配的时候将代码由:if(str1[i]==str2[j]) 改为 if(str1[i]==str2[j] || str2[j]=='?')即可。
    对于'*'的处理,可以将str2根据其中的'*'分为若干个片段,然后依次在str1中分别匹配这几个片段即可,而且对于这几个片段分别匹配,如果第k个片段在str1中匹配不到,后面也可以结束了。这里举例说明一下:对于str1="Oh year.Totay is weekend!",str2=*ye*a*e*",实际上就是在str1中匹配"ye","a","e"这三个片段。
    Oh year.Totay is weekend!
         yea                e
         yea                  e
         yea                     e
         ye         a        e
         ye         a         e  
         ye         a             e
    实际上,能够匹配到上面6种情况,按照我们的如果从左到右的匹配每个片段返回的是第一种情况。这里主要分析这种情况的处理,对于所有情况的输出后面再简单说明一下。
    首先处理str2,根据'*'分成若干个部分,然后依次在str1中进行匹配,使用kmp算法即可。这样判断能否匹配或者只找第一个匹配的子串的负责度是O(m+n)

3. 代码实现

    其中利用了kmp算法,为了使用方便,稍微改了下kmp算法的输入参数,即pat字符串的长度不用'\0'确定,用指定参数确定。

  1. #include <iostream>  
  2. #include <deque>  
  3. using namespace std;  
  4.   
  5. // KMP算法,pat长度由len_pat指定   
  6. void get_next(const char pat[], int next[], int pat_len) {  
  7.   // int len = strlen(pat);  
  8.   int len = pat_len;  
  9.   int i,j; next[0] = -1;  
  10.   for(i=1; i<len; i++) {  
  11.     for(j=next[i-1]; j>=0 && pat[i-1]!=pat[j]; j=next[j])  
  12.       ;  
  13.     if(j<0 || pat[i-1]!=pat[j])  
  14.       next[i] = 0;  
  15.     else   
  16.       next[i] = j+1; // if (pat[i]==pat[next[i]]) next[i]=next[next[i]];  
  17.   }  
  18.   for(int i=0; i<len; i++) {  
  19.     if(pat[i] == pat[next[i]])  
  20.       next[i] = next[next[i]];  
  21.   }  
  22. }  
  23.   
  24. // KMP算法,str长度由'\0'判断,pat长度由len_pat指定   
  25. int kmp_next(const char text[], const char pat[], int pat_len) {  
  26.   int t_length = strlen(text);  
  27.   // int p_length = strlen(pat);  
  28.   int p_length = pat_len;  
  29.   int t,p; int* next = new int[p_length];  
  30.   get_next(pat, next, p_length);  
  31.   for(t=0,p=0; t<t_length,p<p_length; ) {  
  32.     if(text[t] == pat[p])   
  33.       t++,p++;  
  34.     else  
  35.       if(next[p] == -1) // 说明此时p=0,而且pat[0]都匹配不了   
  36.         t++;  
  37.       else   
  38.         p = next[p];    
  39.   }  
  40.   delete []next;  
  41.   return t<t_length ? (t-p_length):-1;    
  42. }  
  43.   
  44. // 切分pat的结构   
  45. struct PAT_INFO {  
  46.   char* pat;  
  47.   int len;  
  48. };  
  49. // 可以匹配通配符的KMP,返回第一个匹配子串在str中的下标   
  50. void KMP_WildCard(char* str, char* pat) {  
  51.   int len_str = strlen(str);  
  52.   int len_pat = strlen(pat);  
  53.   int i,j;  
  54.   deque<PAT_INFO> store;  
  55.   // 切分pat到store中   
  56.   PAT_INFO info;  
  57.   bool new_info = true;  
  58.   for(i=0; i<len_pat; i++) {  
  59.     if(pat[i] == '*') {  
  60.       if(new_info == false// 有info需要保存   
  61.         store.push_back(info);  
  62.         new_info = true;  
  63.     }  
  64.     else {  
  65.       if(new_info) { // 需要新建一个info   
  66.         info.pat = pat + i;  
  67.         info.len = 1;  
  68.         new_info = false;  
  69.       }  
  70.       else { // 不需要新建一个info   
  71.         info.len++;  
  72.       }  
  73.     }  
  74.   } // for  
  75.   // 测试切分结果   
  76.   /*  
  77.   while(store.size() > 0) { 
  78.     info = store.front(); 
  79.     for(i=0; i<info.len; i++) 
  80.       cout << info.pat[i]; 
  81.     cout << endl; 
  82.     store.pop_front(); 
  83.   }*/  
  84.   // 根据切分后的pat序列进行匹配  
  85.   int first_index = -1; // 起始的下标   
  86.   int last_index = 0; // 最后的下标后面的一个位置   
  87.   int next_index = 0; // 下一次开始匹配的下标   
  88.   while(store.size()) {      
  89.     info = store.front();      
  90.     next_index = kmp_next(str+next_index, info.pat, info.len);  
  91.     if(next_index == -1) { // 这个片段没找到,查找任务失败   
  92.       break;  
  93.     }      
  94.     else { // 这个片段找到了,继续找  
  95.       if(first_index == -1) { // 找到的第一个片段   
  96.         first_index = next_index;  
  97.       }  
  98.       last_index += next_index + info.len;   
  99.       next_index = last_index;  
  100.     }  
  101.     store.pop_front();  
  102. //    cout << last_index << endl;  
  103.   }  
  104.   if(store.size())  
  105.     cout << "not found" << endl;  
  106.   else {  
  107.     for(i=first_index; i<last_index; i++)  
  108.       cout << str[i];  
  109.     cout << endl;  
  110.   }  
  111.   
  112. }    
  113.   
  114. int main() {  
  115.   char * str = "Oh year.Totay is weekend!";  
  116.   char * pat =  "*ye*a*e*";  
  117.   cout << "str: " << str << endl;  
  118.   cout << "pat: " << pat << endl;  
  119.   cout << "res: ";  
  120.   KMP_WildCard(str, pat);  
  121.   system("PAUSE");  
  122.   return 0;  
  123. }  

    

4. 所有匹配结果输出
    例子:

    Oh year.Totay is weekend!
    yea                e
    yea                  e
    yea                     e
    ye         a        e
    ye         a         e  
    ye         a             e
    首先是所有结果是什么:如果我们要的是str1匹配到的字符串,那么可见实际上上面6个有3个是重复的,因此对于这种情况,只有得到匹配字符串在str1中的开始index和结束index,然后对于重复的去掉即可。如果要的是str1匹配到的字符的下标,那么这是不会重复的。
    然后说一下匹配方法,基本上与匹配一个的差不多,不过要加上回溯的过程。比如第一次匹配成功后,继续在新的位置匹配最后一个片段,如果成功了就是第二次匹配成功了,否则就要回溯取在新的位置去匹配倒数第二个片段了,依次类推。直到第一个片段都没法再匹配到,不再回溯了。此外值得注意的是,如果第一次匹配都没成功,就不必回溯了,这种情况下,说明不可能存在匹配结果,因为匹配是从左到右的顺序,都会尽量在左边找到合适的片段,如果第一次都没成功,假设在第k个片段上匹配失败了,那么再回溯的话,轮到k片段是空间实际上只会与上次相同或者更小,大的空间都匹配不到,小的空间更不用说了。
    还有一点值得注意:有的一些博文采用的是用str2中一个字符一个字符的匹配,感觉效率会低,而且也没有片段这个方法中的第一次匹配失败就可以停止的规律,也用不了kmp(准确的说是体现不出kmp的优势)。

5. 参考

通配符的字符串匹配算法http://www.cnblogs.com/pangxiaodong/archive/2011/09/07/2169588.html

带通配符*的字符串匹配    http://www.slimeden.com/2010/10/algorithm/stringmatchwithasterisk
hdu 3901 Wildcard 带通配符的字符串匹配    http://blog.csdn.net/kongming_acm/article/details/6656583

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值