串的定义
- 串是由零个或多个字符组成的有限序列,也称字符串;
- 串的表示 ‘abc’
- 串的长度n:串中的字符数目
- 空串 :n=0
串的数据类型
- 对于不同的高级语言,对串的基本操作会有不同的定义方法
ADT 串(string)
Data
串中元素仅由一个字符组成,相邻元素有前驱和后继关系
Operation
StrAssign(T,*chars):生成一个值等于字符串常量chars的串T;
StrCopy(T,S):串S存在,由串S复制得串T;
ClearString(S): 串S存在,将串清空;
StringEmpty(S):若串S为空,返回true,否则返回false;
StringLength(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):用Sub返回串S的第pos个字符起长度为len的子串;
Index(S,T,pos):若主串S存在和串T相同的子串,返回T在S中第pos个字符之后第一次出现的位置,否则返回0;
Replace(S,T,V):用V替换所有S中出现的不重叠的T;
StrInsert(S,pos,T):在串的第pos个字符之前插入串T;
StrDelete(S,pos,len):从串S中删除第pos个字符起长度为len的子串
串的存储结构
- 顺序存储结构
- 一般是用定长数组
- 串的长度值保存在数组的0下标位置,或者数组最后一个下标位置,或者采用结束标价字符’’\0’’
- 堆存储结构
- 以一组地址连续的存储单元存放串值字符序列,但它们的存储空间是在程序执行过程中动态分配而得
- 链式存储结构
朴素的模式匹配算法
- 串的模式匹配:子串的定位
- 目标串: 被匹配的主串
- 模式:子串
- 位置i称为位移,匹配成功则i为有效位移,匹配失败则i为无效位移
int Index(String S, String T, int pos)
{
int i = pos;
int j = 1;
if(i>0)
{
while(i<=S[0]&&j<=T[0])
{
if (S[i]==T[j])
{
++i;
++j;
}
else
{
i = i-j+2;
j=1;
}
}
if(j > T[0])
return i-T[0];
else
return 0;
}
}
KMP模式匹配算法
- 基本原理:找到(如果有的话)已对比字符的重复部分(以T[1]开头的重复子串),可以减少这部分(这部分之前)的重复对比。
- next数组用来存储子串T的当前字符要回溯的下一个位置,即第一个重复部分之后的第一个位置。
void get_next(String T, int *next)
{
int i = 1;
int j = 0;
next[1]=0;
if(j==0||T[i]==T[j])
{
++i;
++j;
next[i]=j;
}
else
j = next[j];
}
}
- j=next[j]的理解:前缀 j 位置之前的值都是比较过且匹配上的,如果T[j] != T[i],那么要考虑重新比较,从哪儿开始比较好呢?肯定回溯的是 j 之前的值,而且回溯的值越大越好。所以先考虑 j 之前(前缀)有没有含T[1]的重复的部分(即 next[j]),如果有,根据 j 之前前缀后缀都匹配上了,那么后缀也有含T[1]的较短的重复片段,所以就可以接着比较T[i]和回溯值,这步的迭代就是把匹配的片段一步一步减短进行比较,如果一直匹配不上,那么j值会回溯到1,即从T[1]开始重新比较。
- 正向理解的话就是,如果T[j] != T[i],匹配失败,就要找 i-j+1之后(后缀)出现的第一个T[1]。鉴于 j 之前的前缀和 i-j+1 后缀都匹配上了,那么找前缀的下一个T[1]位置也行;根据 next[j] 里存储的是 j之前 含 T[1] 重复片段的最大长度, 所以下一个位置就是 j-next[j]+1;再者根据 next[j] 之前是重复片段,已经比较过了,所以从next[j] 位置开始比就行。
int Index_KMP(String S, String T, int pos)
{
int i = pos;
int j = 1;
int next[255];
get_next(T,next);
while(i<=S[0]&&j<=T[0])
{
if(j==0||S[i]==T[j])
{
++i;
++j;
}
else
j = next[j];
}
if(j > T[0])
return i-T[0];
else
return 0;
}
改进的KMP模式匹配算法
void get_nextval(String T, int *next)
{
int i = 1;
int j = 0;
nextval[1]=0;
while(i<T[0])
{
if(j==0||T[i]==T[j])
{
++i;
++j;
if(T[i]!=T[j])
nextval[i]=j;
else
nextval[i]=nextval[j];
}
else
j = nextva[j];
}
}
- 改进部分的理解:正常流程是,j值回溯后,比较T[i]和T[next[j]],但若 T[j]=T[next[j]], T[i]肯定不等于T[next[j]],就需要继续迭代回溯,所以在改进算法中加入了对这部分的判断,以减少迭代回溯的步骤。在程序中,nextval[i]=j,故需要比较的是T[i]和T[j]。