串
- 【string】:由零个或多个字符组成的有限序列,又称字符串
- 记为 s = “a1a2a3…an”(n>=0)
- 空串:”” 或 Φ
- 子串:主串的子序列
- 子串在主串中的位置:第一个字符在主串中的序号
串的比较
s = “a1a2a3…an”,t = “b1b2b3…bm”
满足以下条件之一,s<t
- n<m 且 ai==bi
- 存在某个k<=min(m,n),使ai==bi(i=1,2,…,k-1),ak<bk
ADT
- 不同的高级语言对串的基本操作有不同的定义
ADT 串(string)
Data
串中元素仅由一个字符组成,相邻元素具有前驱和后继关系
Operation
StrAssign(T,*chars): 生成一个其值等于chars的串T
StrCopy(T,S): 由串S复制得到串T
ClearString(S): 将串S清空
StringEmpty(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): 若子串中存在和串T值相同的子串,则返回它在主串中第pos个字符之后第一次出现的位置,否则函数值为0
Replace(S,T,V): 用V替换串S中出现的所有与T相等的不重叠的子串
StrInsert(S,pos,T): 在串S的第pos个字符之前插入串T
StrDelete(S,pos,len): 从串S中删除从第pos个字符起出长度为len的子串
DestroyString(S): 销毁串S
endADT
顺序存储
- 用一组地址连续的存储单元(定长的数组)来存储串中的字符序列
- 用堆存储,由动态分配函数malloc()和free()管理存储空间
链式存储
- 一个结点可存放多个字符,最后一个结点未被占满用#补全
- 不如顺序存储好
朴素的模式匹配算法
- 需要不断地回溯
/* 返回子串T在主串S中第pos个字符之后的位置,若不存在则返回0 */
/* T非空 1<=pos<=StrLength(S) */
int Index( String S, String T, int pos )
{
int i = pos; // i定位主串当前位置下标
int j = 1; // j定位子串当前位置下标
while( i <= S[0] && j <= T[0] ) // 长度存在S[0]、T(0)中
{
if( S[i] == T[i] )
{
i++;
j++;
}
else
{
i = i-j+2; // i退回到上次匹配首位的下一位
j = 1;
}
}
if( j > T[0] ) // 匹配成功,最后 j==T[0]+1
{
return i - T[0]; // 返回子串在主串pos之后的位置
}
else
{
return 0;
}
}
KMP模式匹配算法
- 不需回溯
- 【next数组】:记录子串每个位置匹配失败时,下一次与主串该位置比对的子串下标
- next[0]=0,next[1]=1,之后为 该位置之前序列的最长相同前后缀+1
#include <stdio.h>
typedef char *String;
/* 求next数组 */
void get_next( String T, int *next ){
int i,j;
i = 1;
j = 0;
next[1] = 0;
while( i < T[0] ){ // T[0]存放子串T的长度
if( j==0 || T[i] == T[j] ){ // T[i]表示后缀的单个字符
i++; // T[j]表示前缀的单个字符
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当前位置下标
int j = 1; // j为子串T当前位置下标
int next[255]; // 定义next数组
get_next( T, next ); // 得到next数组
while( i <= S[0] && j <= T[0] ){
if( j==0 || S[i] == T[j] ){
i++;
j++;
}
else
{
j = next[j]; // j回退合适位置
}
}
if( j > T[0] )
{
return i - T[0];
}
else
{
return 0;
}
}
/* 测试 */
int main(){
char str[255] = "ababaaaba";
char str2[255] = "aaba";
int next[255];
int i = 1;
str[0] = 9;
str2[0] = 4;
get_next(str, next);
for(i=1;i<=9;i++){
printf("%d\t",next[i]);
}
printf("\n%d\n",Index_KMP(str,str2,1));
return 0;
}
KMP算法的改进
- 【nextval数组】:解决子串T回溯的位置与该位置字符相同的问题
#include <stdio.h>
typedef char* String;
void get_nextval( String T, int *nextval )
{
int j = 0;
int i = 1;
nextval[1] = 0;
while( i < T[0] )
{
if( 0 == j || T[i] == T[j] )
{
i++;
j++; // 于普通KMP相比仅改动如下:
if( T[i] != T[j] ) // 若当前字符与前缀字符不同
{
nextval[i] = j; // j为nextval在i位置的值
}
else // 若当前字符与前缀字符相同
{
nextval[i] = nextval[j]; // 将前缀字符的nextval赋给i位置的nextval
}
}
else
{
j = nextval[j];
}
}
}
int Index_KMP( String S, String T, int pos )
{
int i = pos;
int j = 1;
int nextval[255];
get_nextval( T, nextval );
while( i <= S[0] && j <= T[0] )
{
if( 0 == j || S[i] == T[j] )
{
i++;
j++;
}
else
{
j = nextval[j];
}
}
if( j > T[0] )
{
return i - T[0];
}
else
{
return 0;
}
}
/* 测试 */
int main(){
char str[255] = "ababaaaba";
char str2[255] = "aaba";
int nextval[255];
int i = 1;
str[0] = 9;
str2[0] = 4;
get_nextval(str, nextval);
for(i=1;i<=9;i++){
printf("%d\t",nextval[i]);
}
printf("\n%d\n",Index_KMP(str,str2,1));
return 0;
}