串
串是一种数据结构,是一种特殊的线性表。
特点
1、其内数据元素都来自字符集。
2、由于其数据元素特殊,所以有些操作不同于一般线性表,例如其操作对象一般是子串
(即一组数据元素),而不是单个的数据元素。
串的相关名词
串:由零个或多个字符组成的有限序列。
空串:含零个字符的串。
空格串:由空格组成的串。
串长:串中所含字符的个数。
串相等:两个字符串的长度相等并且各个对应位置上的字符都相等。
子串:一个串中任意个连续字符组成的子序列(含空串、空格串、串本身)。
真子串:除本身以外的所有子串(即串本身不是自己的真子串)。
模式匹配:在主串T中找(匹配)到第一个与子串p相等的位置。
模式匹配的两种方法
BF算法
在进行模式匹配时采用回溯的方式:
1.从主串的第i
个位置开始,与子串的每个字符逐个比较,若均相等,即为模式匹配成功,位置为i
。
2.若从i
开始匹配,但某个位置上不相等,则i
就不是要找的位置。这时要将比较的位置回溯到主串的i+1
处,子串的比较位置回溯到0
,从i+1
开始往后再比较。
3.直到匹配成功或i>=length(主串)
为止。
每次出现比较不同时,都要进行调整:主串的比较位置回到上次(i
)的下一个位置,子串回到0。
BF算法代码
int Index(Sstring T,Sstring p,int pos){
int i = pos;//主串的开始位置
int j = 0;//子串的开始位置
while(i <= T.len && j <= p.len){
if(T.data[i] == p.data[j]){
i++;
j++;
}else{
i = i-j+1;
j = 0;
}
}
if(j > p.len)
return i-p.len;
else
return 0;
}
时间复杂度分析
主串长度为n
,子串长度为m
。
最好情况复杂度:O(m)
,即主串从pos开始的m个字符正好与子串相等,例如:pos=0,主串为abcde,子串为abc,那么刚开始比较就会匹配成功。
最坏情况复杂度:O(n*m)
,每次比较都是到最后一个字符才发现不相等,例如:pos=0,主串为aaaaaaaaaab,子串为aaaab,每次都是比较到子串的最后一位才发现不等,所以要比较n*m次。
KMP算法
不同于BF算法的是,在比较中发现不相等时,主串指针位置不回溯,调整子串指针位置,从而继续进行下一轮比较。
失败函数getNext()
原理:在比较时出现对应位置字符不相等时,在这之前主串和子串对应位置上还都是相等的,即已经部分匹配,利用这个有效信息,设计一种方法使得尽量减少匹配时间,提高效率。具体方法如下:设计一个失败函数,在某个位置出现比对失败(不相等)时,调用失败函数getNext(),在子串p中,该失败位置之前的串中,找到一个最长的既是真前缀又是真后缀的小子串,使子串p调整到小子串最后一个字符的下一个位置,进行下一轮比较。
注意:失败函数只与子串有关,与主串无关。
应用
子串匹配失败时的位置 j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
子串字符 | a | b | a | a | b | c | a | c |
失败函数计算后的结果next[ ] | -1 | 0 | 0 | 1 | 1 | 2 | 0 | 1 |
注:1.经过失败函数getNext()计算后,所得值为-1时表示,字串的第一个字符就和主串不相等,这时主串的指针位置要后移一格,子串位置归0。
2.失败函数getNext()计算所得值不为-1时,则表示主串指针位置不动,子串指针位置变为所得值,然后继续比较。
失败函数代码
int getNext(char P[], int next[],int m){
int i = 0,j = 1,k = 1;
next[0] = -1;
while(j < m){
while(k < j){
if(P[i] == P[k]){
i++;
k++;
}else{
k = k - i + 1;
i = 0;
}
}
next[j] = i;
j++;
i = 0;
k = 1;
}
return 0;
}
代码原理:既然是找最长且相等的真前缀和真后缀,那就从最长的情况开始逐渐缩小寻找,最长的情况就是:真前缀或真后缀只比串本身少1个字符。
代码分析:那么i
就代表每个位置寻找最长真前缀的开始位置,k
代表寻找最长真后缀的位置,j
则代表某个比对出错的位置,就是在j
之前找的真前缀和真后缀。
如果P[i] == P[k]
时,那么就看下一个位置。但如果在k
没走到j
之前就出现不相等的字符了,那么这串在怎么长都没用,因为它不是真后缀。
等到k
走到j
的位置时,i
正好位于真前缀的下一个位置,正好next数组值要的就是真前缀的最后一个字符的下一个位置。
时间复杂度分析
主串长度为n
,子串长度为m
。
最好情况复杂度:O(m)。
最坏情况复杂度:O(m+n),匹配时因为不相等时不回溯,所以比较次数可以记为n
。
函数改进getNextVal
在KMP算法的基础上,考虑到在匹配失败去获取next值时,如果计算所得位置得字符与当前匹配失败的位置上的字符一样,那么在调整到next[i]的位置上时还是会匹配失败,所以nextVal数组考虑这一点,使得所得结果避免出现与原本匹配失败位置相同的字符位置。
int getNextVal(char P[], int nextVal[],int m){
int i = 0,j = 1,k = 1;
nextVal[0] = -1;
while(j < m){
while(k < j){
if(P[i] == P[k]){
i++;
k++;
}else{
k = k - i + 1;
i = 0;
}
}
while(P[j] == P[i]){
i = nextVal[i];
}
nextVal[j] = i;
j++;
i = 0;
k = 1;
}
return 0;
}
改进之处就在,如果发现计算得到的位置i
处的字符与比较失败的j
处相等的话,即P[j] == P[i]
,就提前获取位置i
处的next值,因为调整过去之后还是会出错,所以提前先获取。
以上均为串相关内容的个人理解,欢迎大家指正!谢谢!