第4章 串

计算机上的非数值处理的对象基本上是字符串数据。

字符串一般简称为串。

4.1 串类型的定义

  1. 串(string)(或字符串)是由零个或多个字符组成的有限序列,一般记为其中s是串的名,单引号括起来的字符序列是串的值(可以是字母、数字或其他字符),字符的数目称为串的长度(零个字符的串称为空串(null string))。单引号本身不属于串。
  2. 串中任意个连续的字符组成的子序列称为该串的子串,包含子串的串称为主串
  3. 字符在序列中的序号为该字符在串中的位置,子串在主串中的位置是子串的第一个字符在主串中的位置。
  4. 只有当两个串的长度相等,且各对应位置的字符都相等时,这两个串才相等。
  5. 空格是串的字符集合中的一个元素,由若干个空格组成的串称为空格串(blank string,不是空串),长度为空格的个数。
  6. 串的逻辑结构类似于线性表,只不过串的数据对象约束为字符集。
  7. 串的操作通常以整体作为操作对象,而不是像线性表一样操作单个元素。

4.2 串的表示和实现

串有3种机内表示方法,如下。

4.2.1 定长顺序存储表示

  1. 类似于线性表的顺序存储结构,用一组连续的存储单元存储串值的字符序列。

  2. 对串长有两种表示方法:

  • 以下标为0的数组分量存放串的实际长度;

  • 在串值后面加一个不计入串长的结束标记字符(如'\0')。

    此时串长为隐含值,不便于操作。

串的这种顺序存储结构的操作基于字符序列的复制,时间复杂度基于复制的字符序列的长度。

而且由于长度受限,可能发生截断,解决办法只有不限定最大长度或动态分配存储空间。

4.2.2 堆分配存储表示

  1. 仍以一组地址连续的存储单元存放串值字符序列,但他们的存储空间是在程序执行过程中动态分配的。

  2. C语言中存在一个称之为“堆”的自由存储区,由malloc()free()函数来管理。

  3. 为处理方便,约定串长也作为存储结构的一部分。

  4. 串的堆分配存储表示:

    typedef struct {  char *ch;  int length;}HString;
  5. 操作仍基于字符序列的复制,但首先进行内存分配操作。

  6. 由于堆分配存储结构的串既有顺序存储结构的优点(处理方便),操作中又对串长没有任何限制,更显灵活,因此在串处理的应用程序中也常被选用。

