串(String)
定义:零个或多个任意字符组成的有序列
特点:内容受限制的线性表,数据元素只能是字符
相关属术语:
**子串:**串中任意个连续的字符组成的子序列(含空串)
e.g.“abcde”的字串——“”、“a”、“ab”、“abcde”…
真子串:不包含自身的子串
**主串:**包含字串的串
**字符的位置:**字符在序列中的序号
**子串的位置:**子串第一个字符在主串中的位置
**空格串:**有一个或多个空格组成的串,区别于空串
< “ \t\t ” vs “” >
**串长度:**串中字符的个数
**串相等:**当且仅当串长度相等且各对应位置上的字符都相同(所有空串都相等)
串的起点是0还是1?
案例
- 病毒感染检测
将人和病毒的DNA均表示成一些字母组成的字符串序列,若病毒的序列在患者的序列中出现过,则确诊。(注意:人的DNA是线性的,病毒的是环状的)
例如:病毒BAA,患者1AAABBBA,患者2BABBBA。1感染2未感染
方法:用baa aba aab baab aaba abaa跟患者DNA序列对比
串的类型定义、存储结构及运算
ADT String{
-数据对象:D = {ai | ai 属于 CharacterSet,i = 1,2,…,n,n>=0}
-数据关系:R1 = {<ai - 1,ai> | ai - 1,ai 属于D,i= 1,2,…,n}
-基本操作:StrAssign(&T,chars)串赋值、StrCompare(S,T)串比较、StrLength(S)求串长、Concat(&T,S1,S2)串连接、求子串、串拷贝、串判空、清空串、Index(S,T,pos)子串的位置、串替换、子串插入、子串删除、串销毁
}ADT String
存储方式
- 循序存储结构——顺序串
- 链式存储结构——链串
顺序存储
不需要额外的空间描述其地址关系
试用数组存储
#define MAXLEN 256
typedef struct{
char ch[MAXLEN+1]; //存储串的一维数组
int length; //串的当前长度
}SString;
编号[0,255],其中0闲置不用,研究算法是更加简便
与线性表的区别:数据元素类型只能是字符。
链式存储
加上直接后继的指针
优点:操作方便
缺点:存储密度(20%)较低
(存储密度 = 串值占/实际分配占)
改善方法:放多个字符在一个节点,密度提升
#define CHUNKSIZE 80
typedef struct Chunk{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct{
Chunk *head,*tail;
int curlen;
}LString; //字符串的块链结构
一般而言,顺序存储更常用,一般只用匹配和查找的功能
串的模式匹配算法
算法目的:确定主串中所含子串(模式串)第一次出现的位置
应用:搜搜引擎、拼写检查、语言翻译、数据压缩
算法种类:
- BF算法:暴力破解,实现效率低
- KMP算法:速度快
BF算法
亦称简单匹配算法,采用穷举法思路
例如:S=“aaaaab” T=“aaab”
主串子串的起点都在1
匹配失败,i回溯,回溯位置为当前位置i,减去移动的位置j-1,加上下一个位置1.即i=i-(j-1)+1=i-j+2
再次进行匹配
失败后再次回溯
j与i依次匹配,且目前j的位置大于子串的长度,匹配成功
算法思路
Index(S,T,pos)
将主串的第pos各字符与模式串的第一个字符比较:
- 相等,急需逐个比较后续字符
- 不相等,从主串的下一个字符其,再次开始匹配
直到主串的一个连续子串字符序列与模式串相等,返回匹配的第一个字符的序号,否则返回值0.
int index_BF(SString S,SString T,int pos){
int i=pos,j=1;
while(i <= S.len && j <= T.len){//匹配成功或主串匹配完就结束
if(S.ch[i] == T.ch[j]){ //主串与子串一次匹配
i++;
j++;
}
else{ //回溯
i = i - j + 2;
}
}
if(j >= T.len) return i-T.len; //匹配成功
else return 0; //匹配失败
}
时间复杂度:
最好情况,T(T.len)
最坏情况:T((S.len-T.len+1)T.len),O(S.lenT.len)
KMP算法
利用已经部分匹配的结果而加快模式串的滑动速度,主串i指针不再回溯,提速到O(m+n)
void get_next(SString T,int & next[]){
int i= 1,j = 0;
next[0] = 1;
while(i < T.len()){
if(j == 0 || T.ch[i] == T.ch[j]){
++i;
++j;
next[i] = j;
}
else j = next[j];
}
}
如果子串本身重复
T=“aaab” S=“aabaabaaaab”,改良以下
void get_nextval(SString T,int & nextval[]){
int i= 1,j = 0;
next[0] = 1;
while(i < T.len()){
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];
}
}
回溯就不那么麻烦了
int index_KMP(SString S,SString T,int pos){
int i = pos,j = 1;
while(i < S.len() && j < T.len()){
if(j==0||S.ch[i)==T.ch[j]){
i++;
j++
}
else {
j = next[j]; //
}
}
if(j >= T.len) return i-T.len; //匹配成功
else return 0; //匹配失败
}