《数据结构》天勤和王道 第四章 串
天勤部分
1. 串的基础
1.1 逻辑结构
1.2 存储结构
1.3 赋值操作
int strAssign(Str& str,char* ch){
if(str.ch){ //如果串中已经有元素就将其释放掉
free(str.ch);
}
int len=0;
char *c=ch;
while(*c){ //统计字符串ch的长度
++len;
++c;
}
if(len==0){ //即使传进来的是空串,也可以进行赋值操作
str.ch=NULL;
str.length==0;
return 1;
}else{
str.ch=(char*)malloc(sizeof(char)*(len+1)); //长度包括结束符
if(str.ch==NULL){ //如果分配空间失败,则返回0
return 0;
}else{
c=ch;
for(int i=0;i<=len;++i,++c){ //把ch的结束符也赋进去
str.ch[i]=*c;
}
str.length=len;
return 1;
}
}
}
1.4 串比较
int strCompare(Str s1,Str s2){
for(int i=0;i<s1.length&&i<s2.length;++i){
if(s1.ch[i]!=s2.ch[i]){
return s1.ch[i]-s2.ch[i];
}
}
return s1.length-s2.length;
}
1.5 串连接
int concat(Str& str,Str str1,Str str2){
if(str.ch){
free(str.ch);
str.ch=NULL;
}
str.ch=(char*)malloc(sizeof(char)*(str.length+str2.length+1));
if(!str.ch){
return 0;
}
int i=0;
while(i<str1.length){
str.ch[i]=str1.ch[i];
++i;
}
int j=0;
while(j<=str2.length){
str.ch[i+j]=str.ch[j];
++j;
}
str.length=str1.length+str2.length;
return 1;
}
1.6 求子串
int subString(Str& substr,Str str,int pos,int len){ //pos为子串的起始下标,len为子串的长度
if(pos<0||pos>=str.length||len<0||len>str.length-pos){
return 0;
}
if(substr.ch){
free(substr.ch);
substr.ch=NULL;
}
if(len==0){ //子串也可取空串
substr.ch=NULL;
substr.length=0;
return 1;
}else{
substr.ch=(char*)malloc(sizeof(char)*(len+1));
int i=pos;
int j=0;
while(i<pos+len){
substr.ch[j]=str.ch[i];
++i;
++j;
}
substr.ch[j]='\0';
substr.length=len;
return 1;
}
}
1.7 清空串
int clearString(Str& str){
if(str.ch){ //如果串非空则清空
free(str.ch);
str.ch=NULL;
}
str.length=0;
return 1;
}
2. KMP算法手工求解next数组
KMP算法是为了从主串中快速地找到想要的子串。
当扫描到这种,一般情况下,会把指针回溯指向主串的第二个元素。
如这样:
但是这样造成效率低下,所以KMP就为了来解决这个问题。KMP可以仅仅比较模式串,并且让指针不回溯。
当指针之前与主串匹配时,此时模式串中有公共前后缀AB。就可以直接移动模式串到前缀与后缀一样的位置上继续进行比较。
而找公共前后缀时的条件是找最长的且长度要小于当前指针指向元素前面的子串的长度的公共前后缀。
这里模式串下标从1开始,不过具体可以看题目要求。
当扫描到六号位时,此时指针前面的子串的最大公共前后缀是ABA,长度为3。
3. KMP算法代码
//求next数组的代码
void getNext(Str substr,int next[]){
int j=1,t=0;
next[1]=0; //无脑写0
while(j<str.length){ //注意这里不能是小于等于,因为①语句j+1会越界
if(t==0||substr.ch[j]==substr.ch[t]){
next[j+1]=t+1; //①
++t;
++j;
}else{
t=next[t];
}
}
}
//KMP算法
int KMP(Str str,Str substr,int next[]){ //str为主串,substr为模式串
int i=1,j=1;
while(i<=str.length&&j<=substr.length){
if(j==0||str.ch[i]==substr.ch[j]){
++i;
++j;
}else{
j=next[j];
}
}
if(j>substr.length){
return i-substr.length;
}else{
return 0;
}
}
4. 求解nextval数组及代码
直接拿上面求next数组的代码进行修改。
void getNextval(Str substr,int nextval[],int next[]){
int j=1,t=0;
next[1]=0; //无脑写0
nextval[1]=0;
while(j<str.length){ //注意这里不能是小于等于,因为①语句j+1会越界
if(t==0||substr.ch[j]==substr.ch[t]){
next[j+1]=t+1; //①
if(substr.ch[j+1]!=substr.ch[next[j+1]]){
nextval[j+1]=next[j+1];
}else{
nextval[j+1]=nextval[next[j+1]];
}
++t;
++j;
}else{
t=nextval[t];
}
}
}
再精简一下:
void getNextval(Str substr,int nextval[]){
int j=1,t=0;
nextval[1]=0;
while(j<str.length){
if(t==0||substr.ch[j]==substr.ch[t]){
if(substr.ch[j+1]!=substr.ch[next[j+1]]){
nextval[j+1]=t+1;
}else{
nextval[j+1]=nextval[t+1];
}
++t;
++j;
}else{
t=nextval[t];
}
}
}
王道的部分
1. 串的定义和基本操作
2. 串的存储结构
2.1 串的顺序存储
2.2 串的链式存储
2.3 基本操作的实现
2.3.1 求子串
2.3.2 比较操作
这个跟天勤的一样,不懂可以看上面的。
2.3.3 定位操作
2.4 朴素模式匹配算法
2.5 KMP算法
朴素模式匹配算法在每次匹配失败后,指向主串的指针总要回溯。影响了总体效率,所以有了KMP算法。
注意一下当第一个元素就匹配失败的情况:
代码如下:
2.6 KMP算法 求next数组(手算)
这个有两种方法:一种是王道讲的这个;一种是天勤上面讲的,根据当前要判断的元素的前面的模式串的子串中公共前后缀的长度加1来确定当前next数组的值(不过要注意模式串下标,有的题目是j从0开始,然后这里需要加1是因为next数组第一个是0,第二个是1)。
例如当第三个元素不匹配时,看模式串前两个元素,公共前后缀长度为0,加1就是1,所以next[3]=1。
当第四个元素不匹配时,此时模式串前三个元素的公共前后缀是“a”,长度是1,所以加1后是2,即next[4]=2。以此类推。
2.7 next数组的优化(nextval数组)
举个例子:
KMP算法不用改变,只是优化next数组,形参也使用优化后的nextval数组。
上面的描述可能有点绕,可以看下面的例子: