数据结构与算法C语言版学习笔记(5)-串,匹配算法、KMP算法
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、串的定义
- 二、串的存储结构
- 1.顺序结构
- 2.链式结构
- 三、串的朴素的模式匹配算法(暴力匹配算法)
- 1.背景
- 2.假设我们要从下面的主串 S="goodgoogle" 中,找到 T="google”这个子串的位置。
- 四、升级版的匹配算法:KMP模式匹配算法
- 1.背景:如果主串 S="aabaabaaf" ,要匹配的子串为 T=“aabaaf” 。
- 2.KMP算法解决的问题:字符串匹配中,将时间复杂度从O(m*n)缩短到O(m+n)
- 3.浅显的KMP匹配过程:
- 4.关键在于如何得知让子串跳到哪个位置去跟主串比较呢?(这里是b)——求最长相等前后缀
- ①一个串的前缀和后缀是什么?
- ②子串为 T=“aabaaf” 的前缀和后缀是什么?
- ③什么叫最长相等前后缀?
- ④根据前缀表求匹配
- ⑤next数组是什么?
- ⑥KMP算法的思想不难,难的是如何计算最长相同前后缀和next数组。
- 五、 KMP算法再举一个例子
- 主串:ababbaabbaababaaacb
- 子串:ababaa
- (1)手算求next数组:求子串每个字母和前面一坨的最长公共前后缀长度
- (2)KMP过程:
- 六、KMP算法的代码实现
- 1.求next数组
- 2.KMP算法
前言
关于串,首先想到的就是字符串。为什么会有字符串这个东西产生呢?
比如外国人说英语,都是字母,但是我们中国人说的话不是字母,只能是汉字,所以汉字这种特殊的、无法被计算机直接阅读的字符,在组成一个短语或者句子时,就形成了字符串。
字符串的产生是为了能够表示和处理文本信息。在计算机科学中,文本是一种非常常见的数据类型,例如输入的命令、输出的结果、存储的文件内容等等。为了能够对文本进行操作和处理,就需要一种能够表示和存储文本的数据类型,于是字符串应运而生。
字符串可以看作是由字符组成的序列,每个字符都有自己的编码表示,例如ASCII码或Unicode码。通过将字符依次排列组合,就可以构成一个完整的字符串。字符串可以进行各种操作,例如连接、截取、替换、查找等等,使得对文本的处理变得更加灵活和方便。
另外,字符串还可以用来表示和处理其他类型的数据,例如将数字转换为字符串进行输出、从用户输入的字符串中解析出数字等等。字符串的产生也是为了满足对不同类型数据的统一处理需求。
一、串的定义
在C语言中,字符和字符串是两个不同的概念,但它们之间存在一些联系和关联。
字符:字符是C语言中最基本的数据类型之一,用于表示单个字符。它使用单引号括起来,例如 ‘A’、‘9’、'!'等。每个字符在内存中占用一个字节的空间。
字符串:字符串是由一系列字符组成的序列,以空字符 ‘\0’ 结尾。在C语言中,字符串实际上是以字符数组的形式存在的。例如,“Hello” 可以表示为一个包含6个字符的字符数组:{‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’}。字符串可以使用双引号括起来,例如 “Hello”。
在数据结构中,串(String)是由零个或多个字符组成的有限序列。它是一种线性数据结构,可以用来表示和处理文本、符号序列等信息。
串的定义可以表示为:一个串S是一个字符的有限序列,记作S = “a1a2…an”,其中每个字符ai属于一个字符集,n表示串的长度。串的长度可以是零,称为空串。
串在存储上通常使用字符数组来表示,其中每个字符占用一个存储位置。通常,字符串的最后一个位置用特殊字符 ‘\0’ 表示串的结束。
二、串的存储结构
1.顺序结构
串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列的。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区。一般是用定长数组来定义。
既然是定长数组,就存在一个预定义的最大串长度,一般可以将实际的串长度值保存在数组的0下标位置,有的书中也会定义存储在数组的最后一个下标位置。但也有些编程语言不想这么干,觉得存个数字占个空间麻烦。它规定在串值后面加一个不计入串长度的结束标记字符,比如“\0”来表示串值的终结。
对于串数组的长度MaxSize,由于串数组长度是提前给定的,所以也很可能发生超出上限的情况。
2.链式结构
三、串的朴素的模式匹配算法(暴力匹配算法)
1.背景
字符串一般是一个有很多字符的组合,比如“Ilikeappleandyou"或者古诗“床前明月光,疑是地上霜”,这个时候我想在一个很大的字符串里面找到指定的子串“and”或者“明月”,应该怎么做呢?
这种子串的定位操作通常称做串的模式匹配, 应该算是串中最重要的操作之一。
2.假设我们要从下面的主串 S=“goodgoogle” 中,找到 T="google”这个子串的位置。
代码思路:设主串str,子串substr。先计算出两个字符串的长度为10和6,大循环从0开始,循环
str_len - substr_len=4次,表示子串最多后移四次就无法匹配成功了。每一次大循环里面,让子串的每一位和主串对应位比较,如果不相等就跳出小循环,大循环让子串后移一位。
int findSubstring(char *str, char *substr) {int str_len = strlen(str);int substr_len = strlen(substr);for (int i = 0; i <= str_len - substr_len; i++) {int j;for (j = 0; j < substr_len; j++) {if (str[i + j] != substr[j]) {break;}}if (j == substr_len) {return i; // 子串在主串中的起始位置}}return -1; // 子串未找到
}
朴素匹配算法是一种简单直观的字符串匹配算法,但它也存在一些缺点:
效率较低:朴素匹配算法的时间复杂度为O(n*m),其中n为主串的长度,m为子串的长度。在最坏的情况下,需要进行大量的字符比较和回溯操作,导致算法效率较低。
回溯次数较多:当主串中的某个字符与子串的第一个字符匹配,但后续字符不匹配时,朴素匹配算法需要回溯到主串中的下一个位置,继续进行匹配。这可能导致大量的回溯操作,影响算法的性能。
没有利用已有信息:朴素匹配算法没有利用已经匹配过的字符信息,每次都从头开始比较。这使得算法的效率较低,尤其是在处理大规模文本时。
所以需要改进算法。
四、升级版的匹配算法:KMP模式匹配算法
1.背景:如果主串 S=“aabaabaaf” ,要匹配的子串为 T=“aabaaf” 。
朴素匹配算法时,主串从第一位开始逐次与子串比较,比较一圈不匹配后又从第二位开始逐次与子串比较,如此往复。那么主串需要不断的回溯,之前比较时得到的信息没有充分利用。
2.KMP算法解决的问题:字符串匹配中,将时间复杂度从O(m*n)缩短到O(m+n)
3.浅显的KMP匹配过程:
(1)第一次匹配时,a-a、a-a、b-b、a-a、a-a、b-f,这时不一致了。
(2)我不想回溯重新匹配,所以第二次匹配,让子串跳到从b之后开始匹配,这样的话,刚好一个循环就能完成匹配。所以KMP算法重要的思想就是:省略了普通算法中逐次比较的第2、3、4、5、、、步,只进行了第1步和可以成功匹配的最后一步。
4.关键在于如何得知让子串跳到哪个位置去跟主串比较呢?(这里是b)——求最长相等前后缀
①一个串的前缀和后缀是什么?
一个字符串的前缀是指从开头到某个位置的子串,后缀是指从结尾到某个位置的子串。换句话说,给定一个字符串S,它的前缀是S的任意一个以开头的子串,而后缀是S的任意一个以结尾的子串。
例如,对于字符串"ABCD",它的前缀包括:“” (空串),“A”,“AB”,“ABC”,而后缀包括:“BCD”,“CD”,“D”,“” (空串)。
②子串为 T=“aabaaf” 的前缀和后缀是什么?
前缀:a、aa、aab、aaba、aabaa
后缀:f、bf、abf、aabf、baabf、abaaf
记忆技巧:前缀:有头无尾 后缀:有尾无头
③什么叫最长相等前后缀?
子串都有自己的前缀和后缀,对每个前缀进行分析,看看他们的前后缀有没有相同的,有几项,就记录为几。
根据子串的前缀来分析子串前缀的前后缀:
比如aaba,前缀a和后缀a相同,长度为1;前缀aa和后缀ba不同,前缀aab和后缀aba不同。
比如aabaa,前缀aa和后缀aa相同,长度为2,是最长的。
这个东西叫做前缀表。
④根据前缀表求匹配
第一次匹配后,b≠f,那么要找f前面的子串的最长相等前后缀,即为2。
数字2意味着什么呢?f之前的前缀是aabaa,意味着后缀aa和前缀aa刚好形成了一个相同且对称的形式。而我们要让第二次匹配时子串跳到b的位置去,因为b在子串的这个数组里刚好下标就是2。
所以第二次匹配时,子串就从主串的b位置开始逐一比较。省略了前面的一些繁琐的步骤,简化了时间复杂度。
⑤next数组是什么?
就是求出最长的相等的前后缀,把长度记录到next数组中。
next数组:当主串与子串的某一位字符不匹配时,子串要回退的位置。
⑥KMP算法的思想不难,难的是如何计算最长相同前后缀和next数组。
五、 KMP算法再举一个例子
主串:ababbaabbaababaaacb
子串:ababaa
(1)手算求next数组:求子串每个字母和前面一坨的最长公共前后缀长度
①a:前面没有,就是0
②ab:前缀a,后缀a,长度为1;
③aba:前缀a,后缀a;前缀ab,后缀ba;长度为1
④abab:前缀ab,后缀ab,长度为2
⑤ababa:前缀aba,后缀aba,长度3
⑥ababaa:前缀a,后缀a,长度1
所以前缀表:
a b a b a a
0 1 1 2 3 1
所以next数组:
a b a b a a
-1 0 0 1 2 0
(2)KMP过程:
这样不断让子串往后面对齐移动,其中省略掉的就是不用让子串每次重新回到主串头位置了,根据已有的信息巧妙地省略掉了公共的、无意义的比较过程。
六、KMP算法的代码实现
1.求next数组
void calculateNext(char *pattern, int *next) {int len = strlen(pattern);int i = 0, j = -1;next[0] = -1;while (i < len) {if (j == -1 || pattern[i] == pattern[j]) {i++;j++;next[i] = j;} else {j = next[j];}}
}
函数 calculateNext 用于计算模式串的 Next 数组。
首先,获取模式串的长度 len,并初始化两个指针 i 和 j,其中** i 表示当前遍历到的位置,j 表示前缀的末尾位置**。
然后,将** Next 数组的第一个元素 next[0] 设置为 -1,表示不存在前缀**。
接下来,使用一个循环,从索引 1 开始遍历子串的字符:
如果 j 等于 -1 或者当前字符 pattern[i] 等于前缀的末尾字符 pattern[j],则说明可以扩展当前位置的前缀长度,即 i++ 和 j++,然后将 j 的值赋给 next[i]。
如果当前字符不匹配,则需要回溯到更短的相等前后缀。将 j 更新为 next[j],即回溯到前缀的前缀。
最后,循环结束后,Next 数组中存储了每个位置的最长相等前后缀的长度。
这个函数的目的是为了通过利用已匹配的部分,避免无谓的字符比较,从而提高字符串匹配的效率。
2.KMP算法
思路:先获取next数组,然后
int kmpSearch(char *text, char *pattern) {int textLen = strlen(text);int patternLen = strlen(pattern);int i = 0, j = 0;int next[patternLen];calculateNext(pattern, next);while (i < textLen && j < patternLen) {//条件为 i 小于文本串的长度且 j 小于模式串的长度if (j == -1 || text[i] == pattern[j]) {//如果 j 等于 -1 或者当前文本串字符 text[i] 等于模式串字符 pattern[j]i++;//说明当前字符匹配成功,继续比较下一个字符,即 i++ 和 j++j++;} else {j = next[j];//如果当前字符不匹配,则需要根据 Next 数组来进行回溯//将模式串向右移动到最大匹配的位置}}if (j == patternLen) { //j 等于模式串的长度return i - j; // 已完全匹配成功,返回匹配的起始位置} else {return -1; // 没有找到匹配的子串}
}
函数 kmpSearch 是使用 KMP 算法在文本串中查找匹配的子串。
首先,获取文本串和模式串的长度,并初始化两个指针 i 和 j,分别指向文本串和模式串的起始位置。
然后,创建一个长度为模式串长度的 Next 数组,并调用 calculateNext 函数来计算模式串的 Next 数组。
接下来,使用一个循环,条件为 i 小于文本串的长度且 j 小于模式串的长度:
如果 j 等于 -1 或者当前文本串字符 text[i] 等于模式串字符 pattern[j],则说明当前字符匹配成功,继续比较下一个字符,即 i++ 和 j++。
如果当前字符不匹配,则需要根据 Next 数组来进行回溯。将 j 更新为 next[j],即将模式串向右移动到最大匹配的位置。
循环结束后,有两种情况:
如果 j 等于模式串的长度,表示模式串已完全匹配成功,返回匹配的起始位置 i - j。
如果 j 不等于模式串的长度,表示没有找到匹配的子串,返回 -1。
相关文章:

数据结构与算法C语言版学习笔记(5)-串,匹配算法、KMP算法
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、串的定义二、串的存储结构1.顺序结构2.链式结构 三、串的朴素的模式匹配算法(暴力匹配算法)1.背景2.假设我们要从下面的主串 S"…...

新版HI3559AV100开发注意事项
新版HI3559AV100开发注意事项 一、在Hi3559A上使用openCV VideoCapture开启.mp4影像档, isOpened一直得到false 在Hi3559A上已经cross compile ffmepg 4.1openCV 3.4.4 但使用openCV VideoCapture开启.mp4影像档, isOpened一直得到false 请问要如何知道是什么原因无法开启影像…...

Django(一、简介,安装与使用)
文章目录 一、Django引入1.web应用程序什么是web?web引用程序的优点web应用程序的缺点什么是web框架 2.纯手写web框架1.web框架的本质2.HTTP协议的特性:3.编写基于wsgire模块搭建web框架代码封装优化代码封装 二、Django框架的学习1.Python中的主流框架2…...

【Linux C IO多路复用】多用户聊天系统
目录 Server-Client mutiplexingServer mutiplexingClient mutiplexing Server-Client 在Linux系统中,IO多路复用是一种机制,它允许一个进程能够监视多个文件描述符(sockets、pipes等)的可读、可写和异常等事件。这样…...

JSON——数组语法
一段JSON可能是以 ”{“ 开头 也可能仅包含一段JSON数组 如下 [ { "name" : "hello,world"}, {"name" : "SB JSON”}, {“name” : "SB互联网房地产CNM“}, ] 瞧,蛋疼不...CJSON过来还是得搜下网…...

运营商大数据精准获客:我们提供精准客源渠道的最大资源体?
运营商大数据精准营销 谈起精准获客,竞争对手永远是为我们提供精准客源渠道的最大资源体! 最新的获客方式,就是从竞争对手的手中把他们的精准客户资源变为自己的。 今年最火的运营商大数据精准营销是拒绝传统营销方式的烧钱推广࿰…...
表象变换与矩阵元
表象变换 一维粒子哈密顿量 表象中的矩阵元 态的表象变换 不难证明 算符的表象变换 坐标表象 Non-denumerable basis...
vue乾坤微前端项目
1、主应用 安装乾坤 npm i qiankun -S 注册微应用并启动: import { registerMicroApps, start } from qiankun;//设置两个微应用 registerMicroApps([{name: vue1, //要跟package.json中的name保持一致entry: //localhost:8081, //本地就这么写container: #cont…...

大语言模型比武
今年随着 ChatGPT 的流行,并在各个领域有一定程度生产级别的应用。国内外也掀起了一股大语言模型浪潮,各大厂商都推出了自己的大语言模型,阿里推出了 通义千问,腾讯推出了 Hunyuan,亚马逊云推出了 Titan,大…...
王道数据结构第五章二叉树的遍历第13题
目录 解题思路 宏定义 二叉树定义 栈定义 实现函数 测试代码 测试结果...
微服务的发展历程的详细说明及每个阶段主流的架构和组件
微服务的发展历程的详细说明及每个阶段主流的架构和组件如下: 一、微服务的发展历程: 起始阶段:这个阶段主要是面向服务的架构(SOA)的兴起。此时,企业开始尝试将单体应用拆分为多个服务,但此时…...

2023年眼镜行业分析(京东眼镜销量数据分析):市场规模同比增长26%,消费需求持续释放
随着我国经济的不断发展,电子产品不断普及,低龄及老龄人口的用眼场景不断增多,不同年龄阶段的人群有不同的视力问题,因此,视力问题人口基数也随之不断加大,由此佩戴眼镜的人群也不断增多。 同时,…...
基础课26——业务流程分析方法论
基础课25中我们提到业务流程分析方法包括以下几种: 价值链分析法:主要是找出或设计出哪些业务能够使得客户满意,实现客户价值最大化的业务流程。要进行价值链分析的时候可以从企业具体的活动进行细分,细分的具体方面可以从生产指…...

【数字图像处理-TUST】实验二-图像噪声生成与滤波降噪
一,题目 读入一幅图像使用两种以上的方法向图像中分别添加噪声输出一幅二值图像,背景为黑色,噪声区域为白色使用三种滤波方法对上述添加了噪声的图像进行降噪处理输出降噪处理后的结果图像 二,实验原理 采用了两种方法添加了噪…...

bilibili快速升满级(使用Docker 容器脚本)
部署bilibili升级运行容器脚本 docker run --name"bili" -v /bili/Logs:/app/Logs -e Ray_DailyTaskConfig__Cron"30 9 * * *" -e Ray_LiveLotteryTaskConfig__Cron"40 9 * * *" -e Ray_UnfollowBatchedTaskConfig__Cron"…...

Android 13.0 Settings主页面去掉FocusRecyclerView相关功能
1.前言 在13.0的系统rom产品定制化开发中,在系统Settings主页面的主菜单中,在测试某些功能的时候,比如开启护眼模式和改变系统密度会在主菜单第一项的网络菜单头部增加 自定义您的设备和设置护眼模式时间安排 等等相关的设置模块 这对于菜单布局显示相当不美观,所以根据系…...

Python(四)字符串
程序员的公众号:源1024,获取更多资料,无加密无套路! 最近整理了一波电子书籍资料,包含《Effective Java中文版 第2版》《深入JAVA虚拟机》,《重构改善既有代码设计》,《MySQL高性能-第3版》&…...
WPF中ElementName与RelativeSource绑定的局限性以及对策
完全来源于十月的寒流,感谢大佬讲解 <Window x:Class"Test_01.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schem…...

基于PHP语言的会员系统搭建(Docker版)
1、操作系统 准备: ubuntu22机器 基础:docker:【精选】Docker微服务-基础_v2/_catalog-CSDN博客 2、安装Docker # Add Dockers official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/…...

文件改名:一次性解决文件名混乱,批量重命名技巧
在日常生活和工作中,我们经常会遇到文件名混乱的问题,例如文件名重复、格式不统一或者文件名错误等。这些问题不仅会给我们带来查找和使用上的困扰,还会影响我们的工作效率。为了解决这些问题,我们可以使用批量重命名技巧…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...
Python网页自动化Selenium中文文档
1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API,让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API,你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...