**
**
串的定义:串是由任意多个字符组成的有限数列,可以为数字、字母、其他。
通常一个串含有(主串与子串),而子串在主串中的位置以子串在主串中的第一个字符位置来表示。而当串中元素为空格时为空串,串操作一般用于文本编辑。它与一般数据结构的区别在于其数据类型为 “字符集”。
【串的分类】:按存储方式:顺序存储(定长顺序)、链式存储与堆分配存储。
【顺序存储】:用一确定长度的字符数组存储串中的字符序列,串的长度不能大于数组的存储空间,对于超出长度的进行舍去。定长顺序表示在C语言中,可通过定义字符数组进行存储,在通过函数strlen统计字符个数。注意字符数组的结束标志 ‘/0’ 会占据一个字节的存储空间, 或者通过定义一个变量Length来存放串长。
【链式存储】:串结构与线性结构类似都可以通过链表进行存储,对于一个节点存入多个储数据元素的链式存储模式称为“块链”。这种存储结构通常不设有头结点和双链结构,但链尾会有设有指针,其长度表示可以通过定义指针变量存储。
#define StringSize 100
typedef struct{
char str[StringSize]; //存储空间
struct StringC *next; //指针域
}StringC;
typedef struct {
StringC *head, *tail; //头结点与尾指针
int length; //定义一个存储长度
}
}
【块链的表示】:块链的存储通常用密度表示,密度的大小与节点大小和存储方式有关,节点大小的调整通常用让一个节点存储多个串值,而针对于串长不等于节点大小时通常会填充一些特殊字符。但串的密度太大通常会引起数据移动的不便。
【堆存储表示】:通过堆内存的动态分配函数malloc()与 free(),为新串分配一定长度的存储空间。
【简单串的存储结构】:
#define StringSize 100;
typedef struct {
char str[StringSize];
int length; //串长度
}chString;
【基本运算】
1-串的赋值–(Sassign).
2-串是否为空–(Sempty)
3-串长度的判断–(Slength)
4-串的复制 --(Scopy)
5-串的比较大小–(Scompare)
6-串的插入–(Sinsert)
7-串的删除(第几个位置与多少长度)–(Sdelete)
8-串的连接–(Sconct)
9-串的清空–(Sclear)
【1-串的赋值–(Sassign)】
void Sassign(chString *S,char cstr[]){
int i=0;
for(i=0;cstr[i]!='\0';i++)
S->str[i]=cstr[i]; //将数组cstr[]中的元素赋值给S
S->length=i;
}
【 2-串是否为空–(Sempty)】
int StrEmpty(chString S){
if(S.length==0)
return 1;
else
return 0;
}
【3.串长度的判断–(Slength)】
int Slength(chString S){
return S.length;
}
【4-串的复制 --(Scopy)】
void Scopy(chString *T;chString S){
int i;
for(i=0;i<S.length;i++) //将串S的字符串赋值给T;
T->str[i]=S.str[i]; //将串S的字符复制给T;
T->length=S.length; //将串s的长度赋值给T;
}
【5-串的比较大小–(Scompare)】
int Scompare(chString S, chString T)
{
int i;
for(i=0;i<S.length && i<T.length;i++)
if(S.str[i] !=T.str[i]) //比较两个字符是否相等
return (S.str[i]-T.str[i]); //返回两个字符的ASCII码值;
return (S.length-T.length); // 比较完毕则返回两个串的长度差值。
}
【6-串的插入–(Sinsert)】
int Sinsert (chString *S,int pos,chString T){
int i;
if(pos<0|| pos-1>S->length){
printf("插入的位置不正确\n");
return 0;
}
if(S->length+T.length<=StringSize) { //在存储空间足够的情况下
for(i=S->length+T.length-1;i>=pos+T.length-1;i--)
//在插入子串T前将串S中的第pos位置后的字符移动len个位置
S->str[i]=S->str[i-T.length];
for(i=0;i<T.length;i++)
s->str[pos+i-1]=T.str[i]; //将串T插入串S中
S->length=S->length+T.length;
}
else if(pos+T.length<=StringSize)
{ //或者串T可以被完全插入到串S中,但会导致串S中的部分字符被舍去
for(i=StringSize;i>T.length+pos-1;i--)
S->str[i]=S->str[i-T.length]; //将pos以后的字符整体移动到最后
for(i=0;i<T.length;i++)
S->str[i+pos-1]=T.str[i];
S->length=StringSize;
return 0;
}
//又或者串T不可以被完全插入到串S中,则将串T中的部分字符被舍去
else{
for(i=0;i<StringSize;i++) //将串T直接插入到串S中
S->str[i+pos-1]=T.str[i];
S->length=StringSize;
return 0;
}
}
【7-串的删除(第几个位置与多少长度)–(Sdelete)】
int Sdelete(chString *S,int pos, int len){
//在串S中删除pos位置开始的len个长度的字符
int i=0;
if(pos<0||len<0||pos+len-1>S->length){
printf("删除位置不正确\n");
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;
}
}
【8-串的连接–(Sconcat)】
int Sconcat(chString *T,chString S){ //将串S连接到串T的末尾,且空间充足
int i,flag;
if(T->length+S.length<=StringSize){
for(i=T->length;i<T+length+S.length;i++)
T->str[i]=S.str[i-T->length];
T->length=T->length+S.length; //修改串长
flag=1; //修改标志
}
else if(T->length<StringSize){ 、
//连接时串长大于StringSize,则将S的部分字符进行舍去
for(i=T->length;i<StringSize;i++)
T->str[i]=S.str[i-T->length]; //修改串长
T->length=StringSize;
flag=0; //修改标志
}
return flag;
}
【9-串的清空–(Sclear)】
void Sclear(chString *S){
S->length=0; //将长度置为0
}
【串的模式匹配】即查找主串中子串的位置的定位操作,常用算法有
Brute-Force朴素模式匹配算法与KMP算法。
【1-Brute-Force算法】通常称其为模式匹配(暴力算法),在一个主串T(目标串)与子串P(模式串)中找到一个与子串P相等的串,并返回P(模式串)串在T串(目标串)的位置。
【Brute-Force算法的主要实现过程】:例如在主串T=”abcdefg“从第pos个字符起与子串P=“def”进行模式匹配,如主串的第一个字符起与子串的第一个字符进行匹配,倘若字符相等,则继续往对后一个字符进行比较,如果字符不等,则回溯子串,继续让它与主串的下一个字符进行比较,如此往复。或者通过(主串)两头比较法提升效率但还需注意中间不匹配的情况。
int B_Findex(chString T,int pos, chString P, int *count)
{ int i,j;
i=pos-1;
j=0;
*count=0; //记录比较次数
while(i<T.length&& j<P.length){
if(T.str[i]==P.str[j]) //匹配串则继续后续字符比较
{
i++;
j++;
}
else{
i=i-j+1; //主串下标
j=0; //模式串下标回溯
}
(*count)++; //记录一次比较次数
}
if(j>=P.length)
return i-j+1; //匹配成功返回模式串在主串的位置
else
return -1; //匹配失败
}
【两头匹配】
int frontandread( chString T, chString P, int pos){
int startpos=pos; //设pos=0
while(startpos<T.Slength()-P.Slength()){
int front=0, rear=P.Slength()-1;
while(front<=rear){
if(T.str[startpos+front] !=P.str[front]||T.str[startpos+rear] !=P[rear])
//主串头不等于模式串的头,或主串尾不等于模式串的尾
break;
else{ //头尾两边逼近
front++;
rear--;
}
}
if(front>rear)
return stratpos; //匹配成功
else
++starpos;
return -1; //匹配失败
}
}
【KMP算法】它是由 D.E.Knuth. J.H.Morris.、V.R.Pratt共同提出,其思路是
当遇到字符不匹配时可以通过滑动若干个字符长度进行下一次的匹配,滑动的距离与T(主串)无关,移动位数=已匹配的字符数-next[i]的函数值,其(核心)取决于next[i]的函数值。
【求next函数值】:设令next[i]=k,当模式串第 i 个字符与主串对应的字符不相等时,需要将字符串滑动到next[i]个字符位置,并重新与主串字符进行比较。
【真前缀与前缀、子串与真子串、其实类似于集合的子集与真子集】
【KMP的匹配过程】
int KMP(chString T, int pos, chString P,int next[],int *count){
int i,j;
i=pos-1;
j=0;
*count=0;
while(i<T.length &&j<P.length)
{
if(j==-1|| T.str[i]==P.str[j]){
i++; //如果匹配成功或者j==-1则继续后续比较
j++;
}
else //否则字符串滑动
j=next[j];+
(*count)++; //记录比较次数
}
if(j>=P.length)
return i-P.length+1; //匹配成功,返回子串位置
else
return -1; //匹配失败
}
【归纳next[ ]的求职过程 (i与j跟代码中的演示的不一样,个人笔记,注意分开理解)】
另外在next[i]=-1时,可能在P[0-k]==p[i-k~i ](且P[k]=P[i])间存在不等字符。
【next值】
void get_next(const chString P,int next[]){
next[0]=-1; //由next[0]=-1开始递推
int i=0,k=-1;
while(i<P.Slength()-1){
if(k==-1){
next[i+1]=0; //由p[k]==p[i],next[i+1]=next[j]+1=k+1
i=1,k=0;
}
}
}
【next[]值的优化】:
void get_next( chString *P,int next[]){
int i,k;
int j=0;
k=-1;
next[0]=-1;
while(j<P->length){
if(k==-1 || P->str[j]==P->str[k]){
j++; //当前字符匹配成功,则继续比较,并且把next[]值存入next数组中
k++;
next[j]=k;
}
else
k=next[k]; //若当前的字符不匹配则继续下一个
}
}
【综上所述–算法效果】
#include
#include
#include
#include
//存储结构
#define StringSize 100
typedef struct {
char str[StringSize];
int length; //串长度
}chString;
//串的赋值
void Sassign(chString *S,char cstr[]){
int i=0;
for(i=0;cstr[i]!='\0';i++)
S->str[i]=cstr[i]; //将数组cstr[]中的元素赋值给S
S->length=i;
}
//串空判别
int SEmpty(chString S){
if(S.length==0)
return 1;
else
return 0;
}
//串长判别
int Slength(chString S){
return S.length;
}
//串的复制
void Scopy(chString *T,chString S){
int i;
for(i=0;i<S.length;i++) //将串S的字符串赋值给T;
T->str[i]=S.str[i]; //将串S的字符复制给T;
T->length=S.length; //将串s的长度赋值给T;
}
//串的比较
int Scompare(chString S, chString T)
{
int i;
for(i=0;i<S.length && i<T.length;i++)
if(S.str[i] !=T.str[i]) //比较两个字符是否相等
return (S.str[i]-T.str[i]); //返回两个字符的ASCII码值;
return (S.length-T.length); // 比较完毕则返回两个串的长度差值。
}
//串的插入
int Sinsert (chString *S,int pos,chString T){
int i;
if(pos<0|| pos-1>S->length){
printf("插入的位置不正确\n");
return 0;
}
if(S->length+T.length<=StringSize) { //在存储空间足够的情况下
for(i=S->length+T.length-1;i>=pos+T.length-1;i--)
//在插入子串T前将串S中的第pos位置后的字符移动len个位置
S->str[i]=S->str[i-T.length];
for(i=0;i<T.length;i++)
S->str[pos+i-1]=T.str[i]; //将串T插入串S中
S->length=S->length+T.length;
}
else if(pos+T.length<=StringSize)
{ //或者串T可以被完全插入到串S中,但会导致串S中的部分字符被舍去
for(i=StringSize;i>T.length+pos-1;i--)
S->str[i]=S->str[i-T.length]; //将pos以后的字符整体移动到最后
for(i=0;i<T.length;i++)
S->str[i+pos-1]=T.str[i];
S->length=StringSize;
return 0;
}
//又或者串T不可以被完全插入到串S中,则将串T中的部分字符被舍去
else{
for(i=0;i<StringSize;i++) //将串T直接插入到串S中
S->str[i+pos-1]=T.str[i];
S->length=StringSize;
return 0;
}
}
//串删除
int Sdelete(chString *S,int pos, int len){
//在串S中删除pos位置开始的len个长度的字符
int i=0;
if(pos<0||len<0||pos+len-1>S->length){
printf("删除位置不正确\n");
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;
}
}
//串S连接到串T的末尾
int Sconcat(chString *T,chString S){ //将串S连接到串T的末尾,且空间充足
int i,flag;
if(T->length+S.length<=StringSize){
for(i=T->length;i<T->length+S.length;i++)
T->str[i]=S.str[i-T->length];
T->length=T->length+S.length; //修改串长
flag=1; //修改标志
}
else if(T->length<StringSize){
//连接时串长大于StringSize,则将S的部分字符进行舍去
for(i=T->length;i<StringSize;i++)
T->str[i]=S.str[i-T->length]; //修改串长
T->length=StringSize;
flag=0; //修改标志
}
return flag;
}
//
void Sclear(chString *S){
S->length=0; //将长度置为0
}
void printarray(chString P,int next[],int length){
//模式串的输出
int j=0;
printf("输出模式串\n");
for(j=0;P.str[j]!='\0';j++)
printf("%c\n",P.str[j]);
}
//返回主串T的第pos位置开始查找子串P,返回位置。
int B_Findex(chString T,int pos, chString P, int *count)
{ int i,j;
i=pos-1;
j=0;
*count=0;
while(i<T.length&& j<P.length){
if(T.str[i]==P.str[j])
{
i++;
j++;
}
else{
i=i-j+1;
j=0;
}
(*count)++;
}
if(j>=P.length)
return i-j+1;
else
return -1;
}
//kmp算法
int KMP(chString T, int pos, chString P,int next[],int *count){
int i,j;
i=pos-1;
j=0;
*count=0;
while(i<T.length &&j<P.length)
{
if(j==-1|| T.str[i]==P.str[j]){
i++;
j++;
}
else
j=next[j];
(*count)++;
}
if(j>=P.length)
return i-P.length+1;
else
return -1;
}
//next[]函数值
void get_next( chString *P,int next[]){
int i,k;
int j=0;
k=-1;
next[0]=-1;
while(j<P->length){
if(k==-1 || P->str[j]==P->str[k]){
j++;
k++;
next[j]=k;
}
else
k=next[k];
}
}
/*【基本运算】
1-串的赋值--(Sassign).
2-串是否为空--(Sempty)
3-串长度的判断--(Slength)
4-串的复制 --(Scopy)
5-串的比较大小--(Scompare)
6-串的插入--(Sinsert)
7-串的删除(第几个位置与多少长度)--(Sdelete)
8-串的连接--(Sconct)
9-串的清空--(Sclear)*/
//主函数
int main (){
chString T,P;
int i,j,k;
int count1=0, count2=0,find;
int next[50];
char b[40] = "cabaadcabaababaabacabababab";
char c[30] = "abaabacababa";
Sassign(&T,b);
Sassign(&P,c);
//仅演示算法运行效率
printf("\n主串T:\t");
for(j=0; j<T.length;j++)
printf("%c",T.str[j]);
printf("\n输出模式串\n");
printf("\n模式串P:\t");
for(i=0; i<P.length;i++)
printf("%c",P.str[i]);
printf("\n赋值成功\n");
get_next(&P,next);
printf("\n输出next[i]的函数值\n");
for(k=0;k<P.length;k++)
printf("%4d",next[k]);
//B_F
if(B_Findex(T,1, P, &count1)>0)
printf("\nB_F算法使用成功\n");
printf("\nB_F的比较次数为%d\n",count1);
//KMP
if( (KMP(T, 1, P,next,&count2))>0){
printf("\nKMP算法运行成功\n");
printf("KMP的比较次数为%d\n",count2);
}
}
【注】个人学习笔记,仅供参考!!!如果对你有帮助就请给个赞吧!
–happy–new-year–
【参考文献】
《C/C++数据结构与算法速学速用大辞典》----陈锐等。
《C和指针》、《C语言语法详解》。