目录
串的定义
串:串(string)是由多个或多个字符组成的有限序列,又叫字符串
一般记为S=“a1a2a3...an”,其中,S是串的名称,用双引号括起来的字符序列是串的值
串中的字符数目n称为串的长度,定义中谈到“有限”是指长度n是一个个有限的数值
零个字符的串称为空串(null string),它的长度为零,可以直接用“""”表示
易混淆概念解释:
空格串:
是只包含空格的串。注意它与空串的区别,空格串是有长度的,而且可以不止一个空格
子串与主串:
串中任意个数的连续字符组成的子序列称为该串子串,相应地,包含子串的串称为主串
子串在主串中的位置就是子串的第一个字符在主串中的序号
串的比较
串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应集中的序号。
我们在c语言中比较两个串是否相等,必须是他们串的长度以及各个对应位置的字符都相等时,才算是相等。
对于两个串不相等时,我们这样定义:
给定;两个串:s=“a1a2a3...an”,t=“b1b2b3...bn”,当满足以下条件之一时,s<t
(1)n<m,且ai=bi(i=1,2,3...n)
(2)存在某个k<=min(m,n),使得ai=bi(i=1,2,3,...,k-1),ak<bk.
例如:
s=“happen”,t=“happy”,因为前四个字母均相等,而两串第五个字母(k值),字母e的ASCLL码是101,而字母y的ASCLL码是121,显然e<y,所以s<t.
串的抽象数据类型
对于串和线性表来讲,线性表更关注的是单个元素的操作,比如查找一个元素,插入或删除一个元素,但传中更多的是查找子串的位置、得到指定位置子串、替换子串等操作。
ADT (string)
Data
//串中元素仅由一个字符组成,相邻元素具有前驱和后继的关系
Operation
StrAssign(T,*chars)://生成一个其值等于字符串常量chars的串T
StrCopy(T,S)://串s纯在。有串S赋值得串T
ClearString(S)://串S存在,将串清空
StringEmpty(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个字符之后第一次出现的位置,否则返回
Replace(S,T,V)://串S、T和V存在,T是非空串。用V替换主串S中出现的所有与T相等的不重叠的子串
StrInsert(S,pos,T)://串S和T存在,1<=pos<=StrLength(S)+1,zai 串S的第pos个字符之前插入串T
StrDelete(S,pos,len)://串S存在,1<=pos<=StrLength(S)-len+1,从串S中删除第pos个字符起长度为len的子串
对于不同高级语言,其实对串的基本操作会有不同的定义方法,不过还好,不同语言除方法名称外,操作是指都是类似的
Index的实现算法
//T为非空串,若主串S中第pos个字符之后存在与T相等的子串
//则返回第一个这样子串在S中的位置,否则返回0
int 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;
}
}
return 0;
】
串的存储结构
串的存储结构与线性表相同,分为两种。
串的顺序存储结构:
串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列的。
“\0”为结束字符,不算入字符串长度
对于串的顺序存储,有一些变化,串值的存储空间可在执行程序过程中分配而得。比如在计算机中存一个自由存储区,叫做“堆”。这个堆可由C语言的动态分配函数,malloc()和free()来管理
串的链式存储结构:
对于串的链式存储结构,与线性表相似,到由于串结构的特殊性,结构中的每个元素数据是一个字符,如果简单得应用链表存储串值,就会存在很大的空间浪费,因此,可以一个结点放多个字符,若一个结点未被占满可用“#”来填补
朴素的模式匹配算法
子串的定位操作通常称做串的模式匹配,是串中最重要的操作之一
假设我们要从下面的主串S=“goodgoogle”中,找到T=“google”这个子串的位置,操作如下:
(1)主串S第一位开始,S与T前三个字母匹配成功,但S第四个字母是d而T的是g。第一位匹配失败
(2)主串S第二位开始,主串S首字母是o,要匹配的T首字母是g,匹配失败
(3)主串S第三位开始,主串S首字母o,要匹配的T首字母是g,匹配失败
(4)主串S第四位开始,主串S首字母是d,要匹配的T首字母是g,匹配失败
(5)主串S第五位开始,S与T,6个字母全匹配,匹配成功
简单来说:就是每读取一个主串的字符便对子串进行一次循环,直至匹配成功
现在用基本的数组操作来实现匹配算法:(长度存在S[0]与T[0]中)
//返回子串T在主串S中第pos个字符之后的位置,若不存在,则函数返回0
//其中,T非空,1<=pos<=StrLength(S)
int Index(String S,String T,int pos)
{
int i=pos;//i用于主串S中当前位置下标值,从pos位置开始匹配
int j=1;//j用于子串T中当前位置下标值
while(i<=S[0]&&j<=T[0]){//当i小于S的长度并且j小于T的长度时,循环继续
if(S[i]==T[j])//量字母相等则继续
{
++i;
++j;
}
else//指针后退重新开始匹配
{
i=i-j+2;//i退回到上次匹配收尾的下一位
j=1;//j退回到子串T的首位
}
if(j>T[0])
return i-T[0];
else
return 0;
}
分析一下:
最好的情况:
一开始就匹配成功,则时间复杂度为O(m)
最坏的情况:
到最后才匹配成功,则时间复杂度为O(m+n)
在数据量多的时候,如此繁琐的算法就显得太低效了
KMP模式匹配算法
挨个遍历的算法是非常糟糕的。于是三位前辈,D.E.Knuth、J.H.Morris、V.R.Pratt发表了一个算法匹配模式,可以大大避免重复遍历的情况,我们把它称之为可努特—莫里斯—普拉特算法,简称KMP算法