串
串(或字符串)是由0个或多个字符组成的有限序列。
串的存储结构
1、定长顺序存储:
#define MAXLEN 255
typedef struct{
char ch[MAXLEN+1];
int length;
}SString;
2、堆式顺序存储:
typedef struct{
char *ch; //若是非空串,则按串长分配存储区
int length; //串的当前长度
}HString;
3、链式存储(一般不用):
#define CHUNKSIZE 80
typedef struct Chunk{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct{
Chunk *head,*tail;
int length;
}LString;
串的模式匹配算法
BF(Brute_Force)算法
算法步骤:
1、分别利用技术指针i和j指示主串S和模式T中当前正待比较的字符位置,i初值为pos,j初值为1
2、如果两个串均未比较到串尾,即i和j分别小于等于S和T的长度时,则循环操作:
(1)S.ch[i]和T.ch[j]比较,若相等,则i和j分别指示串中下个位置,继续比较后续字符
(2)若不等,指针后退重新开始匹配,从珠串的下一个字符(i=i-j+2)起再重新和模式串的第一个字符比较
3、如果j>T.length,说明模式T中每个字符依次和主串S中一个连续的字符序列相等,则匹配成功,返回和模式T中第一个字符相等的字符在主串S中的序号(i-T.length);否则不成功,返回零
总结:就是一个暴力匹配算法,从pos开始挨个作为头匹配,成功为止。
代码实现;
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;
}
(这markdown贴代码这块儿太死亡了吧……(捂脸))
时间复杂度分析:
(1)最好情况下,每趟不成功的匹配都发生在模式串的第一个字符与主串中相应字符的比较。
设主串长度未n,子串长度为m,假设从主串的第i个位置开始与模式串匹配成功,总比较次数(成功+失败)为i-1+m,对于成功匹配的主串,其起始位置由1到n-m+1,假设这些位置上匹配成功概率相等,则最好情况下匹配成功的平均比较次数为 (n+m)/2
最好情况下平均时间复杂度时O(n+m)。
(2)最坏情况下,每趟不成功的匹配都发生在模式串的最后一个字符与主串中相应字符的比较。
总比较次数im,平均比较次数m(n-m+2)/2
最坏情况下平均时间复杂度为O(nm)。
KMP算法
对BF算法的改进:每当一趟匹配过程中出现字符比较不等时,不需回溯i指针,而是利用已经得到的“部分匹配”的结果将模式串向右“滑动”尽可能远的一段距离后,继续进行比较。
需用到一个next[]数组,next[j]的含义是:当模式串第j位与主串第i位“失配”时,模式串的第next[j]位继续与主串第i位匹配;
对该算法的理解:
考虑当模式串第j位与主串第i位“失配”时,模式串的第k位要继续与主串第i位进行匹配,这时k需满足的条件为主串中i位置的前k-1个元素应该与模式串中k位置的前k-1个元素(即前面所有元素)对应相等;而因为模式串已经匹配到j位置了,所以一定满足模式串j位置的前k-1个元素与主串中i位置的前k-1个元素对应相等,由此可推得,k在模式串中需要满足的条件为:j位置的前k-1个元素与k位置的前k-1个元素对应相等。
很显然,设j位置前面所有字符组成的字符串为p,则j前面的两个长度为k-1的字符串是p最长的相等前后缀。
理解算法后,就不难得出next[j]的定义:
j=1时,next[j]=1;(第一位就不相等,只能向后再看看)
其他情况:next[j]=k, j位置前所有字符组成的字符串的最长相等前后缀长度为k-1;
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数组值
可用递推的方法:
首先,next[1]=0是显而易见的;
next[j]=k时,前面分析过,满足j位置的前k-1个元素与k位置的前k-1个元素对应相等,现在要求next[j+1];
分两种情况:
(1)第j个元素和第k个元素相等,则相当于前面说的前后缀延长了一位,这样next[j+1]毫无疑问等于k+1,即next[j+1]=next[j]+1;
(2)第j个元素和第k个元素不相等,则应将第j个元素与模式串中第next[k]=k’个元素比较(想像一下,把模式串同时作为主串和模式串,自己跟自己比),若相等,则说明next[j+1]=k’+1,即next[j+1]=next[k]+1;
若还不相等,递推下去,直至匹配成功,或不存在k‘满足条件,此时next[j+1]=1;
代码实现:
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];
}
}
代码可能有点难理解,不过注意每一步判断第i个元素与第j个元素是否相等时,后面的代码都是求的i下一位的next值,这样就与之前的分析接轨了。
计算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];
}
}
就是把next[j],变成了next[next[…next[j]…];
数组
数组是类型相同的元素构成的有序集合
数组与一维存储空间的位置映射
n维数组A[0…b1-1,0…b2-1,……,0…bn-1]中数据元素存储位置的计算公式:LOC(j1,j2,…,jn)=LOC(0,0,…,0)+(b2 * …* bn * j1+b3*…*bn * j1+…+bn * jn-1+jn)*L
特殊矩阵的压缩
对称矩阵满足:aij=aji
可将其n^2个元素压缩存储到n(n+1)/2个元的空间中,以行序为主序存储其下三角(包括对角线)中的元;
sa[k]与aij之间的对应关系为
广义表
广义表是线性表的推广,一般记作:LS=(a1,a2,…an)
其中n是表长度,ai可以是单个元素,也可以是广义表。
广义表的两个重要运算
(1)取表头GetHead():取出的表头为非空广义表的第一个元素,可以是一个单原子,也可以是一个子表;
(2)取表尾 GetTail():取出的表尾为除去表尾之外,由其余元素构成的表,即表尾一定是一个广义表;
————————————————————————————————————
注:本文所有内容均来源于《数据结构(C语言第二版)》(严蔚敏老师著)