408数据结构学习笔记——串、朴素模式匹配、kmp算法及其改进

简介: 408数据结构学习笔记——串、朴素模式匹配、kmp算法及其改进

1.串的定义(不在大纲范围)

串,即字符串( String)是由零个或多个字符组成的有限序列

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

主串:包含子串的串。

字符在主串中的位置:字符在串中的序号。

子串在主串中的位置:子串的第一个字符在主串中的位置。

串是一种特殊的线性表,数据对象限定为字符集,基本操作(增删改查)对象为子串

0ce935f0e31f453ba561a676935b544b.png其中StrCompare(S, T)字符串比较操作实际上比较的是字符对应的二进制数的大小(ASCII码)

2.串的存储结构(不在大纲范围)

2.1.串的顺序存储

//静态数组方式
#define MAXLEN 255 //
typedef struct{
    char ch[MAXLEN];    //静态分配一个数组,数组每个元素存储一个字符
    int length;    //串的实际长度
}sString;
//动态分配方式,使用完后要手动free
typedef struct{
    char *ch;    //按串长度分配空间,ch指向串的首地址
    int length;    //串的实际长度
}hString;
hString str;
str.ch = (char*)malloc(MAXLEN * sizeof(char));
str.length = 0;

2.2.串的链式存储

typedef struct stringNode{
    //因为*next指针的大小为4,如果ch元素的个数过低,则存储密度就低
    char ch[4]; 
    struct stringNode *next;
}stringNode, *string;

3.朴素模式匹配

542a89e5a6f240839c0bca635fc89873.png

算法思想:从S的首个字符开始选取与T相同长度的字符内容,逐一与T的字符串内容进行匹配。如果有任意一个字符不同,则进行下一轮匹配;如果都相同,则结束循环,返回进入循环的S的字符的地址

最坏时间复杂度:O(mn)

int index(sString S, sString T){
    int k = 1;
    int i = k, j = 1;
    //遍历S和T
    while (i <= S.length && j <= T.length){
        //当前s和t的字符相同,则继续匹配
        if (S.ch[i] == T.ch[j]){
            i++;
            j++;
        }
        //当前s和t的字符不相同,则k向后移动,从k处继续下一轮循环
        else{
            k++;
            i = k;
            j = 1;
        }
    }
    //s和t相同,则返回k
    if (j > T.length) return k;
    else return 0;
}

缺点:没有考虑到子串和主串的在匹配中可能含有公共前缀,需要经常回溯

4.kmp算法(选择题)

改进思路:

1.主串指针不回溯,只有模式串指针回溯

2.当j = k时,匹配失败,说明前k - 1个元素是匹配成功的,因此,可以将模式串指针(主串指针不动)移动到下一个已经匹配成功部分的模式串(子串)中的最大公共前后缀的位置上

串的前缀:包括第一个字符,且不包括最后一个字符的子串(即不包括自身)

串的后缀:包含最后一个字符,且不包含第一个字符的子串(即不包括自身)

next数组:当j = k时,匹配失败,前k - 1个字符组成的字符串记为S,next[j] = S的最长相等前后缀长度+1(S自身比较)。特别的,next[1] = 0,next[2] = 1(任何模式串)

1.next[1] = 0的含义是设主串的匹配指针为i,模式串的匹配指针为j,next[1]出现的场合是模式串和主串的第一次匹配就失败,此时,执行操作:j = next[j] 即j = next[1] = 0,执行完后执行i++,j++就可以直接重新对主串的下一个元素和模式串的第一个元素进行匹配

2.next[2]的求法是:设字符串为abab,j = 2,即需要求a的最长相等前缀后缀,因为前缀不包含最后一个字符,后缀不包含第一个字符,因此,最长相等前后缀为0,next[2] = a的最长相等前后缀长度 + 1 = 0 + 1 = 1

3.为什么next[j] = S的最长相等前后缀长度+1中,需要+1:如果不+1,仅仅是移动到最长相等前后缀的最后一个元素,而我们需要进行匹配的是下一个元素

例:主串:ABABBA 模式串:ABAA    i为主串工作指针,j为模式串工作指针

当i = 4,j = 4时,匹配失败,模式串的最长相等前后缀长度为1,如果不加1,j = next[j] = 1,这样就会回到A,而按照kmp算法的思想A不需要再次进行匹配,我们需要进行的下次匹配是模式串的B

时间复杂度O(m+n)

5.kmp算法的改进

d627c9ce01ca4f0ba597333d11e31fb5.png

以王道书图中的例子为例。

  1. j = 4 指向a,i = 4 指向b时,匹配失败,则 j = next[4] = 3
  2. 进行下一轮匹配:j = 3 指向a,i = 4 指向b,匹配失败,则 j = next[3] = 2
  3. 进行下一轮匹配:j = 2 指向a,i = 4 指向b,匹配失败,则 j = next[2] = 1
  4. 进行下一轮匹配:j = 1 指向a,i = 4 指向b,匹配失败,则 j = next[1] = 0
  5. 进行下一轮匹配:j++,i++,j = 1 指向a,i = 5 指向a

从中可知,2 - 5的匹配结果其实是已知的,因为在 1 的时候就已经进行过一次模式串为a,主串为b的匹配,因此,2 - 5的匹配实际上是可以进行优化的