4.2.3 串的块链存储表示

  1. 和线性表的链表类似,也可以采用链表方式存储串值。

  2. 每个结点可以存放一个字符,也可以存放多个字符(此时结点中包含一个数组)(此时无法占满时补上#或其他的非串值字符)。

  3. 为便于联结操作,以链表存储串值时,除头指针外还可附设尾指针指向最后一个结点,并给出当前串的长度。称如此定义的串存储结构为块链结构。不必建立双向链表。

  4. 串的存储密度=串值所占的存储位/实际分配的存储位。

    结点大小为1时,存储密度小,运算处理方便,但存储占用量大。

  5. 串的链式存储结构对联接等操作有一定方便之处,但因为占用存储量大且操作复杂而不如另外两种存储结构灵活。

4.3 串的模式匹配算法

4.3.1 求子串位置的定位函数Index(S, T, pos)

  1. 子串的定位操作通常称做串的模式匹配(其中T称为模式串)。

  2. 若用最简单的思路进行定位,则从pos开始比较,若对应位置二者相等则二者指针均后移,若不等则T指针后退至1,S指针后退至本次开始匹配的位置(i-(j-1))的下一个位置(i-(j-1)+1),继续匹配。

  3. 一般情况下,上述算法的时间复杂度为O(n+m),n和m分别为主串和模式串的长度。

  4. 但在某种特殊情况下,如主串为00000000000000000000000000000000000000000000000000001,模式串为00000001时,会发生不断回溯的情况,可以看出算法在最坏情况下的时间复杂度为O(n*m)。

    而且这种情况十分常见,如在只有0、1两种字符的文本串的处理中。

4.3.2 模式匹配的一种改进算法

串的第0位存储长度值,故从1开始计数。

  1. 这种改进算法是D.E.Knuth与J.H.Morris和V.R.Pratt同时发现的,故被称为克努特-莫里斯-普拉特操作,简称KMP算法。

  2. 此算法可以在O(n+m)的时间数量级上完成串的模式匹配操作。

  3. 改进的思路是:每当一趟匹配过程中出现字符比较不等时,不需回溯i指针,而是利用已经得到的**“部分匹配”的结果**将模式串向右“滑动”尽可能远的一段距离后,继续进行比较:

  4. 该算法的关键在于,当发生失配时,模式串“向右滑动”可行的距离有多远,即下一次主串的失配字符应与模式串中哪个字符再比较?

  5. 假设下一次主串的失配字符应与模式串的第k个字符相比。

    此时主串匹配到i、模式串匹配到j,可知k不会超过j,而且k越小则滑得越远。

    由失配可知主串从i-j+1i-1和模式串的1j-1是完全相同的,当然也满足主串的i-k+1i-1和模式串的j-k+1j-1是完全相同的。

    既然下一次是ik相比,那么一定有主串的i-k+1i-1和模式串的1k-1完全相同,而且对一切大于kk'都不满足。正是这个原因才使我们不用比较中间这些字符。

    由这两个角度分析可知,k应满足的条件是:模式串的j-k+1j-11k-1完全相同,即模式串的头k-1个字符和尾k-1个字符完全相同。

    可以看出,k的取值与主串无关。

  6. 由此可知,模式串的每一个j都一一对应这一个k,记next[j]=k,得到模式串的next函数定义:

    可以看出,j>1时,第j位的next值为前j-1位长的字符串的前缀和后缀的最大重叠字符个数+1,不存在则为1

  • j=1时,next[j]=0
  • 设A=k11~k-1=j-k+1j-1,当集合A不空时,next[j]=max(A)
  • 其他情况,next[j]=1

有了next函数,每次若匹配则ij均增1,失配时则i不变而j退到next[j]位置(若next[j]为零,则i前移1位)。

KMP算法如下:

int Index_KMP(SString S, SString T, int pos) {  i = pos;  j = 1;  while (i <= S[0] && j <= T[0]) {    if (j == 0 || S[i] == T[j]) {      ++i;      ++j;    } else      j = next[j];  }  if (j > T[0])    return i - T[0];  else    return 0;}

KMP算法是在已知模式串的next函数值的基础上执行的,那么,如何求得模式串的next函数值呢?

经分析知,只需让模式串和自己进行匹配,若匹配则next[i]=j+1同时ij均后移;若失配则让第i位和next[j]再进行比较,直到next[j]=0结束套娃:

void get_next(SString T, int next[]) {  i = 1;  next[1] = 0;  j = 0;  while (i 0]) {    if (j == 0 || T[i] == T[j]) {      ++i;      ++j;      next[i] = j;    } else      j = next[j];  }}
  1. j=1时,next[1]=0

  2. j>1时,设next[j]=k(即模式串1k-1j-k+1j-1完全相同):

  • 此时若第k位和第j位也相同,说明next[j+1]=k+1,代入得next[j+1]=next[j]+1

  • 此时若第k位和第j位不相同,说明当模式串和自己进行匹配时,在i=jj=k时出现了失配。此时的“模式串”应退到next[k]处和第j个字符比较:

  • 若比较成功,则说明1next[k]j-next[k]+1j完全相同,即next[j+1]=next[k]+1

  • 若比较失败,则“模式串”退到next[next[k]]位置和第j个字符比较……(禁止套娃)

    最终直到比较成功(next[j+1]=next[...next[k]...]+1)或无法找到(next[...next[k]...]=0next[j+1]=1)为止。

