串,即字符串(String)是由零个或多个字符组成的有限序列
例如:S=“Hello World!”;有的地方用双引号(Java、C);有地方用单引号(Python)
子串——任意个连续的字符组成;主串——包含子串的串
空串——无字符;空格串——字符是空格,每个空格字符占1 B
字符在主串中的位置:字符在串中的序号;子串在主串中的位置:子串的第一个字符在串中的序号
(注意:序号是从1开始,而不是0开始)
串 VS 线性表:
串是一种特殊的线性表,数据元素之间呈现线性关系
串的数据对象:字符集(如中文字符、英文字符、数字字符、标点字符等)
串的基本操作:通常以子串为操作对象,如增删改查;Index(S,T)——定位操作;StrCompare (S,T)——比较操作
串的比较操作:先出现更大的符号的串更大(ASCII码:小写字母97>大写字母65);长串前缀与短串相同时,长串更大
拓展——乱码问题:在文件中,原本采用的是一套编码规则;打开文件时,软件以为采用的是另一套编码规则
静态数组实现——定长顺序存储(函数执行结束后,系统自动回收)
动态数组实现——堆分配存储(用完需要手动free)
#define MAXLEN 255
typedef struct{
char ch[MAXLEN];
int length;
}SString;
typedef struct{
char *ch;
int length;
}HString;
保存字符串的长度数据
方案一:变量length放在数组的末尾
方案二:变量length放在数组的开头ch[0]
优点:字符的位序和数组下标相同;缺点:ch[0]为char型,只能放下1B=8bit,表示范围0-255
方案三:没有length变量,以符号’\0’表示结尾(对应ASCII码的0)
方法:遍历到’\0’计算length,适用于不常访问字符串长度的情况
方案四:ch[0]废弃不用,末尾声明变量length(结合方案一、二)
typedef strut StringNode{
char ch;
struct StringNode *next;
}StringNode,*String;
typedef strut StringNode{
char ch[4]; //每个结点存放多个字符
struct StringNode *next;
}StringNode,*String;
#define MAXLEN 255
typedef struct{
char ch[MAXLEN];
int length;
}SString;
bool SubString(SString &Sub,SString S,int pos,in len){
if(pos+len-1>S.length)
return false;
for(int i=pos;i<pos+len;i++)
Sub.ch[i-pos+1]=S.ch[i]; //此时子串的位置为i-pos+1
Sub.length=len;
return true;
}
int StrCompare(SString S,SString T){
for(i=1;i<=SString S&&i<=SString T;i++){ //此处结束长度为两串中较短的
if(S.ch[i]!=T.ch[i])
return S.ch[i]-T.ch[i]; //扫描过程,return为直接将值相减
}
return S.length-T.length; //扫描结束,长度较长的更大
}
int Index(SString S,SString T){
int i=1,n=StringLength(S),m=StringLength(T);
SString sub;
while(i<=n-m+1){ //前面定义i初始为1,所以+1
SubString(Sub,S,i,m); //i初始必为1,在主串里取一个长度相同的子串
if(StrCompare(sub,T)!=0) //比较两个子串是否一样
i++; //不一样就后移继续比较
else
return i; //一样就返回子串位置
}
return 0; //全部比较,没有相等子串
}
子串:一定是主串中存在的才叫“子串”;模式串:想尝试在主串中找到的串,未必存在
串的模式匹配:在主串中找到与模式串相同的子串,并返回其所在位置
与定位操作相似,但只要有一个字符不同就可以停止检查当前子串
时间复杂度为O(nm);n远大于m
int Index(SSring S,SString T){
int k=1; //k为检查子串在主串的位置
int i=k;j=1; //i为检查子串的检查字符的位置,j为子串检查字符的位置
while(i<=S.length&&i<=T.length){
if(S.ch[i]==T.ch[j]){
i++;
j++;
}else{
k++;
i=k;
j=1;
}
}
if(j>T.length) //循环结束是因为子串匹配成功
return k;
else //循环结束是因为子串到末尾也没匹配到
return 0;
}
//方法二:不设置变量k
int Index(SSring S,SString T){
int i=1;j=1;
while(i<=S.length&&i<=T.length){
if(S.ch[i]==T.ch[j]){
i++;
j++;
}else{
i=i-j+2; //i-(j-1)+1; j-1子串扫描过的,+1后移一位
j=1;
}
}
if(j>T.length)
return i-T.length; //返回第一个字符的位置
else
return 0;
}
模式串指针回溯位置——int next[7]
例如:g o o g l e
当j=k且发现字符不匹配是,令j=next[k];若当前两字符匹配,i++,j++;
若j=1时发生不匹配,则应让j回到0,i++,j++(让i++,j依旧为1);若j=2时发生不匹配,则应让j回到1;
若j=3时发生不匹配,则应让j回到1;若j=4时发生不匹配,则应让j回到1;
若j=5时发生不匹配,则应让j回到2;若j=6时发生不匹配,则应让j回到1
int Index_KMP(SString S,SString T,int next[]){
int i=1,j=1;
while(i<=S.length||j<=T.length){
if(j==0||S.ch[i]==T.ch[j]){ //如果从0开始,为i-1、j-1
i++;
j++; //初始时或匹配,继续往后比较
}else{
j=next[j]; //不匹配,模式串向右移动
}
if(j>T.length)
return i-T.length;
else
return 0;
}
}
串的前缀:包含第一个字符,且不包含最后一个字符的子串
串的后缀:包含最后一个字符,且不包含第一个字符的子串
当第j个字符匹配失败,由前1~j-1个字符组成的串记为S,则next[j-1]=S的最长相等前后缀长度+1
特别地,next[1]=0;且next[2]=1,前后缀无相等next[x]=1(0+1)
时间复杂度为O(m+n)
void get_next(SString T,int next[]){
int i=1,j=0;
next[1]=0;
while(i<T.length){
if(j==0||T.ch[i]==T.ch[j]){
i++;
j++;
next[i]=j;
}else
j=next[j];
}
}
int Index_KMP(SString S,SString T){
int i=1,j=1;
int next[T.length+1]; //next[0]为空才能对应
get_next(T,next); //时间复杂度为O(n)
while(i<=S.length||j<=T.length){ //时间复杂度为O(m)
if(j==0||S.ch[i]==T.ch[j]){
i++;
j++;
}else{
j=next[j];
}
if(j>T.length)
return i-T.length;
else
return 0;
}
}
next[j]的优化:当子串和模式串不匹配时j=nextval[j]
思路:如果next回溯的字符和此字符相同,可将回溯字符的next给此字符:
//先算出next数组,先令nextval[1]=0
for(int j=2;j<=T.length;j++){
if(T.ch[next[j]]==T.ch[j])
nextval[j]=nextval[next[j]];
else
nextval[j]=next[j];
}
广义表(列表Lists)是n>=0个元素的有限序列,其中每一个元素或者是原子,或者是一个广义表;例如 LS=(a 1,a 2, … ,an)
其中:LS为表名,n为表的长度,每一个a i为表的元素;习惯上,一般用大写字母表示广义表,小写字母表示原子
表头:若LS非空(n>=1),则其第一个元素就是表头;记作head(LS)=a 1;注意:表头可以是原子,也可以是表子
表尾:除表头之外的其他元素组成的表;记作tail(LS)=(a 2,a 3,…,an);注意:表尾不是一个元素,而是一个子表;例如:A=(x,y,z) 长度为3,表头为x,表尾为(y,z);每一项都是原子
B=(()) 长度为1,表头、表尾均为();代表一个空子表
C=(a,(b,c)) 长度为2,表头为a,表尾为((b,c));由原子a和子表(b,c)组成
D=(a,D) 长度为2,表头为a,表尾为(D);由原子a和它本身组成
广义表的性质:
广义表和线性表的区别:广义表是线性表的推广,线性表是广义表的特例;广义表可以兼容线性表、数组、树和有向图等常用数据结构
广义表的基本运算