串(数据结构)

前言:

串就是我们所说的字符串,计算机对数据的处理主要包括数值计算和非数值计算,在计算机发展的早期,数值计算比较普遍,然而随着社会的发展和计算需求的发展,在计算机上做非数值处理的计算越来越多,而在计算机上非数值处理大部分是对字符串的处理,处理这些字符串数据比起处理整型、浮点型数据都要复杂很多,所以把串作为一种独立的数据结构进行研究。


一.串导学

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]。
原创文章 46 获赞 56 访问量 3866

猜你喜欢

转载自blog.csdn.net/qq_46009744/article/details/105927651