1- 使用暴力搜索来确定目标字符串在原字符串第一次出现的下标
暴力搜索的大概过程:
(1)采用两个指针(具体实现中用两个数组下标控制),第一次用子串的第一个字符和原串的第一个字符相比较,如果相同,两个指针各向后移动一次,接着比较这两个字符是否相等
(2)如果某一次匹配失败,原串的指针移动到下一个字符上,子串指针回到第一个字符上重新开始比较,不断移动比较,如果找到了则返回此次比较时原串的指针,如果没找到则返回NOT_FOUND即可
(3)确定找到了和没找到的条件:在原串中找到了该子串,则此时子串的指针指向0结束标志,即子串的最后。在原串中没有找到子串,此时子串超出了原串
(4)函数代码实现过程
int searchString(const char *src, const char *sub) {
int srcLen = strlen(src);
int subLen = strlen(sub);
int srcIndex = 0;
int subIndex = 0;
for (; srcIndex < srcLen - subLen + 1; srcIndex++) {
for (subIndex = 0; sub[subIndex]; subIndex++) {
if (src[srcIndex + subIndex] != sub[subIndex]) {
break;
}
}
if (0 == sub[subIndex]) {
return srcIndex;
}
}
//采用两层循环完成搜索,外层循环来控制原串字符下标的移动
//并判断是否没有找到。
//内层循环用来移动子串的字符下标并判断每次单个字符匹配成功与否,
//成功则判断下一对字符,并检测子串是否匹配结束,失败则跳出本次循环
//原串的真实下标加1,子串从头开始匹配
return NOT_FOUND;
}
对匹配失败的结束条件 srcIndex < srcLen - subLen + 1理解:
(5)时间复杂度分析:如果原串长度为m,子串长度为n,时间复杂度
大约为O(m*n)
2- 使用kmp算法,利用next数组来提高移动的效率确定第一次出现的下标
kmp算法的粗略过程和next数组的实现:
(1)暴力搜索的效率低,是由于其存在着子串下标的 回溯, 即如果失配了,那么无论子串当前的指针指向第几个字符,都得回到起始字符再来和原串的下一个字符匹配,当两个字符串长度很长时,这样来来回回的 移动 回溯,使得其效率低, 为了减少移动的次数,使用kmp算法,每次失配后,尽可能多的移动,使得子串指针不 回溯,这样就减少了移动,提高了效率
(2)创建next数组,暂称为移动量数组,有了next数组,才能进行kmp算法查找子串是否存在,创建一个next数组,只需要一个子串即可
int *getNext(const char *sub, int subLen) {
int index = 2;
int j = 0;
int *next = NULL;
next = (int *) calloc(sizeof(int), subLen);
while (sub[index]) {
//当子串没到0结束标志时就一直继续
if (sub[index-1] == sub[j]) {
next[index++] = ++j;
} else {
if (j == 0) {
next[index++] = j;
} else {
j = next[j];
}
}
}
return next;
}
next数组创建的过程:
(3)用next数组实现kmp算法的过程
next数组可以理解成 子串在匹配到某个字符时失配了,此刻查询next数组中,它所对应的值,那么下次进行匹配时,不需要 回溯 只要将指向子串的指针直接后移它next数组中所对应的值,跳过前面的字符串,直接开始比较
(4)实现kmp算法查找子串
4.1- 同暴力搜索,先从子串和原串的第一个字符开始找起,该字符匹配,则将两个串的指针一起后移一次,再比较,直到不对应
4.2-如果不对应,原串指针位置不变,子串指针失配前为p,指向子串的第p+1个字符,那么失配后指针的 位置直接移动到 next[p]的值所对应的位置, next[p]即为移动量,不回溯,直接忽略子串前next[p]个字符,直接和原串当前的指针进行比较
4-3-找完了没找到返回NOT_FOUND,找到了返回 i - p 此时 i - p 就是子串第一次出现在原串中的首字符下标
int kmpMatch(const char *src, const char *sub) {
int srcLen = strlen(src);
int subLen = strlen(sub);
int i = 0; //指向原串的变量
int j = 0; //指向子串的变量
int *next;
next = getNext(sub, subLen);
while (sub[j] && srcLen - i >= subLen - j) {
//sub[j]:子串没比较完
//srcLen - i >= subLen - j :子串还在原串内匹配
if (src[i] == sub[j]) {
i++;
j++;
continue;
//本次字符相同,指针均后移一位,继续循环
} else if (j != 0) {
//字符失配,且子串下标不为0
j = next[j];
//下一次j移动到next[j]开始匹配
} else {
//字符失配,且子串下标为0
i++;
//下一次子串从头开始,且原串指针后移一次
}
}
free(next);
return sub[j] ? NOT_FOUND : i - j;
//sub[j]非0时此刻子串超出了原串也没匹配到,为0时
// j 就是子串长度,i - j 即子串首次出现原串中的下标
}
(5)时间复杂度分析:如果原串长度为m,子串长度为n,时间复杂度
大约为O(m+n)