第四章 串

一、串的定义和实现

1.串的定义

定义:由n个字符组成的有限序列

        其中n为0的串称为空串。

表示:用单括号引起来。eg: S='abcdeff'

子串:串中任意多个连续的字符组成的子序列称为该串的子串。


主串:包含子串的串。

空格串:由一个或多个空格组成的串。串的长度为空格字符的个数。

2.串的存储结构

①定长顺序存储表示

用一组地址连续的存储单元来存储串值得字符序列。

#define MAXLEN 255
typedef struct{
    char ch[MAXLEN];    //每个分量存储一个字符
    int length;        //串的实际长度
}SString;

超过预定义的串值时会被舍去,称为截断


被截断的数据再次使用会造成错误,所以为了克服这种弊端,只能不限串长的最大长度,所以要采用动态分配的方式。

②堆分配存储表示

堆分配存储表示仍然以一组地址连续的存储单元存放串值得字符序列,但是它们的存储空间是在程序执行过程中动态分配得到的。

typedef struct{
    char *ch;            // 按串长分配存储区,ch指向串的基地址
    int length;        //串的长度
}HString;

C语言解释


在c语言中,有个称之为:"堆"的自由存储区,并用malloc()和free()函数来完成动态存储管理。
        malloc():为每个新产生的串分配一块实际串长所需要存储空间,若分配成功,则返回一个指向起始地址的指针,作为串的基址。这个串有ch指针来指示。若分配失败,则返回NULLL。

        free():释放掉已分配的空间。

③块链存储表示

        类似于线性表的链式存储结构,可以采用链表方式存储串值。

特点:每个结点可以存放多个或1个字符。每个结点称为块。

二、串的模式匹配

模式匹配:子串的定位操作,求的是子串在主串中的位置。


统考不考算法题。

1.简单的模式匹配算法(暴力匹配算法)

算法思想:

        子串匹配主串。

                第一步:从子串的第一个字符开始匹配主串的第一个字符

                        如果成功,子串字符++ 和主串字符++再次比较

                        如果失败,子串第一个字符和主串字符++比较

                第二步:如果主串字符匹配到尽头了,还没成功,表示失败。

暴力算法匹配核心:子串匹配失败,就从头再来。和主串++的位置开始比较

课本上算法思想:从主串的第一个字符起,与模式串的第一个字符比较,若相等,逐个比较后续字符;否则从主串的下一个字符起,重新和模式串的字符比较。

int index(SString s,SString t){
    int i=1,j=1;
    while(i<=s.length && j<=t.length){
        if(s.ch[i] == T.ch[j]){
            ++i;
            ++j;   //继续比较后续字符
        }else{
            i=i-j+2;    
            j=1;    //指针后退重新开始匹配
        }
        
    }
    if(j>t.length)
        return i-t.length
    else
        return 0;

}

简单模式匹配算法的最坏时间复杂度为O(mn),其中n和m分别为主串的模式串的长度。

分析:

        暴力匹配中,每趟匹配失败都是模式串后移一位再从头开始比较,这就是其低效率的根源。

        假如说现在有这样一种场景,若以匹配相等的前缀序列中有某个后缀正好是模式串的前缀,我们是不是可以将模式串向后滑动到与这些字符对齐的位置呢。主串i指针无须回溯,并从该位置进行比较。


Q:这个位置该如何确定呢?

        模式串向后滑动位数仅与模式串本身结构相关,与主串无关。

2.串的模式匹配算法---KMP算法

KMP这个算法,研究的是子串指针回溯到哪的问题。即从子串的哪个位置开始匹配。

①字符串的前缀、后缀和部分匹配值

在开始研究KMP之前,还必须要搞清楚几个概念。


前缀:指除了最后一个字符以外,字符串的所有头部子串。

后缀:指除了第一个字符外,字符串的所有尾部子串。

部分匹配值:字符串的前缀和后缀的最长相等前后缀长度。

