1、定义
串是由零个或多个字符组成的有限序列,又名字符串
2、抽象数据类型
- Data 串中元素仅由一个字符组成,相邻元素具有前驱和后继关系
- Operation
- StrAssign( T, *chars ) 生成一个其值等于字符串常量chars的串T
- StrCopy( T, S ) 串S存在,由串S复制得串T
- ClearString( S ) 串S存在,将串清空
- StringEmpty( S ) 若串为空 返回true 否则返回false
- StrLength( 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 ) 串存在 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
- StrDelete( S, pos, len ) 串存在 1≤pos≤StrLength(S)+1 从串S种删除第pos个字符起长度为len的子串
3、查找子串-(1)-使用自带API
//T为非空串。若主串S中第pos个字符之后存在与T相等的子串,则返回第一个这样的子串在S中的位置,否则返回0
int Index( string S, string T, int pos)
{
if (pos <= 0)
{
return 0;
}
int s_len = StrLength(S);
int t_len = StrLength(T);
int i = pos;
string sub;
while(s_len - i + 1 >= t_len)
{
SubString(sub, S, i);
if (StrCompare(sub, T) != 0)
{
++i;
}
else
{
return i;
}
}
return 0;
}
4、串的存储结构
5、朴素模式匹配算法
6、查找子串-(2) 朴素模式匹配算法
//手写查找子串操作
//前提 主串S和要匹配的子串T的长度存在数组的第一个位置 即 S[0] T[0]中
//返回子串T在主串S中第pos个字符之后的位置,若不存在 则返回0
//T非空 1≤pos≤StrLength(S)
int Index(string S, string T, int pos)
{
int i = pos;
int j = 1;
while (i <= S[0] && j <= T[0])
{
//当主串中余下的长度 小于子串时,无需再进行比较
if (S[0]-i+1 < T[0]-j+1)
{
return 0;
}
if (S[i] == T[j])
{
++i;
++j;
}
else
{
i = i-j+2;//i退回上次匹配首位的下一位
j = 1;
}
}
if (j > T[0])
{
return i-j+1;
}
else
{
return 0;
}
}
7、查找子串-(3) KMP模式匹配算法
1)、相对于朴素模式 可以省略的操作
①若子串中首字符 与其后面的字符均不等,且已于主串比较过并且相等,那么这些字符的匹配判断都可省略,下次判断将从子串的首位与 主串与子串最后一个相等的下个位置开始判断
②对于在子串中有与首字符开始依次相等的字符集,可以省略其匹配判断
子串的4、5位与首字符相等,所以主串中对应的第4、5位不需再次与子串中第1、2位相比较;直接对子串j=3与主串i=6进行比较
2)、KMP模式匹配算法 思想
3)、子串各个位置的 j 值变化函数
①next数组定义
②解释
中间Max后的式子表示串的前后相似度,P 1 : 子串的首字符;P k-1 : 子串中第k-1个字符;P j-k+1 : 子串的第 j-k+1个字符;P j-1 : 与主串相等的子串中的最后一个位置。
总结: j = 与主串依次相等的子串 部分中,前缀与后缀相等的部分中,前缀相似字符的 下一个位置。
注:j从1开始,最大为子串的长度
j=1时,子串未与主串开始比较,表示下次需比较j=1位置的字符
j=2时,子串的第一个位置字符与主串第一个字符相比较,且相等
j=3时,子串的第3个位置字符之前的所有字符 均与主串相比,且相等
4)、next数组值推导
①
②
③
④
结论:如果前后缀一个字符相等,k值是2,两个字符相等 k值是3,n个相等k值就是n+1
5)、代码
//KMP模式匹配算法
//通过计算返回子串T的next数组,计算出当前要匹配的串T的next数组
void get_next(string T, int *next)
{
int i=1; //i 表示子串的下标从1开始依次增加,T[i]用于表示后缀字符
int j=0; //j 表示与后缀相等的个数/下标,T[j]用于表示前缀字符
next[1] = 0;
while (i < T[0]) //此次T[0]表示串T的长度,假设串种第1个位置存储长度
{
if (j == 0 || T[i] == T[j])//T[i]表示后缀的单个字符;T[j]表示前缀的单个字符
{
++i;
++j;
next[i] = j;
}
else
{
j = next[j]; //若字符不相同,则j值回溯
}
}
}
//返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回0
//T非空,1≤pos≤StrLength(S)
int Index_KMP(string S, string T, int pos)
{
int i = pos; //i用于主串S当前位置下标值,若pos不为1,则从pos位置开始匹配
int j = 1; //j用于子串T中当前位置下标
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退回合适的位置,i值不变
}
}
if (j > T[0])
{
return i - T[0];
}
else
{
return 0;
}
}
5)、图视
8、KMP模式匹配算法-改进
根据上图,我们发现,当中②③④⑤步骤,其实是多余的判断。由于T串的第二、三、四、五位置的字符都与首位'a'相等,那么可以用首位next[1]的值去取代与它相等的字符后续next[j]的值。
我们用数组为nextval,取代next数组
//KMP算法 改良
//更新子串nextval数组
void get_nextval(string T, int *nextval)
{
int i=1, j=0;
nextval[1] = 0;
while (i < T[0])
{
if (j == 0 || T[i] == T[j])
{
++i;
++j;
if (T[i] != T[j])
{//若当前字符与前缀字符不同 则当前的j为nextval在i位置的值
nextval[i] = j;
}
else
{//如果与前缀字符相同,则前缀字符的nextval值赋值给nextval在i位置的值
nextval[i] =nextval[j];
}
}
else
{
j = nextval[j];
}
}
}
//实际匹配算法不变,与Index_KMP相同,只需将next换为nextval
nextval值推导