数据结构与算法C语言版学习笔记(5)-串,匹配算法、KMP算法

news2024/12/25 22:32:42

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、串的定义
  • 二、串的存储结构
    • 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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1189441.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VS Code+DevChat助力非专业开发也能玩转代码编程

一、前言 偶然间网上瞎逛&#xff0c;看到DevChat 发布了一款 VS Code 插件&#xff0c;可提供类似chatgpt一样的“一站式 AI 辅助编程”体验。据说&#xff0c; DevChat 直接对接 GPT-4 还让免费用&#xff0c;目前免费注册收邮件即可获取key&#xff0c;再也不用麻烦的外部手…

人工智能在汽车业应用的五项挑战

在汽车行业扩展人工智能应用时需要注意的问题 随着更多企业投资于汽车人工智能 (AI) 解决方案&#xff0c;我们也愈加接近大规模部署 5 级全自动驾驶汽车。汽车行业的组织如果希望加入这场 AI 带来的颠覆性变革&#xff0c;就应该已提前考虑如何成功和大规模地将人工智能部署到…

商人宝:收银系统一般多少钱

推荐方案基于来自各大电商平台的用户评价数据&#xff0c;为您推荐以下收银系统数据&#xff1a; 1.硬件方面 小票打印机、扫码枪、收银盒等硬件终端设备都是不可或缺的。一般小票打印机、扫码枪、收银盒&#xff0c;价格在2-500元之间&#xff0c;性能稳定、使用寿命长、使用…

记录第一次银行测试岗面试【总结几点面试不要犯得错误】

LZ在一个18线小城市做测试&#xff0c;近来想走出自己的舒适区&#xff0c;去做一点不一样的测试工作。 18线地区&#xff0c;测试工作并不多。最好的差不多就是LZ目前待着的公司了。遂决定去魔都闯荡几年&#xff0c;对一个在魔都无房无车无户口的人来讲&#xff0c;这意味着…

循环链表的设计与基本操作的实现

目录 一.循环链表的设计 二.循环链表的实现 三.循环链表的总结 一.循环链表的设计 1.循环链表的结构设计: typedef struct CNode{int data;struct CNode* next;}CNode ,*CList; 2.循环链表的示意图: 3.循环链表和单链表的区别: 唯一区别,没有空指针,尾节点的后继为头,为循…

订水商城实战教程09-跑马灯

目录 1 跑马灯效果2 创建数据源3 创建变量4 搭建组件5 数据绑定6 录入测试数据总结 上一篇我们介绍了轮播图如何开发&#xff0c;本节我们介绍一下跑马灯的效果开发。 1 跑马灯效果 通常小程序会增加一点动画的效果来让页面显得不那么死板&#xff0c;我们这里增加了一个跑马灯…

软件测试|测试方法论—边界值

边界值分析法是一种很实用的黑盒测试用例方法&#xff0c;它具有很强的发现故障的能力。边界值分析法也是作为对等价类划分法的补充&#xff0c;测试用例来自等价类的边界。 这个方法其实是在测试实践当中发现&#xff0c;Bug 往往出现在定义域或值域的边界上&#xff0c;而不…

深度学习4:BatchNormalization(批规范化)

一、起源 训练深度网络的时候经常发生训练困难的问题&#xff0c;因为&#xff0c;每一次参数迭代更新后&#xff0c;上一层网络的输出数据经过这一层网络计算后&#xff0c;数据的分布会发生变化&#xff0c;为下一层网络的学习带来困难。 Batch Normalizatoin 之前的解决方…

【小黑送书—第四期】>>用“价值”的视角来看安全:《构建新型网络形态下的网络空间安全体系》

经过30多年的发展&#xff0c;安全已经深入到信息化的方方面面&#xff0c;形成了一个庞大的产业和复杂的理论、技术和产品体系。 因此&#xff0c;需要站在网络空间的高度看待安全与网络的关系&#xff0c;站在安全产业的高度看待安全厂商与客户的关系&#xff0c;站在企业的高…

