数据结构——串(字符串)

news2024/11/16 1:15:31

文章目录

    • **一 串的定义和实现**
      • **1 定义**
      • **2 串的存储结构**
        • **2.1 定长顺序存储表示**
        • **2.2 堆分配存储表示**
        • **2.3 块链存储表示**
      • **3 串的基本操作**
    • **二 串的模式匹配**
      • **1 简单的模式匹配算法**
      • **==2 串的模式匹配算法——KMP算法==**
        • **2.1 字符串的前缀,后缀和部分匹配值**
        • **2.2 KMP算法的原理**
        • **2.3 KMP算法的优化**

一 串的定义和实现

1 定义

串是由零个或多个字符组成的有限序列,可以是字母,数字或者其他字符

由一个或多个空格组成的串称为空格串(空格也是一种符号)

如“hello world 232@#%^&”

2 串的存储结构

2.1 定长顺序存储表示

#define maxline 255
typedef struct
{
    char ch[maxline];
    int length;
}Sstring;

一组连续的存储单元

超过的长度会发生截断;一般字符串的结尾都有一个隐含的“\0”,不计入长度

在这里插入图片描述

2.2 堆分配存储表示

仍是一组连续的存储单元,但是存储空间是执行过程中动态分配的

typedef struct
{
    char *ch;
    int length;
}Hstring;

2.3 块链存储表示

每个结点可以存放一个字符,也可以存放多个字符

每个结点称为块,整个链表称为块链结构

最后一个结点占不满时通常用#补上

在这里插入图片描述

3 串的基本操作

Strassign(&T,chars);            \\T赋值到chars
Strcopy(&T,S);                  \\S复制到T
Strempty(S);                    \\判空
Strcompare(S,T);                \\比较ST,S>T返回值大于0
Strlength(S);                   \\求串长
Substring(&Sub,S,pos,len);      \\用sub返回s从pos位置长度为len的子串
Concat(&T,S1,S2);               \\T返回s1和s2的串接
Index(S,T);                     \\若S中存在T相同的子串,返回第一次出现的位置,否则为0
Clearstring(&S);                \\清空
Destroystring(&S);              \\销毁

二 串的模式匹配

1 简单的模式匹配算法

模式匹配:子串的定位操作,求的是子串在主串中的位置

采用定长顺序存储结构

暴力算法

int Index(Sstring S,Sstring R)
{
    int i =1,j=1;
    while(i<=S.length && j<= T.length)
    {
        if(S.ch[i]==T.ch[j])
        {
            i++;
            j++;
        }
        else
        {
            i=i-j+2;           \\比对失败倒退下标
            j=1;
        }
    }
    if(j >T.length)
        return i-T.length;     \\找到了位置并返回
    else
        return 0;              \\没找到
}

时间复杂段:O(mn),m,n分别是子串和主串的长度

算法思想:主串和子串的字符一一对比,如果不对,子串倒退到一开始,主串倒退到刚刚比较的的下一个位置

当子串为“00001”,主串为“0000000000000000000000001”时,可以预想到查找效率极低,需要匹配到最后一个位置才能找到

在这里插入图片描述

2 串的模式匹配算法——KMP算法

从刚刚的例子可以看出第四次和第五次是不需要进行的,因为从第三次的结果可以看出“b” “c” “a”是无需进行比较的,仅需向右滑动三个位置

如果已匹配相等的前缀序列中有某个后缀正好是子串的前缀,那么就可以将子串向后滑动到与这些相等字符对齐的位置

2.1 字符串的前缀,后缀和部分匹配值

前缀:除最后一个字符意外,字符串的所有头部子串

后缀:除第一个字符外,字符串的所有尾部子串

部分匹配值:字符串的前缀和后缀的最长相等前后缀长度,以下记为PM

‘a’:前后缀为空,PM=0

‘ab’:前缀‘a’,后缀‘b’,‘a’ ⋂ \bigcap ‘b’= ∅ \varnothing ,PM=0

‘aba’,前缀‘a,ab’,后缀‘a,ba’,并集为‘a’,PM=1

‘abab’,前缀‘a,ab,aba’,后缀‘b,ab,bab’,并集‘ab’,PM=2

‘ababa’,前缀‘a,ab,aba,abab’,后缀‘a,ba,aba,baba’,并集‘a,aba’,PM=3

所以‘ababa’的部分匹配值为00123

那么刚刚例子中abcac的PM就为

编号12345
Sabcac
PM00010

