此文章仅作为自己学习过程中的记录和总结,同时会有意地去用英文来做笔记,一些术语的英译不太准确,内容如有错漏也请多指教,谢谢!


一、串(String)的定义:

  • 串(String):由零个或多个字符组成的有限序列,又名叫字符串。

一般记作 s=“a1a2…an” (n>=0),其中s是串的名称,用双引号(有些书也用单引号)括起来的字符序列是串的值,注意引号不属于串的内容。ai (1<=i<=n)可以是字母、数字或其它字符。 i 就是该字符在串中的位置。串中的字符数目 n 称为串的长度。

这里解释几个概念:

  • 空串(null string):零个字符的串,它的长度为零,可以用两引号""表示,也可以用希腊字母(空集)表示。
  • 空格串:指只包含空格的串,是有内容有长度的,而且可以不只有一个空格。
  • 子串与主串:串中任意个数的连续字符组成的子序列称为该串的子串,相应地,包含子串的串称为主串。子串在主串中的位置就是子串的第一个字符在主串中的序号。
    (eg: “lover”&“over”, “friend”&“end”, “believe”&“lie”)

二、串的比较:

串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号。

因此,要在C语言中比较两个串是否相等,必须是它们串的长度以及它们各个对应位置的字符都相等时,才算是相等。即给定 s1=“a1a2…an”,s2=“b1b2…bn”,当且仅当n=m,且a1=b1, a2=b2, …, an=bn时,才认为s1=s2

对于两个不相等的串(s1=“a1a2…an”, s2=“b1b2…bm”, n !=m),当满足以下条件之一时,认为s1<s2

  1. n<m,且ai=bi (i=1,2,…,n)。(eg:“hap”&“happy”)
  2. 存在某个k<=min(m,n),使得ai=bi (i=1,2,…,k-1), ak<bk(eg:“happen”&“happy”)

(应用:电子词典中查找单词实现的原理,其实就是字符串这种数据结构的典型应用。)


关于上述“对应字符集中的序号”的相关知识:

计算机中的常用字符是使用标准的ASCII编码,由7位二进制数来表示一个字符,总共可以表示128个字符。后来发现一些特殊符号,128个不够用,于是扩展ASCII码由8位二进制数表示一个字符,总共可以表示256个字符。
扩展后的ASCII码已经足够满足以英语为主的语言和特殊符号进行输入、存储、输出等操作的字符需要了。但对于其它语言和文字而言,如中文,256个字符显然是不够的。于是后来就有了Unicode编码,比较常用的是由16位二进制数表示一个字符,这样就总共可以表示216个字符(约为6.5万多个)。
(为了和ASCII码兼容,Unicode编码的前256个字符与ASCII码完全相同。)

ASCII码表:
Unicode码表:


三、串的抽象数据类型:

串的逻辑结构和线性表很相似,不同之处在于串针对的是字符集,也就是串中的元素都是字符。
因此,对于串的基本操作与线性表是有很大差别的。线性表更关注的是单个元素的操作,比如查找、插入或删除一个元素;而串中更多的是查找子串位置、得到指定位置的子串、替换子串等操作。

Data: 串中元素仅由一个字符组成,相邻元素具有前驱和后驱的关系。

