• ​​总目录​​
  • ​​前言​​
  • ​​五、串​​
  • ​​5.1串的定义​​
  • ​​5.2串的比较​​
  • ​​5.3串的抽象数据类型​​
  • ​​5.4串的存储结构​​
  • ​​5.4.1串的顺序存储结构​​
  • ​​5.4.2串的链式存储结构​​
  • ​​5.5朴素的模式匹配算法​​
  • ​​实例​​
  • ​​5.6 KMP模式匹配算法​​
  • ​​什么是KMP?​​
  • ​​为什么要用KMP算法?​​
  • ​​KMP模式匹配算法实现​​
  • ​​5.7总结​​

总目录

​​一、数据结构概述​​二、算法概述
三、线性表
四、栈与队列
五、串
六、树
七、图
八、查找
九、排序

​数据结构与算法基础(青岛大学-王卓)​

前言

文章来源于大话数据结构

提升编程基础能力
数据结构、操作系统、计租、网络
陆续会慢慢更新!

资料获取

大话数据结构--串_子串

五、串

5.1串的定义

串string)是由零个或多个字符组成的有限序列,又名叫字符串。

一般记为s = “a1a2a3a4…an”(n>0),其中,s是串的名称,用双引号括起来的字符序列是串的值,单引号不属于串的内容

串中的字符数目n称为串的长度,零个字符的串称为空串,可以用“”表示也可以用空集表示

5.2串的比较

两个数字其实很好比较,但是字符串之间是如何进行比较的呢?

比如:“silly” 和“stupid”它俩谁大?

第一个字母都是s,我们认为大小不存在差异,而第二个字母,i比t更靠前,所以我们说:“silly” > “stupid”

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

计算机中常用字符是使用标准ASCII编码,更准确一点,由7位二进制数表示一个字符,共可以表示128个字符,后来不够用了扩展ASCII码由8位二进制数表示一个字符,共可以表示256个字符,已经足够满足以英语为主的语言和特殊符号进行输入、存储、输出等操作的字符需要了。

显然这256个字符是不够的,因此后来就有了Unicode 编码,比较常用的是由16位的二进制数表示-一个字符,这样总共就可以表示216个字符,约是65万多个字符,足够表示世界上所有语言的所有字符了。当然,为了和ASCII码兼容Unicode的前256个字符与ASCII码完全相同。

5.3串的抽象数据类型

对于串的基本操作与线性表是有很大差别的。线性表更关注的是单个元素的操作,比如查找一个元素,插入或删除一个元素,但串中更多的是查找子串位置、得到指定位置子串、替换子串等操作。

ADT 串(string)
Data
串中元素仅由一个字符组成,相邻元素具有前驱和后继关系。
Operation
StrAssign (T,*chars) :生成一个其值等于字符串常量chars的串T。
StrCopy(T,s):串S存在,由串S复制得串T。
ClearString(s) :串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) -pos+1,用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) -len+1。
从串S中删除第pos个字符起长度为len的子串。
endADT

对于不同的高级语言,其实对串的基本操作会有不同的定义方法

在C#中,字符串操作有ToLower转小写、ToUpper转大写、IndexOf从左查找子串位置

/*T为非空串。若主串S中第pos个字符之后存在与T相等的子串,*/
/*则返回第一个这样的子串在S中的位置,否则返回0 */
int a Index (String S, String T, int pos )
{
int n,m,i;
String sub;
if (pos > 0)
{
n=StrLength (s) ; /*得到主串S的长度*/
m = StrLength (T) ; /*得到子串T的长度*/
i = pos;
while (i <= n-m+1 )
{
SubString (sub,S,i,m) ;/*取主串第i个位置*/
/*长度与T相等子串给sub */
if (StrCompare (sub,T)!= 0) /★ 如果两串不相等*/
++i;
else /*如果两串相等*/
return i; /*则返回i值*/
}
}
return 0; /*若无子串与T相等,返回0 */
}

5.4串的存储结构

5.4.1串的顺序存储结构

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

既然是定长数组,就存在一个预定义的最大串长度,一般可以将实际的串长度值保存在数组的0下标位置,有的书中也会定义存储在数组的最后一个下标位置。但也有些编程语言不想这么干,觉得存个数字占个空间麻烦。它规定在串值后面加一个不计入串长度的结束标记字符,比如“\0” 来表示串值的终结,这个时候,你要想知道此时的串长度,就需要遍历计算一下才 知道了,其实这还是需要占用一个空间,何必呢。

大话数据结构--串_数据结构_02

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

