当前位置: 首页 > news >正文

【数据结构】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,(n0)。串中字符数目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) kmin(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. 串的存储结构

串的存储结构与线性表相同,分为两种:顺序存储结构和链式存储结构。

  1. 串的顺序存储结构使用一组地址连续的存储单元来存储传中的字符序列。
  2. 串的链式存储结构与线性表相似,但是如果一个字符占用一个结点,就会存在很大的空间浪费。
    串的链式存储结构除了在连接串与串操作时,有一定方便之外,总的来说不如顺序存储灵活,性能也不如顺序存储结构好。

对于串的顺序存储有一些变化,串值的存储空间可在程序执行过程中动态分配而得。

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"的位置。通常需要进行下面的步骤:

  1. 主串S第一位开始,S与T的前三个字符都匹配成功,但S的第四个字符为"d"而T的第四个字符为"g"。第一位匹配失败。
  2. 主串S第二位开始,S首字符为"o"而T的首字符为"g",第二位匹配失败。
  3. 同理,第三位和第四位都匹配失败
  4. 主串第五位开始,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)是由零个或多个字符组成的有限序列&#xff0c;又叫字符串。 一般记为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&#xff08;通常缩写为ES&#xff09;是一种标准化的脚本语言规范&#xff0c;由ECMA International&#xff08;前身为European Computer Manufacturers Association&#xff0c;欧洲计算机制造商协会&#xff09;制定。自1997年发布首个版本以来&#xff0c;ECMAS…...

竞赛课第六周(树状数组的应用)

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

SpringCloud Alibaba Sentinel 实现熔断功能

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

开源免费AI引擎:智能合同审查技术的应用与优势

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

易舟云凭证保存查看的3种方式

文章目录 1、保存为图片2、导出为Excel3、跨期批量导出 1、保存为图片 点击记账凭证详情&#xff0c;点击“下载-保存为图片”&#xff0c;即可下载图片&#xff01; 2、导出为Excel 导出为Excel可以对单张凭证导出&#xff0c;也可以对指定月份的记账凭证进行批量导出。 1…...

Node.js 开发技巧

轻松创建 HTTP 服务器&#xff1a; 使用 Node.js&#xff0c;你可以轻松创建自己的 HTTP 服务器。只需几行代码&#xff0c;你就可以像一位传统的酒保一样为客户端提供服务。记住&#xff0c;不要忘记问客户端想要些什么&#xff01; const http require(http);const server …...

【LeetCode】二叉树类题目详解

二叉树 二叉树的理论基础 二叉树是结点的度数之和不超过2的树&#xff0c;二叉树总共有五种基本形态 二叉树的种类主要有&#xff1a; 满二叉树完全二叉树 二叉树的存储方式 顺序存储链式存储 二叉树的遍历方式 先序遍历&#xff08;深度优先搜索&#xff09;中序遍历&…...

Lua语法(六)——面相对象编程

参考链接&#xff1a; 系列链接: Lua语法(一) 系列链接: Lua语法(二)——闭包/日期和时间 系列链接: Lua语法(三)——元表与元方法 系列链接: Lua语法(四)——协程 系列链接: Lua语法(五)——垃圾回收 系列链接: Lua语法(六)——面相对象编程 使用Lua表 进行类的模拟&#xff0…...

CSS-浮动文字环绕布局、隐藏属性display、overflow、三角形制作、鼠标样式

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

创建个人百度百科需要什么条件?

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

VR紧急情况模拟|V R体验中心加盟|元宇宙文旅

通过VR技术实现紧急情况模拟&#xff0c;提升安全应急能力&#xff01; 简介&#xff1a;面对突发紧急情况&#xff0c;如火灾、地震、交通事故等&#xff0c;正确的反应和应对能够有效减少伤害和损失。为了提高人们在紧急情况下的应急能力&#xff0c;我们借助先进的虚拟现实…...

【Django】必须登陆才能访问功能实现

一、直接使用session传递登录状态(不推荐&#xff0c;但能用) 这是最简单、最直接的方法。 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网址&#xff0c;我下载的下图这个&#xff0c;下载完了之后运行exe文件安装ctex。 2. 下载LaTe…...

动态指定easyui的datagrid的url

动态指定easyui的datagrid的url 重新指定datagrid url的请求方法&#xff1a; $("#dg").datagrid("options").url"xxx"注意&#xff0c;如果直接使用 $(#btnq).bind(click, function(){ $(#dg).datagrid({ url: xxx });//重新指定url$(#dg)…...

数据可视化的3D问题

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

使用yolov8实现自动车牌识别(教程+代码)

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

RabbitMQ的介绍

为什么使用 MQ&#xff1f; 流量削峰和缓冲 如果订单系统最多能处理一万次订单&#xff0c;这个处理能力在足够应付正常时段的下单&#xff0c;但是在高峰期&#xff0c;可能会有两万次下单操作&#xff0c;订单系统只能处理一万次下单操作&#xff0c;剩下的一万次被阻塞。我们…...

算法-快速幂

算法-快速幂 时间复杂度 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; }...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...