友情链接:数据结构专栏

目录

    • 【知识框架】
    • 一、串的定义
    • 二、串的存储结构
      • 1、定长顺序存储表示
      • 2、堆分配存储表示
      • 3、块链存储表示
    • 三、串的基本操作
    • 四、串的模式匹配(重点)
      • 1、简单的模式匹配算法
      • 2、KMP算法
        • (1)字符串的前缀、后缀和最大公共前后缀长度
        • (2)对算法的改进方法
        • (3)KMP算法的进一步优化
  • 附录
    • 上文链接
    • 下文链接
    • 专栏
    • 参考资料

【知识框架】

一、串的定义

串( string)是由零个或多个字符组成的有限序列,又名叫字符串。
一般记为:S=′a1a2...an′(n>=0)S='a_1a_2...a_n '(n>=0)S=′a1​a2​...an′​(n>=0)。
其中,SSS是串名,单引号括起来的字符序列是串的值; ana_nan​ 可以是字母、数字或其他字符;串中字符的个数nnn称为串的长度。
另外还有一些其它概念:

  • 空串:n=0n=0n=0时的串称为空串。
  • 空格串:是只包含空格的串。注意它与空串的区别,空格串是有内容有长度的,而且可以不止一个空格。
  • 子串与主串:串中任意个数的连续字符组成的子序列称为该串的子串,相应地,包含子串的串称为主串。
  • 子串在主串中的位置就是子串的第一个字符在主串中的序号。

串的逻辑结构和线性表极为相似,区别仅在于串的数据对象限定为字符集。在基本操作上,串和线性表有很大差别。线性表的基本操作主要以单个元素作为操作对象,如查找、插入或删除某个元素等;而串的基本操作通常以子串作为操作对象,如查找、插入或删除一个子串等。

二、串的存储结构

1、定长顺序存储表示

类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。在串的定长顺序存储结构中,为每个串变量分配一个固定长度的存储区,即定长数组。

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

串的实际长度只能小于等于MAXLEN,超过预定义长度的串值会被舍去,称为截断。串长有两种表示方法: 一是如上述定义描述的那样,用一个额外的变量len来存放串的长度;二是在串值后面加一一个不计入串长的结束标记字符“\0”,此时的串长为隐含值。
在一些串的操作(如插入、联接等)中,若串值序列的长度超过上界MAXLEN,约定用“截断”法处理,要克服这种弊端,只能不限定串长的最大长度,即采用动态分配的方式。

2、堆分配存储表示

堆分配存储表示仍然以一组地址连续的存储单元存放串值的字符序列,但它们的存储空间是在程序执行过程中动态分配得到的。

typedef struct{char *ch; //按串长分配存储区,ch指向串的基地址int length;  //串的长度
}HString;

在C语言中,存在一一个称之为“堆”的自由存储区,并用malloc()和free()函数来完成动则返回一个指向起始地址的指针,作为串的基地址,这个串由ch指针来指示;若分配失败,则返回NULL。已分配的空间可用free()释放掉。
上述两种存储表示通常为高级程序设计语言所采用。块链存储表示仅做简单介绍。

3、块链存储表示

类似于线性表的链式存储结构,也可采用链表方式存储串值。由于串的特殊性(每个元素只有一个字符),在具体实现时,每个结点既可以存放一个字符, 也可以存放多个字符。每个结点称为块,整个链表称为块链结构。图(a)是结点大小为4 (即每个结点存放4个字符)的链表,最后一个结点占不满时通常用“#”补上;图(b)是结点大小为1的链表。

三、串的基本操作

  • StrAssign(&T, chars): 赋值操作。把串T赋值为 chars
  • Strcopy(&T, S): 复制操作。由串S复制得到串T。
  • StrEmpty(S): 判空操作。若S为空串,则返回TRUE,否则返回 FALSE
  • StrCompare(S,T): 比较操作。若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0。
  • StrEngth(S): 求串长。返回串S的元素个数
  • Substring(&Sub,S,pos,1en):求子串。用Sub返回串S的第pos个字符起长度为len的子串。
  • Concat(&T,S1,S2): 串联接。用T返回由S1和S2联接而成的新串。
  • Index(S,T): 定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则函数值为0
  • Clearstring(&S): 清空操作。将S清为空串
  • Destroystring(&S): 销毁串。将串S销毁
    不同的高级语言对串的基本操作集可以有不同的定义方法。在上述定义的操作中,串赋值StrAssign、串比较 StrCompare、求串长 Strength、串联接 Concat及求子串 Substring五种操作构成串类型的最小操作子集,即这些操作不可能利用其他串操作来实现;反之,其他串操作(除串清除 Clearstring和串销毁 Destroystring外)均可在该最小操作子集上实现。
    例如,可利用判等、求串长和求子串等操作实现定位函数 Index(S,T)。