eg:ababa

②PM(partial Match)表

通过该表,计算出子串后移的位置。

        该表由三项组成

                第一项:编号,从1开始

                第二项:字符,从第1个位置依次列出所有字符

                第三项:该字符对应的部分配置值(该字符之前的字符地最长前缀数量)



eg:abcac
 

后移位数:已匹配的字符数 - 对应的部分匹配值

整个匹配过程中,主串始终没有后退,所以KMP算法可以在O(n+m)的时间数量级上完成模式匹配。

③next数组

还有一种思想:(做题感悟)

        next数组的本质:子串在匹配主串失效时,未来我子串的指针要回溯到哪个地方,从子串的哪个位置开始匹配,所以我就去找子串的失效的位置前面字符串的最长匹配前缀,既然前缀和后缀一样,我完全可以略过前缀的字符串,从前缀的下一个字符串和刚才在主串中失效的位置进行接下来的比较。

要求:要会手动求next数组

使用部分匹配时,每当匹配失败,就去找它前一个元素的部分匹配值,这样用起来不方便。

于是我们发明next数组,当哪个元素匹配失败,直接看它自己的部分匹配值即可。

Q:问题来了,我们怎么计算这个next数组表?

        

动作:

        把PM表第三个字段的元素值,进行右移1位
        第一个位置右移没了,这个位置填 -1

        最后一个位置没有位置了,这个位置的数字直接舍去
 

需要注意的是:有的题目将next的值在原来的基础上都加了一个1,这样更符合人们所理解的未来匹配失败的话,未来主串失败的地方上要从子串的第几个位置上开始匹配。

                --->那如何区分有的是加了1的还是没加1呢?

                                      看第一个next值的元素的值,如果为-1,代表没在原来基础上加一

                                                                                     如果为0,代表在原来基础上加一  

在使用next数组时,有这样一种特殊情形:

        主串i这个位置和子串j=1(也就是子串的第一个字符)这个位置,发生了匹配失效的情形,此时会做一个额外的处理,让i++,j++;
 

3.KMP算法的进一步优化

Q:这个next数组怎么用的

这个next数组表示的是子串在这里匹配失败了,那未来要从子串的下标这里开始进行匹配。(注意:子串的next数组从零开始,也就是意味者next数组为零时,要从第子串的第一个字符开始匹配)

其实next数组还有缺陷。

        下面这个例子说明一下,

                主串s:aaabaaaab

                模式串p:aaaab

                

缺陷情况:

        s[4]≠p[4],此时去查next数组,p[4]对应的next值为3,说明要匹配子串字符是P[3];

        s[4]≠p[3],此时去查next数组,p[3]对应的next值为2,说明接下来要匹配子串字符是p[2]
        s[4]≠p[2] .....

        s[4]≠p[1]....

        主串指针和模式串指针++

这里发现有多次重复不匹配的现象。

                --->那么请思考,引发这种问题出现的原因是什么?

                                只有想明白这个问题,才能更好地记住nextval的计算方式

①nextval数组

nextval数组本质上是一个递归的思想

        即该位置上的字符要发生匹配失败的情形,那此时会按照next的数组上的位置对应的字符进行比较,假如说,对应的字符和发生匹配失败的字符相等的话,那此时就还需要额外的对比一次,还不如直接找要再次匹配字符对应的next值。

        其实计算nextval数组就是利用了上面的思想。

nextval数组值要怎么计算 ?(按照加一的next值来说,这样方便解释)

                1^{o} 先计算next数组

                2^{o} 观察三个地方  next[]对应的那个字符a、next[]值对应的字符b、next[]值v

                3^{o} 开始写nextval数组

                        ①nextval[0]  =0;    (固定)

                        情形一:假如 a==b,将b的v写到a对应的nextval值上

                        情形二:假如 a!=b,将a的v写到a对应的nextval值上去

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值