[ADT of strings:]Operations:StrAssign(T, *chars): 生成一个其值等于字符串常量chars的串T。StrCopy(T, S): 串S存在,由串S复制得到串T。ClearString(S): 若串存在,将串清空。StringEmpty(S): 若串S为空,返回true,否则返回false。StrLength(S): 返回串S的元素个数,即串的长度。StrCompare(S, T): 若S>T,返回值>0;若S==T,返回值=0;若S<T,返回值<0。Concat(T, S1, S2): 用T返回由S1和S2连接而成的新串。SubString(Sub, S, pos, len): 串S存在,1<=pos<=StrLength(S),且0<=len<=StrLength(S)+1-pos。用Sub返回串S的第pos个字符起长度为len的子串。Index(S, T, pos): 串S和T存在,T是非空串,1<=pos<=StrLength(S)。若主串S中存在和串T值相同的子串,则返回它在主串S中第pos个字符之后第一次出现的位置,否则返回0。Replace(S, T, V): 串S,T和V存在,T是非空串。用V替换主串S中出现的所有与T值相等的不重叠的子串。StrInsert(S, pos, T): 串S和T存在,1<=pos<=StrLength(S)+1。在串S的第pos个字符之前插入串T。StrDelete(S, pos, len): 串S存在,1<=pos<=StrLength(S)+1-len。从串S中删除第pos个字符起长度为len的子串。endADT

此处来看一下Index操作的具体实现:

首先,为了示意清楚以及方便,先声明定义:

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int SElemType; //SElemType的类型根据实际需求而定,此处假设为int。
typedef int Status;/*"Status" is defined as a type of a function, with its returned value representing the result of the function.(Status是函数的类型,它的返回值势函数结果状态代码,0或1.)*/

接下来是Index(S, T, pos)的实现代码:

