字符串篇

来源:互联网 发布:拍照软件电脑版 编辑:程序博客网 时间:2024/06/02 05:31

<<------表示方法------>>

1. 定长表示方法

#define MAXSTRLEN 255  

typedef unsigned char SString[MAXSTRLEN + 1];    //0号单元存放串的长度

2.堆分配表示方法

typedef struct

{

       char   *ch;      //基址

       int      length;  //串长

}HString;

 

<<------常用算法------>>

1. 模式匹配算法(KMP算法)

子串的定位问题通常称为模式匹配。

一般算法

int Index(SString S, SString T, int pos)

{   //返回子串T在主串S中的第pos个字符之后的位置。若不存在,则返回。

       //其中T非空,<=pos<=StrLength(S).

       i = pos; j = 1;

       while(i <= S[0] && j <= T[0])

       {

              if(S[i] == T[j])

              {  ++i;    ++j; }         //继续比较后续字符

              else

              {   i = i-j+2;  j = 1;  }  //i, j指针后退重新开始匹配

       }

 

       if(j > T[0]) return i - T[0];

       else return 0;

}

上述算法最坏情况复杂度为O(n*m).例如,当模式串味’00000001’,主串为’00000000000000000000000000001’时,由于模式中前面均为0,与主串相同,每次比较都是在最后一个1不同,然后指针回溯,做了很多无用功。

KMP算法:

改进的算法复杂度为O(n+m)其改进在于:每当一趟匹配过程中出现字符串比较不等时,不需要回溯i指针,而是利用已经得到的部分匹配结果将模式向右滑动尽可能远的一段距离后,继续进行比较。

1:主串为ababcabcacbab, 模式串为abcac

   与一般算法相比,本算法的最大特点是i不用回溯,只需将j向右滑动即可,例如,在一般算法中,当i=7,j=5字符比较不等时,须回溯至i=4,j=1。但是经过观察可以发现,在(i=4,j=1)(i=5,j=2)(i=6,j=3)此三者无需进行。因为,从上次匹配结果可以知道,主串中第4,5,6个字符必然是’b’,’c’,’a’(即模式串中的2,3,4个字符)。因为模式串中的第一个字符是a,因此无需和此三者相比,只需将模式向右滑动3个字符继续进行j=7,j=2的字符比较。

推演至一般情形。假设主串为’s1s2…sn,模式串为’p1p2…pm,从上例分析可知,为了改进算法,需要解决下述问题:当匹配过程中产生失配(即si!=pj)时,模式串向右滑动可行的距离多远,换句话说,当主串中第i个字符和模式中第j个字符失配时,主串中第i个字符(i不回溯)应与模式中的哪个字符再比较?

