数据结构——串——王道

2023-11-12

目录

定义

串和线性表的联系及不同

串的基本操作

存储结构

顺序存储

链式存储

基本操作的实现

字符串模式匹配算法

朴素模式匹配算法

KMP算法


定义

串,即字符串( String)是由零个或多个字符组成的有限序列。一般记为

S='a_{1}a_{2}......a_{n} ' (n\geq0 )
其中,S是串名,单引号括起来的字符序列是串的值;a_{i}可以是字母、数字或其他字符;串中字符的个数 n 称为串的长度。n =0时的串称为空串(用\varnothing表示)。

例:
S="Hello World!"                 T="wait wait wait baby" 

一些概念:

名称 含义 举例
子串 串中任意个连续的字符组成的子序列。 "wait w','baby’是串T的子串
主串 包含子串的串。 T是子串 'baby' 的主串
字符在主串中的位置 字符在串中的序号。 ‘o'在S中的位置是5第一次出现)

子串在主串中的位置

子串的第一个字符在主串中的位置。 'World'在S中的位置为7

注意:位序从1开始排

串和线性表的联系及不同

串是一种特殊的线性表,数据元素之间呈线性关系。

串的对象限定为字符集(如中文字符、英文字符、数字字符、标点字符等)

串的基本操作,如增删改查等通常以子串为操作对象。

串的基本操作

假设有串T="",S="Who are you?”,W="are''

StrAssign(&T,chars) 赋值操作。把串T赋值为chars。
StrCopy(&T,S) 复制操作。由串s复制得到串T。
StrEmpty(S) 判空操作。若s为空串,则返回TRUE,否则返回FALSE。
StrLength(S) 求串长。返回串s的元素个数。
ClearString(&S) 清空操作。将s清为空串。
DestroyString(&S) 销毁串。将串s销毁(回收存储空间)。
Concat(&T,S1,S2) 串联接。用T返回由S1和S2联接而成的新串
Index(S,T) 定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则函数值为0。
StrCompare(S,T) 比较操作。若S>T,则返回值>0;若S=T,则返回值=0﹔若S<T,则返回值<0。

存储结构

顺序存储

#define MAXLEN 255    //预定义最大串长为255
//静态数组实现(定长顺序存储)
typedef struct{
    char ch[MAXLEN];  //每个分量存储一个字符
    int length;       //串的实际长度
}SString;


//动态数组实现(堆分配存储)
typedef struct{
    char *ch;         //按串长分配存储区,ch指向串的基地址
    int length;       //串的长度
}HString;
HString s;
s.ch =(char *)malloc(MAXLEN * sizeof(char));
s.length = 0;

 优点:字符的位序和数组下标相同。

链式存储

 方式一:每个结点存储一个字符

typedef struct StringNode{
    char ch;    //每个结点存储1个字符
    struct StringNOde * next;
}StringNode,*String;

方式二: 每个结点存储多个字符,没有字符的位置可以用'#'或'\0'补足

typedef struct StringNode{
    char ch[4];        //每个结点存储多个字符
    struct StringNode *next;
}StringNode,*String;

基本操作的实现

#define MAXLEN 255     //预定义最大串长为255
typedef struct{
    char ch[MAXLEN];   //每个分量存储一个字符
    int length;        //串的实际长度
}SString;

 SubString(&Sub,S,pos,len)

求子串。用sub返回串S的第pos个字符起长度为len的子串。

//求子串
bool SubString (SString &Sub,SString S,int pos,int len){
    //子串范围越界
    if(pos+len-1 >S.length)
        return false;
    for(int i=pos; i<pos+len; i++)
        Sub.ch[i-pos+1] = S.ch[i];
    Sub.length = len;
    return true;
}

StrCompare(S,T)

比较操作。若S>T,则返回值>o;若S=T,则返回值=0;若S<T,则返回值<0。

int StrCompare(SString S,SString T){
    //从第一个字符开始比较,直到其中一个串的字符先被比较完
    for(int i=1; i<=S.length &&i<=T.length; i++){
        if(S.ch[i]!=T.ch[i])         //字符不相等
            return S.ch[i]-T.ch[i];  //返回字符大小比较
    }
    //扫描过的所有字符都相同,则长度长的串更大
    return S.length-T.length;
}