此算法的时间复杂度为O(m),通常m作为模式串的长度较小,因此对整个匹配算法来说,所增加的这点时间是值得的。

虽然4.3.1节的算法时间复杂度是O(n*m),但一般情况下实际的执行时间近似于O(n+m),因此仍被采用。

KMP算法仅当模式与主串之间存在许多“部分匹配”的情况下才显得更快。而且由于主串的指针不需回溯,所以对输入的庞大的文件很有效,可以边读入边匹配。

上述获得next函数的算法在某些情况下尚有缺陷。因为定义的是每次失配则退到next[j]处,可是如果next[j]处的字符仍和j处的字符相等,我们本可以跳过和next[j]的比较,直接和next[next[j]]比较。即next函数可以做的改进是让next[j]直接等于next[next[j]](如果仍相等则继续向前)。

这就是nextval函数:

void get_nextval(SString T, int next_val[]) {  i = 1;  nextval[1] = 0;  j = 0;  while (i 0]) {    if (j == 0 || T[i] == T[j]) {      ++i;      ++j;      if (T[i] != T[j])        nextval[i] = j;      else        nextval[i] = nextval[j];    } else      j = nextval[j];  }}

4.4 串操作应用举例

4.4.1 文本编辑

  1. 文本编辑的实质是修改字符数据的形式和格式,一般包括串的查找、插入和删除等基本操作。

  2. 可以把文本看成一个字符串,称为文本串,页则是文本串的子串,行又是页的子串。

  3. 为管理文本串的页和行,编辑程序为文本串建立了相应的页表和行表,即建立了各子串的存储映像。

    页表的每一项给出了页号和该页的起始行号,而行表的每一项则指示每一行的行号、起始地址和该行子串的长度。

  4. 文本编辑程序中设立页指针、行指针和字符指针,分别指示当前操作的页、行和字符。

4.4.2 建立词索引表

  1. 信息检索的主要操作是在大量的存放在磁盘上的信息中查询一个特定的信息,为了提高查找效率,一个重要的问题是建立一个好的索引系统。
  2. 按书名检索并不方便,更好的办法是按书名关键词索引。为了便于查询,可设定此索引表为按词典有序的线性表。
  3. 设定数据结构:
  • 词表存放关键词,数量有限,故采用顺序表;
  • 索引表为有序表,主要为查找用,为提高查找效率,宜采用顺序表;
  • 索引表中每个索引项包含关键词和书号索引:
  • 关键词为常驻结构,考虑节省存储,采用堆分配存储表示的串类型;
  • 书号索引在索引表的生成过程中逐个插入,且不同关键词的书号索引个数不等,甚至可能相差很多,则宜采用链表。