5.4.2串的链式存储结构

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

大话数据结构--串_数据结构_03

当然,这里一个结点存多少个字符才合适就变得很重要,这会直接影响着串处理的效率,需要根据实际情况做出选择。串的链式存储结构除了在连接串与串操作时有一定方便之外,总的来说不如顺序存储灵活,性能也不如顺序存储结构好。

5.5朴素的模式匹配算法

在文章(相当于一个大串)中找到单词的定位,这种子串的定位操作通常称做串的模式匹配

这是串中很重要的操作之一

实例

我们要找到主串S=“wyjbat”中,找到T = “bat”这个子串的位置。通常要进行下面的步骤。

1.主串S第一位开始,S与T字母进行匹配

大话数据结构--串_线性表_04

2.多次比较从4位开始,S与T,3个字母全匹配,匹配成功



大话数据结构--串_子串_05


简单的说,就是对主串的每一个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T的长度的小循环,直到匹配成功或全部遍历完成为止。

这个算法也太低效了,不玩!!

5.6 KMP模式匹配算法

什么是KMP?

三个科学家D.E.Knuth、 J.H.Morris 和V.R.Pratt,以他们的姓氏命名的

为什么要用KMP算法?

因为暴力匹配效率太低

比如我们要在主串中匹配子串ABCD,这很简单对吧,常规的思路就是一位一位的匹配(这个思路就是暴力匹配算法)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b9kx80Ep-1637065551742)(C:/Users/Jack/AppData/Roaming/Typora/typora-user-images/image-20211116093132194.png)]

大话数据结构--串_线性表_06

如果对不上了,我们让子串往后移动

大话数据结构--串_数据结构_07

上面这种思路可以用代码来实现

/**

* 暴力破解法

* @param ts 主串

* @param ps 模式串

* @return 如果找到,返回在主串中第一个字符出现的下标,否则为-1

*/

public static int bf(String ts, String ps) {

char[] t = ts.toCharArray();

char[] p = ps.toCharArray();

int i = 0; // 主串的位置

int j = 0; // 模式串的位置

while (i < t.length && j < p.length) {

if (t[i] == p[j]) { // 当两个字符相同,就比较下一个

i++;

j++;

} else {

i = i - j + 1; // 一旦不匹配,i后退

j = 0; // j归0

}

}

if (j == p.length) {

return i - j;

} else {

return -1;

}

}

上面的程序没问题,只能说不够好!

主串匹配失败的位置除了第一个A之外再也没有A了,我们为什么知道主串前面只有一个A?因为前三个已经匹配过了啊!它们都是匹配的。

我们可以这样保存i不动,移动j

大话数据结构--串_模式匹配_08

KMP算法的思想就是利用已经部分匹配的有效信息,保持i指针不回溯,通过修改j指针,让子串尽量的移动到有效的位置!

再详细的可以观看这篇文章,写的很好!

我就不更了,日后在更


KMP模式匹配算法实现

/*通过计算返回子串T的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 11 T[i]== T[j]) /* T[i]表示后缀的单个字符,*/
/* T[j]表示前缀的单个字符*/
{
++i;
++j;
next[i]=j;
}
else
j= next[j]; /*若字符不相同,则j值回溯*/
}
}
/*返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。*/
/* T非空,1SposSStrLength (s)。*/
int Index KMP (String s, String T, int pos )
{
int i= pos;/*i用于主串S当前位置下标值,若pos不为1, */
/*则从pos位置开始匹配*/
int j= 1; /*j用于子串T中当前位置下标值*/
int next[255] ; /*定义一next数组*/
get_ next(T, next); /*对串工作分析,得到next数组*/
while(i<= S[0] &j<= T[0]) /*若i小于S的长度且j小于T的长度时,*/
/*循环继续*/
{
if (j==0日1 S[i]== T[j]) /*两字母相等则继续,与朴素算法增加了*/
/*j=0判断*/
++i;
++j;
else /*指针后退重新开始匹配★/
{
j = next[j]; /* j退回合适的位置,i值不变*/
}
if(j > T[0] )
return i-T[0] ;
else
return 0;
}

5.7总结

串是由零个或多个字符组成的有限序列,又名字符串。本质上是一种线性表的扩展,相对于线性表关注一个个元素来说,串这种结构更多的是关注它子串的应用问题,如查找、替换等操作。现在的高级语言都有针对串的函数可以调用,我们在使用这些函数的时候,同时也应该要理解它当中的原理,以便于再碰到复杂问题时,可以更灵活的使用