int Index(Sring S, String T){int i = 1, n = StrLength(S), m = StrLength(T);String sub;while(i <= n-m+1){SubString(sub, S, i, m); //取主串第i个位置,长度为m的串给subif(StrCompare(sub, T) != 0){++i;}else{return i;  //返回子串在主串中的位置}}return 0;    //S中不存在与T相等的子串
}

四、串的模式匹配(重点)

1、简单的模式匹配算法

子串的定位操作通常称为串的模式匹配,它求的是子串(常称模式串)在主串中的位置。这里采用定长顺序存储结构,给出一种不依赖于其他串操作的暴力匹配算法。

int Index(SString S, SString T){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;}
}

下图展示了模式串T=′abcac′T = 'abcac'T=′abcac′和主串SSS的匹配过程

简单的模式匹配算法的最坏时间复杂度为O(nm)O(nm)O(nm),其中nnn和mmm分别为主串和模式串的长度。

2、KMP算法

在上面的简单匹配中,每趟匹配失败都是模式后移一位再从头开始比较。而某趟已匹配相等的字符序列是模式的某个前缀,这种频繁的重复比较相当于模式串在不断地进行自我比较,这就是其低效率的根源。
因此,可以从分析模式本身的结构着手,如果已匹配相等的前缀序列中有某个后缀正好是模式的前缀,那么就可以将模式向后滑动到与这些相等字符对齐的位置,主串i指针无须回溯,并继续从该位置开始进行比较。而模式向后滑动位数的计算仅与模式本身的结构有关,与主串无关。

KMP算法的特点就是:仅仅后移模式串,比较指针不回溯。很是牛掰。

(1)字符串的前缀、后缀和最大公共前后缀长度

要了解子串的结构,首先要弄清楚几个概念:前缀后缀部分匹配值。前缀指除最后一个字符以外,字符串的所有头部子串;后缀指除第一个字符外,字符串的所有尾部子串;部分匹配值则为字符串的前缀和后缀的最大公共前后缀长度。下面以′ababa′' ababa'′ababa′为例进行说明

  • ′a′'a'′a′ 的前缀和后缀都为空集最大公共前后缀长度长度为0。
  • ′ab′'ab'′ab′ 的前缀为{a}\{a\}{a} ,后缀为{b},{a}∩{b}=NULL\{b\}, \{a\}∩\{b\}=NULL{b},{a}∩{b}=NULL,最大公共前后缀长度长度为0。
  • ′aba′'aba'′aba′ 的前缀为{a,ab}\{a,ab\}{a,ab}, 后缀为{a,ba},{a,ab}∩{a,ba}={a}\{a,ba\}, \{a,ab\}∩\{a,ba\} =\{a\}{a,ba},{a,ab}∩{a,ba}={a}, 最大公共前后缀长度长度为1
  • ′abab′'abab'′abab′ ,前缀∩后缀,{a,ab,aba}∩{b,ab,bab}={ab}\{a, ab, aba\} ∩\{b, ab ,bab\}=\{ab\}{a,ab,aba}∩{b,ab,bab}={ab},最大公共前后缀长度长度为2。
  • ′ababa′'ababa'′ababa′ ,前缀∩后缀, {a,ab,aba,abab}∩{a,ba,aba,baba}={a,aba}\{a, ab, aba, abab\} ∩ \{ a, ba, aba, baba\}=\{ a, aba\}{a,ab,aba,abab}∩{a,ba,aba,baba}={a,aba}, 公共元素有两个,最大公共前后缀长度长度为3。

故字符串′ababa′'ababa'′ababa′的最大公共前后缀长度为00123。
这个值有什么作用呢?
回到最初的问题,主串为 ′abacabcacbab′'abacabcacbab'′abacabcacbab′,子串为 ′abcac′'abcac'′abcac′。
利用上述方法容易写出子串′abcac′'abcac'′abcac′的最大公共前后缀长度为00010,将最大公共前后缀长度值写成数组形式,就得到了最大公共前后缀长度(Partial match,PM)的表。

下面用PM表来进行字符串匹配:

第一趟匹配过程:
发现ccc与aaa不匹配,前面的2个字符′ab′'ab'′ab′是匹配的,查表可知,最后一个匹配字符bbb对应的部分匹配值为0,因此按照下面的公式算出子串需要向后移动的位数:
移动位数=已匹配的字符数−对应最大公共前后缀长度移动位数=已匹配的字符数 - 对应最大公共前后缀长度移动位数=已匹配的字符数−对应最大公共前后缀长度
因为2−0=22-0=22−0=2,所以将子串向后移动2位,如下进行第二趟匹配:

