数据结构 串(BF、KMP)

1、字符串是内容受限的线性表,线性结构的推广。

2、所有空串相等。

3、多采用顺序存储,一般不用插入删除。


1、串的顺序存储

用一组地址连续的存储单元存储串值的字符序列,可用定长数组表示。从下标为1的数组分量开始存储。【确定了串空间大小】

typedef struct{
   char ch[MAXLEN+1]; //从下标为1的数组分量开始存储
   int length;
}SString;

【动态分配释放字符数组空间】

在C语言中,存在一个称之为“堆”(Heap)的自由存储区,可以为每个新产生的串动态分配一块实际串长所需的存储空间,若分配成功,则返回一个指向起始地址的指针,作为串的基址,同时为了以后处理方便,约定串长也作为存储结构的一部分。这种字符串的存储方式也称为串的堆式顺序存储结构,定义如下:

typedef struct 
{
    char *ch; //若是空串为NULL,否则按串长分配存储区
    int length;
}HString;

2、串的链式存储结构

顺序串的插入和删除操作不方便,需要移动大量的字符。因此,可采用单链表方式存储串。由于串结构的特殊性——结构中的每个数据元素是一个字符,则在用链表存储串值时,存在一个“节点大小”的问题,即每个节点可以存放一个字符,也可以存放多个字符。当节点大小大于1时,由于串长不一定是节点大小的整倍数,则链表中的最后一个节点不一定全被串值占满,此时通常补上“#”或其他的非串值字符(通常“#”不属于串的字符集,是一个特殊符号)。此方法存储密度较低。

例如,图4.3( a )所示为节点大小为4(每个节点存放4个字符)的链表,图4.3(b)所示为节点大小为1的链表。

 块链结构

typedef struct Chunk{
    char ch[MAXLEN];
    struct Chunk *next;
}Chunk;

typedef struct{
    Chunk *head,*tail;
    int length;
}LString;

3、串的模式匹配算法

(1)BF算法(暴力破解法)

思路:从S的每一个字符开始依次与T的字符进行匹配。

将主串的第pos个字符和模式串的第一个字符比较,若相等,继续逐个比较后续字符;
若不等,从主串的下一字符起,重新与模式串的第一个字符比较。

直到主串的一个连续子串字符序列与模式串相等。返回值为S中与T匹配的子序列第一个字符的序号,即匹配成功。否则,匹配失败,返回值0;

 匹配失败:

i回溯:i=i-j+2 //(i-j+1)+1

j从头开始:j=1

匹配成功:

return i-T.length

 【算法步骤】
ch[0]=0;

①分别利用计数指针i和指示主串S和模式T中当前正待比较的字符位置,i初值为pos,j初值为1。

②如果两个串均未比较到串尾,即i和j均分别小于等于S和T的长度时,则循环执行以下操作:
S.ch[i]和T.ch[j]比较,若相等,则i和j分别指示串中下个位置,继续比较后续字符;若不相等,指针后退重新开始匹配,从主串的下一个字符( i=i-j+2)起再重新和模式的第一个字符(j=1)比较。
③如果j > T.length,说明模式T中的每个字符依次和主串S中的一个连续的字符序列相等,则匹配成功,返回和模式T中第一个字符相等的字符在主串S中的序号( i-T.length);否则称匹配不成功,返回0。

int Index_BF(SString S,SString T,int pos)
{
    i=pos;j=1;
    while(i<=S.length && j<=T.length) //未比较到串尾
    {
        if(S.ch==T.ch) //成功比较后继字符
        {
            ++i;
            ++j;
        }
        else //失败指针后退回溯,重新匹配
        {
            i=i-j+2;
            j=1;
        }
    }
    if(j>T.length) return i-T.length;
    else return 0;//fail
}

时间复杂度:

(1)最好情况:每趟不成功匹配都发生在模式串中第一个字符与主串中相应字符比较。

设从主串第i个位置开始与模式串匹配成功,则前面共比较i-1次。第i趟匹配成功的字符比较次数为m,总次数:i-1+m。匹配成功的主串从1到n-m+1,成功概率相等。

\frac{1}{n-m+1}\sum_{i=1}^{n-m+1} i-1+m=1/2(n+m)

O(n+m)

(2)最坏情况:每趟不成功匹配都发生在模式串中最后一个字符与主串中相应字符比较。

主串前n-m个位置都匹配到子串最后一位,比较m次。最后m位也各比较了一次。

总次数:(n-m)*m+m=(n-m+1)*m

O(nxm)


(2)KMP算法
每当一趟匹配过程中出现字符不等时,无需回溯主串指针,而是利用已经得到的“匹配”的结果将模式向右滑动尽可能的远的一段距离再比较。

·提速至O(m+n)

·主串指针不必回溯

·利用部分匹配结果加快模式串滑动速度

为此,定义next[j]函数,表明当模式中第j个字符与主串中相应字行“失配”时,在模式中需重新和主串中该字符进行比较的字符的位置。

*每次比较的位置编号(next[j])=最大公共前缀和长度+1

 *转自原视频评论

next数组(有些叫next-val数组):
1.必须先求模式串S 每一个字符前面的那个字符串的最大公共前后缀长度,将这一系列长度存成一个数组,求出来的每个长度其实就是和模式串每一个对应位置上做比较的下标
例如:模式串是ABACABC的最长公共前后缀长度数组为:我们将最长公共前后缀长度记作LCPSF,现在从模式串第一个字符A开始,A的前面字符串为null,所以A之前的子串的LCPSF是0;来到B,B的前面字符串是A,A是单独的字符不存在公共前后缀,所以长度也是0;来到A,A前面的子串是AB,LCPSF为0;来到C,C前面的子串是ABA,LCPSF为1;来到A,A前面的子串是ABAC,LCPSF为0;来到B,B之前子串为ABACA,LCPSF为1;来到C,C前面子串为ABACAB,LCPSF为2;到此这个最长公共前后缀数组就出来了=【0,0,0,1,0,1,2】将这个数组从第二个值开始每个值加1=【0,1,1,2,1,2,3】就是将要和子串对应位置比较的下标

 

 与BF算法不同之处:

当匹配过程中产生失配时,指针i不变,指针j退回到next[j]所指示的位置上重新比较。并且当指针j退回0时,指针i和j需要同时+1。即若失配,应从主串i+1个字符重新匹配。

int Index_KMP(SString S, SString T, int pos)
{
    i = pos;
    j = 1;
    while (i <= S.length && j <= T.length) // 未比较到串尾
    {
        if (j==0||S.ch == T.ch) /*J=0 成功比较后继字符
        {
            ++i;
            ++j;
        }
        else // 模式串向右移动
        {
            j=next[j]; /*
        }
    }
    if (j > T.length)
        return i - T.length;
    else
        return 0; // fail
}

计算next函数值

int main()
{
    string s;
    cin >>s;
    int next[110],i,j;
     j=0,next[1]=0,i=1;
   while(i<s.length())
   {
 
       if(j==0||s[i]==s[j]){++i,++j;next[i]=j;}
       else j=next[j];
   }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值