字符串
字符串的存储结构和线性表相同,分为顺序存储和链式存储
字符串比较比较字符串中每一个字符的ASCII码的值
字符串匹配问题:
方法一:BF算法(Brute Force)
暴力求解
算法描述:两个字符串母串str1和子串str2,长度分别为len1和len2,首先将str1[0]和str2[0]比较,若相等,则进行下一位比较,直到子串str2匹配完,则结束,若不相等,则子串后移以为继续相同操作
时间复杂度为O(len1*len2)
方法二: KMP算法
KMP算法最需要关注的点是子串,而不是母串
下面几个思考将一步步优化BF算法,得出KMP算法。
思考一
如下图示:子串是不重复的,那么我们匹配到下标为2这个元素的时候,子串失配了,按照BF算法我们会用子串的a元素和母串的b元素进行匹配。但是这样是错误的回溯,所以我们希望下一次匹配是如下图所示,这样可以避免错误回溯
思考二
如下图所示,如果子串含有重复的元素,按照思考一中的方案是不可行的,所以我们要保证所有具有可行性的方案能够检验匹配
下图第二次匹配应该从下标2进行匹配
所以我们只需要对子串进行操作,记录子串应该在哪跳过一些错误的回溯
KMP算法中通过next数组对此进行了一个记录。
next数组是当子串失配时,next数组对应的元素可以指导子串的哪一个元素进行下一轮的匹配
思考三
如何推出next数组:
将思考一二中的例子自行推断
如图将子串对应的next数组填入,填入原则遵循:
next数组是当子串失配时,next数组对应的元素可以指导子串的哪一个元素进行下一轮的匹配
所以上图下标为2时失配,next数组指向0则如下图示,从子串下标为0处开始匹配:
其中-1位特判,当是第一个字符就不匹配则第一个字符与母串下一个字符进行匹配
将思考二的next数组填入:
next数组是当子串失配时,next数组对应的元素可以指导子串的哪一个元素进行下一轮的匹配
(重要内容说三遍!!)
所以上图下标为4时失配,next数组指向2则如下图示,从子串下标为2处开始匹配:
而next数组的到原理其实是子串当前位之前的串的前缀和后缀最大相同的值:
如思考二例子中:
下标为4的字符之前的串为abab,前缀和后缀最大相同的为ab,长度为2,对应next为2
下标为3的字符之前的串为aba, 前缀和后缀最大相同的为a ,长度为1,对应next为1
依次可得对应的next数组
思考四
如何编码求得next数组?
由思考三可知:求next数组即为求前缀和后缀最大相同的值
我们令i表示前缀,j表示后缀,子串为str2
分步解决:
1、当出现前缀和后缀相同的情况,即是例子中i = 0,j = 2这种情况,得出next[3] = 1
那么这种情况即为:
if(str2[i] == str2[j] ) //前缀等于后缀
{
i++;
j++;
next[j] = i;
}
当i = 1 , j = 3 时,任然如此,可以得到next[4] = 2
2、在情况一中,我们并没有考虑当 前缀和后缀不相等的情况,所以当不相等时
if(str2[i] == str2[j] ) //前缀等于后缀
{
i++;
j++;
next[j] = i;
}
else
{
i = next[i] //见解析a
}
解析a:
前缀和后缀的匹配也变成了子串和母串的匹配问题,但是此时的局部的next数组已经相对来说已经完成,我们将后缀串比作母串,前缀串比作子串,则我们需要将 i 回溯到next数组指向的位置,进行跳跃匹配并防止思考二中出现的问题
最后补全初始情况,以及i,j,next[0]的初始值,匹配的范围得出代码:
//len为字符串str2长度
void get_next(char *str2, int *next, int len)
{
next[0] = -1;
int i = -1;
int j = 0;
while(j < len - 1){
if(-1 == i || str2[i] == str2[j]) //前缀等于后缀
{
i++;
j++;
next[j] = i;
}
else
{
i = next[i]; //见解析a
}
}
}
KMP算法实现:
int Index_KMP(char *str1, char *str2){
int i = 0;
int j = 0;
int next[255];
int len1 = strlen(str1);
int len2 = strlen(str2);
get_next(str2, next, len2);
while(i < len1 && j < len2){
if(-1 == j || str1[i] == str2[j]){
i++;
j++;
}
else{
j = next[j];
}
}
if(j == len2)
return i - j;
else
return -1;//没有找到
}
KMP算法基本完成!
KMP算法优化
当出现此种特例,按照原先的KMP算法思想,母串中的b将与子串的每一个元素进行匹配,所以我们需要更改get_next函数将元素相同的情况统一起来,使next数组进行有效指导。
更改代码如下:
void get_next(char *str2, int *next, int len)
{
next[0] = -1;
int i = -1;
int j = 0;
while(j < len - 1){
if(-1 == i || str2[i] == str2[j]) //前缀等于后缀
{
i++;
j++;
//更改next[j] = i;
if(str2[i] != str2[j])
next[j] = i;
else//如果相同
next[j] = next[i];
}
else
{
i = next[i];//见解析a
}
}
}