【数据结构】04串
串
- 1. 定义
- 2. 串的比较
- 3. 串的存储结构
- 4. 具体实现
- 5. 模式匹配
- 5.1 常规思路实现
- 5.2 KMP模式匹配算法
- 5.2.1 next数组计算
- 5.2.1 代码计算next数组
- 5.2.2 KMP算法实现
1. 定义
串(string)是由零个或多个字符组成的有限序列,又叫字符串。
一般记为s= a 1 , a 2 , . . . , a n , ( n ≥ 0 ) a_1,a_2,...,a_n,(n\ge0) a1,a2,...,an,(n≥0)。串中字符数目n称为串的长度。零个字符的串称为空串。
2. 串的比较
给定两个串:s= a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,t= b 1 , b 2 , . . . , b m b_1,b_2,...,b_m b1,b2,...,bm,当满足以下条件之一时, s < t s<t s<t:
(1) n < m n<m n<m,且 a i = b i a_i=b_i ai=bi,例如: s = h a p , t = h a p p y s=hap,t=happy s=hap,t=happy,就有 s < t s<t s<t。
(2)存在某个 k ≤ min ( m , n ) k\le\min(m,n) k≤min(m,n),使得 a i = b i a_i=b_i ai=bi, a k < b k a_k<b_k ak<bk,就有 s < t s<t s<t。例如 s = h a p p e n s=happen s=happen, t = h a p p y t=happy t=happy,此时 k = 4 k=4 k=4,且字符有: e < y e<y e<y,则 s < t s<t s<t。
3. 串的存储结构
串的存储结构与线性表相同,分为两种:顺序存储结构和链式存储结构。
- 串的顺序存储结构使用一组地址连续的存储单元来存储传中的字符序列。
- 串的链式存储结构与线性表相似,但是如果一个字符占用一个结点,就会存在很大的空间浪费。
串的链式存储结构除了在连接串与串操作时,有一定方便之外,总的来说不如顺序存储灵活,性能也不如顺序存储结构好。
对于串的顺序存储有一些变化,串值的存储空间可在程序执行过程中动态分配而得。
4. 具体实现
为了方便管理,利用C++的类实现了String:
#include <iostream>
using namespace std;class String
{friend ostream& operator<<(ostream& os, const String& s); // 友元函数
private:int max_size = 10;int extend_size = 10;char* ptr; // 字符指针int len;// 扩展内存void ExtendString(){char* new_ptr = new char[max_size + extend_size];memset(new_ptr, 0, sizeof(char) * (max_size + extend_size));// 拷贝数据memcpy(new_ptr, ptr, sizeof(char) * max_size);// 清空内存delete[] ptr;// 指向新内存ptr = new_ptr;// 更新最长大小max_size = max_size + extend_size;}public:String() // 构造函数{// cout << "调用了默认构造函数" << endl;ptr = new char[max_size]; // 初始化内存memset(ptr, 0, sizeof(char) * max_size);len = 0; // 字符串长度}String(const char* s) // 构造函数{cout << "调用了构造函数" << endl;int len = strlen(s);ptr = new char[this->max_size]; // 初始化内存memset(ptr, 0, sizeof(char) * this->max_size);while (this->max_size < len){ExtendString();}// 拷贝数据memcpy(this->ptr, s, sizeof(char) * len);this->len = len;}String(const String& s) // 构造函数{cout << "调用了拷贝构造函数" << endl;ptr = new char[s.max_size]; // 初始化内存memset(ptr, 0, sizeof(char) * s.max_size);// 拷贝数据memcpy(ptr, s.ptr, sizeof(char) * s.len); // 拷贝字符串len = s.len;max_size = s.max_size;}String& operator=(const char* str) // 赋值函数{cout << "调用了重载的赋值函数char*" << endl;int len = strlen(str);while (strlen(str) > this->max_size){ExtendString();}memcpy(this->ptr, str, sizeof(char) * len);this->len = len;return *this;}String& operator=(const String& str) // 赋值函数{cout << "调用了重载的赋值函数String" << endl;while (str.max_size> this->max_size){ExtendString();}memcpy(this->ptr, str.ptr, sizeof(char) * str.len);this->len = str.len;return *this;}// 清空字符串void clear() {memset(ptr, 0, sizeof(char)*max_size); // 内存置0len = 0;}// 返回字符串长度int length()const {return len; }// 返回从第pos个字符开始,长度为len的子串String sub(int pos, int len) {// 创建临时对象String tmp;for (int i = 0; i < len; i++){tmp.ptr[i] = this->ptr[i + pos - 1];}tmp.len = len;return tmp;}// 将str插入到当前字符第pos个字符之后String Insert(int pos, const String& str){int whole_len = str.len + this->len;while (this->max_size < whole_len){ExtendString();}// 插入字符// 1.保存第pos个字符之后的所有字符,后移str_len位 即:pos-1开始的len-pos个字符移动到pos+str_len-1for (int i = this->len; i > pos; i--){this->ptr[i + str.len - 1] = this->ptr[i - 1]; // }// 2.插入字符从 str.ptr[0]到 str.ptr[len-1] 到pos-1至pos+str.len-1;memcpy(&(this->ptr[pos]), &(str.ptr[0]), sizeof(char) * str.len);this->len = whole_len;return *this;}~String(){delete[] ptr;ptr = nullptr;len = 0;}bool operator<(const String& str)const{int i = 0;for (i = 0; i < this->len && i < str.len; i++){if (this->ptr[i] == str.ptr[i]){continue;}if (this->ptr[i] > str.ptr[i]) // 前面都相等,一旦有一个字符大,则整个字符串都大{return false;}else // 前面都相等,当前字符小{return true;}}if (this->len < str.len)// 退出比较的原因是前面字符都相等,但当前字符串的字符较短{return true;}return false;}bool operator>(const String& str)const{int i = 0;for (i = 0; i < this->len && i < str.len; i++){if (this->ptr[i] == str.ptr[i]){continue;}if (this->ptr[i] < str.ptr[i]) // 前面都相等,一旦有一个字符小,则整个字符串都小{return false;}else // 前面都相等,当前字符大{return true;}}if (this->len > str.len)// 退出比较的原因是前面字符都相等,但当前字符串的字符更长{return true;}return false;}
};// 重载输出
ostream& operator<<(ostream& os, const String& s)
{for (int i = 0; i < s.len; i++){os << s.ptr[i];};return os;
}int main(void)
{String s1;s1 = "ace";String s2 = s1;String s3("bdf");String s4 = "asw"; // 自动类型转换,调用赋值函数/*s3 = s2.sub(1, 2);*/cout << s1 << endl;s1.clear();cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout <<( s3 < s2) << endl;s2.Insert(3, s3);cout << s2 << endl;return 0;
}
5. 模式匹配
在一段字符串中去定位子串的位置的操作称作串的模式匹配,这是串中最重要的操作之一。
假设我们要从主串"S=goodgoogle"中,找到子串"T=google"的位置。通常需要进行下面的步骤:
- 主串S第一位开始,S与T的前三个字符都匹配成功,但S的第四个字符为"d"而T的第四个字符为"g"。第一位匹配失败。
- 主串S第二位开始,S首字符为"o"而T的首字符为"g",第二位匹配失败。
- 同理,第三位和第四位都匹配失败
- 主串第五位开始,S与T,6个字母全匹配,匹配成功。
简单来说,对主串的每个字符作为子串开头,与要匹配的子串进行匹配。对主串做外部循环,每个字符开头做子串T长度的内部循环,直到匹配成功或主串遍历完成为止。
5.1 常规思路实现
int Index(const String& T){int i, j = 0;for (i = 0; i < this->len; i++){for (j = 0; j < T.len; j++){if (this->ptr[i+j] == T.ptr[j]) // 主串以i开头的T长度的子串匹配{continue;}break;}if (j == T.len) // 子串匹配成功{return i; // 返回此时子串在主串中的位置}}return -1; // 匹配失败}
5.2 KMP模式匹配算法
常规思路的匹配算法需要挨个遍历主串和子串,效率低效。KMP算法的思路是当发现某一个字符不匹配的时候,由于已经知道之前遍历过的字符,利用这些信息避免暴力算法中"回退"的步骤。
KMP算法的原理:
1)在匹配过程中,主串的指针不需要回溯,只回溯子串的指针。
2)如果子串和主串中前n个字符匹配成功,遇到匹配失败的字符时,子串回溯的下标由子串的内容决定(回溯到:匹配失败前,子串内容最长相等前后缀 的长度),然后继续比较。
前缀:包含首位字符但不包含末位字符的子串。如:ababa的前缀包括:a,ab,aba,abab
后缀:包含末位字符但不包含首位字符的子串。如:ababa的后缀包括:a,ba,aba,baba。
则对于字符串:ababa最长公共前后缀为:aba。
具体流程:依次匹配主串和子串的字符,当遇到子串与主串字符不匹配时,子串指针回溯,并从回溯的位置继续与主串当前位置字符比较。如图中所示:ABABABCAA与ABABC匹配,当遇到主串字符A时,子串字符为C,此时无法匹配,子串指针回溯到第3个字符即A处,继续比较。KMP算法的关键是如何获取子串回溯位置,即next数组。
5.2.1 next数组计算
对于子串:ABABC
第一个字符A,没有前缀没有后缀,对应的next数组值为0。
第二个字符AB,包含的前后缀有:A B 对应的next数组值为1。
第三个字符的子串为ABA,包含的前后缀有:A A AB BA ,最长的长度为1。则next数组值为1。
第四个字符的子串为ABAB,包含的前后缀有:A B AB AB ABA BAB,最长的公共前后缀长度为2。next数组值为2。
第五个字符的子串为ABABC,包含的前后缀有:A C AB BC ABA ABC ABABA BABC,最长的公共前后缀长度为0。next数组值为0。
因此对于ABABC的next数组值为:0,1,1,2,0。
对于子串:AABAAF计算next数组:
初始化:i 和 j 其中i指向的是后缀末尾,j指向的是前缀末尾即把S[0,j]看作是子串,把S[1,i]看作是主串来理解。初始时j=0,i=1;next[0]=0。
前后缀不同的情况:S[i]!=S[j],此时需要查询next[j-1]的值,查询到j需要回退的位置,必须要满足j>0,直到
S[i]==S[j]或者回退到初始位置。
前后缀相同的情况:S[i]==S[j],j++。
更新next数组:next[i]=j。
模拟运行:
- 初始化:next[0] = 0. i =1 ,j =0.
- i=1,j=0.S[0]=A==S[1]=A => j++,j=1. next[1]=1.
- i=2,j=1;S[1]=A != S[2]= B => j=next[j-1]=next[0]=0 ,S[0]!=S[2] j>0. next[2] = 0.
- i=3,j=0;S[0] =A ==S[3]=A => j++,j=1. next[3] = 1.
- i=4,j=1;S[1]=A == S[4]= A => j++,j=2. next[4] = 2.
- i=5,j=2;S[2]=B!=S[5]=F =>j = next[j-1]=next[1] = 1 ,S[1]!=S[5] j=next[j-1]=next[0]=0 j>0.next[5]=0.
5.2.1 代码计算next数组
// 获取next数组int* getNext(){// 创建next数组,长度与字符串长度一致delete[] next;next = new int[this->len];memset(next, 0, sizeof(int) * this->len);// 初始化int i, j = 0;next[0] = 0;for (i = 1; i < this->len; i++){// 前后缀不相同的情况while (j > 0 && ptr[i] != ptr[j]){// 回溯jj = next[j - 1];}// 前后缀相同的情况if (ptr[i] == ptr[j]){j++;}// 更新nextnext[i] = j;}return next;}
5.2.2 KMP算法实现
/ KMP算法int KMP(String& T){// 获取子串next数组int* next_ptr = T.getNext();// 开始匹配int i = 0, j = 0;while (i < len){if (ptr[i] == T.ptr[j]) // 匹配成功{i++; j++;}else if (j > 0) // 匹配失败,子串指针回溯,并且需要保证回溯位置不越界(即无法回溯到首个字符前){j = next_ptr[j - 1];}else // 子串第一个字符就匹配失败i++;if (j == T.len)// 子串已经到达末尾,即全部匹配上{return i - j; // 返回位置}}return -1; // 匹配失败}
相关文章:

【数据结构】04串
串 1. 定义2. 串的比较3. 串的存储结构4. 具体实现5. 模式匹配5.1 常规思路实现5.2 KMP模式匹配算法5.2.1 next数组计算5.2.1 代码计算next数组5.2.2 KMP算法实现 1. 定义 串(string)是由零个或多个字符组成的有限序列,又叫字符串。 一般记为s a 1 , a 2 , . . . ,…...
LAMMPS如何识别多孔结构的孔隙及其大小
关注 M r . m a t e r i a l , \color{Violet} \rm Mr.material\ , Mr.material...

JavaScript ECMAScript标准的与时俱进:从ES6至ES14的革新之路与关键技术特性剖析
ECMAScript(通常缩写为ES)是一种标准化的脚本语言规范,由ECMA International(前身为European Computer Manufacturers Association,欧洲计算机制造商协会)制定。自1997年发布首个版本以来,ECMAS…...

竞赛课第六周(树状数组的应用)
实验内容: HDU 1166 敌兵布阵【线段树】 线段树的应用 敌兵布阵 C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取…...

SpringCloud Alibaba Sentinel 实现熔断功能
一、前言 接下来是开展一系列的 SpringCloud 的学习之旅,从传统的模块之间调用,一步步的升级为 SpringCloud 模块之间的调用,此篇文章为第十六篇,即使用 Sentinel 实现熔断功能。 二、 Ribbon 系列 首先我们新建两个服务的提供者…...

开源免费AI引擎:智能合同审查技术的应用与优势
随着数字化转型的加速,合同作为商业活动中的重要法律文件,其审查和管理变得越来越重要。传统的合同审查方式耗时且容易出错,而智能AI合同审查技术的引入,为这一领域带来了革命性的变化。本文将探讨智能AI合同审查技术的应用和优势…...

易舟云凭证保存查看的3种方式
文章目录 1、保存为图片2、导出为Excel3、跨期批量导出 1、保存为图片 点击记账凭证详情,点击“下载-保存为图片”,即可下载图片! 2、导出为Excel 导出为Excel可以对单张凭证导出,也可以对指定月份的记账凭证进行批量导出。 1…...
Node.js 开发技巧
轻松创建 HTTP 服务器: 使用 Node.js,你可以轻松创建自己的 HTTP 服务器。只需几行代码,你就可以像一位传统的酒保一样为客户端提供服务。记住,不要忘记问客户端想要些什么! const http require(http);const server …...

【LeetCode】二叉树类题目详解
二叉树 二叉树的理论基础 二叉树是结点的度数之和不超过2的树,二叉树总共有五种基本形态 二叉树的种类主要有: 满二叉树完全二叉树 二叉树的存储方式 顺序存储链式存储 二叉树的遍历方式 先序遍历(深度优先搜索)中序遍历&…...
Lua语法(六)——面相对象编程
参考链接: 系列链接: Lua语法(一) 系列链接: Lua语法(二)——闭包/日期和时间 系列链接: Lua语法(三)——元表与元方法 系列链接: Lua语法(四)——协程 系列链接: Lua语法(五)——垃圾回收 系列链接: Lua语法(六)——面相对象编程 使用Lua表 进行类的模拟࿰…...

CSS-浮动文字环绕布局、隐藏属性display、overflow、三角形制作、鼠标样式
文字环绕布局 CSS文字环绕布局是指在网页中让文字环绕在图片或其他元素周围的布局方式。这通常通过CSS中的float属性来实现。你可以将图片设置为float: left;或float: right;,然后在文本元素中使用clear属性来清除浮动,以确保文字不会覆盖图片。另外&am…...

创建个人百度百科需要什么条件?
互联网时代,创建百度百科词条可以给个人带来更多的曝光和展现,相当于一个镀金的网络名片,人人都想上百度百科,但并不是人人都能创建上去的。 个人百度百科词条的创建需要满足一定的条件,今天伯乐网络传媒就来给大家聊聊…...

VR紧急情况模拟|V R体验中心加盟|元宇宙文旅
通过VR技术实现紧急情况模拟,提升安全应急能力! 简介:面对突发紧急情况,如火灾、地震、交通事故等,正确的反应和应对能够有效减少伤害和损失。为了提高人们在紧急情况下的应急能力,我们借助先进的虚拟现实…...
【Django】必须登陆才能访问功能实现
一、直接使用session传递登录状态(不推荐,但能用) 这是最简单、最直接的方法。 1.登录视图添加标识 添加登录状态标识 request.session[is_logged_in] False def user_login(request):# 这是一个登录状态标识request.session[is_logged_in] Falseif request.…...

wps使用Latex编辑公式没有Latex formula
wps使用Latex编辑公式没有Latex formula 1. 下载CTEX2. 下载LaTeXEE3. 配置Miktex4. 配置latexee5. 用管理员权限运行latexeqedit.exe6. wps插入latex公式 1. 下载CTEX 下载CTEX网址,我下载的下图这个,下载完了之后运行exe文件安装ctex。 2. 下载LaTe…...
动态指定easyui的datagrid的url
动态指定easyui的datagrid的url 重新指定datagrid url的请求方法: $("#dg").datagrid("options").url"xxx"注意,如果直接使用 $(#btnq).bind(click, function(){ $(#dg).datagrid({ url: xxx });//重新指定url$(#dg)…...

数据可视化的3D问题
三维对象非常流行,但在大多数情况下会对解释图形的准确性和速度产生负面影响。 以下是对涉及 3d 的主要图形类型的回顾,并讨论了它们是否被认为是不好的做法。 1、3D 条形图:不要 这是一个 3d 条形图。 你可能很熟悉这种图形,因为…...

使用yolov8实现自动车牌识别(教程+代码)
该项目利用了一个被标记为“YOLOv8”的目标检测模型,专门针对车牌识别任务进行训练和优化。整个系统通常分为以下几个核心步骤: 数据准备: 收集包含车牌的大量图片,并精确地标记车牌的位置和文本信息。数据集可能包含各种环境下的…...

RabbitMQ的介绍
为什么使用 MQ? 流量削峰和缓冲 如果订单系统最多能处理一万次订单,这个处理能力在足够应付正常时段的下单,但是在高峰期,可能会有两万次下单操作,订单系统只能处理一万次下单操作,剩下的一万次被阻塞。我们…...
算法-快速幂
算法-快速幂 时间复杂度 O(logk) //求 m^k mod p int qmul(int m,int k,int p) {int res1%p;while(k){if(k&1){res*m;res%p;}m*m;m%p;k>>1;}return res; }...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...