算法作用:从字符串中寻找是否存在子串
BF
暴力求解,定义下标i,j开始从头遍历,i和j相同就一起+1,遇到不同的字符就让j返回到子串0,i回到开始位置+1
时间复杂度O(m*n)
//str:主串
//sub:子串
//如果找到返回在str中的下标,找不到返回-1
int BF(char* str,char* sub)//暴力求解 时间复杂度 m*n
{
assert(str&&sub);
int lenstr = strlen(str);
int lensub = strlen(sub);
if (lenstr == 0 || lensub == 0)
{
return -1;
}
int i = 0;
int j = 0;
while (i < lenstr && j < lensub)
{
if (str[i] == sub[j])
{
i++;
j++;
}
else
{
j = 0;//j返回最开始
i = i - j + 1;//令i回到开始位置+1
}
}
if (j >= lensub)//走完了子串 找到子串
{
return i-j;//返回是子串时i开始位置
}
return -1;
}
KMP
再遍历过程中主串的i不回退,子串j要回退到next数组的位置
next数组就是 保存子串某个位置匹配失败后,j回退的位置
如果能够再主串中找到和子串匹配的一部分
此时看到5和2又可以继续匹配,但是c不等于a那么 j又要继续回退到2所在的next数组位置
如何求next数组
next[ j ] = k k就是j要回退的位置下标
规定 next[0] = -1 next[1]=0
1、规则:找到匹配成功部分的两个相等的真子串(不包含本身)的长度,一个以下标0开始,另一个以j-1下标结尾
j=7 那么寻找0-6 是否有以a开头,b结尾的真子串,不包含本身
那么也就是0-1 a b 和5 - 6 a b 长度是2 那么下标7的K=2
再此例子中 子串是可以重合的
上面是手求,在遍历过程中如何求k值呢
next [ 0 ] = -1 next [ 1 ] = ? next [ j ] = k next [ j+1 ]=?
如果从0位置开始想有些不好想,假设next [ j ]=k 就可以写出
这里面还有一个条件是p [ k ] = = p [ i -1 ] 才有p [ i ] = k +1
那么如果不相等呢?p [ k ] ! = p [ i -1 ] 那么p [ i ] = ?
规律 如果next [ j ] = k , next [ j + 1] 如果要是增加 那么肯定是+1
那么要是减少 不一定是减到0
在子串回退的过程中 会多次出现回退到 j = 0 k = -1 也就是回退到-1的情况
等于根本没有找到a 开头 b结尾的真子串 那么就是 k = -1 k + 1 = 0
也就是在写代码过程中,k == -1 也要进入循环
这种回退到-1的情况 可能会多次出现
kmp时间复杂度
O(m+n)求next数组需要遍历一遍子串m 主串比较需要遍历比较n次
NextVal数组优化
在4 匹配失败 j 要回退到3 -> 2 ->1 ->0 ->-1 为什么不能一次性回退到-1呢 既然他们都是a
规则:
1、回退到的位置和当前字符一样,就写回退那个位置的nextval值。
2、如果回退到的位置和当前字符不一样,就写当前字符原来的next值。
此时代码还是和求next的K一样 但是不同是 要根据规则判断
写了两种方式 一种是j=0 开始依次求nextval
另一种是j = 2 求next
区别在于控制的是 j 和 j-1
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
void GetNextVal(const char* sub, int* nextval, int lenSub)
{
int i = 0;
int k = -1;//前一项的k
nextval[0] = -1;
while (i < lenSub)
{
if (k == -1||sub[i] == sub[k])
{
i++;
//next[i] = k+1;
k++;
if (sub[i] != sub[k])
{
nextval[i] = k;
}
else
{
nextval[i] = nextval[k];
}
}
else
{
k = nextval[k];
}
}
}
void GetNext(const char* sub,int* next,int lenSub)
{
next[0] = -1;
if (lenSub == 1)
{
return;
}
next[1] = 0;
int i = 2;//当前i下标
int k = 0;//前一项的k
while (i < lenSub)
{
if (k == -1||sub[i - 1] == sub[k])
{
next[i] = k + 1;
i++;
k++;
}
else
{
k = next[k];//K回退
}
}
}
//str:代表主串
//sub:代表子串
//pos:代表从主串的pos位置开始找
int kmp(const char* str,const char* sub,int pos)
{
assert(str && sub);
int lenStr = strlen(str);
int lenSub = strlen(sub);
if (lenStr == 0 || lenSub == 0) return -1;
if (pos < 0 || pos >= lenStr) return -1;//pos范围0~lenStr-1 判断是否越界.
//int* next=(int*)malloc(sizeof(int) * lenSub);
//assert(next!=NULL);
GetNext(sub, next,lenSub);
int* nextval = (int*)malloc(sizeof(int) * lenSub);
assert(nextval != NULL);
GetNextVal(sub, nextval, lenSub);
int i = pos;//遍历主串
int j = 0;//遍历子串
while (i < lenStr && j < lenSub)
{
if (j == -1||str[i] == sub[j])
{
i++;
j++;
}
else
{
j = nextval[j];//如果第一个就不相等 j会回退到-1 上面就进入 i++ j++==0 继续匹配
}//回退到相等(str[i] == sub[j])如果能成功匹配就和BF一样
}
if (j >= lenSub)
{
return i - j;//参考bf算法 求起始位置 画图
}
return -1;//j>=lenStr跳出 说明没有找到返回-1
}
int main()
{
//printf("%d\n", BF("ababcabcdabcde", "abcd"));//5
//printf("%d\n", BF("ababcabcdabcde", "abcdf"));//-1
//printf("%d\n", BF("ababcabcdabcde", "ab"));//0
printf("%d\n", kmp("ababcabcdabcde", "abcd",0));//5
printf("%d\n", kmp("ababcabcdabcde", "abcdf",0));//-1
printf("%d\n", kmp("ababcabcdabcde", "ab",0));//0
//printf("%d\n", kmp("babcde", "aaaaab", 0));
return 0;
}