excel表的筛选后自动求和

一般都使用subtotal函数。 通过看一个大佬的视频&#xff0c;发现可以有更简单的方法。 首先任意筛选数据(ctrlshiftl)&#xff0c; 然后选中需要求和的列的最下方的空白单元格&#xff0c;再按alt。 回车即可。 实质它还是用的subtotal函数

“锡安主义”贝尔福宣言希伯来抵抗运动犹太启蒙改革运动奋锐党闪米特人雅利安人

目录 “锡安主义” 贝尔福宣言 希伯来抵抗运动 犹太启蒙改革运动 奋锐党 闪米特人 雅利安人 “锡安主义” “锡安主义”是一种政治和民族运动&#xff0c;旨在支持并促进犹太人建立自己的国家并在历史上与宗教上的祖先之地——巴勒斯坦地区建立一个独立的国家。这一运动…

RHCE8 资料整理(五)

RHCE8 资料整理 第五篇 系统管理第18章 进程管理18.1 进程介绍18.2 查看进程18.3 向进程发送信号18.4 进程优先级 第19章 日志19.1 rsyslog的配置19.2 查看日志 第20章 网络时间服务器20.1 时间同步必要性20.2 配置时间服务器20.3 配置客户端 第21章 计划任务21.1 at21.2 cront…

java测试private

java测试private变量、方法 📕反射的基本作用、关键? 反射是在运行时获取类的字节码文件对象,然后可以解析类中的全部成分反射的核心思想和关键就是:得到编译以后的class文件对象可以破坏封装性(很突出)也可以破坏泛型的约束性(很突出)更重要的用途是适合:做java高级…

【tgcalls】Instance接口的实例类的创建

tg 里有多个版本,因此设计了版本管理的map,每次可以选择一个版本进行实例创建这样,每个客户端就可以定制开发了。tg使用了c++20创建是要传递一个描述者,里面是上下文信息 G:\CDN\P2P-DEV\tdesktop-offical\Telegram\ThirdParty\tgcalls\tgcalls\Instance.cpp可以看到竟然是…

谷歌桌面布局修改

前言 近期接到一个关于谷歌EDLA认证的需求&#xff0c;我负责的是谷歌原生桌面布局的修改&#xff0c;通过研究源码&#xff0c;将涉及到了一些修改思路发出来&#xff0c;大家可以参考一下有没有对你有用的信息。主要修改内容有&#xff1a; 1、搜索栏、底部导航栏未居中 2、…

VBA技术资料MF81:查找特定填充颜色的最后单元格

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

简单漂亮的登录页面

效果图 说明 开发环境&#xff1a;vue3&#xff0c;sass 代码 <template><div class"container"><div class"card-container"><div class"card-left"><span><h1>Dashboard</h1><p>Lorem ip…

基础课26——业务流程分析方法论

基础课25中我们提到业务流程分析方法包括以下几种&#xff1a; 价值链分析法&#xff1a;主要是找出或设计出哪些业务能够使得客户满意&#xff0c;实现客户价值最大化的业务流程。要进行价值链分析的时候可以从企业具体的活动进行细分&#xff0c;细分的具体方面可以从生产指…

Stable Diffusion WebUI扩展sd-webui-controlnet之Canny

什么是Canny? 简单来说,Canny是计算机视觉领域的一种边缘检测算法。 关于Canny算法大家可以去看我下面这篇博客,里面详细介绍了Canny算法的原理以及代码演示。 OpenCV竟然可以这样学!成神之路终将不远(十五)_maxminval opencv-CSDN博客文章浏览阅读111次。14 图像梯度…

统计学习笔记 第 5 部分:破碎系数

照片由 Unsplash上的 资源数据库提供 1&#xff1a;背景与动机 正如本系列之前的文章所述&#xff0c;统计学习理论为理解机器学习推理问题提供了一个概率框架。用数学术语来说&#xff0c;统计学习理论的基本目标可以表述为&#xff1a; 图片由作者提供 本文是统计学习理论系…