字符串匹配算法

来源:互联网 发布:js防止注入攻击代码 编辑:程序博客网 时间:2024/03/29 16:18

                                                                        字符串匹配算法 

问题描述:于笔试中碰到写一个字符串匹配的算法, 只写了简单实现(回溯)版
 
字符串匹配的方法:
 
  简单实现(回溯)
分析:由于在出现不匹配的时候需要移动到前次开始匹配位置的下一个位置再开始匹配子串。这个算法在当模式串 与主串中有许多“部分匹配”的情况下效率是很差的。它会返回到原先已经匹配过,但实际上完全可以跳过的位置再次匹配。
举例:主串  000000000000000000000000000000000000000000000000001
      模式串  0000001
模式串每次都是在最后一位发现不匹配, 然后算法从主串下一个位置重新开始匹配。
所以时间复杂度O(N*M)
但在一般情况下 很少存在像以上那种“高度相似情况”,所以效率还是可以的(基本上接近于O(N+M))。这也是一直在用这个算法的原因。
 
KMP算法
       分析:KMP中已经不需要把主串中的i 回溯 故O(N+M)其主要依据的是一个next[]索引数组,该索引数组完全由模式串决定 而与 主串无关。(要是与主串相关,这些个值也确定不下来)。关键内容是生成next[]数组。匹配过程跟回溯版是一样的。
 
  Next[]数组的确定:
  
i 为主串的 索引
j 为模式串的索引
k为当主串中第i个字符与第j个字符不匹配时主串中第i 个字符应该与模式串中的第k个字符比较.
 
令next[j] = k ,next[j]表示当模式中第j个字符与主串中第 i个字符不匹配时,在模式串中需要重新和主串中该位置字符比较的字符位置。下面给出next[j]的函数定义:
        ------          0       当j = 1时
        | 
