【数据结构】串

2023-11-17

串的顺序实现

初始化

#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的第一个元素固定为0
    next[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的第一个元素固定为0
    next[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];
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【数据结构】串 的相关文章

随机推荐

  • 【frida逆向开发】frida-rpc远程调用某安app方法获取token

    目录 一 使用fiddler对app进行抓包 二 反编译app定位关键代码 三 frida rpc调用相关方法 一 使用fiddler对app进行抓包 通过抓包可以看到请求参数中X App Token e8f1c71569a7166b6aa
  • AD隐藏元件标号

    1 右键点击元件 不是标号 2 查找相似对象 3 点击应用 4 再点确定 5 把Show Name右边的勾取消
  • Go 语言进阶(一) -- Go hertz http框架、kitex RPC微服务框架、gorm 数据库框架三件套用法详解

    Go 框架三件套 1 概论 Gorm Gorm 是一个已经迭代了10年 的功能强大的 ORM框架 在字节内部被广泛使用并且拥有非常丰富的开源扩展 Kitex Kitex 是字节内部 Golang 微服务 RPC 框架 具有高性能 强可扩展的
  • 必看的知识

    学习路线 必看的知识 Spring实战 Spring 4 x企业应用开发实战 深入分析Java Web技术内幕 修订版 Effective Java Thinking in Java Java核心技术 Core Java Thinking
  • 赫拉(hera)分布式任务调度系统

    相关介绍 赫拉 hera 分布式任务调度系统之架构 基本功能 一 赫拉 hera 分布式任务调度系统之项目启动 二 赫拉 hera 分布式任务调度系统之开发中心 三 赫拉 hera 分布式任务调度系统之版本 四 赫拉 hera 分布式任务调
  • 修正了一个通信bug

    该BUG导致用户在打开webchat使用界面时不会读取联系人UPT 有时候刷新界面后会解决这个问题 经过发现是判断webchat是否存在在线用户以便于打印在线和离线联系人的分支结构出错 现在bug已经解除 同时解决了一个UPT串截断出错的b
  • java实现文件的断点续传的下载

    java的断点续传是基于之前java文件下载基础上的功能拓展 首先设置一个以线程ID为名的下载进度文件 每一次下载的进度会保存在这个文件中 下一次下载的时候 会根据进度文件里面的内容来判断下载的进度 package com ldw mult
  • Win10/Win11子系统(一)——wsl2+Ubuntu20.04安装记录

    windows子系统Ubuntu20 04安装过程记录 前言 一 安装前准备 二 开始安装 三 更换镜像源 四 安装图形化界面 五 警告处理 六 迁移子系统 前言 我和我最后的倔强 坚持不换windows的口号被现实打败了 装双系统会影响到
  • Hive SQL使用中遇到的问题与解决方案(持续更新

    近期 因统计分析 数据处理的工作需求 经常使用Hive SQL 因此记录遇到的一些问题 1 desc formatted 表名 确定表的信息 行 列 存储路径 在确定Hive 数据仓库中表的存储路径时 很有帮助 2 SQL GROUP BY
  • 【MedusaSTears】IntelliJ IDEA 自动生成方法注释模板设置(入参每行1个如图)

    快捷键 按键 按键 按键tab 效果图 设置方式 参考资料 https blog csdn net yuruixin china article details 80933835 我也是参考这个文章设置的 只不过我改了一些其它的内容 修改如
  • “疫情”防控时期大势所趋,智慧社区尽显“智慧”迎来新的发展热潮

    近期 国内新冠肺炎疫情在各地再次反扑 各种变异毒株 境外输入压力让疫情防控变的更加严峻 社区防控是第一道防线 进出小区人员登记 出示健康码 测量体温 居家隔离等是每个社区都要面临的防控压力 但是如果对社区内的居民不能精确管理 就会导致很多的
  • 1234. 替换子串得到平衡字符串

    有一个只含有 Q W E R 四种字符 且长度为 n 的字符串 假如在该字符串中 这四个字符都恰好出现 n 4 次 那么它就是一个 平衡字符串 给你一个这样的字符串 s 请通过 替换一个子串 的方式 使原字符串 s 变成一个 平衡字符串 你
  • Markdown预览 代码块自动化加代码行数-VSCode

    Markdown预览 代码块自动化加代码行数 VSCode 官方地址 https shd101wyy github io markdown preview enhanced zh cn markdown basics id 代码行数 第一步
  • JToolBarTest JToolBar 的一个测试类

    package com test JToolBarTest import javax swing JButton import javax swing JFrame import javax swing JToolBar public cl
  • python笔记:变量赋值与注意事项

    1 单个变量赋值 a 变量未赋值会报错 a 1 正确写法 2 多个变量赋值 方法1 a b c 1 方法2 a b c 1 1 1 print a b c 1 1 1 3 基本变量类型 五大类 字符串 string 数字 Numeric 列
  • 第1章 NumPy基础

    为何第1章介绍NumPy基础 在机器学习和深度学习中 图像 声音 文本等首先要数字化 如何实现数字化 数字化后如何处理 这些都涉及NumPy NumPy是数据科学的通用语言 它是科学计算 矩阵运算 深度学习的基石 PyTorch中的重要概念
  • 分布式消息队列RocketMQ--事务消息--解决分布式事务的最佳实践

    分布式消息队列RocketMQ 事务消息 解决分布式事务的最佳实践 标签 事务消息exactlyRocketMQKafka分布式消息队列 2016 12 23 22 08 7789人阅读 评论 8 收藏 举报 分类 分布式消息队列Rocke
  • Windows Server2012R2 VisualSVN3.9.7-Server在线修改密码搭建

    经过核验 按下面这样的方式去升级 从3 0 0升级到3 9 7 同时支持用户通过web界面修改密码 每个用户忘记密码要管理员来修改 工作量不大 但真的是耗时费力 还不讨好 1 安装软件准备 1 1 软件准备 1 Windows Server
  • 自动化测试系列 —— UI自动化测试

    UI 测试是一种测试类型 也称为用户界面测试 通过该测试 我们检查应用程序的界面是否工作正常或是否存在任何妨碍用户行为且不符合书面规格的 BUG 了解用户将如何在用户和网站之间进行交互以执行 UI 测试至关重要 通过执行 UI 测试 测试人
  • 【数据结构】串

    串 串的顺序实现 简单的模式匹配算法 KMP算法 KMP算法的进一步优化 串的顺序实现 初始化 define MaxSize 50 typedef char ElemType 顺序存储表示 typedef struct ElemType d