[Achieve the operation of "Index".]
/*T为非空串。若主串S中第pos个字符之后存在与T相等的子串,则返回第一个这样的子串在S中的位置,否则返回0。*/
int Index(String S, String T, int pos)
{int m, n, i;String sub;if (pos > 0){n = StrLength(S);m = StrLength(T);i = pos;while (i <= n+1-m){SubString(sub, S, i, m); //取主串第i个位置,长度与T相等的子串给sub比较。if (StrCompare(sub, T) != 0){i++;}else{return i;}}}return 0;
}

四、串的顺序存储结构:

串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列的。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区。一般是用定长数组来定义。

既然是定长数组,就存在一个预定义的最大串长度。此处有几种获得串长度值的方法

  1. 一般可以将实际的串长度值保存在该定长数组的0下标位置。
  2. 有的书中也会定义存储在数组的最后一个下标位置。
  3. 有些编程语言又规定在串值后面加一个不计入串长度的结束标记字符,如’\0’来表示串值的终结。此时若想知道串的长度,则需要遍历计算了。

不足: 由于是用定长数组来定义串变量,串的顺序存储方式其实是容易出问题的。由于字符串的操作,比如两串的连接Concat,新串的插入StrInsert,以及字符串的替换Replace,都有可能使得串的长度超过了数组的长度MAXSIZE。

针对这个不足,对于串的顺序存储就有了一些变化。串值的存储空间可在程序执行过程中动态分配而得。比如在计算机中存在一个自由存储区,叫做 “堆”。这个堆可以由C语言的动态分配函数 malloc() 和 free() 来管理。


五、串的链式存储结构:

对于串的链式存储结构,与线性表是相似的。但是由于串结构的特殊性(结构中的每个元素数据是一个字符),如果也简单的应用链表存储串值,即一个结点对应一个字符,就会造成很大的空间浪费。因此,一个结点可以存放一个字符,也可以考虑存放多个字符。最后一个结点若是未被占满时,可以用"#"或其他非串值字符补全,如图所示。

当然,此处一个结点存放多少个字符才合适就变得更重要了。这会直接影响着串处理的效率,需要根据实际情况做出选择。

但是串的链式存储结构除了在连接串与串的操作上有一定方便,总的来说还是不如顺序存储结构灵活,性能也不如顺序存储结构,因此我们也不过多讨论了。


(注:“六、BF算法(朴素的模式匹配算法)” 及 “七、KMP模式匹配算法” 部分内容引用自用户@Sirim233333的帖子(算法)通俗易懂的字符串匹配KMP算法及求next值算法,只做笔记用,若有不妥立刻删除,感谢!)


六、BF算法(朴素的模式匹配算法):

  • BF算法:即暴力(Brute Force)算法,是普通的模式匹配算法,是一种蛮力算法。

BF算法的思想:
将主串S的第一个字符与模式串T的第一个字符进行匹配。若相等,则继续比较主串S的第二个字符和模式串T的第二个字符;若不相等,则比较主串S的第二个字符和模式串T的第一个字符,依次比较下去,直到得出最后的匹配结果。
即:首先S[1]和T[1]比较,若相等,则再比较S[2]和T[2],一直到T[M]为止;若S[1]和T[1]不等,则S向右移动一个字符的位置,再依次进行比较。已知n为主串长度,m为模式串长度。如果存在k (1≤k≤n)且S[k+1…k+m]=T[1…m],则匹配成功;否则失败。

若令n为主串长度,m为模式串长度,则该算法最坏情况下要进行 m * (n-m+1) 次比较,时间复杂度为 O(m*n)



在串的操作中,有BF算法的很典型的体现:对子串的定位操作

对子串的定位操作通常称作 “串的模式匹配”,是串操作中最重要的操作之一。
思路简单来说就是:对主串的每一个字符作为子串的开头,与模式串进行匹配。对主串做大循环,每个字符开头做模式串T的长度的小循环,直到匹配成功或者全部遍历完成为止。

上面已通过使用串的其他操作来实现模式匹配的算法Index,现在只用基本的数组来实现同样的算法。
假设主串S和模式串T的长度存在S[0]和T[0]中。 实现代码如下:

[Achieve the operation of "Index" with BF algorithm.]
/*返回模式串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。*/
/*串T非空,1<=pos<=StrLength(S)。*/
int Index(String S, String T, int pos)
{int i, j;i = pos; //i表示主串S中当前位置下标。若pos不为1,则从pos位置开始匹配。j = 1;   //j表示模式串T中当前位置的下标值。初始化为1,即从头开始。while (i <= S[0] && j <= T[0]) //若i小于主串S长度,且j小于串T的长度时循环。{if (S[i] == T[i]){i++;j++;}else //指针后退,重新开始匹配。{i = i - j + 2; //i退回到上次匹配首位的下一位。j = 1; //j退回到串T的首位。}}if (j > T[0]) //j大于串T长度代表匹配成功。{return i - T[0]; //此时i处于串T末位的下一位,因此要减去一个串T长度。}else{return 0;}
}
T(n)=O(m*n)

总的来说,BF算法,即朴素模式匹配算法,正如其名Brutal Force,是一种很低效的蛮力算法。很多年前,科学家们认为这种有多个0和1重复字符的字符串,模式匹配需要挨个遍历的算法是非常糟糕的。

于是有三位前辈,D.E.Knuth, J.H.Morris, V.R.Pratt 发表了一个模式匹配算法,可以大大避免重复遍历的情况,我们称之为Knuth-Morris-Pratt算法,简称KMP算法


七、KMP模式匹配算法:

由前面所说,朴素算法思路简单暴力,但两个串都需要依次重复遍历,算法的时间复杂度为O(n*m),效率很低。科学家们为了提高效率,发表了一种可以避免重复遍历的算法,我们称之为KMP算法。

一般地,在匹配中,我们是不知道主串的内容的,而模式串是我们自己定义的。
朴素算法中,模式串P的第 j 位失配,我们处理的方法是默认的把串P后移一位。
但我们知道,在前一轮的比较中,串P的前 (j-1) 位与主串S中间对应的某 (j-1) 个元素已经匹配成功了。这就意味着,在一轮的匹配中,我们知道了主串S的部分内容。因此,我们可以利用这些内容,让P多移几位,减少遍历的次数,而非无脑暴力地依次重复遍历。

朴素算法与kmp算法的比较:

  • 朴素算法: 每次失配,S串的索引i定位的本次尝试匹配的第一个字符的后一个。P串的索引j定位到1;T(n)=O(n*m)。
  • KMP算法: 每次失配,S串的索引i不动,P串的索引j定位到某个数。T(n)=O(n+m),时间效率明显提高。

注意,此处的“定位到某个数”,就是接下来要引入的next值其数值实际上就是P往后移的位数。


此处举一个例子:

  • ① 例如,Pj处失配(图中绿色的是Pj),则我们可以确定P1…Pj-1是与Si…Si+j-2相匹配的位置,即它们是对应相等的。

  • ② 假设P1…Pj-1中,P1…Pk-1 (1<k<j)与Pj-k+1…Pj-1 是对应相等的。为了下面说的清楚,我们把这种关系叫做 “首尾重合”于是我们可以推出,P1…Pk-1 与Si…Si+j-2
    (我们可以发现,后面一段Pj-k+1…Pj-1 的开头位置是Pj-k+1,结束位置是Pj-1,由j-k+1=j-(k-1)可知,此段的长度与前面一段是相同的。这也是为何我们称之为“首尾重合“。)

  • ③ 接下来要做的就是把模式串右移。

  • ④ 为了表示下一轮匹配时 j定位的地方,我们将其定义为数组next[j],而next[j]的数值就是第j个元素前 j-1个元素首尾重合部分个数加1。(我们可以这样理解:j值的多少取决于当前字符之前的串的前后缀的相似度。)当然,为了能遍历完整,首尾重合部分的元素个数应取到最多,即next[j]应取尽量大的值。
    "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。以”ABCDABD”为例,进行介绍:
    ”A”的前缀和后缀都为空集,共有元素的长度为0;
    ”AB”的前缀为[A],后缀为[B],共有元素的长度为0;
    ”ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
    ”ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
    ”ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为”A”,长度为1;
    ”ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,长度为2;
    ”ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

  • ⑤ 最后,如果我们知道了一个字符串的next值,那么KMP算法也就很好懂了。相比朴素算法,当发生失配时,i不变,j=next[j]就好了。接下来就是怎么确定next值了。


八、KMP算法 – next数组的简单计算:

我们规定任何一个串,next[1]=0。(next[1]在kmp算法中不会用到)

此处举一个简单例子来演示推导next数组的过程:

总结之后,我们可以得到一个十分重要的公式


九、KMP算法 – 求next数组的算法:

void get_next(String T, int* next)
{int i, j;i = 1;j = 0;next[1] = 0;while (i < T[0]) //T[0]存放着模式串T的长度。{if (j == 0 || T[i] == T[j]) //T[i]表示后缀的单个字符,T[j]表示前缀的单个字符。{i++;j++;if (T[i] != T[j]) //若当前字符与前缀字符不同。{next[i] = j; //则当前的j为next在i位置的值。}else{next[i] = next[j]; //若与前缀字符相同,则将前缀字符的next值赋next在i位置的值。}}else{j = next[j]; //若字符不相同,则j回溯。}}
}
T(n)=O(n+m)

串--串的定义,顺序、链式存储结构,BF、KMP模式匹配算法(C语言描述)相关推荐

  1. Python 数据结构 之 串 的链式存储结构

    本文所采用的数据结构模板为 <数据结构教程>C语言版,李春葆.尹为民等著. 改篇所涉及到的是 串 的链式存储结构. 用Python仿照C语言来实现. 文章转载请注明:  Python 数据 ...

  2. 数据结构(二):线性表包括顺序存储结构(顺序表、顺序队列和顺序栈)和链式存储结构(链表、链队列和链栈)...

    还记得数据结构这个经典的分类图吧: 今天主要关注一下线性表. 什么是线性表 线性表的划分是从数据的逻辑结构上进行的.线性指的是在数据的逻辑结构上是线性的.即在数据元素的非空有限集中 (1) 存在唯一的 ...

  3. 【数据结构】 实验报告10 顺序、链式存储结构的二叉树递归遍历、层次遍历求高度

    一.实验目的和要求 (源码在最后) 要求: 两种及以上存储结构(建议 顺序存储结构和链式存储结构各一).两种及以上方法(建议 递归遍历和层次遍历方法各一).分析各代码性能. 抽象数据类型(二叉树)独立 ...

  4. 数据结构---线性表的静态/动态分配与顺序/链式存储

    线性表---基于严魏敏版数据结构c语言实现---谭浩强版c语言 数据元素在计算机中的存储分为顺序存储和链式存储 顺序存储:借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系 链式存储:借助指示元 ...

  5. 队列的定义、循环队列的顺序存储结构及链式存储结构

    文章目录 1 队列的定义 1.1 文字定义 1.2 代码定义 2 循环队列 2.1 循环队列的定义 2.2 循环队列的顺序存储结构 3 队列的链式存储结构 3.1 链队列的入队操作 3.2 链队列的出 ...

  6. 线性表的顺序、链式存储结构基本操作

    线性表的抽象数据类型 operation InitList(&L):初始化操作,建立一个空的线性表L ListEmpty(L):若线性表为空,返回true,否则返回false ClearLis ...

  7. 实验一 链式存储结构的基本操作

    广州大学学生实验报告 开课实验室:计算机科学与工程实验(电子楼418A) 2019年4月27日 学院 计算机科学与教育软件学院 年级.专业.班 计算机科学与技术172班 姓名 xxx 学号 17061 ...

  8. 数据结构和算法:(3)3.2线性表的链式存储结构

    线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素也就是说你这个可以放在A地点,这个可以放在E地点,A地点和E地点中间可以隔开一个C地点和D地点,这样是允许的),这组存储单元可以存在 ...

  9. 《数据结构》c语言版学习笔记——单链表结构(线性表的链式存储结构Part1)

    线性表的链式存储结构 数据结构系列文章 第二章 单链表结构 文章目录 线性表的链式存储结构 前言 一.单链表的建立 代码 二.单链表的读取 代码 三.单链表的插入 代码 四.单链表的删除 代码 五.单 ...

最新文章

  1. 「洛谷P1343」地震逃生 解题报告
  2. 【C++提高班】c++数组遍历比较相邻的数值
  3. zookeper安装_ZooKeeper安装和配置
  4. 除非Microsoft FTP 服务(FTPSVC)正在运行,否则无法启动FTP站点。服务目前已停止...
  5. 指针06 - 零基础入门学习C语言46
  6. 8本前沿技术书,助力这届「青年人」将科幻变成现实
  7. 修改数据库端口为51433
  8. turtle生成二叉树
  9. Java 初始化块
  10. python实现人脸检测及识别(1)---- 采集人脸数据
  11. 【Luogu1484】种树(贪心,堆)
  12. 【PTA-python】第4章-15 换硬币 (20 分)
  13. Vue实现状态的不同颜色
  14. 我做过的最好的$ 4.90
  15. cocos 躲避球游戏(2) --资源导入和碰撞检测
  16. Android O 自定义prop的问题小总结
  17. win在哪打开java_win7系统打开java控制面板的操作方法
  18. 思科交换机冗余星形设计,附带sw1核心交换机配置
  19. 【淘宝API开发系列】获得商品评论 API 返回值说明
  20. 上海交大商界领袖GDBA——泰兴访学之旅(信息化)

热门文章

  1. 部署Kubernetes集群(二进制 v1.18.8版)
  2. 【大学英语四六级】四六级阅读高频词汇
  3. git clone no matching host key type found. Their offer: ssh-rsa,ssh-dss... 报错
  4. [编程题] 困兽之斗
  5. 刷酸记录(迪维维A酸乳膏)20190905
  6. Linux内防火墙永久开启或者关闭操作命令
  7. VMware workstation Pro 15注册码
  8. Unity幸运转盘项目实战
  9. oracle utl_file权限,Oracle内建包UTL_FILE使用说明
  10. Unity+Android 打开安卓文件管理器,并拿到文件的真实路径