Next[j] |  max( k | 1 < k < j 且 ‘p1p2p3…p(k-1) ’ ='p(j-k+1)……p(j - 1)’ )
        |
         ------  1   其他情况
 
Next[]数组事例:(严格按照定义来推导)
 
 j     1  2  3 4 5 6  7 8
模式串  a b  a a b c  a c
next[j] 0
 
 
 
 
 
 
 
 
推导:
有定义可得:
j = 1    有定义可得:next[1] = 0 ;
 
j =2  next[2] 
  因为 1< k < j  故属于第三中情况 故next[2] = 1 ;
 
  j = 3   next[3]
  由 1 < k < j   k = 2  ‘ p1p2 …..p(k-1) ’  = p1 = a  ; ‘ p(j – k+1)…p(j-1)’ = p2 = b ;
所以 p1 != p2  故next[3] = 1 (属于第三种情况)
(以下简称左式‘ p1p2 …..p(k-1) ’  跟右式 ‘ p(j – k+1)…p(j-1)’)
 
j = 4  next[4]
同理可得 k = 2 or 3
k = 2 时 左式 = p1= a  右式 = p3 = a 满足条件2
k = 3 时 左式 = p1p2 = ab 右式 = p2p3 = ba 不满足条件  故可得 next[4] = 2
 
j = 5  next[5]
k = 2 or 3 or  4
k = 2 时 左式 = p1 = a  右式 = p4 = a 满足条件2
k = 3 时  左式 = p1p2 = ab  右式 = p3p4 = aa 不满足
k = 4 时 左式 = p1p2p3 = aba 右式= p2p3p4 = baa 不满足
故 next[5] = 2
 
j = 6 next[6]
k = 2 or 3 or  4 or 5
k = 2 , 左式 = p1 = a , 右式= p5 = b 不满足条件2
k = 3,左式 =p1p2 =ab, 右式= p4p5= ab  满足条件2
k = 4, 左式 =p1p2p3=aba,右式= p3p4p5=aab 不满足
k = 5, 左式 =p1p2p3p4 =abaa, 右式= p2p3p4p5= baab 不满足2
故 由以上得 只有k=3满足条件 所以next[6] = 3
 
j = 7 next[7]
k = 2 or 3 or 4 or 5 or 6
k =2, 左式=p1=a, 右式= p6 = c 不满足2
k =3, 左式= p1p2 = ab, 右式= p5p6 = bc,不满足2
k =4, 左式=p1p2p3 =aba, 右式=p4p5p6 =abc,不满足2
k=5, 左式=p1p2p3p4=abaa, 右式=p3p4p5p6=aabc, 不满足2
k=6, 左式=p1p2p3p4p5=abaab, 右式=p2p3p4p5p6=baabc,不满足2
故由上所得 属于第三种情况 所以next[7] = 1
 
j = 8 next[8]
k = 2 or 3 or 4 or 5 or 6 or 7
k =2, 左式=p1=a, 右式= p7= a , 满足2
k =3, 左式= p1p2 = ab, 右式= p6p7 = ca,不满足2
k =4, 左式=p1p2p3 =aba, 右式=p5p6p7 =bca,不满足2
k=5, 左式=p1p2p3p4=abaa, 右式=p4p5p6p7=abca, 不满足2
k=6, 左式=p1p2p3p4p5=abaab, 右式=p3p4p5p6p7=aabca,不满足2
k=7, 左式=p1p2p3p4p5p6=abaabc,右式=p2p3p4p5p6p7=baabca,不满足2
故由上所得 只有k=2满足条件 所以next[8] = 2
 
 
程序推导:
 
如何用程序来实现求next[j] 从分析其定义出发 用递推方法求next[j]函数值
 
有定义可得 next[1] = 0 ;
 
设next[j] = k , 这表明存在 左式 = 右式
此时 next[j + 1] =  有2中情况
(1)    若pk = pj  表明 左式pk = 右式pj 这个就说明 next[j +1] = k + 1
即 next[j + 1 ] = k +1 = next[j] + 1
 
(2)    若 pk != pj 表明 左式pk != 右式pj
此时又把求next[j]数值问题看成一个模式匹配问题。主串和模式串 都是模式串自己,所以当pj != pk 时 应将模式串往右滑动至模式串next[k]个字符与主串中j对应比较.若next[k ] = k’ 且 pj = pk’ 则又回到(1)的情况了.所以可得 next[j+1] = k’ +1 = next[k] + 1;
若存在一次推倒还没找到相等的, 则继续往下找.直到成功匹配或者不存在这样的值(到了 0就不存在了) 则next[j +1] = 1 ;
      
举例:
 
  j     1 2  3 4 5 6  7 8
模式串  a b  a a b c  a c
next[j] 0
 
 
有定义可得 next[1] = 0
 
因为next[1] = 0 属于情况2中直接不存在这样的值
故next[2] = 1
 
p2 != p1  next[1] = 0还是属于情况2中不存在这样值
next[3] = 1
 
p3 = p1 相等 属于情况1
故 next[4] = 1 + 1 = next[1] + 1 = 2
 
p4 != p2 继续往下找 k ‘= next[2] = 1  p4 = p1
故 next[5] = 1 + 1 = next[2] + 1 = 2
 
p5 = p2 属于1中的情况  k = 2
next[6] = 2 + 1 = next[5] + 1 = 3
 
next[6 = 3 ]   p6 != p3 继续往下找 k’= next[3] = 1 p6 ! = p1    next[1 ] = 0到底
属于不存在的情况 故 next[7] = 1;
 
next[7] = 1   p7 =  p1  所以k = next[7] = 1
故  next[8] = 1+1 = next[7] + 1 = 2
 
程度代码 请参照<数据结构 C语言版> 严蔚敏 清华大学出版社
 
/////////////////////////
//index  (字符从0开始)
//回溯
//pos 主串中开始匹配位置
//返回 -1 代表匹配不成功
//返回非负值 则代表主串中匹配的位置
 
int Index(const char* s, int lenS, const char *t, int lenT, int pos)
{
 int i = pos;
 int j = 0;
 while(i < lenS && j < lenT)
 {
  if(s[i] == t[j]) //匹配
  {
   i++;
   j++;
  }
  else  //不匹配 退到主串在该循环开始前的下一个位置 继续匹配
  {
   i = i - j + 1;
   j = 0 ;              //模式串重新开始
  }
 }
 if(j >= lenT)                 //匹配成功
  return (i - lenT);
 else
  return -1;              //匹配不成功
}

////////////////////////////////////////////
//kmp 版 字符从1开始
//
//
//返回 0 代表匹配不成功
//返回非负值 则代表主串中匹配的位置
void Get_next(const char *t, int lenT, int next[])
{
 int i;
 int j;
 next[1] = 0;
 while(i < lenT)    //整个过程是模式串跟自己的一个匹配过程
 {
  if(0 == j || t[i] == t[j])
  {
   i++;
   j++;
   next[i] = j;
  }
  else
   j = next[j];
 }
}
int Index_kmp(const char *s,int lenS, const char *t, int pos, int lenT, int next[])
{
 int i = pos;
 int j = 1 ;
 while(i < lenS && j < lenT)
 {
  if(s[i] == s[j])
  {
   i++;
   j++;
  }
  else
  {
   i = next[j];
  }
 }
 if( j > lenT)
  return (j - lenT);
 else
  return 0;
}
 
由于kmp中 <数据结构 C语言版> 严蔚敏 清华大学出版社书中  0位置巧妙的运用.导致我实在想不出理由来更改如此神奇的应用.故只把回溯版的代码改成0位置开始. 但因为没有定义书中的字符串结构,所以长度都是用参数穿过去的.特别注意 长度应该在函数外 用strlen(s) 而不是sizeof(s).具体区别就不再详细说了..还是关于那个 '/0' .看下就会明白了
更新代码:

/////////////////////////////
//demonstrate index 
//
//字符串匹配
//


#include 
<stdio.h>
#include 
<string.h>
#include 
<malloc.h>


/////////////////////////
//index  (字符从0开始) 
//回溯
//pos 主串中开始匹配位置
//返回 -1 代表匹配不成功
//返回非负值 则代表主串中匹配的位置

int Index(const char* s, int lenS, const char *t, int lenT, int pos)
{
    
int i = pos;
    
int j = 0;

    
while(i < lenS && j < lenT)
    
{
        
if(s[i] == t[j]) //匹配
        
            i
++;
            j
++;
        }

        
else  //不匹配 退到主串在该循环开始前的下一个位置 继续匹配
        {
            i 
= i - j + 1;
            j 
= 0 ;              //模式串重新开始
        }

    }


    
if(j >= lenT)                 //匹配成功
        return (i - lenT);
    
else
        
return -1;              //匹配不成功
}