采用PM的移动算法:

移动位数 = 已匹配的字符数 - 最后一个匹配成功的字符对应的PM

在这里插入图片描述

主串没有回退,时间复杂度:O(n+m),大大提高了效率

2.2 KMP算法的原理

通过上述的部分匹配值可以得出匹配失败时主串对比的移动位数

写成式子:move=(j-1)-PM[j-1]

但是失败时总是去找匹配失败的上一个PM值,使用起来不太方便,所有将所有PM表右移一位,得到next数组

编号12345
Sabcac
PM-10001

第一个元素右移后用-1来填充,因为若是第一个元素匹配失败,只需要将子串向右移动一位再比较,不需要计算子串移动的位数

最后一个元素溢出,但上一个PM本来就是计算下一个元素的,所有这个PM不存在下一个元素,可以舍弃

移动位数:move = (j-1) - next[j]

所以指针J回退的位置:j=j-move=next[j]+1

为了再使得公式简单简洁,再把next数组整体+1

编号12345
Sabcac
PM01112

最终的子串指针变化公式j=next[j]

next[j]的含义:在子串的第j个字符与主串发生失配时,则跳到子串的next[j]位置重新与主串当前位置进行比较

怎么推理next数组的一般公式?

假设主串为S1S2……Sn,模式串为p1p2……pm,当主串的第i个字符与模式串的第j个字符失配,子串应向右滑动多远,然后与模式串的那个字符比较?

假设此时应该与第k(k<j)个字符继续比较,则模式串的前k-1个字符的子串必须满足下列条件,且不可能存在k,>k满足下列条件

​ ‘p1p2……pk-1’=‘ pj-k+1pj-k+2……pj-1

(1)若满足上述条件,失配时,将模式向右滑动至模式中第k个字符和主串第i个字符对齐,此时模式中前k-1个字符的子串必定与主串中第i个字符之前长度为k-1的子串相等,只需从模式第k个字符与主串第i个字符继续比较即可

在这里插入图片描述

(2)不满足上述条件时(k=1),直接将模式串右移j-1位,让主串的第i个字符与模式串第一个字符对比,此时右移位数最大

(3)若模式串的第一个子串就与主串的第i个字符失配时,规定next[1]=0;模式串右移一位,从主串的下一个位置i+1与模式串的第一个位置继续对比

即next函数的公式为

在这里插入图片描述

首先可知next[1]=0,next[j]=k;那next[j+1]为多少呢,有两种情况:

(1)若pk=pj,则满足条件 ‘p1p2……pk-1’=‘ pj-k+1pj-k+2……pj-1’ ,所以next[j+1]=k+1,即next[j+1]=next[j]+1

(2)若pk ≠ \neq =pj,不满足上述条件,则把p1…pk向右滑动到next[k]与pj比较,若pnext[k]与pj还是不匹配,继续找更短的相等前后缀,继续用pnext[next[k]]比较,直到更小的k’满足条件,next[j+1]=k’+1;如果不存在k,则令next[j+1]=1

举例说明:

j123456789
模式abaabcaba
next[j]011223123

j=1:初始next[1]=0

j=2:往前不存在p0与p1相等,则next[2]=1

j=3:往前不存在p与p2相等,则next[3]=1

j=4:通过p3的next判断pnext[3]即p1=p3,则next[4]=next[3]+1=2,同时也是next[4]=k+1=2

j=5:通过pnext[next[4]]即p1=p4,则k=next[next[4]]=1,next[5]=k+1=2

j=6:p5=pnext[2],则k=2,next[6]=k+1=3

j=7:不存在p与p6相等,则next[7]=1

j=8:p7=p1,则k=1,next[8]=1+1=2

j=9:p8=p2,则k=2,next[9]=2+1=3

代码如下

void getnext(Sstring T,int next[])
{
    int i =1,j=0;
    next[1]=0;
    while(i<T.length)
    {
        if(j==0||T.ch[i]==T.ch[j])
        {
            i++;j++;
            next[i]=j;
        }
        else
            j = next[j];
    }
        
}

int IndexKMP(Sstring S,Sstring T,int next[])
{
    int i =1,j=1;
    while(i<=S.length && j<= T.length)
    {
        if(j==0||S.ch[i]==T.ch[j])
        {
            i++;
            j++;
        }
        else
        {
            j=next[j];
        }
    }
    if(j>T.length)
    {
        return i-T.length;
    }
    else
    {
        return 0;
    }
}

