串的定义与操作
1.有关术语:
- 串:由零个或多个字符组成的有限序列。
n个字符C1,C2,…,Cn组成的串记作:s=‘C1C2…C~~n’ n>=0
其中:s为串名, C1C2…Cn为串值 n为串长 - 空串:不含字符的串/长度为零的串。
C语言: s=" " ; - 空格串:仅含空格字符’ ’的串。
C语言: s1=’ ’ s2=’ ’ - 子串:串s中任意个连续的字符组成的子序列称为串s的子串。
主串—包含某个子串的串。
例 主串: st=“ABC123A123CD”
子串 :s1=“ABC” s3=“123A” s4=“ABCA”
s2=“ABC12” s5=“ABCE” s6=“321CBA”
2.串变量、字符变量的定义与使用:
例1 串变量
char st[]="abc\'*123"; //字符串数组定义
gets(st); scanf("%s",st); //字符串读入
strcpy(st,"data");//字符串函数
puts(st); printf(“st=%s\n”,st); //字符串读出
例2 字符变量
char ch='A'; //字符的定义
ch=’B’; //字符运算
ch=getchar(); //字符读入并赋值
scanf("%c",&ch); printf("ch=%c\n",ch); //字符读写
3.串的基本操作与串函数:
#inculde <string.h>
//c语言字符串处理函数
(1)strcpy(t,s)---s的串值复制到t中。
执行:strcpy(t,"data"); 有:t="data"
(2)strlen(s)----求s的长度
strlen("data*123")=8 strlen("")=0
(3)strcat(s1,s2)----s2的值接到s1的后面。
设 s1="data" , s2="123"
执行:strcat(s1,s2);
有: s1="data123" , s2="123"
(4)strcmp(s1,s2)---比较s1和s2的大小
若s1<s2,返回负整数 如:"ABC"<"abc"
若s1=s2,返回0如: 如:"abc"="abc"
若s1>s2,返回正整数 如:"ABCD">"ABC“
(5) strstr(s1,s2)----若s2是s1的子串,则指向s2在s1中第1次出现时的第1个字符的位置;否则返回NULL。
设 s1="ABE123*DE123bcd"
s2="E123"
有 strstr(s1,s2)=3
s1="ABE123*DE123"
(6)Replace(s,t,v)----置换
用v代替主串s中出现的所有与t相等的不重叠的子串
设 s="abc123abc*ABC", t="abc", v="OK"
执行:Replace(s,t,v);
有: s="OK123OK*ABC", t=”abc”, v="OK"
设 A="abcaaaaaABC", B="aa", C="aaOK"
执行:Replace(A,B,C);
有: A="abcaaOKaaOKaABC", B="aa", C="aaOK“
(7) StrInsert(s,i,t)----将t插入s中第i个字符之前。
设 s="ABC123"
执行: StrInsert(s,4,"**");
有: s="ABC**123"
(8)用Replace(s,t,v)实现删除
设 s="ABC//123"
执行:Replace(s,"//","")
有: s="ABC123"
(9)用Replace(s,t,v)实现插入
设 s="ABC123"
执行:Replace(s,"123","**123")
有: s="ABC**123"
串的存储表示和实现
1.顺序存储----用一维字符数组表示一个串的值:
- 运算举例:
例 联接运算: 给定串s1,s2,将s1,s2联接为串t,记作: Concat(t,s1,s2)
设 s1=”123”, s2=”ABCDE”
执行: Concat(t,s1,s2); — > 有: t=”123ABCDE”
实现Concat(t,s1,s2)的方法:
2.串的堆分配存储表示 - 提供一个足够大的连续存储空间,存放字符串的值。
例1: C语言中利用动态分配使用系统堆。
{
char *ps1,*ps2;int len;
scanf(”%d”,&len); //输入长度值
ps1=(char *)malloc(len); //ps1指向分配的存储空间
gets(ps1); puts(ps1); //输入一个串,再输出
ps2=(char *)malloc(80); //ps2指向已经分配的存储空间
strcpy(ps2,”abc123ok”) ; //赋值,再输出
puts(ps2);
free(ps1);free(ps2); //释放存储空间
}
堆存储结构描述,定义将串长作为存储结构的一部分:
typedef struct {
char *ch; //若是非空串,则按串长
//分配存储区,否则ch为NULL
int length; //串长度
} HString;
HString str;
用以表示字符串“abc123ok”
例2:字符串赋值操作
int StrAssign( HString *T, char *chars)
{ char *c; int i;
if (T->ch) free(T->ch); /*释放T原有空间*/
for(i=0,c=chars; *c; i++,++c); /*求chars的串长i*/
if (!i)
{T->ch=NULL;T->length=0;} /*当chars为空串时*/
else {
if (!(T->ch=(char *) malloc(i*sizeof(char))))
return OVERFLOW; //无空间可分配
T->length=i;
for(; i>0; i--) /*复制chars串值到串T*/
T->ch[i-1]=chars[i-1];
}
return OK;
例3:输出字符串
void StrPrint(HString T)
{
int i;
for(i=0;i<T.length;i++)
putchar(T.ch[i]);
}
void main(void)
{
HString str;
StrAssign(&str,”abcd123”);
StrPrint(str);
}
3.串的单链表表示
例1 一个结点只放1个字符
存储密度为 0.33
struct node1
{ char data; //为一个字符
struct node1 *next; //为指针
}*ps1;
例2 一个结点放4个字符
存储密度为 0.67
struct node4
{ char data[4]; //为4个字符的串
struct node4 *next; //为指针
}*ps2;
- 存储密度=串值所占存储位/实际分配存储位
- 假设单链表数据元素本身的存储量为N,指针域所占的存储量为M,则存储密度为:N/(N+M)。
模式匹配
- 朴素匹配算法:
- 主串:S[ ] = ‘ababcabcacbab’
子串:T[ ] = ‘abcac’
朴素匹配算法:约定S[0](主串),T[0](子串) 存放串长
//即 从1开始存字符
int Normal(String S, String T, int pos)
{
int i,j;
i=pos;j=1; //pos 开始匹配的位置
while(i<=S[0] &&j<=T[0])
{
if(S[i]==T[j]){i++;j++;}
else{i=i-j+2; j=1;}
//若遇到不相同的字符 则T的首元素与S上次开始匹配的元素的下一个元素开始匹配
}
if(j>T[0]) //匹配成功 j比子串长度多一
return i-T[0]; //匹配成功后i对应T末字符
故i-T[0]为 s匹配成功的首元素的位置
else
return -1; //无该子串
}
-
一般情况
效率可以近似认为O(m+n) -
极端特殊情况
算法时间复杂度为O((n-m+1)*m)
*KMP算法
观察上述过程可知:
- 1、T串中首字符a 与后面字符都是不相等的
2、T串中的a与S串后面的b, c, d, e也都可以在第一次匹配之后就确定是不相等的
结论:第二、三、四、五次匹配没有必要,只需保留第一、六次匹配,也即 i 的值没有必要回溯
- 问题:T串后面含有首字符a怎么办?
- 主串: abcababcabc
模式串: abcabx
KMP算法原理: - 假设现在文本串S匹配到 i 位置,模式串T匹配到 j 位置
- 如果当前字符匹配成功(即S[i] == T[j]),则令i++,j++,继续匹配下一个字符;
- 如果当前字符匹配失败(即S[i] != T[j]),则令 i 不变,j = next[j] (next[j] <= j – 1)。此举意味着失配时,模式串T相对于文本串S向右移动了j - next [j] (至少为1) 位。
next[]算法:
void Get_Next(String T, int *next)
{
int i,j;
i=1; j=0;
next[1]=0;
while(i<T[0])
{
if(j==0||T[i]==T[j])
{i++; j++; next[i]=j;}
else{j=next[j];} //若字符不相同,则j值回溯
}
KMP算法:
int KMP(String S, String T, int pos)
{
int i=pos,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 -1;
}
复杂度分析
- Get_Next时间复杂度为O(m)
- KMP内部while循环时间复杂度O(n)
- 故而整个算法的时间复杂度为O(n+m)
**更多模式匹配算法
- oyer-Moore算法
这个算法KMP算法的不同点是在作s[k+1…k+m]与t[1…m]的匹配测试时是从右到左,而不是从左到右。 - Rabin-Karp算法
这个算法用到数论中诸如两个整数关于第三个整数取模的等价性等初等概念。 - Sunday算法