数据结构 (六) 串的应用
数据结构 (六) 串的应用
串的基本操作
串的最小操作子集
-
串赋值StrAssign
String StrAssign(String str,char s[]) { int i=0; while(s[i]) { str->ch[++str->length]=s[i]; i++; } return str; }
-
串比较StrCompare
void StrCompare(String *s1,String *s2) { int i; if (s1->length!=s2->length) { printf("两个串不相同\n"); } else { for(i=0;i<s1->length;i++) { if (s1->data[i]!=s2->data[i]) { printf("两个串不相同\n"); return; } } printf("两个串相同\n"); } return; }
-
求串长StrLength
int getStrLength(String str) { return str->length; }
-
串联接Concat
//C++ 使用 + 运算符 string res = str1 + str2; //C++ 使用 strcat()函数 strcat(str1, str2); //C ++中用于字符串连接的append() string1.append(string2); //使用循环逐个字符添加 #include<iostream> #include<string.h> using namespace std; int main() { char x[100]="Hello", y[100]="World"; int i; for(i=0; x[i] != '\0'; i++);//pointing to the index of the last character of x for(int j=0; y[j] != '\0'; j++,i++) { x[i]=y[j]; } x[i]='\0'; cout<<"Concatenated String:\n"; cout<<x<<endl; return 0; }
-
求子串SubString
String SubString(String *s,int start,int len) { if (start>s->length+1||start+len>s->length+1) { printf("不存在该子串!\n"); } String s1,*r; r=&s1; InitString(&s1); int i,j=0; for (i=start-1;i<len;i++) { r->data[j]=s->data[i]; r->length++; j++; } return s1; }
以上操作不可能利用其他串操作实现;
定位函数Index(S,T)
算法思想:在主串中取从第一个字符起、长度和串T相等的字串,与串T相比较,若相等则求得函数值为i,否则i值增1,直至串S中不存在和串T相等的字串为止。
int Index(String S,String T){
int i = 1,n = StrLength(S),m = StrLength(T);
while(i<=n-m+1){
SubString(sub,S,i,m);
if(StrCompare(sub,T)!=0)
++i;
else
return i; //返回子串在主串中的位置
}
return 0; //S中不存在与T相等的子串
}
注:
-
gets(str)与scanf("%s", str)的区别:
gets(str)允许输入的字符串含有空格;scanf("%s", str)不允许含有空格
串的匹配算法
子串的定位操作通常称为串的模式匹配,它求的是子串(常称模式串)在主串中的位置。
主串S:' s1,s2...sn'
模式串:‘p1,p2...pm'
-
简单的模式匹配算法
这里采用定长顺序结构,给出一种不依赖于其他操作的暴力匹配算法。
int Index(String S,String T){ int i = 1,j = 1; while(i<=S.length&&j<=T.length){ if(S.ch[i]==T.ch[j]){ ++i;++j; //继续比较后续字符 } else{ i = i-j+2;j = 1; //指针后退重新开始匹配 } } if(j>T.length) return i-T.length; else return 0; }
暴力模式匹配算法的最坏时间复杂度为O(nm),其中n和m分别为主串和模式串的长度。
-
改进的模式匹配算法------KMP算法
如果已匹配相等的前缀序列中有某个后缀正好是模式的前缀,那么就可以将模式滑动到与这些相等字符对齐的位置,主串i指针无需回溯,并继续从该位置进行比较。而模式向后滑动位数的计算也仅与模式本身的结构有关,与主串无关。
next函数
\[next[j]= \begin{cases} 0,&&j=1\\ max\big\{k|1<k<j且'p_1\cdots p_{k-1}'='p_{j-k+1}\cdots p_{j-1}'\big\},&&当此集合不空时\\ 1,&&其他情况 \end{cases} \]求next值的程序如下:
void get_next(String T,int next[]){ int i = 1,j = 0; next[1] = 0; while(i<T.length){ if(j==0||T.ch[i]==T.ch[j]){ ++i;++j; next[i] = j; //若pi=pj,则next[j+1]=next[j]+1 } else j = next[j]; //否则令j=next[j],循环继续 } }
KMP匹配算法:
int Index_KMP(String S,String T,int next[]){ int i = 1,j = 1; while(i<=S.length&&j<=T.length){ if(j==0||S.ch[i]==T.ch[j]){ ++i;++j; //继续比较后续字符 } else{ j = next[j]; //指针后退重新开始匹配 } } if(j>T.length) return i-T.length; else return 0; }
KMP算法的时间复杂度是O(m+n),主要用在主串与子串有很多"部分匹配"时才显得比普通算法快很多,其主要优点是主串不回溯。
-
KMP算法的进一步优化
上述定义的next数组在某些情况下尚有缺陷,还可以进一步优化。若出现了pj=pnext[j],需要再次递归,将next[j]修正为next[next[j]],直至两者不相等为止,更新后的数组命名nextval。计算next数组修正值的算法如下,此时匹配算法不变。
void get_nextval(String T,int nextval[]){ int i = 1,j = 0; nextval[1] = 0; while(i<T.length){ if(j==0||T.ch[i]==T.ch[j]){ ++i;++j; if(T.ch[i]!=T.ch[j])) nextval[i] = j; else nextval[i]=nextval[j]; } else j = nextval[j]; } }