2.3 KMP算法的优化

前面的next数组仍有缺项,比如‘aaaab ’和‘aaabaaaab’ 匹配时

在这里插入图片描述

不应该出现pj=pnext[j],当pj ≠ \neq =sj时,下次匹配必然还是pnext[j]与sj比较,毫无意义

如果出现pj=pnext[j],需要再次递归,将next[j]修正为next[next[j]],直到不相等,新数组命名为nextval

void getnextval(Sstring T,int nextval[])
{
    int i =1,j=0;
    nextval[1]=0;
    while(i<T.length)
    {
        if(j==0||T.ch[i]==T.ch[j])
        {
            i++;j++;
            if(T.ch[i]!=T.ch[j])
                nextval[i]=j
            else
                nextval[i]=nextval[j];
        }
        else
            j = nextval[j];
    }
        
}

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

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

相关文章

一起学SF框架系列5.4-模块Beans-DefaultListableBeanFactory

在生成ApplicationContext过程中&#xff0c;AbstractRefreshableApplicationContext.refreshBeanFactory()完成旧BeanFactory关闭&#xff0c;创建新的BeanFactory&#xff0c;即new DefaultListableBeanFactory(…)。然后bean实例化时调用本类的preInstantiateSingletons方法…

网安笔记12 IPsec

IPSec 基于通信IP环境下一种端到端&#xff0c;保证数据安全的机制 包含 两个安全协议&#xff0c;一个密钥管理协议&#xff0c; 标准价秘密技术为基础 DES/其他分组加密算法键值hash算法认证公钥有效的数字证书 AH协议提供信息源验证、完整性保证ESP提供信息源验证、机密…

华为OD机试真题 JavaScript 实现【求解立方根】【牛客练习题】

一、题目描述 计算一个浮点数的立方根&#xff0c;不使用库函数。保留一位小数。 数据范围&#xff1a;∣val∣≤20 。 二、输入描述 待求解参数&#xff0c;为double类型&#xff08;一个实数&#xff09; 三、输出描述 输出参数的立方根。保留一位小数。 四、解题思路…

Git工作流(随笔)

目录 前言 一、工作流概述 1、概念 2、分类 二、集中式工作流 1、概述 2、介绍 3、操作过程 三、功能分支工作流 1、概述 2、介绍 3、操作过程 1&#xff09;创建远程分支 2&#xff09;删除远程分支 四、GitFlow工作流 1、概述 2、介绍 3、操作过程 五、Forki…

Linux系统:CentOS编译Linux内核

目录 一、实验 1.下载内核 2.解压内核源码 3.配置依赖的环境 4.进入源码目录&#xff0c;使用make menuconfig开启菜单选项&#xff0c;手动选择内核功能 5.编译内核 6.安装模块 7.安装内核 8.验证新内核版本 一、实验 1.下载内核 &#xff08;1&#xff09;官网下载…

010:vue中el-table 隐藏表头的两种方法

第010个 查看专栏目录: VUE — element UI echarts&#xff0c;openlayers&#xff0c;cesium&#xff0c;leaflet&#xff0c;mapbox&#xff0c;d3&#xff0c;canvas 免费交流社区 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例…

【Kubernetes存储篇】StorageClass存储类动态生成PV详解

文章目录 一、StorageClass存储类理论二、案例&#xff1a;Storageclass存储类实战演示1、搭建NFS服务端2、搭建NFS供应商(provisioner)3、创建StorageClass存储类4、创建PVC&#xff0c;通过StorageClass动态生成PV5、创建Pod挂载PVC 三、步骤总结 一、StorageClass存储类理论…

web测试工程师的工作职责

web测试工程师的工作职责1 职责: 1、 负责数据平台产品的测试工作&#xff0c;参与产品需求分析&#xff0c;负责方案制定,并能预先评估项目风险&#xff0c;确保测试活动的顺利开展; 2、 深入理解系统内部的设计原理&#xff0c;并能从测试的角度提供优化意见; 3、 根据产品需…

LVS+Keepalived 高可用群集实战部署

目录 前言 一、Keepalived工具介绍 1、Keepalived的作用 2、 Keepalived体系主要模块 二、keepalived工作原理 1、VRRP &#xff08;虚拟路由冗余协议&#xff09; 2、VRRP &#xff08;虚拟路由冗余协议&#xff09; &#xff08;1&#xff09;keepalived.conf是主配置文…

qt 使用qtpdfium实现读取pdf文件

