在用计算机进行非数值处理问题时经常要用到串。串也是一种线性结构。与线性表不同的是:串的操作特点是一次操作干个数据元素,即一个子串。串可以用顺序存储结构和链式存储结构存储。串的顺序存储结构空间效率和时间效率都更高。模式匹配是串最重要和最复杂的一个操作。Brute – Force和KMP算法是两种最经常使用的串的模式匹配算法。
C语言的串函数
1. 串长度 int strlen(char*str)
2. 拷贝char *strcpy(char*str1,char*str2)
3. 比较int strcmp(char*str1,char*str2)
4. 字符定位char* strchr(char*str,char ch)
5. 子串查找char* strstr(char* s1,char* s2)
6. 连接 char* strcat(char*str1,char* str2)
例子1.名和姓的对换问题。英国和美国人的姓名是名在前姓在后,但在有些情况下,需要把姓名写成姓在前名在后中间加一个逗号的形式。编写一个程序实现把名在前姓在后的姓名表示法转换成姓在前名在后中间加一个逗号的姓名表示法 。
程序设计思想:因为C语言自动在串末尾添 加结束标志’\0’,所以实现方法 是:首先,把原姓名串name的空格改写为’\0’(注意此时结束标记’\0’后边,即指针p+1指示的,是原姓名串name的姓部分;此时的原姓名串name表示 的是原来姓名的名部分);然 后,把原姓名串name的姓部分、逗号和名部分分步添 加到新姓名串newName中,最后,恢复 原姓名串name为开始时的状态。
#include <string.h>
#include <stdio.h>
void ReverseName(char*name,char*newName)
{
char* p;
p = strchr(name,' '); //定位空格位置
*p = '\0'; //把空格替换为\0
strcpy(newName,p+1); //把姓复制到 新串
strcat(newName,","); //连接逗号
strcat(newName,name); //新姓名串newName
*p = ' '; //恢复修改的数据
}
void main(void)
{
char name[] = "William Topp",newName[30];
ReverseName(name,newName);
printf("ReverseName:%s\n",newName);
}
串的存储结构
串的存储结构有顺序存储结构和链式存储结构两种。由于串的顺序存储结构不仅实现各种操作方便 ,而且空间效率和时间效率都较高,所以更为常用。
串的顺序存储结构
1. 静态数组结构
串的静态数组是指用静态内存分配方法定义的数组。由于此时数组元素的个数是在编译时确定的,在运行时是不可改变的,所以也称为定长数组结构。串的静态 数组结构体可定义如下:
typedef struct
{
char str[MaxSize];
int length
} String;
其中,MaxSize表示数组元素的个数,str表示存储串值的数组名,length瑶池示串的当前长度,必须满足length<=MaxSize,String是为结构体定义的名字。
2. 动态数组结构
动态数组是指用动态内存分配方法定义的数组。静态数组结构中数组元素的个数是编译时确定的,而动态数组结构中数组元素的个数是在用户申请动态数组空间时才确定的。因此,动态数组结构体定义中要增加一个指出动态数组元素个数的域。串的动态数组结构体可定义如下:
typedef struct
{
char* str;
int maxLength;
int length;
} DString;
其中,str是动态数组的数组名,str指向动态数组的首地址,maxLength表示动态数组元素的最大个数,length表示串的当前长度,必须满足length<=maxLength,DString是为结构体定义的名字。由于动态数组在定义时不能直接给出数组的元素个数,所以动态数组的数组名和元素个数是分开定义的,分别用结构体的成员分量str和maxLength表示。
串的链式存储结构
1. 单字符结点链
单字符结点链就是每个结点的数据域只包括一个字符。单字符结点链的结构体定义如下:
typedef struct Node
{
char str;
struct Node* next;
}SCharNode;
在上述结构体定义中,每个字符域str所占的存储空间为1个字节,而每个指针域next所占的存储空间为2个或3个(根据机器不同而不同)字节。显然,单字符结点链的空间利用效率非常低
2. 块链
块链就是每个结点的数据域包括若干个字符。块链的结构体定义如下:
typedef struct Node
{
char str[Number];
struct Node* next;
} NCharNode;
其中,Number为每个结点数据域的字符个数。当Number数值比较大时,块链的空间利用效率比单字符结点链的空间利用效率要高很多。当Number数值比较大时,块链的空间利用效率和串的顺序存储结构的空间利用效率接近。
串基本操作的实现算法
一般来说,串的顺序存储结构比串的链式存储结构内存空间利用效率高,所以在此只讨论顺序存储结构。
1. 静态数组下串基本操作的实现算法(SString.h)
typedef struct
{
char str[MaxSize];
int length;
} String;
//初始化操作
void Initiate(String* S)
{
S->length = 0;
}
//插入子串操作
int Insert(String* S,int pos,String T)
{
//在串S的pos位置插入子串T
int i;
if(pos < 0 || pos > S->length)
{
printf("参数pos出错!\n");
return 0;
}
else if(S->length + T.length > MaxSize)
{
printf("数组空间不足无法插入!\n");
return 0;
}
else
{
for(i = S->length - 1;i >= pos; i--)
S->str[i + T.length] = S->str[i]; //依次后移数据元素
for(i = 0;i < T.length; i++)
S->str[pos + i] = T.str[i]; //插入数据
S->length = S->length + T.length; //产生新的串的长度
return 1;
}
}
//删除子串操作
int Delete(String *S,int pos,int len)
{
//删除串S从pos位置开始长度为len的子串值
int i;
if(S->length <= 0)
{
printf("数组中未存放字符无元素可删!\n");
return 0;
}
else if(pos < 0 || len < 0 || pos + len > S->length)
{
printf("参数pos和len不合法");
return 0;
}
else
{
for(i = pos + len; i <= S->length - 1; i++)
S->str[i-len] = S->str[i]; //依次前移数据元素
S->length = S->length - len; //产生新的串长度
return 1;
}
}
//取子串操作
int SubString(String S,int pos,int len,String*T)
{
//取串S从pos位置开始长度为len的子串值赋给串T
int i;
if(pos < 0 || len < 0 || pos + len > S.length)
{
printf("参数pos和len出错!\n");
return 0;
}
else
{
for(i = 0;i < len; i++)
T->str[i] = S->[pos + i]; //给子串T赋值
T->length = len; //给子串T的长度域赋值
return 1;
}
}
例1:设串采用静态数组存储结构,串s1=”Data”,串s2=”Structure”,要求编写程序实现把串s1插入到串s2的最开始位置,并显示串s2中的字符。
#include <stdio.h>
#define MaxSize 100
#include "SString.h"
void main(void)
{
String myString1 = {"Data ",5},
myString2 = {"Structure",9};
int i = 0;
Insert(&myString2,0,myString1);
for(i = 0;i < myString2.length; i++)
printf("%c",myString2.str[i]);
printf("\n");
}
3. 动态数组下口中基本操作的实现算法
串的动态数组结构体定义为:
typedef struct {
char * str;
int maxLength;
int length;
} DString;
与静态数组下串的基本操作相比,动态数组下串的基本操作要增加初始化操作和撤消操作。初始化操作用来建立存储串的动态数组空间以及给相关的数据域赋值 ,撤消操作用来释放动态数组空间。动态数组下插入子串操作,删除子串操作,取子串操作等与静态数组下串的这些操作差别很小。
DString.h源码
typedef struct
{
char * str;
int maxLength;
int length;
} DString;
//初始化
void Initiate(DString*S,int max,char* string)
{
int i;
S->str = (char*) malloc(sizeof(char)*max); //申请动态数组空间
S->maxLength = max; //置动态数组元素最大个数
S->length = strlen(string); //置串的当前长度值
for(i = 0;i < S->length; i++)
S->str[i] = string[i];
}
//插入子串操作
int Insert(DString*S,int pos,DString T)
{
//在串S的pos位置插入子串T
int i;
char* p;
if(pos < 0 || pos > S->length)
{
printf("参数pos出错!");
return 0;
}
else
{
if(S->length + T.length > S->maxLength)
{
//重新申请数组空间,原数组元素存放在新数组的前面
p = (char*)realloc(S->str,(S->length + T.length) * sizeof(char));
if(p == NULL)
{
printf("内存空间不足!");
return 0;
}
}
for(i = S->length - 1;i >= pos; i--)
S->str[i + T.length] = S->str[i]; //依次后移数据元素
// for(i = pos;i < S->length; i++)
// S->str[i] = T.str[i - pos];
for(i = 0;i < T.length; i++)
S->str[i + pos] = T.str[i]; //插入
S->length = S->length + T.length; //产生新的串长度值
return 1;
}
}
//删除子串操作
int Delete(DString*S,int pos,int len)
{
int i;
if(S->length <= 0)
{
printf("数组中未存放字符无元素可删!\n");
return 0;
}
else if(pos < 0 || len < 0 || pos + len > S->length)
{
printf("参数pos或len不合法");
return 0;
}
else
{
for(i = pos + len; i <= S->length - 1; i++)
S->str[i - len] = S->str[i]; //依次前移数据元素
S->length = S->length - len; //产生新的串长度值
return 1;
}
}
//取子串操作
int SubString(DString*S,int pos,int len,DString*T)
{
int i;
if(pos < 0 || len < 0 || pos + len > S->length)
{
printf("参数pos或len出错!\n");
return 0;
}
else
{
for(i = 0;i < len; i++)
T->str[i] = S->str[i + pos];
T->length = len;
}
}
//撤消操作
void Destroy(DString* S)
{
free(S->str);
S->maxLength = 0;
S->length = 0;
}
例2:编写一个程序测试上述动态 数组存储结构下串操作函数 的正确性。
设计:设动态数组存储结构下串抽象数据类型 的实现文件 为”DString.h”,程序如下
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include "DString.h" //包含动态数组的串操作文件
void main(void)
{
DString myString1,myString2,myString3;
int i,max1 = 10,max2 = 20,max3 = 10;
//测试初始化函数
Initiate(&myString1,max1,"Data");
Initiate(&myString2,max2,"Structure");
Initiate(&myString3,max3,"");
//测试插入函数
Insert(&myString2,0,myString1);
for(i = 0;i < myString2.length; i++) //DataStructure
printf("%c",myString2.str[i]);
printf("\n");
//测试删除函数
Delete(&myString2,0,5);
for(i = 0;i < myString2.length; i++) //tructure
printf("%c",myString2.str[i]);
printf("\n");
//测试取子串函数
SubString(&myString2,0,5,&myString3); //truct
for(i = 0;i < myString3.length; i++)
printf("%c",myString3.str[i]);
printf("\n");
//测试撤消函数
Destroy(&myString1);
Destroy(&myString2);
}
串的模式匹配算法
串抽象数据类型中讨论的查找操作也称作串的模式匹配操作。模式匹配操作的具体含义是:在主串(也称作目标串)S中,从位置start开始查找是否存在子串(也称作模式串)T,如在主串S中查找到一个与模式串T相同的子串,则函数返回模式串T的第一个字符在主串S中的位置;如在主串S中未查找到一个与模式串T相同的子串,则函数返回-1。(算法实现均用顺序存储结构)
Brute-Force算法
算法思想:从主串s=”s0,s1,…,s(n-1)”的第一个字符开始与模式串t=”t0,t1,…,t(m-1)”的第一个字符比较,若相等则继续比较后续字符;否则从主串s的第二个字符开始重新与模式串t的第一个字符比较,若相等则继续比较后续字符;否则,从主串s的第三个字符开始重新与模式串t的第一个字符比较;如果此不断继续,若存在模式串t中的每个字人均产值衣次与主串s中的一个连续字符序列相等,则匹配成功,函数返回模式串t的第一个字符在主串s中的下标;若比较完主串s的所有字符序列,不存在一个与模式串t相等的子串,则匹配失败,函数返回-1。
#include <stdio.h>
#define MaxSize 1000
#include "SString.h"
int BFIndex(String S,int start,String T);
void main(void)
{
String s = {"cddcdc",6};
String t = {"cdc",3};
printf("主串S:%s\n",s.str);
printf("子串T:%s\n",t.str);
printf("子串在主串的位置%d\n",BFIndex(s,0,t));
}
int BFIndex(String S,int start,String T)
{
/*
查找主串S从start开始的子串T,找到返回T在S中的开始字符下标,否则返回-1
*/
int i = start,j = 0,v;
while(i < S.length && j < T.length)
{
if(S.str[i] == T.str[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
}
if(j == T.length) v = i - T.length;
else v = -1;
return v;
}
KMP算法
KMP算法是三位学者在Brute-Force算法的基础上同时提出的模式匹配的改进算法。KMP算法的特点是:消除了Brute-Force算法的主串指针在相当多个字符比较相等后,只要有一个字符比较不相等便需要回退的缺点。
KMPIndex.h
参考:http://blog.youkuaiyun.com/buaa_shang/article/details/9907183
/*
查找主串S从start开始的子串T,找到返回T在S中首字符的下标,否则返回-1,
数组next中存放有模式串T的next[j]值
*/
/*
int KMPIndex(String S,int start,String T,int next[])
{
int i = start,j = 0,v;
while(i < S.length && j < T.length)
{
if(j == -1 || S.str[i] == T.str[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if(j == T.length) v = i - T.length;
else v = -1;
return v;
}
*/
int KMPIndex(String S,int start,String T,int next[])
{
int i = start,j = 0,v;
while(i < S.length && j < T.length)
{
if(S.str[i] == T.str[j])
{
i++;
j++;
}
else if(j == 0)
{
i++;
j++;
}
else
{
j = next[j];
}
}
if(j == T.length) v = i - T.length;
else v = -1;
return v;
}
/*
求子串T的next[j]值并存于数组next中
*/
void GetNext(String T,int next[])
{
int j = 1,k = 0;
next[0] = -1;
next[1] = 0;
while(j < T.length)
{
if(T.str[j] == T.str[k])
{
next[j + 1] = k + 1;
j++;
k++;
}
else if(k == 0)
{
next[j + 1] = 0;
j++;
}
else
{
k = next[k];
}
}
}
main
#include <stdio.h>
#define MaxSize 1000
#include "SString.h"
#include "KMPIndex.h"
void main(void)
{
String s = {"cddcdc",6};
String t = {"cdc",3};
int next[10];
GetNext(t,next);
printf("主串S:%s\n",s.str);
printf("子串T:%s\n",t.str);
printf("子串在主串的位置%d\n",KMPIndex(s,0,t,next));
}