5.2
1.串是由零个或多个字符组成的有限序列,又名叫字符串。
2.串中的字符数目n称为串的长度。
3.零个字符的串称为空串,它的长度为0,也可以直接用双引号""直接括起来。
4.空格串,是包含空格的串。空格串是有长度的,而且可以不止一个空格。
5.子串与主串,串中任意个数的连续字符组成的子序列称为该串的子串,包含子串的串称为主串。
6.子串在主串中的位置就是子串的第一个字符在主串中的序号
5.3串的比较:
1.将两个字符串都从左至右对齐,然后开始比较,字符串的数量可以不同,比较字符串时一一对应,按照ascii码的方式,字母在前的小,到第一个不同字母比较完之后就完。
5.4串的数据结构:
ADT 串(string)
Data
串中元素仅有一个字符组成,相邻的元素具有前驱和后继关系
Operation
StrAssign (T,*chars): 生成一个其值等于字符串的常量chars的串T
StrCopy (T,S):串S存在,由串S复制得到串
ClearString(S): 串s存在,将串清空
StringEmpty(S):若串S为空,返回true,否则返回false
StrLength(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
Strlength(S,pos,len): 串S存在,1<=pos<=Strlength(S)-len+1
` 从串S中删除第pos个字符起长度为len的子串
endADT
5.4.1index的操作(取主串中,与子串相同的字符串的第一个字符的位置)
/*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);
m=Strlength(T);
i=pos; //刚开始这个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.6朴素的模式匹配法:
//返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0
//T非空,1<=pos<=Strlength(S)
int Index(String S, String T, int pos)
{
int i=pos; //i主要用于主串s中当前位置的下标,若pos不为1
//则从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退回到上次匹配首位的下一位,详细看下图1处
j=1; //j退回到子串T的首位
}
}
if(j>T[0]) //上面的while结束有两种情况,i>S[0] 或者 j>T[0] 当i>S[0]一定没有对应的字符串匹配,当j>T[0]时,表示走完了字串,找到了相对应的
return i-T[0]; //详细看下图2处
else
return 0;
}
朴素算法的时间复杂度:
5.7KMP模式匹配法
1.kmp算法的普遍理解:如下图,kmp算法的使用前提是T串中的首字符a与后面的均不相等,由于a到e子串T与主串S都对应相同,所以下一次直接比较a与f即可,这便是kmp的引入
2.实际上,j的回溯位置的变化与主串S没有什么关系,主要是看子串T中是否有相同的字符,如下图中不用在比较a,b,
j从6回溯到j=3
关于j的回溯的数组next的原理:如下图
这里对于5这个位置下要看前4个字符,第一个a与最后一个a相同,相同字母只有一个,所以5下就写2
这里对于6这个位置下要看前5个字符,前两个字符ab与最后两个字符ab完全相同,所以6下就写3
如下图:
j回溯位置下标的数组:
//通过计算返回子串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]表示串的长度
{
if(j==0 || T[i]==T[j])
{
++i;
++j;
next[i]=j; //将j回溯的位置保存到next中
}
else
j=next[j]; //j回溯到对应位置
}
}
下面是KMP的整体代码:
//返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0
int Index_KMP(String S, String T, int pos)
{
int i=pos; //i就是那里的代码值不同就从哪里开始,如图5-7-3 i为6就从6开始
int j=1; //j用于当前位置中下标值
int next[255]; //定义一next数组
get_next(T,next); //对串做分析,得到next数组
while(i <= s[0] && j <= T[0]) //若i小于s的长度且j小于且小于T的长度时,循环继续
{
if(j==0 || s[i]==T[j]) //两个字母相等则继续,相对于朴素算法增加了j=0判断
{
++i;
++j;
}
else
{
j=next[j]; //如果字母不相同,则j的值回溯
}
}
//后面两行代码和朴素算法相同
if(j>T[0])
return i-T[0];
else
return 0;
}
kmp算法的时间复杂度分析:
在这里,创建next数组时间复杂度为o(m),后面的时间复杂度为o(n),则整体的时间复杂度为o(m+n)
kmp的核心思路:如果子串中一前一后有相同的字符,后面的字符不管与主串中是相同还是不同,对于前面的字符在往后走时不用在与之前
5.7.4kmp模式匹配算法的改进
//求模式串T的next函数修正值并存入数组nextval,整个nextval还是以计算next的方法来构建,区别在于数值不同,目的就是减少j的回溯次数,以减少复杂度
void get_nextval(String T, int *nextval)
{
int i,j;
i=1;
j=0;
nextval[1]=0;
while(i<T[0]) //T[0]表示串的长度
{
if(j==0 || T[i]==T[j]) //T[i]表示后缀的单个字符,T[j]表示前缀的单个字符
{
++i;
++j;
/* 这里的代码是改进后不同于之前的next算法
if(T[i]!=T[j]) //若当前字符与前缀字符不同
nextval[i]=j; //就将next中的值给nextval
else
nextval[i]=nextval[j]; //字符相同,nextval与之前相同字符所对应的nextval值相同
*/
}
else
j=nextval[j]; //相当于计算next数组中的值,只是改了个名,就这样理解
}
}
5.7.4 kmp算法的改进
1.相对于之前的kmp算法j的回溯位置变化太多,而改进后,j回溯的变化位置不多,这里应该是T中有太多连续字符相同的情况下
next数组中,数字不同的程度与j回溯到不同位置的次数成正比,数字越复杂,回溯的次数越多,时间复杂度越大。
2.nextval[]数组变化是:第一数组值为0,往后每一个数组值,如果串中的字符与next数组中对应位置的字符相同,则nextval中的值也与对应位置的nextval相同,如果不同则nextval值就是next值