前言:
串就是我们所说的字符串,计算机对数据的处理主要包括数值计算和非数值计算,在计算机发展的早期,数值计算比较普遍,然而随着社会的发展和计算需求的发展,在计算机上做非数值处理的计算越来越多,而在计算机上非数值处理大部分是对字符串的处理,处理这些字符串数据比起处理整型、浮点型数据都要复杂很多,所以把串作为一种独立的数据结构进行研究。
一.串导学
1.课程结构
(概述)
第1章 数据结构、ADT、算法
(基础)
第2章 线性表
第3章 栈和队列
第4章 字符串
第5章 数组与广义表
第6章 树和二叉树
第7章 图
(应用)
第9章 查找
第10章 内部排序
第11章 外部排序
2.知识点搜索
字符串还是一种线性结构,它的存储也有顺序和链式两种结构实现,它的操作和线性表的c操作也是相同的,但是它的这些操作是针对字符串所特有的一些操作,所以我们只是简单的讨论一下串特有的逻辑结构以及操作的实现。
3.串也是一种数据结构
在c/c++中我们也了解过字符串了,但是在那里更像是一种数据类型,而在这我们是当做数据结构。
串是一种特殊的线性表,特殊在:
(1)数据元素都来自字符集!
也就是说串中每一个元素的数据类型都是字符型的,他的数据对象是字符集。
(2)由于数据元素特殊,它的操作有些不同于一般线性表。
例如:操作对象一般是子串(即一组数据元素),而不是单个数据元素
4.串的应用
串的应用是十分广泛的
比如:搜索引擎中的字符串查找匹配、文字处理软件wps office、生物信息处理等等,这些实际上都是对字符串进行处理。
5.带着问题去学习
数据结构中的“串” 和C/C++中的“串” 有何区别?
串与线性表的关系?
“串” 这种数据结构的作用?
掌握:
1.串的特点(与线性表的区别)
2.模式匹配算法(BF,KMP)
二.串的基本概念
1.串的定义
由零个或多个字符组成的有限序列
还要了解几个定义:
(1)空串:含零个字符的串称为空串,用Ф表示。
空串是可以存在的,一定要区分开空串和空格串,空格串是指一个字符串是空格的。
(2)串长:串中所含字符的个数称为该串的长度(n)
(2)串的表示:S=“a1a2…an” ,每个ai(1≤i≤n)代表一个字符。
例子:
2.串的操作
操作:(与线性表同的略。因为字符串是一种特殊的线性结构,所以也有与线性表相同的操作,在此省略)
(1)串相等:当且仅当两个串的长度相等并且各个对应位置上的字符都相同,称两个串相等。
例如,“abc”与“abc”相等,“abc”和“acb”不相等,“abc”和“a bc”不相等
(2)子串:一个串中任意个连续字符组成的子序列(含空串)称为该串的子串。
例如,“a”、“ab”、“abc”和“abcd”等都是“abcde”的子串
(3)真子串:是指不包含自身的所有子串。
(注意是 所有 并且不包含自身)
例:“abcde”有多少个真子串?
15个,(注意空串也算是一个子串)
推广:n(n+1)/2个(利用数学上的等差数列求和即可得出)
(4)空串也是一个子串
3.串的基本操作
串的很多操作都是在子串的基础上进行操作的
(1)串插入操作
StrInsert(S,pos,T):在串S的位置为pos的字符之前插入串T
示例: S=“Beig”,
T = “jin”
则执行StrInsert(S,4,T)后
S=“Beijing”,
(2)串删除操作
StrDelete(S,pos,len):从串S中删除位置为pos的字符起长度为len的子串
示例: S=“Beijing”,
则执行StrDelete(S,4,3)后
S=“Beig”,
(3)串链接、求子串、串替换等
4.串与线性表的关系
(1)相同点:
数据元素都来自字符集
操作对象是子串
基本操作:模式匹配
(2)区别:
一般线性表
逻辑结构:一对一
存储结构:顺序表、链表
运算规则:随机、顺序存取
操作对象:单个元素
串
逻辑结构:一对一
存储结构:顺序串、链式串
运算规则:随机、顺序存取
(串相等、模式匹配)
操作对象:子串
三.模式匹配
(模式匹配是串的重要操作)
1.串的模式匹配
(1)定位:
(串的模式匹配又叫做定位,即在主串T中找是否存在子串P)
①设有主串T和子串P,
②在主串T中找到第一个与子串P相等的子串的位置(第一次出现的位置)
(这个位置当然是子串在主串中出现的第一个字符的位置,然后把这个位置返回)。
(2)目标串:主串 T
(3)模式串:子串 P
(在模式匹配中我们通常把目标串叫做主串,用T表示,同理,模式串为子串,用P表示)
(4)结果:
①模式匹配成功:在目标串T中找到一个模式串P;
②模式匹配不成功:目标串T中不存在模式串P。
示例:
目标 T : “Beijing”
模式 P : “jin”
匹配结果 = 3
(3呢,就是子串在主串中的位置,当然在这里是从下标为0开始计算的,也可以从1开始计算,那就是4了,是比较灵活的)
2.模式匹配的应用
• 自然语言处理:信息检索
• 生物信息处理:DNA匹配
• 语音识别:输入的语音符号化
• 网络安全:病毒入侵检测
(以上几块领域在计算机数据处理中应用很大,可见模式匹配的重要性)
3.模式匹配的作用
(以我们此次的疫情生物病毒举例)
(1)研究者将人的DNA和病毒DNA均表示成由一些字母组成的字符串序列。
(2)然后检测某种病毒DNA序列是否在患者的DNA序列中出现过,如果出现过,则此人感染了该病毒,否则没有感染。
(3)例如:
假设病毒的DNA序列为baa,
患者1的DNA序列为aaabbba,则感染,
患者2的DNA序列为babbba,则未感染。
(注意,人的DNA序列是线性的,而病毒的DNA序列是环状的)
四.BF算法
1.模式匹配算法
(模式匹配算法我们只学习BF算法和KMP算法,当然还有别的模式匹配算法)
(1)BF算法
①最简单、最易理解 (我们通常又把它叫做简单模式匹配算法)
②采用回溯法 (在学习c语言时其实用到过)
(2)KMP算法
(3)BM算法
(4)KR算法
(5)……
2.算法过程
(1)采用回溯法:
①从主串的第i个位置开始,与子串的每个字符逐个比较,若均相等,则找到,位置为i;
②否则,即在某个位置出现了不等,则说明从该起点开始的子串不是模式串,换一个新起点(第i+1个位置),重新开始,继续逐一比较,
③直到找到,或i>=Length(T)为止。
(2)例子:
(在这个例子中起始位置从0开始计算的)
用指针i指向主串T,指针j指向子串P
(3)特点:回溯
每趟的:
①主串回溯到上次起点的下一个位置;
②子串(模式串)回到0
3.算法执行
(1)Index(T,P,pos)
①将主串的第pos个字符和模式的第一个字符比较,若相等,继续逐个比较后续字符;若不等,从主串的下一字符起,重新与模式的第一个字符比较。
②直到主串的一个连续子串字符序列与模式相等 。返回值为T中与P匹配的子序列第一个字符的序号,即匹配成功。
③否则,匹配失败,返回值 0
(2)代码实现
(这个算法不难,注意我们在这是用的下标从1开始的位置)
int Index(Sstring T,Sstring P,int pos) //T代表主串,p代表子串,pos代表从主串的哪个位置开始找
{
i=pos; //i的初值是pos
j=1; //j的初值是1 ,这里是从下标为1开始计算的 ,前面的例子是从0开始计的
while(i<=T.len&&j<=P.len) //即主串没到头并且子串没到头就循环
{
if(T.data[i]==P.data[j]) //如果两个串开始有相等的
{
++i; //i++ 主串向后移
++j; //j++ 子串向后移
}
else //否则的话就是不等了 ,就要回溯
{
i=i-j+2; //i-j+2代表i要回溯的那个位置 ,主串回溯到上次起点的下一个位置,如果从0开始计的话,那么为i=i-j+1
j=1; //j回溯到1,如果从0开始计的话,j=0;
}
}
if(j>P.len) // 如果j往后走的过程中超过了P子串的长度,即匹配全部成功的话
return i-P.len; //代表i在这一次匹配过程中它最先出发的那个点 下标 位置
else
return 0;
}
(3)为什么主串回溯的位置是i-j+2?
4.算法复杂度分析
若n为主串长度,m为子串长度
(1)最好情况复杂度:O(m)
目标串的前 m 个字符正好等于模式串的 m 个字符。
例:若n为主串长度,m为子串长度
S=‘0001000001’,
T=‘0001’,
pos=1
(2)最坏情况复杂度:O(nm)
每次到最后一个字符才发现不匹配,这时再“倒回去”进行比较
比较次数:m(n-m)(n-m个各比较了m次)
例:若n为主串长度,m为子串长度
S=‘0000000001’,
T=‘0001’,
pos=1
(3)算法评价
算法简单,易于理解,但效率不高。
五.KMP算法
BF算法虽然简单好理解,但是效率却不高,接下来的是改进的模式匹配算法,叫做kMP算法。
1.来源
由以下三人同时发明的算法,因此给它起名字为KMP算法,
D.E.Knuth
J.H.Morris
V.R.Pratt
其中D.E.Knuth(唐纳德·克努特)被人们成为算法和程序设计的先驱者,他有一套经典的著作是计算机程序设计艺术,这套艺术被誉为算法中真正的圣经。
《计算机程序设计艺术 第1卷 基本算法》
《计算机程序设计艺术 第2卷 半数值算法》
《计算机程序设计艺术 第3卷 排序与查找》
他曾经说过:B-F算法中,匹配失败后不必完全从头再来(不必回溯),找到可以利用的信息,跳跃性匹配……
那么kmp算法就是他所说的改进的模式匹配算法。
2.发现BF算法的改进点
(1)BF算法:
当比较到某个位置不匹配时,不管什么情况,都进行回溯!回溯到该次比较的起始点的下一个位置,算法效率低。
(2)KMP算法改进:
在匹配失败时,主串中的指针i不需要回溯,而是在模式中找出适当的字符继续比较,以此提高效率
利用已经部分匹配这个有效信息,保持i指针不回溯,通过修改j指针,让模式串尽量地移动到有效的位置。
(已经部分匹配是指子串前面已经有几个字符匹配好了)
(3)有效:i不回溯,j要从哪里开始?
通过next函数找到j要移动的位置k
(通过一个自己编写的函数找到下一次要移动的位置)
3.KMP算法要解决的两个问题
(1)如何根据失败函数进行比较?
失败函数:匹配失败时,通过next函数求得当前匹配失败点的位置 j 所对应的移动位置 k
例:假设:失败函数已经建立
总结KMP算法:
模式 P 的第 j 位失配时:
①若 j > 0,下一趟比较时模式串 P的起始比较位置是P[next(j)],目标串 T 的指针不回溯
(j>0时,j=next(j); i不动)
②若 j = 0,则目标串 T指针进一,模式串P 指针回到 0
(j=0时,j=0; i++)
(2)next函数如何求得?
匹配不成功的那一刻,T[i]!=P[j],但: ‘P0P1…Pj-1’=‘Ti-j+1,Ti-j+2…Ti-1’
假设下一步与模式中第k(k<j)个字符比较,则模式中前k-1个字符必须满足: ‘P0P1…Pk-1’=‘Pi-k+1,Pi-k+2…Pi-1’
于是得出:
‘P0P1…Pk-1’=‘Pj-k+1,Pj-k+2…Pj-1
4.KMP算法的时间复杂度
(1)设主串T的长度为 n,模式串P长度为 m;
(2)求next数组的时间复杂度为 O(m);
(3)匹配中因主串T不回溯,比较次数可记为n,
(4)所以KMP算法总的时间复杂度为 O(n+m)。
5.模式匹配算法比较
(1)BF算法
①最简单、最易理解
②采用回溯法
(2)KMP算法
①效率较高,可提速到O(n+m)
②主串指针不回溯
6.next函数的改进
例子:
(1)提出问题:
next[j]=k 即 Ti 与 Pj 比较不相等时,继续与 pk 比较,但是若 pk=pj 显然肯定还要失败,所以 next[j] 可以改进。
(2)改进:
next[j] = k,如果Pj=Pk,则 主串中Ti和Pj不等,不需再和pk进行比较,而直接和pnext[k]进行比较。
(3)如何求nextnal:
比较P[j]和P[k],
若不等,则 nextval[j]=k(next[j]);
若相等,nextval[j]=nextval[k];
六.总结
1. 串数据结构的特点:
重点掌握:数据对象限定了字符集,操作对象是一组数据元素
2. 模式匹配算法:BF、KMP
重点掌握:熟悉next[j]函数的定义,会算next[j]和改进的nextval[j]。