////////////////////////////////////////////
//kmp 版 字符从1开始
//
//
//返回 0 代表匹配不成功
//返回非负值 则代表主串中匹配的位置

void Get_next(const char *t, int lenT, int next[])
{
    
int i;
    
int j;

    i 
= 1 ;
    j 
= 0 ;
    
    next[
0= -1;
    next[
1= 0;

    
while(i < lenT)    //整个过程是模式串跟自己的一个匹配过程
    {
        
if(0 == j || t[i] == t[j])
        
{
            i
++;
            j
++;
            next[i] 
= j;
        }

        
else
            j 
= next[j];
    }


}


int Index_kmp(const char *s,int lenS, const char *t,  int lenT,int pos, int *next)
{
    
int i = pos;
    
int j = 1 ;


    
while(i < lenS && j < lenT)
    
{
        
if( j == 0 || s[i] == t[j])
        
{
            i
++;
            j
++;
        }

        
else
        
{
            j 
= next[j];    // 
        }

    }


    printf(
" i = %d j = %d  ", i,j);

    
if( j >= lenT)
        
return (i - lenT+1);   
    
else
        
return 0;
}


void main(int argc, char *argv[])
{
    
char p[] = " aaabaaaab" ;    //第一个字符置空,模拟从1开始存储
    char t[] = " aaaab";

    
int lenS = strlen(p);
    
int lenT = strlen(t);
    

    
int *next = (int*) malloc(lenT*sizeof(int));
    
    Get_next(t, lenT, next);
    
    
    
//int pos = Index(p, lenS, t, lenT, 0);
    int pos = Index_kmp(p, lenS, t, lenT, 1 ,next);

    
if(pos == 0)
    
{
        printf(
"Not found!  ");
    }

    
else
        printf(
"find %d  ", pos);

    free(next);   
//释放内存有错!
    next = NULL;
}
原创粉丝点击