第二趟匹配过程:
发现ccc与bbb不匹配,前面4个字符′abca′'abca'′abca′是匹配的,最后一个匹配字符a对应的部分匹配值为1,4−1=34-1=34−1=3,将子串向后移动3位,如下进行第三趟匹配:

第三趟匹配过程:
子串全部比较完成,匹配成功。整个匹配过程中,主串始终没有回退,故KMP算法可以在O(n+m)O(n + m)O(n+m)的时间数量级上完成串的模式匹配操作,大大提高了匹配效率。

(2)对算法的改进方法

使用部分匹配值时,每当匹配失败,就去找它前一个元素的部分匹配值,这样使用起来有些不方便,所以将PM表右移一位,这样哪个元素匹配失败,直接看它自己的部分匹配值即可。将上例中字符串′abac′' abac'′abac′的PM表右移一位,就得到了next数组:

有时为了使公式更加简洁、计算简单,将next数组整体+1。因此,next数组就变成:

最终得到子串指针变化公式j=next[j]j=next[ j ]j=next[j]。
next[ j ]的含义是:在子串的第j个字符与主串发生失配时,则跳到子串的next[ j ]位置重新与主串当前位置进行比较。

通过分析,可以知道,除第一个字符外,模式串中其余的字符对应的next数组的值等于其最大公共前后缀长度加上1

next[j]=最大公共前后缀长度+1next[ j ] = 最大公共前后缀长度 + 1next[j]=最大公共前后缀长度+1

科学的推导得出以下公式:

通过科学推论,我们可以写出求next数组的程序如下:

void get_next(String T, int *next){int i = 1, j = 0;next[1] = 0;while (i < T.length){if(j==0 || T.ch[i]==T.ch[j]){ //ch[i]表示后缀的单个字符,ch[j]表示前缀的单个字符++i; ++j;next[i] = j;    //若pi = pj, 则next[j+1] = next[j] + 1}else{j = next[j];  //否则令j = next[j],j值回溯,循环继续}}
}

与next数组的求解相比,KMP算法就简单许多,和简单模式匹配算法很相似:

int Index_KMP(String S, String T){int i=1, j=1;int next[255];  //定义next数组get_next(T, next);    //得到next数组while(i<=S.length && j<=T.length){if(j==0 || S.ch[i] == T.ch[j]){ //字符相等则继续++i; ++j;}else{j = next[j];   //模式串向右移动,i不变}}if(j>T.length){return i-T.length;  //匹配成功}else{return 0;}
}

(3)KMP算法的进一步优化

前面定义的next数组在某些情况下尚有缺陷,还可以进-步优化。如图4.7 所示,模式′aaaab′' aaaab '′aaaab′在和主串′aaabaaaaab′' aaabaaaaab '′aaabaaaaab′进行匹配时:

显然后面3次用一个和p4相同的字符跟S4比较毫无意义,必然失配。
比较毫无意义。那么如果出现了这种类型的应该如何处理呢?
如果出现了,则需要再次递归,将next[j]next[j]next[j]修正为next[next[j]]next [next[j]]next[next[j]],直至两者不相等为止,更新后的数组命名为nextval。计算next数组修正值的算法如下,此时匹配算法不变。

void get_nextval(String T, int *nextval){int i = 1, j = 0;nextval[1] = 0;while (i < T.length){if(j==0 || T.ch[i]==T.ch[j]){    //ch[i]表示后缀的单个字符,ch[j]表示前缀的单个字符++i; ++j;if(T.ch[i] != T.ch[j]){ //若当前字符与前缀字符不同nextval[i] = j;  //则当前的j为nextval在i位置的值}else{//如果与前缀字符相同//则将前缀字符的nextval值给nextval在i位置上的值nextval[i] = nextval[j];}}else{j = nextval[j];  //否则令j = next[j],j值回溯,循环继续}}
}

总结改进过的KMP算法,它是在计算出next值的同时,如果a位字符与它next值指向的b位字符相等,则该a位的nextval就指向b位的nextval值,如果不等,则该a位的nextval值就是它自己a位的next的值。
这块逻辑很简单,有了next数组,我们很容易就能推导出它的nextval数组了。


附录

上文链接

数据结构:栈和队列

下文链接

数据结构:树

专栏

数据结构专栏

参考资料

1、严蔚敏、吴伟民:《数据结构(C语言版)》
2、程杰:《大话数据结构》
3、王道论坛:《数据结构考研复习指导》
4、托马斯·科尔曼等人:《算法导论》

数据结构:串(String)【详解】相关推荐

  1. 数据结构与算法详解目录

    数据结构与算法详解是一本以实例和实践为主的图书,主要是经典的数据结构与常见算法案例,来自历年考研.软考等考题,有算法思路和完整的代码,最后提供了C语言调试技术的方法. 后续配套微课视频. 第0章  基 ...

  2. 数据结构--图(Graph)详解(二)

    数据结构–图(Graph)详解(二) 文章目录 数据结构--图(Graph)详解(二) 一.图的存储结构 1.图的顺序存储法 2.图的邻接表存储法 3.图的十字链表存储法 4.图的邻接多重表存储法 二 ...

  3. java语言链栈_Java语言实现数据结构栈代码详解

    近来复习数据结构,自己动手实现了栈.栈是一种限制插入和删除只能在一个位置上的表.最基本的操作是进栈和出栈,因此,又被叫作"先进后出"表. 首先了解下栈的概念: 栈是限定仅在表头进行 ...

  4. 用数据结构c语言写成绩排序,C语言数据结构 快速排序实例详解

    C语言数据结构 快速排序实例详解 一.快速排序简介 快速排序采用分治的思想,第一趟先将一串数字分为两部分,第一部分的数值都比第二部分要小,然后按照这种方法,依次对两边的数据进行排序. 二.代码实现 # ...

  5. String详解(intern、StringBuffer、StringBuilder)

    "+"操作符,它执行的加入对原始类型(如int和double),重载对String对象进行操作.'+'两个字符串操作数进行串联. java不考虑让开发者支持运算符重载.在支持运算符 ...

  6. C++中string详解与用法

    string详解 string不是基本数据类型,是C++中对char封装成的类,所以C语言中没有string. String类是不可变的,对String类的任何改变,都是返回一个新的String类对象 ...

  7. C++ string详解

    C++ string详解 -------------------------------------------------------------------------------- 来源:CSD ...

  8. 数据结构--图(Graph)详解(四)

    数据结构–图(Graph)详解(四) 文章目录 数据结构--图(Graph)详解(四) 一.图中几个NB的算法 1.普里姆算法(Prim算法)求最小生成树 2.克鲁斯卡尔算法(Kruskal算法)求最 ...

  9. 数据结构--图(Graph)详解(三)

    数据结构–图(Graph)详解(三) 文章目录 数据结构--图(Graph)详解(三) 一.深度优先生成树和广度优先生成树 1.铺垫 2.非连通图的生成森林 3.深度优先生成森林 4.广度优先生成森林 ...

  10. 数据结构--图(Graph)详解(一)

    数据结构–图(Graph)详解(一) 文章目录 数据结构--图(Graph)详解(一) 一.图的基本概念 1.图的分类 2.弧头和弧尾 3.入度和出度 4.(V1,V2) 和 < V1,V2 & ...

最新文章

  1. 前5月全国快递业务量累计完成396.5亿件 同比增50.1%
  2. 5-5图层的链接-新版本不常用
  3. c语言文件.ppt,c语言 文件.ppt
  4. Vue中Three.js的天空盒
  5. turbo c语言教程,C语言基础教程(六)Turbo C 程序设计初步(1)
  6. 在Emacs下用C/C++编程(转载)
  7. Qt 环境下MAPX组件的编程
  8. php md5校验工具下载,md5校验工具下载_md5校验工具下载「最新|免费」-太平洋下载中心...
  9. 如何在php中添加动图,PS如何制作gif动图
  10. 金融管理系统测试报告(内部测试案例)
  11. 安卓 sdk 离线包_百度离线ocr识别开发sdk包
  12. H264编码器6( H.264整数DCT公式推导及蝶形算法分析)
  13. 制作dns服务器,宽带提速方法 制作dns服务器提速宽带 -电脑资料
  14. django的admin速度慢优化:date_hierarchy、list_filter
  15. After Effect(AE)PuppetTools脚本插件使用
  16. 微信小程序引爆朋友圈的秘密
  17. flex布局实现div内容垂直居中
  18. 贝叶斯概率问题(美团笔试题)
  19. TansorFlow和Keras的入门教程
  20. mac上expo start后的lan地址失效,始终是127.0.0.1

热门文章

  1. python储物柜难题_转角那1㎡不做储物间?太浪费了!好好利用还解决收纳难题...
  2. 关于柔性显示器工作原理及发展前景的探究
  3. ffmpeg实现硬件转码(使用FFmpeg调用NVIDIA GPU实现H265转码H264)
  4. Linux第7章Gdk及Cairo基础,GNOME 平台的2D图形编程(GTK,GDK,Cairo...) 简介 [转]...
  5. shell script简介
  6. 你真的打算凑合过完这一生吗(转)
  7. 程序猿生存指南-7 相亲之路(下)
  8. 学生用台灯什么光对眼睛好?开学季精选真正适合孩子的护眼台灯
  9. 快速应对面试--分门别类--6.链表
  10. think.php教程,Thinkphp_thinkphp教程_自学php网