【数据结构】串

  • 串的顺序实现
  • 简单的模式匹配算法
  • KMP算法
  • KMP算法的进一步优化

串的顺序实现

初始化

#define MaxSize 50
typedef char ElemType;//顺序存储表示
typedef struct{ElemType data[MaxSize];int length;
}SString;/*** 初始化串*/
void InitString(SString *string) {for (int i = 0; i < string->length; ++i) {//全部初始化为‘\0’string->data[i] = '\0';}string->length = 0;//长度赋值为0
}

赋值操作

/*** 赋值操作*/
void StrAssign(SString *string, char *str) {//循环赋值for (int i = 0; i < strlen(str); ++i) {string->data[i] = str[i];}string->length = strlen(str);
}

复制操作

/*** 复制操作:把串 str 复制到 string*/
void StrCopy(SString *string, SString str) {int i, j;for (i = 0; i < strlen(str.data); ++i) {string->data[i] = str.data[i];}string->length = str.length;
}

判空操作

/*** 判空操作*/
bool StrIsEmpty(SString string) {if (string.length == 0) {return true;} else {return false;}
}

比较操作

/*** 比较操作:首先比较同位序的字母大小,其次比较字符串的长度大小*/
int StrCompare(SString string, SString str) {for (int i = 0; i < string.length && i < str.length; ++i) {if (string.data[i] != str.data[i]) {return string.data[i] - str.data[i];}}return string.length - str.length;
}

截取操作

/*** 截取操作:用 sub 返回串 string 的第 pos 个字符起长度为 len 的子串*/
bool SubString(SString *sub, SString string, int pos, int len) {if (pos + len > string.length) {return false;}for (int i = pos; i < pos + len; ++i) {sub->data[i - pos] = string.data[i];}sub->length = len;return true;
}

连接操作

/*** 连接操作:把两个串相连*/
void StrConcat(SString *sub, SString string, SString str) {int i;for (i = 0; i < string.length; ++i) {sub->data[i] = string.data[i];}for (int j = 0; j < str.length; ++j, ++i) {sub->data[i] = str.data[j];}sub->length = string.length + str.length;
}

简单的模式匹配算法

所谓串的模式匹配算法,是一种专门定位子串在主串中位置的方法(方案、思想),整个定位的过程称为模式匹配。此外,在模式匹配的过程中,子串通常又被称为“模式串”。

串模式匹配的实现方法有很多种,本节先给大家讲一种最容易理解、最简单的方法,称为 BF 算法。

采用 BF 算法定位模式串在主串中的位置,就是简单粗暴的从主串的起始位置开始,不断地将模式串中的字符和主串中的字符进行对比。

具体来讲,假设对模式串 A(abcac)和主串 B(ababcabacabab)进行模式匹配,BF 算法的执行过程如下:


将模式串 A 与主串 B 的首字符对齐,逐个判断相对的字符是否相等
在这里插入图片描述
由于模式串 A 与主串 B 的第 3 个字符匹配失败,此时将模式串 A 后移一个字符的位置,采用同样的方法重新匹配
在这里插入图片描述
两个串依旧匹配失败,模式串 A 继续后移一个字符的位置
在这里插入图片描述
模式串 A 继续向后移动
在这里插入图片描述
模式串 A 与主串 B 共匹配了 6 次才成功,最终定位到模式串 A 位于主串 B 第 6 的位置处,整个模式匹配的过程就称为 BF 算法。

int Index(SString s,SString t){//s:主串,t:子串int i = 0,j = 0;while (i<=s.length&&j<=t.length){if (s.data[i]==t.data[j]){++i;++j;}else{i = i - j + 2;j = 0;}}if (j>t.length){return i - t.length;}else{return 0;}
}

KMP算法

学过 BF 算法的读者应该知道,该算法的实现思想很简单,就是 “傻瓜式” 地将模式串(假定为子串的串)与主串中的字符一一匹配。KMP 算法不一样,它可以略过一些原本就不可能成功的匹配过程,提高模式匹配的效率。

例如,对主串 A(“ABCABCE”)和模式串 B(“ABCE”)进行模式匹配,KMP 算法只需要匹配 2 次。
在这里插入图片描述
显示第一次匹配失败,从整个匹配过程可以获得的信息是:模式串中 “ABC” 和主串对应的字符相同,但模式串中的字符 ‘A’ 与 ‘B’ 和 ‘C’ 不同。这也就意味着,下次模式匹配时没必要再让串 B 中的 ‘A’ 与主串中的字符 ‘B’ 和 ‘C’ 一一匹配,它们绝不可能相等。

因此第二次模式匹配开始前,我们改变指针 j 的指向

在这里插入图片描述
模式串直接跳过主串中的第 2、3 个字符,从第 4 个字符处开始第二次模式匹配,最终匹配成功。KMP 算法的整个匹配过程只进行了 2 次,而如果用 BF 算法需要匹配 4 次。

和 BF 算法相比,KMP 算法只需要移动指针 j 的位置,可以略过一些原本就不可能成功的匹配过程,减少匹配的次数,提高模式匹配的效率。


对于初学者而言,KMP 算法最大的难点是:当模式匹配失败后,如何修改指针 j 的位置。

请大家先记住一句话:指针 j 的新位置只和模式串有关,与主串无关。接下来通过一个实例,给大家演示如何只通过模式串确定指针 j 的位置。

将模式串 B 改为 “ABCAE”,第一次匹配的过程如下图所示:

在这里插入图片描述
匹配失败时模式串中字符 ‘E’ 前的 ‘A’ 与模式串开头的 ‘A’ 相等,因此我们可以将指针 j 指向模式串中的第 2 个字符,下次直接从 i 和 j 的位置开始匹配,这就是 KMP 算法重定位指针 j 的方法。

也就是说,模式匹配失败后指针 j 的新位置可以通过匹配失败位置前的字符计算得出。进一步讲,只要给定一个模式串,我们就可以确定匹配失败后指针 j 的新位置。

当模式串和主串进行模式匹配时,模式串中的每个字符都可能导致匹配失败,而失败后指针 j 的新位置是可以计算出来的。模式串中有多少个字符,就可以计算出多少个指针 j 的新位置,它们是一一对应的关系。我们通常会各个字符对应的 j 的新位置存储到一个数组中,并给这个数组起名为 Next 数组,数组中的值统称为 next 值。


模式串中各个字符对应的 next 值的计算方式是,取该字符前面的字符串(不包含自己),其前缀字符串和后缀字符串相同字符的个数再 +1 就是该字符对应的 next 值。

前缀字符串指的是位于模式串起始位置的字符串,例如模式串 “ABCD”,则 “A”、“AB”、“ABC” 都属于前缀字符串;后缀字符串指的是位于串结尾处的字符串,还拿模式串 “ABCD” 来说,“D”、“CD”、“BCD” 为后缀字符串。

注意,模式串中第一个字符对应的值为 0,第二个字符对应的值是 1 ,这是固定不变的。因此模式串 “ABCAE” 中各个字符对应的 next 值如图

在这里插入图片描述
各个字符对应 next 值的计算过程是:

  • 第三个字符 ‘C’:在前面的字符串 “AB” 中,前缀字符串和后缀字符串相等个数为 0,0 + 1 = 1,所以字符 ‘C’ 对应的 next 值为 1。
  • 第四个字符 ‘A’:在前面的字符串 “ABC” 中,前缀字符串和后缀字符串相等个数为 0,0 + 1 = 1,所以字符 ‘A’ 对应的 next 值为 1。
  • 第五个字符 ‘E’:在前面的字符串 “ABCA” 中,前缀字符串和后缀字符串相等个数为 1,1 + 1 = 2,所以字符 ‘E’ 对应的 next 值为 2。

当字符 ‘E’ 匹配失败时,指针 j 指向模式串数组中第 2 个字符,即 ‘B’


那么,如果编写程序计算出模式串对应的 NEXT 数组呢?

可以设计这样一个算法:刚开始时令 j 指向模式串中第 1 个字符(j=1),i 指向第 2 个字符(i=2)。接下来,对每个字符做同样的操作:

  • 如果 i 和 j 指向的字符相等,则 i 后面第一个字符的 next 值为 j+1,同时 i 和 j 做自加 1 操作,为求下一个字符的next 值做准备;
  • 如果 i 和 j 指向的字符不相等,则执行j=next[j]修改 j 的指向,然后以同样的方法对比 i 和 j 指向的字符,以此类推。当 j 的值为 0 时,将 i 后面第一个字符的 next 值置为 1。

例如,计算模式串 “aaacd” 对应的 NEXT 数组,实现过程为:

  • 前两个字符对应的 next 值分别为 0 和 1(j=1, i=2);

  • 由于 i 和 j 指向的字符相等,所以第三个字符 ‘a’ 的 next 值为 j +1 = 2,同时 i 和 j 各自加 1(此时 j=2,i=3)。

在这里插入图片描述

  • 由于 i 和 j 指向的字符相等,所以第四个字符 ‘c’ 的 next 值为 j +1 = 3,同时 i 和 j 各自加 1(此时 j=3,i=4)。
    在这里插入图片描述
  • 此时 i 和 j 指向的字符不相等,执行 j = next[j] 修改 j 的指向
    在这里插入图片描述
  • 从上图可以看到,i 和 j 指向的字符又不相同,继续执行 j = next[j]
    在这里插入图片描述

由于 j 和 i 指向的字符仍不相等,继续执行 j=next[j] 得到 j=0,字符 ‘d’ 对应的 next 值为 1。

实际上,当第一次比较 i 和 j 不相等时,意味着匹配失败位置前的最长前缀和后缀字符串不相同;执行 j=next[j] 后,i 和 j 仍不相等,意味着匹配失败位置前的次长前缀和后缀字符串也不相同,以此类推。当 j = 0 时,意味着匹配失败位置前没有相等的前缀和后缀字符串。

这里给出上述思想实现 NEXT 数组的 C 语言代码:

void GetNext(SString string,int* next){//string是字串int i = 1,j = 0;//next的第一个元素固定为0next[i] = 0;while (i < string.length){if (j==0||string.data[i]==string.data[j]){++i;++j;next[i] = j;//Pi=Pj,则next[J+1]=next[J]+1}else{j = next[j];}}
}

假设主串 A 为 “ababcabcacbab”,模式串 B 为 “abcac”,KMP 算法进行模式匹配的过程为:

  • 第一次匹配如图所示,匹配结果失败,指针 j 移动至 next[j] 的位置;

在这里插入图片描述

  • 第二次匹配如图所示,匹配结果失败,执行 j=next[j] 操作

在这里插入图片描述

  • 第三次匹配成功

在这里插入图片描述
使用 KMP 算法只需匹配 3 次,而同样的问题使用 BF 算法则需匹配 6 次才能完成。

KMP 算法:

int KMP(SString s,SString t,int* next){int i = 1,j = 1;while (i<=s.length&&j<=t.length){if (s.data[i]==t.data[j]){++i;++j;}else{j = next[j];}  }if (j>t.length){return i - t.length;}else{return 0;}
}

KMP算法的进一步优化

只需要更改next即可

void GetNextVal(SString string,int* next){//string是字串int i = 1,j = 0;//next的第一个元素固定为0next[i] = 0;while (i < string.length){if (j==0||string.data[i]==string.data[j]){++i;++j;if (string.data[j]!=string.data[i]){next[i] = j;//Pi=Pj,则next[J+1]=next[J]+1}else{next[i] = next[j];}}else{j = next[j];}}
}

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

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

相关文章

点云从入门到精通技术详解100篇-基于车载激光点云的道路标线提取及分类方法

目录 前言 车载 LiDAR 技术基础理论 2.1 车载 LiDAR 系统组成 2.2 车载 LiDAR 系统工作原理

Python基础continue和break关键字

continue 和 break 关键字 continue 含义&#xff1a;表示跳过本次循环&#xff0c;继续下次循环 注意&#xff1a;continue在while循环中不可以使用 例子&#xff1a; for i in range(5): if i 3: continue else: print(i) #结果为0,1,2,4 当i 3的时候&#xff0c;跳…

变压器耐压试验电压及电源容量的计算

被试变压器的额定电压为&#xff08;11081. 25%&#xff09; /10. 5kV&#xff0c; 联接组标号为 YNd11。 试验时高压分接开关置于第 1 分接位置&#xff0c; 即高压侧电压为 126kV&#xff0c; 高、 低压电压比 K1126/&#xff08;√310. 5&#xff09; 6. 93。 现以 A 相试验…

Cmake入门(一文读懂)

目录 1、Cmake简介2、安装CMake3、CMakeLists.txt4、单目录简单实例4.1、CMakeLists.txt4.2、构建bulid内部构建外部构建 4.3、运行C语言程序 5、多目录文件简单实例5.1、根目录CMakeLists.txt5.2、源文件目录5.3、utils.h5.4、创建build 6、生成库文件和链接外部库文件7、注意…

Mysql5.7(Docker环境)实现主从复制

文章目录 前言一、MySQL主从数据库同步如何实现&#xff1f;(理论)1.1 为什么要使用数据库主从1.2 数据库主从实现原理是什么&#xff1f; 二、Docker环境配置MySQL5.7主从(实践)2.1 配置安装Master2.2 配置安装Slave 前言 本文章将以MySQL5.7版本来讲诉MySQL主从复制的原理以…

centos 编译安装的php多版本 切换

centos 编译安装的php多版本 切换 wheris php php: /usr/bin/php /usr/lib64/php /etc/php.ini /etc/php.d /usr/local/php /usr/local/php7.4 /usr/share/php /usr/share/man/man1/php.1.gz/usr/bin/php: php可执行脚本&#xff0c;任何版本的php 通过软连接到这可以实现全局…

STM32CubeMX systick bug?

发觉用新版&#xff08;V6.9.1&#xff09;的它生成代码&#xff0c;会有问题。可能是 BUG。具体如下&#xff1a; 一个简单的点灯程序&#xff0c;用 Keil MDK 5.38a&#xff08;compiler version 6&#xff09;编译。 如果在变量前&#xff0c;不加上关键字“volatile”&am…

VEX —— Functions|Geometry

目录 geoself —— 返回当前几何体的handle addpoint —— 添加点到几何体 addprim —— 添加面到几何体 addvertex —— 添加一顶点到面 minpos —— 查找点离几何体表面最近的位置 nearpoint —— 查找离几何体最近的点 nearpoints —— 查找离几何体所有最近的点 ne…

【计算机网络】UDP数据包是如何在网络中传输的?

List item 创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多计算机网络知识专栏&#xff1a;计算机网络&#x1f525;…

Vue项目使用axios配置请求拦截和响应拦截以及判断请求超时处理提示

哈喽大家好啊&#xff0c;最近做Vue项目看到axios axios官网&#xff1a;起步 | Axios 中文文档 | Axios 中文网 (axios-http.cn)​​​​​​ 重要点&#xff1a; axios是基于Promise封装的 axios能拦截请求和响应 axios能自动转换成json数据 等等 安装&#xff1a; $ npm i…

树莓派搭建K8S集群

最近学习k8s知识&#xff0c;想用树莓派搭建集群&#xff0c;在网找了不少&#xff0c;就发现一篇文章可以搭建成功香橙派4和树莓派4B构建K8S集群实践之一&#xff1a;K8S安装&#xff0c;参考了不少&#xff0c;这里主要记录下遇到的一些问题&#xff0c;参考的文章&#xff0…

入门Python编程:了解计算机语言、Python介绍和开发环境搭建

文章目录 Python入门什么是计算机语言1. 机器语言2. 符号语言&#xff08;汇编&#xff09;3. 高级语言 编译型语言和解释型语言1. 编译型语言2. 解释型语言 Python的介绍Python开发环境搭建Python的交互界面 python学习专栏python基础知识&#xff08;0基础入门&#xff09;py…

C高级 DAY3

一、shell中的变量 shell本身是擅长运行指令&#xff0c;是一种弱数据类型语言 它与c语言中定义变量有所不同 C中&#xff1a; 存储类型 数据类型 变量名;shell中&#xff1a; 变量变量的值 ----->如果变量的值中间没有空格直接使用 变量变量的值 ----->变量…

【uni-app】

准备工作&#xff08;Hbuilder&#xff09; 1.下载hbuilder&#xff0c;插件使用Vue3的uni-app项目 2.需要安装编译器 3.下载微信开发者工具 4.点击运行->微信开发者工具 5.打开微信开发者工具的服务端口 效果图 准备工作&#xff08;VScode&#xff09; 插件 uni-cr…

c++day3

stack.h #ifndef STACK_H #define STACK_H #include <iostream> //#define max 128 using namespace std; class Stack { private:int* stack;//数组指针int top;//栈顶元素int max;//栈容量 public://构造函数Stack();//析构函数~Stack();//定义拷贝构造函数Stack(cons…

Android窗口层级(Window Type)分析

前言 Android的窗口Window分为三种类型&#xff1a; 应用Window&#xff0c;比如Activity、Dialog&#xff1b;子Window&#xff0c;比如PopupWindow&#xff1b;系统Window&#xff0c;比如Toast、系统状态栏、导航栏等等。 应用Window的Z-Ordered最低&#xff0c;就是在系…

matlab如何遍历文件夹及子文件夹下的所有文件

需求 有一个比较深层的文件夹&#xff0c;每个文件夹及其子文件夹下都可能存在我所需要的csv文件&#xff0c;写一个函数&#xff0c;输入文件夹路径后可以返回所有符合要求的csv文件。 代码实现 % folder_path为输入的文件夹&#xff0c;str为指定的文件所特有的关键字&…

Vue导航守卫beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave

Vue导航守卫以我自己的理解就是监听页面进入,修改,和离开的功能。每个守卫接受三个参数 to: Route: 即将要进入的目标路由对象 from: Route: 当前导航正要离开的路由 next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。 next(): 进行…

docker运行并进入MySQL容器

1、运行容器 $ docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456 mysql这是一个使用Docker命令行工具来启动一个MySQL容器的命令。下面&#xff0c;让我们来详细解析这条命令&#xff1a; docker run docker: Docker的命令行工具。 run: Docke…

Codeforces Round 827 (Div. 4) D 1e5+双重for循环技巧

Codeforces Round 827 (Div. 4) D 做题链接&#xff1a;Codeforces Round 827 (Div. 4) 给定一个由 n个正整数 a1,a2,…,an&#xff08;1≤ai≤1000&#xff09;组成的数组。求ij的最大值&#xff0c;使得ai和aj共质&#xff0c;否则−1&#xff0c;如果不存在这样的i&#…