假设此时应与模式中第k(k<j)个字符继续比较,则模式中前k-1个字符的子串必须满足下列关系式(1),且不可能存在k’>k满足此关系式(1

’p1p2…pk-1 = ’si-k+1si-k+2…si-1            (1)

而已经得到的部分匹配的结果是

’pj-k+1pj-k+2…pj-1 = ’si-k+1si-k+2…si-1            (2)

由(1)和(2)可得

’p1p2…pk-1 = ’pj-k+1pj-k+2…pj-1                (3)

反之,若模式串中存在满足(3)的两个子串,则当匹配过程中,主串中第i个字符与模式中第j个比较不等时,仅需将模式向右滑动至模式中第k个字符和主串中第i个字符对齐,此时,模式中头k-1个字符的子串’p1p2…pk-1必定与主串中第i个字符之前长度为k-1的字符’si-k+1si-k+2…si-1相等,由此,匹配仅需从模式中的第k个字符与主串中的第i个字符继续进行。

若令next[j]=knext[j]表明当模式中第j个字符与主串中相应字符失配时,在模式中需重新和住传中该字符进行比较的字符的位置。由此,可以引出模式串中的next函数的定义:

由此可以推出下列模式串的next函数值:

在求得模式的next函数之后,匹配可如下进行:假设以指针ij分别之时主串和模式中正待比较的字符,令i的初值为posj 的初值为1。若在匹配过程中si=pj,则ij分别增1,否则,i不变,j退到next[j]的位置在比较,若相等,则指针各自增1,否则j再退到下一个next位置,以此类推,直至下列两种可能:一种是j退到某个next值时字符比较相等,则指针各自增1,继续进行匹配;另一种是j退到职位0(即模式的第一个字符失配),则此时须将模式向右滑动一个位置,即从主串的下一个字符si+1起和模式重新开始匹配。如下图所示:

KMP算法如下:

int Index(SString S, SString T, int pos)

{   //返回子串T在主串S中的第pos个字符之后的位置。若不存在,则返回。

       //其中T非空,<=pos<=StrLength(S).

       i = pos; j = 1;

       while(i <= S[0] && j <= T[0])

       {

              if(j == 0 || S[i] == T[j])

              {      ++i; ++j; }         //继续比较后续字符

              else

              {   j = next[j]; }         //模式串向右移动

       }

 

       if(j > T[0]) return i - T[0];

       else return 0;

}

 

KMP算法是在next函数基础上执行的,那么next函数如何求的呢?从上述讨论可见,此函数值仅取决于模式串本身而合相匹配的主串无关。我们可以分析其定义出发用递归的方法求的next函数值。

由定义得,     next[1]=0                    (5)

next[j]=k,这表明在模式串中存在下列关系:

’p1p2…pk-1 = ’pj-k+1pj-k+2…pj-1            6

其中k为满足1<k<j的某个值,并且不可能存在k’>k满足(6),此时next[j+1]=?可能有两种情况:

(1)      pk = pj,则表明在模式串中

’p1p2…pk-1pk’ = ’pj-k+1pj-k+2…pj-1 pj           7

并且不可能存在k’>k满足此式,即next[j+1]=k+1,即

       next[j+1]  = next[j]+1                     8

(2)      pk != pj,则表明在模式串中

’p1p2…pk-1pk’ != ’pj-k+1pj-k+2…pj-1 pj            (9)

此时可把next函数求值看成是一个模式匹配问题,整个模式串既是主串又是模式串,而当前在匹配过程中,已有’pj-k+1pj-k+2…pj-1’=’p1p2…pk-1,则当pj != pk,应将模式向右滑动至以模式中的第next[k]个字符和主串中的第j个字符相比较。若next[k]=k’,且pj = pk’,则说明在主串中第j+1个字符之前存在一个长度为k’(即next[k])的最长子串,和模式中从首字符起长度为k’的子串相等,即

’p1p2…pk-1pk’’ != ’pj-k’+1pj-k+2…pj-1 pj  (1<k’<k<j)      (10)

这就是说next[j+1]=k’+1

       next[j+1] = next[k]+1                        (11)

同理,若pj != pk’,则将模式继续向右移动直至将模式中第next[k’]个字符和pj对齐,……,依此类推,直至pj和模式中某个字符匹配成功或者不存在任何k’(1<k’<j)满足(10),则

next[j+1] = 1                              (12)

例如,如下图所示,已求得前6个字符的next函数值,前求next[7],因为next[6]=3,有p6!=p3,则需比较p6p1(因为next[3]=1),这相当于将子串模式向右滑动。由于p6!=p1,而且next[1]=0,所以next[7]=1,而因为p7=p1,则next[8]=2

根据上述分析所得记过,可得next函数的算法。如下:

void get_next(SString T, int next[])

{

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

       while(i < T[0])

       {

              if(j == 0 || T[i] == T[j])

              {  ++i;  ++j;  next[i] = j;  }

              else j = next[j];

       }

}

上述算法复杂度为O(m)。但尚有缺陷。例如模式’a a a a b’在和主串’a a a b a a a a b’匹配时当i=4,j=4s.ch[4] != t.ch[4],由next[j]的指示还需要进行i=4,j=3,i=4,j=2,i=4,j=13次比较。实际上,因为模式中第1,2,3个字符和第4个字符都相等,因此不需要再和主串中第4个字符比较,而可以将模式一气向右移动4个字符的位置直接进行i=5,j=1是的字符比较。这就是说,若按上述定义得到next[j]=k,而模式中pj=pk,则当主串中字符sipj比较不等时,不需要再和pk进行比较,而直接和Pnext[k]进行比较,换句话或,此时的next[j]next[k]相同。由此可得修正的next函数算法。

void get_nextval(SString T, int nextval[])

{

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

       while(i < T[0])

       {

              if(j == 0 || T[i] == T[j])

              {

                     if(T[i] != T[j])  nextval[i] = j;

                 else     nextval[k] = next[j];

              }

              else  j = nextval[j];

}

原创粉丝点击