此处仅作为笔记和配置指导。 要想获取源码可Git hub上下载&#xff0c;这里推荐一个&#xff1a;QtPdfium_Build_Source: 基于Google pdfium 库&#xff0c;可编译和编译好的Qtpdfium 库&#xff0c;用于加载pdf文件进行预览 (gitee.com) 目前小编根据推荐的编译方法&#xf…

STM32F0实现数字化SPWM纯正弦波逆变器

一、理论基础 所谓SPWM&#xff0c;就是通过只有开关两个状态&#xff08;离散&#xff0c;数字的&#xff09;的PWM序列产生正弦波&#xff08;连续&#xff0c;模拟的&#xff09;的方法。其理论基础一句话就能说明白&#xff1a;冲量相等而形状不同的窄脉冲加在具有惯性的环…

golang语法的注意事项

目录 1.源文件以.go结尾2.过文件只能存在一个mian 函数 (入口函数)3. go严格区分大小写4. golang的每行代码是一个语句 默认存在 ; 不需要自己写了5. golang 是每一行每一行的执行的 不要把多个语句写在一行6. 定义变量 或是导入的包未使用 无法同过编译7. {} 都是成对出现 为了…

Xtrak 塔克小车巡线代码以及红绿灯识别相关小改动

做的小改动&#xff1a; 只更改了xtark_follow_line.py 增加了一次HSV分割来做红绿灯识别&#xff08;建议到时候用手机或者平板显示红色或者绿色图片&#xff09;&#xff0c;红绿图片&#xff1a; 将检测道路位置的方式从重心变为最接近屏幕中心的道路像素点。 检测不到道路之…

ubuntu 22.04部署dzzoffice及安装onlyoffice插件

目录 一、配置阿里源 二、安装数据库 三、安装依赖组件 &#xff08;一&#xff09;安装php7.4 &#xff08;二&#xff09;安装apache2 四、下载 dzzoffice 五、安装dzzoffice 六、安装onlyoffice插件 &#xff08;一&#xff09;从github下载小胡版onlyoffice替代原来…

如何学习及计算机编程,入门看这一篇就够了---以c语言为例

信息时代&#xff0c;计算机变成不再是专业性很强的一门学科&#xff0c;更多的是变成了一种工具 用计算机爬取信息&#xff08;爬虫&#xff09;进行数据分析&#xff0c;数据可视化&#xff08;大数据的某个方面&#xff09;处理海量的数据&#xff0c;如excel&#xff08;百…

声音生成项目(4)——从VariantAutoencoder(VAE)到VQ-VAE矢量量化变分编码器

文章目录 论文介绍步骤具体讲解自定义矢量量化层获取最近距离的码字的索引计算推导损失函数相关参考 矢量量化层的代码实现完整代码实现 论文介绍 不同于变分编码器和自动编码器&#xff0c;vq-vae中的latent space是离散的&#xff0c;并不是连续的&#xff0c;这能够避免后验…

第3章“程序的机器级表示”:理解指针

指针是 C 语言的一个重要特色。它们提供一种统一方式&#xff0c;能够远程访问数据结构。 指针基本的概念其实非常简单&#xff0c;下面的代码说明了许多这样的概念&#xff1a; struct str { /* Example Structure */int t;char v; };union uni { /* Example Union */int t;…

大厂C++面试基础题第1辑——虚函数七题精讲之一

> “虚函数的作用” 是面向对象的C编程最基础也最核心的知识点&#xff0c;如果不能无法正确回答本题&#xff0c;则只此一题&#xff0c;不管大厂还是小厂&#xff0c;都铁定无缘了。 概述 “虚函数” 是 C面向对象三最&#xff1a;最基础、最重要、最关键的知识点。我们从…

什么是Vue的Vite构建工具?如何使用Vite进行项目开发

什么是Vue的Vite构建工具&#xff1f;如何使用Vite进行项目开发 介绍 Vite是一个由Vue.js核心团队开发的构建工具。它的目标是提供一种快速的开发体验&#xff0c;同时保持生产环境的稳定性和可靠性。Vite使用了ES模块作为开发环境的原生模块格式&#xff0c;通过在开发服务器…

C++11中的关键字constexpr

文章目录 1、constexpr修饰普通变量2、constexpr修饰函数3、constexpr修饰类的构造函数 constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力&#xff0c;而不必等到程序运行阶段。C 11 标准中&#xff0c;constexpr 可用于修饰普通变量、函数&…