a - 数据结构实验之串一:kmp简单应用_数据结构(C语言版)_笔记_3相关推荐

  1. a - 数据结构实验之串一:kmp简单应用_【在线教学示范课案例】数据结构(刘航)...

    一.教师简介 刘航,网络空间安全学院教师,本科生"数据结构"."算法设计综合实验" 和研究生"算法设计与优化"课程的主讲教师.近年来积极开展 ...

  2. a - 数据结构实验之串一:kmp简单应用_串的两种模式匹配方式(BF/KMP算法)

    串的两种模式匹配方式(BF/KMP算法) 前言 串,又称作字符串,它是由0个或者多个字符所组成的有限序列,串同样可以采用顺序存储和链式存储两种方式进行存储,在主串中查找定位子串问题(模式匹配)是串中最 ...

  3. a - 数据结构实验之串一:kmp简单应用_中高级面试必备:快速入门数据结构和算法

    一 前言 1 为什么要学习算法和数据结构? 解决特定问题. 深度优化程序性能的基础. 学习一种思想:如何把现实问题转化为计算机语言表示. 2 业务开发要掌握到程度? 了解常见数据结构和算法,沟通没有障 ...

  4. a - 数据结构实验之串一:kmp简单应用_Java程序员必会之数据结构与算法全梳理

    常见的数据结构 链表 LinkedHashSet LinkedList 底层数据结构由链表和哈希表组成. 数据的添加和删除都较为方便,就是访问比较耗费时间. 数组 ArrayList 访问数据十分简单 ...

  5. 邓公数据结构C++语言版学习笔记1

    1. 对于计算幂2n2^n2n的算法优化 暴力算法时间复杂度O(n)O(n)O(n) __int64 power2BF_I(int n) //幂函数2^n算法(蛮力迭代版),n >= 0{ __ ...

  6. 【swjtu】数据结构实验4_基于改进KMP算法的子串查找与替换

    实验内容及要求: 从键盘输入主串s以及子串t1和t2.编写程序,将主串s中所有t1子串替换为t2子串,输出替换后得到的串以及t1被替换的次数.要求子串查找采用改进KMP算法. 实验目的:掌握KMP算法 ...

  7. 邓公数据结构C++语言版学习笔记——二叉树

    二叉树的遍历 一. preorder--先序遍历VLR 1. 递归先序遍历 2. 迭代先序遍历 3.先序遍历图解 二. inorder--先序遍历LVR 1. 递归中序遍历 2.迭代中序遍历 3.迭代 ...

  8. 数据结构实验之串一:KMP简单应用

    Description 给定两个字符串string1和string2,判断string2是否为string1的子串. Input 输入包含多组数据,每组测试数据包含两行,第一行代表string1(长度 ...

  9. 数据结构实验之串三:KMP应用

    Description 有n个小朋友,每个小朋友手里有一些糖块,现在这些小朋友排成一排,编号是由1到n.现在给出m个数,能不能唯一的确定一对值l和r(l <= r),使得这m个数刚好是第l个小朋 ...

最新文章

  1. C语言入门练习 - 第一期 变量、输入输出、表达式与顺序语句(题解)
  2. 第九周项目二-我的数组类
  3. 海量数据处理——位图法bitmap
  4. 【Linux系列】【基础版】第二章 文件、目录管理
  5. JavaScript 技术篇-JSON字符串在线快速格式化查看实例演示,json.cn网址格式化json字符串
  6. php+spl+栈,PHP SPL标准库之数据结构栈(SplStack)介绍
  7. gitlab一键安装 笔记
  8. web安全day4--DHCP部署与安全
  9. pid调节软件_三面大疆惨败,因为不懂PID的积分抗饱和
  10. 时域离散信号/系统(matlab)
  11. 想回味Windows95?模拟器+浏览器搞定
  12. Word批量生成软件
  13. 7.15周三晚8点,dotnet课堂全新起航,张善友/陈计节/刘腾飞我们一起来聊聊abp的故事...
  14. GE、西门子、PTC、SAP、阿里、腾讯、海尔..工业互联网的『未来战争』
  15. 夜,依旧寂寞心照不宣!
  16. 多家银行手机转账现高危漏洞 ,用户资金或被非法窃取
  17. 分享几张大佬程序员常用壁纸,你值得拥有!
  18. 【网络协议趣谈】网络分层的含义
  19. swaks邮件伪造获取键盘记录
  20. 《你一定爱读的极简欧洲史》

热门文章

  1. tensorflow 入门笔记(二)
  2. java摄像头推流,流媒体服务 javaCV-2 推流
  3. linux蜂鸣器控制实验,【Linux公开课】蜂鸣器使用、LCD背光控制、触摸屏校准、GPIO操作...
  4. Cognos8.3 + oracle9i数据集市 建cube性能调整
  5. 不同域名指向静态图片文件
  6. 桥接模式(Birdge)
  7. 如何快速入手 Shell 脚本编程
  8. 那些在错误道路上一路狂奔的国产VR
  9. 问题007:JDK版本与JRE版本不同导致java.exe执行类文件错误 java.lang.UnsupportedClassVersionError错误...
  10. JDK6和JDK7中的substring()方法