数据结构与算法(串)
目录
一. 串及其基本运算
1.串的基本概念
2.串的基本运算
二. 案例引入
三. 串的存储结构
1.串的顺序存储结构
2. 串的链式存储结构
四. 模式匹配
一. 串及其基本运算
1.串的基本概念
所谓串(String)指的是字符串。串的处理在文本编辑、信息检索以及自然语言翻译等方面有重要的应用。
串是由多个或零个字符组成的有限序列。
(4-1)
- (1)串长:一个串中包含字符元素个数称为串的长度,简称串长,即在式(4-1)中的n。
- (2)空串:当一个串中没有字符元素时称该串为空串,即在式(4-1)中n=0。
- (3)合法字符:在不同的编程语言下合法字符有不同的规定。通常英文字母,阿拉伯数字以及常用的标点符号都是合法字符。
- (4)两个串相等:如果两个串的串长相等,并且对应位置上的字符元素也相等,则称这两个串相等。
- (5)子串:一个串中的连续字符组成的新串称为原串的子串。n个字符构成的串,假设每个字符都不一样,共有 n(n+1)/2+1 个子串。
- (6)字符在串中的位置:某字符在串中的编号称为该字符在串中的位置,即在式(4-1)中的i。
- (7)子串在串中的位置: 某子串的第一个字符元素在原串中的位置称为该子串在串中的位置。
【例4-1】设A,B,C,D分别为如下形式的串: A=“SOFTWARE”; B=“SOFT WARE” C=“SOFT”; D=“”;
(1)A,B,C,D各个串的长度分别是:8,9,4和0;
(2)串C和串D均是串A的子串;
(3)子串C在串A中的位置为1;
(4)串A和串B不相等。 注意:空串的长度为0,并且是任意串的子串
2.串的基本运算
- 串赋值 StrAssign(&s, constantchar)
初始条件:s是一个串变量,constantchar是一个串常量或者是一个被赋值的串变量。
操作结果:串变量s的值被赋值成constantchar。
- 串比较 StrCompare(s, t)
运算条件:自左向右逐个字符相比,如果字符的ASCII码相等,则继续比较下一个位置的字符,直到出现对应位置字符的ASCII码不相等或者其中一个串已经比较完所有字符为止。
操作结果:如果s=t,则返回值为0;如果s>t,则返回值为1;如果s<t,则返回值为-1。
- 求串长 StrLength(s)
初始条件:串s存在。
操作结果:返回值为串s中字符的个数
- 连接串 StrConcat(s, t)
初始条件:串s和串t均存在。
操作结果:返回值为s和t连接在一起的新串。
- 生成新串 StrSub(s, i ,j)
初始条件:串s存在,n为字符串长度。
操作结果:返回从串s的第i个字符开始的j个字符,形成一个新的子串。
二. 案例引入
串的应用实例
1.信息加密
2.用户登陆验证
三. 串的存储结构
1.串的顺序存储结构
串的紧缩格式节省内存,但是不容易分离出来单个字符,导致在处理单个字符的时候不方便;串的非紧缩格式相对占用内存空间较大,但是容易处理单个字符。后面的讨论都是基于非紧缩格式。
首先顺序串的类型定义如下:
typedef struct
{char data[MaxSize]; //字符数组int length; //表示串长
}SqStr;
1.串赋值
【算法步骤】(1)当t[i]不等于'\0'时,依次将t[i]赋值给s.data[i]。(2)当t[i]等于'\0'时,结束循环,此时的i值是字符串长度,将其赋值给s.length。【算法描述】
void StrAssign(SqStr &s,char t[]) //s设置为引用类型参数
{int i;for (i=0;t[i]!='\0';i++) //设置字符数组元素值s.data[i]=t[i];s.length=i; //设置串长
}
【算法分析】
串赋值算法的时间复杂度为O(n),其中n为数组t中的字符个数。
2.串比较
【算法描述】
int StrCmpare(SqStr s,SqStr t)
{int i,len;if (t.length>s.length) len=s.length; else len=t.length; //len保存两个串的长度较小值for (i=0;i<len;i++) if (s.data[i]>t.data[i]) //判断两字符对应位置ASCII码大小return 1;else if (s.data[i]<t.data[i])return -1;if (s.length==t.length) //循环结束后,若没有结果,则比较两字符的串长return 0;else if (s.length>t.length) return 1;else return -1;
}
【算法分析】
平均时间复杂度为O(n)
3.求串长
【算法步骤】
由于在设计顺序串的类型定义方式时,用length保存了串的长度,所以在算法中只需返回length的值即可。
【算法描述】
int StrLength(SqStr s)
{return s.length;
}
【算法分析】
该算法的时间复杂度为 O(1)。
4.串连接
【算法步骤】
(1)定义保存连接之后串的变量r,并令其长度为s.length+t.length。
(2)将串s中的字符依次保存在r.data[i]中,i的取值范围为0~ s.length-1。
(3)将串t中的字符依次保存在r.data[i]中,i的取值范围为s.length ~ s.length+t.length-1。
【算法描述】
SqStr StrConcat(SqStr s,SqStr t)
{SqStr r;int i;r.length=s.length+t.length; //新串的长度为两串之和for (i=0;i<s.length;i++) //将串s中的字符依次保存在r.data[i]中r.data[i]=s.data[i];for (i=0;i<t.length;i++) //将串t中的字符依次保存在r.data[i]中r.data[s.length+i]=t.data[i];return r;
}
【算法分析】
该算法中存在两个并列循环,根据时间复杂度求和定理,该算法的时间复杂度为两个循环执行时间的较大者,所以该算法的时间复杂度为O(n),n为s.length和t.length的较大值。
5.求子串
【算法步骤】
(1)定义一个保存子串的变量r,令其长度为0。
(2)判断如果满足条件(i<=0 || i>s.length || j<0 || i+j-1>s.length),则表示输入有误,直接返回r。
(3)通过循环结构将依次将s.data[n]赋值给r.data[n-i+1],n的取值范围为i-1至i+j-2。
【算法描述】
SqStr StrSub(SqStr s,int i,int j)
{SqStr r;int n;r.length=0;if (i<=0 || i>s.length || j<0 || i+j-1>s.length) //判断是否输入有误return r; for (n=i-1;n<i+j-1;n++) r.data[n-i+1]=s.data[n]; // n的取值范围为i-1至i+j-2r.length=j;return r;
}
【算法分析】
该算法的时间复杂度为O(n),n为子串的长度。
【例4-3】设有两个字符串“TAIZHOU”,“TAI”。试用顺序存储方式实现这两个字符串的串比较、串连接以及求子串操作。 参考主函数代码: int main() {int i;SqStr s1,s2,s3,s4;char t[]=“TAIZHOU";char r[]=“TAI";StrAssign(s1,t); //调用串赋值算法StrAssign(s2,r); //调用串赋值算法printf("%d\n",StrCmpare(s1,s2)); //调用串比较算法s3=StrConcat(s1,s2); //调用串连接算法for(i=0;i<StrLength(s3);i++) //调用求串长算法{printf("%c",s3.data[i]);}printf("\n");s4=StrSub(s1,1,3); //调用求子串算法for(i=0;i<StrLength(s4);i++) //调用求串长算法{printf("%c",s4.data[i]);}printf("\n");return 0; }
2. 串的链式存储结构
所谓链式存储结构是指采用链表的形式来保存一个串。这和前面学习的链表的操作有很多相似的地方,每一个结点由数据域和指针域组成,区别在于串的链式存储结构中结点的数据域可以存储多个字符,其个数称为结点大小。
首先结点类型定义如下:
typedef struct Strnode
{ char data; //字符struct Strnode *next; //指针
} LStrNode;
2.串比较
int StrCmpare (LStrNode * s, LStrNode * t)
{LStrNode *p,*q;p=s->next;q=t->next;while(p!=NULL&&q!=NULL) //满足循环条件时,逐个比较对应结点的data值{if(p->data==q->data){p=p->next;q=q->next;}else if( p->data<q->data)return -1; else return 1;}if(p==NULL&& q==NULL) //循环结束,若没得到结果,则判断p,q的位置return 0;else if(p==NULL&& q!=NULL)return -1;elsereturn 1;
}//该算法的平均时间复杂度计算方式和顺序串比较算法相似,为O(n),n为串s和t中长度的较小值。
3.求串长
【算法步骤】
(1)定义指针变量p指向链串s的首结点。
(2)当p不等于NULL时进入循环,通过变量i记录进入循环的次数。
(3)当p等于NULL时结束循环,此时变量i中保存的数值即为串长。
【算法描述】
int StrLength(LStrNode *s)
{int i=0;LStrNode *p=s->next; //p指向链串s的首结点while (p!=NULL) { i++;p=p->next;}return i; //i保存计数结果
}
【算法分析】
串赋值算法的时间复杂度为O(n),其中n为链串中字符个数。
4.串连接
LStrNode *StrConcat(LStrNode *s,LStrNode *t)
{LStrNode *str,*p=s->next,*q,*r; //p指向链串s的首结点str=(LStrNode *)malloc(sizeof(LStrNode));r=str;while (p!=NULL) { q=(LStrNode *)malloc(sizeof(LStrNode));q->data=p->data;r->next=q;r=q; //尾插法p=p->next;}p=t->next; //p指向链串s的首结点while (p!=NULL) { q=(LStrNode *)malloc(sizeof(LStrNode));q->data=p->data;r->next=q;r=q; //尾插法p=p->next;}r->next=NULL;return str;
}//该算法的时间复杂度为O(n),n为链串s和t中长度的较大值。
5.求子串
【算法描述】
LStrNode *StrSub(LStrNode *s,int i,int j)
{int k;LStrNode *str,*p=s->next,*q,*r;str=(LStrNode *)malloc(sizeof(LStrNode));str->next=NULL;r=str; if (i<=0 || i>StrLength(s) || j<0 || i+j-1>StrLength(s)) //判断是否输入有误return str; for (k=1;k<i;k++) //p指向链串s第i个结点p=p->next;for (k=1;k<=j;k++) //将链串s的从i开始的j个结点依次利用尾插法存入str{ q=(LStrNode *)malloc(sizeof(LStrNode));q->data=p->data;r->next=q;r=q;p=p->next;}r->next=NULL;return str;
}//该算法中存在两个并列的循环结构,所以时间复杂度为O(n),n两个循环次数较大者。
例4-4】设有两个字符串“TAIZHOU”,“TAI”。试用链式存储方式实现这两个字符串的串比较、串连接以及求子串操作。 参考主函数代码: int main() {LStrNode* s1,*s2,*s3,*s4,*p;char t[]=" TAIZHOU ";char r[]=" TAI ";StrAssign(s1,t); //调用串赋值算法StrAssign(s2,r); //调用串赋值算法printf("%d\n",StrCmpare(s1,s2)); //调用串比较算法s3=StrConcat(s1,s2); //调用串连接算法p=s3->next;while(p!=NULL){printf("%c",p->data);p=p->next;}printf("\n"); s4=StrSub(s1,1,4); //调用求子串算法p=s4->next; while(p!=NULL){printf("%c",p->data);p=p->next;}printf("\n");return 0; }
四. 模式匹配
模式匹配是是指给定一个串n,判断另外一个串m中是否存在和串n相等的子串,其中串n称为模式串,串m称为目标串。
若存在,则称匹配成功,反之称为匹配失败。
一种传统的模式匹配方法称为BF(Brute-Force)算法,也称为朴素匹配或者简单匹配算法。
算法的基本思想是从目标串的第一个字符开始逐个与模式串进行比较,出现不一致的时候则从目标串的下一个字符开始再次逐个与模式串进行比较,依次类推直到匹配成功或者失败为止。
朴素算法(Brute-Force(BF)暴力算法)
如果两个指针(i,j)指向的元素相同则指针后移,不相同则需要回退指针(主串指针回退到上次匹配首位的下一个位置,子串指针回退到开头位置),重复进行上述操作直到主串指针指向主串末尾,即如下所示:
【算法描述】
int BFfuction(SqStr m, SqStr n)
{int i=0, j=0;while(i<m.length&&j<n.length) { //当前两个字符相等if (m.data[i]== n.data[j]){i++;j++;}else//当前两个字符不相等{ i=i-j+1;j=0;}}if (j>=n.length)return (i-n.length);else return (-1);
}
除了BF算法,还有哪些模式匹配算法?
KMP,KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。 KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。