Index(S,T)

定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则函数值为0。

int Index(SString S,SString T){
    int i=1,n=StrLength(S),m=StrLength(T);    //Strlength(S)是求串长的操作
    SString sub;                //用于暂存子串
    while(i<=n-m+1){            //n-m+1为所有子串的数目
        SubString(sub,S,i,m);   //取出一个子串(从i开始到m结束)
        if(StrCompare(sub,T)!=0)
            i++;                //不相等,进行下一个子串的比较
        else
            return i;            //相等,返回子串在主串中的位置
    }
    return 0;                    //S中不存在与T相等的子串
}

字符串模式匹配算法

朴素模式匹配算法

 字符串模式匹配∶在主串中找到与模式串相同的子串,并返回其所在位置。

子串:主串的一部分,一定存在

模式串:不一定能在主串中找到

逻辑:

主串长度为n模式串长度为m,将所有长度为m的子串依次与模式串对比(最多对比n-m+1个子串),直到找到一个完全匹配的子串或所有的子串都不匹配为止。

 和上面实现的Index(S,T)函数一致,即是暴力解法。

int Index(SString S,SString T){
    int i=1,n=StrLength(S),m=StrLength(T);    //Strlength(S)是求串长的操作
    SString sub;                //用于暂存子串
    while(i<=n-m+1){            //n-m+1为所有子串的数目
        SubString(sub,S,i,m);   //取出一个子串(从i开始到m结束)
        if(StrCompare(sub,T)!=0)
            i++;                //不相等,进行下一个子串的比较
        else
            return i;            //相等,返回子串在主串中的位置
    }
    return 0;                    //S中不存在与T相等的子串
}

KMP算法

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度为O(m+n) 

 首先根据模式串T求next数组出来。

对于模式串T ='abaabc',可得出以下结论
当第6个元素匹配失败时,可令主串指针i不变,模式串指针j=3
当第5个元素匹配失败时,可令主串指针i不变,模式串指针j=2
当第4个元素匹配失败时,可令主串指针i不变,模式串指针j=2
当第3个元素匹配失败时,可令主串指针i不变,模式串指针j=1
当第2个元素匹配失败时,可令主串指针i不变,模式串指针j=1
当第1个元素匹配失败时,匹配下一个相邻子串,令j=0,i++,j++

并将这些放到next数组

next[0] next[1] next[2] next[3] next[4] next[5] next[6]
0 1 1 2 2 3

结合代码逻辑进行处理

//当元素匹配失败时,i不变,j指向next[j]的值
if(S[i] != T[j]) j=next[j];

//当第1个元素匹配失败时,令j=0,i++,j++
if(j==0)  {i++;j++ }
int Index_KMP(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] ){
            ++j;
            ++i;            //继续比较后继字符
        }
        else
            j = next[j];    //模式串向右移动
    }
    if(j>T.length)
        return i-T.length;  //匹配成功
    else
        return 0; 
}

不同之处在于,

朴素模式匹配算法在匹配失败时,主串指针i会疯狂回溯。

匹配失败时,主串指针i不回溯

求模式串的next数组(手算练习)

next数组的作用:当模式串的第j个字符失配时,从模式串的第 next[j] 的继续往后匹配

对于任何模式串,next[1]为0 ,next[2]为1

其他next:在不匹配的位置前,划一条分界线,模式串一步一步向右移动,直到分界线之前“能对上”,或模式串完全跨过子串,此时j指向哪儿,next[j]的值就是哪儿

对于模式串“google”的next数组为

next[0] next[1] next[2] next[3] next[4] next[5] next[6]
0 1 1 1 2 1

练习1:

模式串:T=ababaa

序号j 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 2 3 4

举例:当匹配到第5个元素失败时,模式串向右移动,直到分界线之前的内容一致。此时j指向第3个字符,则next[ 5 ] 为3

 

练习2: 

序号j 1 2 3 4 5
模式串 a a a a b
next[j] 0 1 2 3 4

 KMP算法的进一步优化

主要是对next数组进行优化,将next数组优化为nextval数组。

 对练习1的next数组进行优化:

序号j 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 2 3 4
nextval[j] 0 1 0 1 0 4

 举例解释:在匹配到第3个元素失败时,按next[j],j应该指向第1个元素,但我们可以知道第3个元素是a,匹配失败了,所以当 j 指向第一个元素时也会不匹配。所以,我们可以直接将其优化,去除这多余的一步,即当你在第3个元素匹配失败时,next的指向为和第3个元素相同的字符,则 j 的指向再回退一步,指向next[ 1 ]

练习2的优化: 

序号j 1 2 3 4 5
模式串 a a a a b
next[j] 0 1 2 3 4
nextval[j] 0 0 0 0 4

 next数组——》nextval数组的代码

nextval[1] = 0;
for(int j=2 ; j<=T.length ; j++){
    if(T.ch[next[j]]==T.ch[j])    
        nextval[j] = nextval[next[j]]
    else
        nextval[j] = next[j];
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构——串——王道 的相关文章

  • 没有强命名的代码签名是否会让您的应用程序容易被滥用?

    尝试了解authenticode代码签名和强命名 我是否正确地认为 如果我对引用一些 dll 非强命名 的 exe 进行代码签名 恶意用户就可以替换我的 DLL 并以看似由我签名但正在运行的方式分发应用程序他们的代码 假设这是真的 那么您似
  • 为什么两个不同的 Base64 字符串的转换会返回相等的字节数组?

    我想知道为什么从 base64 字符串转换会为不同的字符串返回相同的字节数组 const string s1 dg const string s2 dq byte a1 Convert FromBase64String s1 byte a2
  • 动态加载程序集的应用程序配置

    我正在尝试将模块动态加载到我的应用程序中 但我想为每个模块指定单独的 app config 文件 假设我的主应用程序有以下 app config 设置
  • 按成员序列化

    我已经实现了template
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 秒表有最长运行时间吗?

    多久可以Stopwatch在 NET 中运行 如果达到该限制 它会回绕到负数还是从 0 重新开始 Stopwatch Elapsed返回一个TimeSpan From MSDN https learn microsoft com en us
  • Asp.NET WebApi 中类似文件名称的路由

    是否可以在 ASP NET Web API 路由配置中添加一条路由 以允许处理看起来有点像文件名的 URL 我尝试添加以下条目WebApiConfig Register 但这不起作用 使用 URIapi foo 0de7ebfa 3a55
  • 使用实体框架模型输入安全密钥

    这是我今天的完美想法 Entity Framework 中的强类型 ID 动机 比较 ModelTypeA ID 和 ModelTypeB ID 总是 至少几乎 错误 为什么编译时不处理它 如果您使用每个请求示例 DbContext 那么很
  • 堆栈溢出:堆栈空间中重复的临时分配?

    struct MemBlock char mem 1024 MemBlock operator const MemBlock b const return MemBlock global void foo int step 0 if ste
  • C++ OpenSSL 导出私钥

    到目前为止 我成功地使用了 SSL 但遇到了令人困惑的障碍 我生成了 RSA 密钥对 之前使用 PEM write bio RSAPrivateKey 来导出它们 然而 手册页声称该格式已经过时 实际上它看起来与通常的 PEM 格式不同 相
  • 将多个表映射到实体框架中的单个实体类

    我正在开发一个旧数据库 该数据库有 2 个具有 1 1 关系的表 目前 我为每个定义的表定义了一种类型 1Test 1Result 我想将这些特定的表合并到一个类中 当前的类型如下所示 public class Result public
  • 使用 Bearer Token 访问 IdentityServer4 上受保护的 API

    我试图寻找此问题的解决方案 但尚未找到正确的搜索文本 我的问题是 如何配置我的 IdentityServer 以便它也可以接受 授权带有 BearerTokens 的 Api 请求 我已经配置并运行了 IdentityServer4 我还在
  • 转发声明和包含

    在使用库时 无论是我自己的还是外部的 都有很多带有前向声明的类 根据情况 相同的类也包含在内 当我使用某个类时 我需要知道该类使用的某些对象是前向声明的还是 include d 原因是我想知道是否应该包含两个标题还是只包含一个标题 现在我知
  • 如何在 C 中调用采用匿名结构的函数?

    如何在 C 中调用采用匿名结构的函数 比如这个函数 void func struct int x p printf i n p x 当提供原型的函数声明在范围内时 调用该函数的参数必须具有与原型中声明的类型兼容的类型 其中 兼容 具有标准定
  • 这些作业之间是否存在顺序点?

    以下代码中的两个赋值之间是否存在序列点 f f x 1 1 x 2 不 没有 在这种情况下 标准确实是含糊不清的 如果你想确认这一点 gcc 有这个非常酷的选项 Wsequence point在这种情况下 它会警告您该操作可能未定义
  • 链接器错误:已定义

    我尝试在 Microsoft Visual Studio 2012 中编译我的 Visual C 项目 使用 MFC 但出现以下错误 error LNK2005 void cdecl operator new unsigned int 2
  • WPF/C# 将自定义对象列表数据绑定到列表框?

    我在将自定义对象列表的数据绑定到ListBox in WPF 这是自定义对象 public class FileItem public string Name get set public string Path get set 这是列表
  • 向现有 TCP 和 UDP 代码添加 SSL 支持?

    这是我的问题 现在我有一个 Linux 服务器应用程序 使用 C gcc 编写 它与 Windows C 客户端应用程序 Visual Studio 9 Qt 4 5 进行通信 是什么very在不完全破坏现有协议的情况下向双方添加 SSL
  • 混合 ExecutionContext.SuppressFlow 和任务时 AsyncLocal.Value 出现意外值

    在应用程序中 由于 AsyncLocal 的错误 意外值 我遇到了奇怪的行为 尽管我抑制了执行上下文的流程 但 AsyncLocal Value 属性有时不会在新生成的任务的执行范围内重置 下面我创建了一个最小的可重现示例来演示该问题 pr
  • 对来自流读取器的过滤数据执行小计

    编辑问题未得到解答 我有一个基于 1 个标准的过滤输出 前 3 个数字是 110 210 或 310 给出 3 个不同的组 从流阅读器控制台 问题已编辑 因为第一个答案是我给出的具体示例的字面解决方案 我使用的实际字符串长度为 450 个

随机推荐

  • unity3d 对大图额外加载

    加载 UI 背景大图 lua 使用 if self findBack image nil then resMgr UnLoadBigImage self findBack image self findBack image nil self
  • 【华为云】E: You don‘t have enough free space in /var/cache/apt/archives/.

    目录 一 购买华为云系统盘空间 二 扩容前的准备 三 扩容 扩大已有MBR分区 起因 使用华为云服务器 在安装Xfce桌面环境时报错 E You don t have enough free space in var cache apt a
  • sql 数据库删除,修改,增加列语句

    ALTER TABLE 添加 修改 删除表的列 约束等表的定义 查看列 desc 表名 修改表名 alter table t book rename to bbb 添加列 alter table 表名 add column 列名 varch
  • [翻译&摘抄]ES6 中的元编程:Symbol

    原文地址 Metaprogramming in ES6 Symbols and why they re awesome 原文作者 Keith Cirkel 译文出自 掘金翻译计划 转自 https juejin im post 5a0e65
  • LM5118 DC-DC电源降压芯片带载能力不够问题

    1 现象 主机系统带载到2A时 系统反复重启 2 分析 示波器测量VCC 5V 稳压源一显示到2A VCC 5V就会掉到0V 把如下二极管断开 万用表测量电流到3 7A就是掉下 3 解决 查规格书可知电流检测电阻的计算 原来为0 03R 按
  • javascript解析XML生成树形结构

    前两天一个朋友去一家公司面试 面试题是用javascript解析一个XML 生成树形结构 今天闲着没事就试了试 源代码
  • Unity自带的相应事件

    Unity自带的相应事件 代码 条件 各个响应事件 鼠标移入移出 鼠标按下 抬起 点击 鼠标拖拽 选择事件接口 系统按键事件的接口 代码 using UnityEngine using UnityEngine EventSystems pu
  • 数据结构题目-哈希

    目录 A Hash表 线性探测法解决冲突 B 求3阶B 树的深度 C 输出3阶B 树的构造过程 D Hash表 链表法解决冲突 仅作储存代码使用 A Hash表 线性探测法解决冲突 include
  • Python当中reverse()函数

    Hello大家好 今天我想和大家分享一下Python当中的reverse 函数 reverse 函数顾名思义就是反转的意思 但是我们要注意反转的内容只能是python当中的列表 千万不要忘记了 例子如下 arr 1 2 3 4 5 6 ar
  • js数据类型之对象object类型(数组与自定义对象)

    对象object 数组与自定义对象 JavaScript 中的所有事物都是对象 字符串 数值 数组 函数 此外 JavaScript 允许自定义对象 JavaScript 提供多个内建对象 比如 String Date Array 等等 对
  • CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout+Toolbar实现渐变透明的状态栏

    在之前的一篇博文里面我已经说明了CoordinatorLayout使用过程中遇到的问题 之后又发现结合CollapsingToolbarLayout使用时的另一个问题 CollapsingToolbarLayout里面的ImageView为
  • [架构之路-208]- 人人都是产品经理 - 什么是产品经理?产品经理具体是做什么的?

    目录 一 什么是产品经理 产品经理具体做什么 二 产品经理的岗位职责 三 产品经理的职业规划 一 什么是产品经理 产品经理具体做什么 在外行人看来 产品经理常常被误认为是 经理 其实产品经理只是一个岗位名称 并不是真正意义上的 经理 或者说
  • 深入浅出UML类图(五)

    实例分析3 售票机控制程序 某运输公司决定为新的售票机开发车票销售的控制软件 图I给出了售票机的面板示意图以及相关的控制部件 图I 售票机面板示意图 售票机相关部件的作用如下所述 1 目的地键盘用来输入行程目的地的代码 例如 200表示总站
  • python的几个重要基本概念

    1 整数 小数 布尔值和空值 整数 int类型 计算机中整数是有最大值的 与计算机的存储能力有关 即使是这样计算机中的整数值也是很大很大的 这一点基本上不需要担心的 小数 也称 浮点数 float类型 小数就是带小数点的数包括 1 0 等等
  • sqlite数据库-------清除数据,数据库文件大小不变解决方法

    现象 删除表格的全部数据 DELETE FROM Name 原因 当在sqlite中删除了大量数据后 数据库文件的大小还是那样 没有变 原因是 从Sqlite删除数据后 未使用的磁盘空间被添加到一个内在的 空闲列表 中用于存储你下次插入的数
  • IP协议号与传输层端口

    网络层 数据包的包格式里面有个很重要的字段叫做协议号 比如在传输层如果是tcp连接 那么在网络层ip包里面的协议号就将会有个值是6 如果是udp的话那个值就是17 传输层 传输层 通过接口关联 端口的字段叫做端口 应用层 协议号是存在于IP
  • 设计模式:桥接模式(c++实现案例)

    桥接模式 桥接模式是一种结构型设计模式 可将业务逻辑或一个大类拆分为不同的层次结构 从而能独立地进行开发 桥接模式通过将继承改为组合的方式来解决这个问题 具体来说 就是抽取其中一个维度并使之成为独立的类层次 这样就可以在初始类中引用这个新层
  • 2016年蓝桥杯省赛C/C++ A组-寒假作业

    题目 现在小学的数学题目也不是那么好玩的 看看这个寒假作业 每个方块代表1 13中的某一个数字 但不能重复 比如 6 7 13 9 8 1 3 4 12 10 2 5 以及 7 6 13 9 8 1 3 4 12 10 2 5 就算两种解法
  • ug产品摆正高级技巧_UG NX如何摆正产品零件模型

    原标题 UG NX如何摆正产品零件模型 有时 我们拿到一个产品模型 按F8也是一个歪的视图 如图 那么该如何才能将产品摆正呢 其实很简单 我们只需要移动下就好了 按ctrl t移动对象 选中模型 变化选项里面运动选择从csys到csys 指
  • 数据结构——串——王道

    目录 串 定义 串和线性表的联系及不同 串的基本操作 存储结构 顺序存储 链式存储 基本操作的实现 字符串模式匹配算法 朴素模式匹配算法 KMP算法 串 定义 串 即字符串 String 是由零个或多个字符组成的有限序列 一般记为 其中 S