改进方法:在next数组的基础上,如果出现匹配失败的情况,需要多进行一次 j 指向的模式串元素是否与更新后 j = next[j] 指向的元素相等的判断,如果两者相等,则继续递归进行 j = next[next[j]],直至两者不相等为止,然后将结果更新为nextval数组

6.王道课后题

9988172c280f4902afd894fc95840003.png

  1. next[1]出现在模式串的第一个元素与主串当前元素匹配失败的情况下,将模式串的工作指针归零,然后进行j++和i++操作,从头开始匹配模式串和主串的下一个元素
  2. 取模式串中最大相等前后缀的长度作为模式串右移的距离,k的最大值是j - 1,因为前缀不包括最后一个字符,后缀不包括第一个字符
  3. 其他情况是模式串的最大相等前后缀仅为第一个字符和最后一个字符,取next[j]  = 1就是直接 j 移动到模式串的最后一个字符的当前位置,模式串从第一个位置,主串从第i个位置进行匹配

9a76211ce2344c688006110c1666e09d.png

解:除了next[1]不固定+1外(需要从头开始匹配模式串和子串),所有next数组都要固定 + 1,表示从模式串和子串的公共前后缀的下一个字符开始匹配(公共前后缀的意义就是将模式串某些前缀和后缀相同,在公共前后缀的前缀+1处匹配失败后,不需要匹配,直接快速移动到公共前后缀的后缀+1处进行下次匹配)

  1. j = a, next[1] = 0(next[1]从头开始匹配模式串和子串,固定为0)
  2. j = a a, next[2] = 0 + 1 = 1(前缀不能包括最后一个字符,后缀不能包括第一个字符)
  3. j = aa b, next[3] = 1 + 1 = 2(公共前后缀长度为1)
  4. j = aab a, next[4] = 0 + 1 = 1(第一个字符为a,最后一个字符为b,没有公共前后缀)
  5. j = aaba a, next[5] = 1 + 1 = 2
  6. j = aabaa c, next[6] = 2 + 1 = 3

2.解:next[j]的j代表取模式串的前j个字符计算公共前后缀

  1. i = 1 = a, j = 1 = a, 匹配成功 i++, j++
  2. i = 2 = a, j = 2 = a, 匹配成功 i++, j++
  3. i = 3 = b, j = 3 = b, 匹配成功 i++, j++
  4. i = 4 = a, j = 4 = a, 匹配成功 i++, j++
  5. i = 5 = a, j = 5 = a, 匹配成功 i++, j++
  6. i = 6 = b, j = 6 = c, 匹配失败 j = next[j] = 3
  7. i = 6 = b, j = 3 = b, 匹配成功 i++, j++
  8. i = 7 = a, j = 4 = a, 匹配成功 i++, j++
  9. i = 8 = a, j = 5 = a, 匹配成功 i++, j++
  10. i = 9 = b, j = 6 = c, 匹配失败 j = next[j] = 3
  11. i = 9 = b, j = 3 = b, 匹配成功 i++, j++
  12. i = 10 = a, j = 4 = a, 匹配成功 i++, j++
  13. i = 11 = a, j = 5 = a, 匹配成功 i++, j++
  14. i = 12 = c, j = 6 = c, 匹配成功 i++, j++
  15. i > 模式串长度 结束循环 return true


相关文章
|
1月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
72 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
1月前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
34 4
|
1月前
|
机器学习/深度学习 搜索推荐 算法
探索数据结构:初入算法之经典排序算法
探索数据结构:初入算法之经典排序算法
|
1月前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
8天前
|
算法 数据安全/隐私保护 索引
OFDM系统PAPR算法的MATLAB仿真,对比SLM,PTS以及CAF,对比不同傅里叶变换长度
本项目展示了在MATLAB 2022a环境下,通过选择映射(SLM)与相位截断星座图(PTS)技术有效降低OFDM系统中PAPR的算法实现。包括无水印的算法运行效果预览、核心程序及详尽的中文注释,附带操作步骤视频,适合研究与教学使用。
|
16天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。
|
17天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
18天前
|
存储 算法 决策智能
基于免疫算法的TSP问题求解matlab仿真
旅行商问题(TSP)是一个经典的组合优化问题,目标是寻找经过每个城市恰好一次并返回起点的最短回路。本文介绍了一种基于免疫算法(IA)的解决方案,该算法模拟生物免疫系统的运作机制,通过克隆选择、变异和免疫记忆等步骤,有效解决了TSP问题。程序使用MATLAB 2022a版本运行,展示了良好的优化效果。
|
17天前
|
机器学习/深度学习 算法 芯片
基于GSP工具箱的NILM算法matlab仿真
基于GSP工具箱的NILM算法Matlab仿真,利用图信号处理技术解析家庭或建筑内各电器的独立功耗。GSPBox通过图的节点、边和权重矩阵表示电气系统,实现对未知数据的有效分类。系统使用MATLAB2022a版本,通过滤波或分解技术从全局能耗信号中提取子设备的功耗信息。
|
17天前
|
机器学习/深度学习 算法 5G
基于MIMO系统的SDR-AltMin混合预编码算法matlab性能仿真
基于MIMO系统的SDR-AltMin混合预编码算法通过结合半定松弛和交替最小化技术,优化大规模MIMO系统的预编码矩阵,提高信号质量。Matlab 2022a仿真结果显示,该算法能有效提升系统性能并降低计算复杂度。核心程序包括预编码和接收矩阵的设计,以及不同信噪比下的性能评估。
36 3
下一篇
无影云桌面