计算机上的非数值处理的对象大部分是字符串数据,字符串一般简称为串(string)。
串的定义
串(string)(或字符串)是由零个或多个字符组成的有限序列。
串的概念解释:
- 空串(null string):零个字符的串。
- 子串:串中任意个连续的字符组成的子序列,子串在主串中的位置则以子串的第一个字符在主串中的位置来表示。
- 主串:包含子串的串相应的称为主串。
- 空格串:只包含空格的串
- 相等串:位数相等,对应每个位置的字符相同
串的抽象类型定义
串的逻辑结构和线性表相似,区别仅在于串的数据对象约束为字符集。然而,串的基本操作和线性表有很大区别。在线性表中通常以“单个元素”作为操作对象;而在串中,通常以“串的整体”作为操作对象。
ADT String
{
Data
串中元素仅由一个字符组成,有前驱后继的关系
Operation
StaAssign(&T,chars) 生成一个其值等于chars的串T
StrCopy(&T,S) 由串S复制得串T
ClearString(&T) 将串T清空
StrEmpty(S) 若串S为空,返回true
StrCompare(S,T) 若S>T 其返回值大于0,S=T 返回值=0,S<T 返回值<0
StrLength(S) 返回字符串S的长度
Concat(&T,S1,S2) 用T返回由S1、S2组成的新串
SubString(&Sub,S,pos,len) 用Sub返回串S的第pos个字符起长度为len的子串
Index(S,T,pos) 若主串S中存在和串T值相同的子串,则返回它在主串中第pos个字符后
第一次出现的位置,否则函数值为0
Replace(&S,T,V) 用V替换主串S中出现的所有与T相等的不重叠的子串
StrInsert(&S,pos,T) 在串S第pos个位置之前插入串T
StrDelete(&S,pos,len) 在串S中删除第pos个字符起长度为len的子串
}
串的存储结构
1、串的顺序存储结构
类似于线性表,用一组地址连续的存储单元存储串值的字符序列。
-----串的定长顺序存储结构-----
#define MAXLEN 255
typedef struct{
char ch[MAXLEN+1]; //存储串的一维数组
int length; //串的当前长度
}SString;
注:以上定义方式是静态的,再编译的时候就确定了串的空间大小。根据实际需要,串的变化是很大的,因此最好的是在程序执行过程中动态的分配和释放字符数组空间。所以有了——“堆”(Heap)。
------串的堆式存储结构------
typedef struct{
char *ch; //若是非空串,则按串长分配存储区,否则ch为NULL
int length; //串的当前长度
}HString;
2、串的链式存储结构
对于串结构的特殊性——结构中的每个数据元素是一个字符,则在链表存储串值时,会造成空间的利用率不足、即“结点大小”的问题。每个结点可以存储一个字符,也可以存放多个字符。
------串的链式存储结构------
#define CHUNKSIZE 80 //可由用户定义的块大小
typedef struct Chunk{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct{
Chunk *head,*tail; //串的头尾指针
int length; //串的当前长度
}LString;
特此声明:以下算法均利用串的顺序存储模式进行示例。
串的模式匹配算法
子串的定位运算通常称为串的模式匹配或串匹配。
假设我们要从主串S=“goodgoogle”,找到T=“google”这个子串的位置。如果匹配成功,则返回子串的第一个字符在主串中的位置。所以有了两种著名的算法:BF算法与KMP算法。
模式匹配算法不一定从第一个位置开始,可以指定主串中查找的起始位置pos。
1、BF算法(Brute-Force) O(m*n)
特点:古典、直观、经典、朴素、穷举的
【算法步骤】
①分别利用计数指针i与j 分别指向pos与1。
②当i<=S.length并且j<=T.length时执行以下操作
- 如果i指向S当前字符等于j指向T当前字符,则i和j分别指向下一个位置。
- 否则,i和j归位即 (i=i-j+2)
③若j大于T的长度,那么匹配成功,返回i-T.length,否则返回0
int Index_BF(SString S,SString T,int pos)
{
int i=pos,j=1;
while(i<=S.length&&j<=T.length)
{
if(S.ch[i]==T.ch[j])
{
i++;
j++;
}
else
{
i=i-j+2;
j=1;
}
}
if(j>T.length)
return i-T.length;
else
return 0;
}
【算法分析】
注:这次没有gif……懒得做了……所以我用了excel 哈哈哈哈
第一趟匹配 | g | o | o | d | g | o | o | g | l | e |
g | o | o | g | l | e | |||||
第二趟匹配 | g | o | o | d | g | o | o | g | l | e |
g | o | o | g | l | e | |||||
第三趟匹配 | g | o | o | d | g | o | o | g | l | e |
g | o | o | g | l | e | |||||
第四趟匹配 | g | o | o | d | g | o | o | g | l | e |
g | o | o | g | l | e | |||||
第五趟匹配 | g | o | o | d | g | o | o | g | l | e |
g | o | o | g | l | e |
由上图可知,最好的情况是从第一个字符就匹配成功了,最坏的情况就如上表所示,到最后才匹配成功。所以可得BF算法的时间复杂度:O(m*n)。
2、KMP算法 O(m+n)
KMP算法是由Knuth、Morris和Pratt同时设计的,因此简称KMP算法。这种算法进行了改进,时间效率提高了许多。
KMP算法分为了两个部分,第一个部分是求next[],第二个部分是匹配……,相比较于BF算法,KMP算法省略了上表的第二、三、四趟比较。并且指向主串的指针不回溯。。所以kmp算法的精力主要集中在 子串的处理。。来吧……上代码……
//计算next函数值
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])
{
j++;i++;
next[i]=j;
}
else
j=next[j];
}
}
//计算next函数修正值
void get_nextval(SString T,int nextval[])
{
int i=1,j=0;
nextval[1]=0;
while(i<T.length)
{
if(j==0||T.ch[i]==T.ch[j])
{
i++;j++;
if(T.ch[i]!=T.ch[j])
nextval[i]=j;
else
nextval[i]=nextval[j];
}
else
j=nextval[j];
}
}
//KMP算法
int Index_KMP(SString S,SString T,int pos)
{
int i=pos,j=1;
while(i<=S.length&&j<=T.length)
{
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函数定义:
博客内容借鉴于:
①《数据结构》 作者:严蔚敏
②《大话数据结构》 作者:程杰