目录
前言
话不多说直接以题为例子进行讲解。本题来源自力扣
提示:以下是本篇文章正文内容,下面案例可供参考
一、经典字符串匹配的例题
二、BF算法
BF的实现思路:
现有两个字符串,str1与str2,将str1的字符依此与str2的字符相比较。若遇见两个不相同的字符,从str1字符的现有位置跳到下一个字符上在重新进行比较,直到比较结束。代码实现就是遍历两个字符串,进行上述规则的比较。双重for循环进行遍历,如果str1的剩余字符的个数与str2的个数相同,还没有比较成功就说明不存在相同的字符串了,因为这时候str1的字符个数比str2的个数还要小就没有比较的必要了。所以遍历str1的结束条件就是下标 <=(str1的符个数-str2的字符个数)。str2正常遍历,之后就是在str2中的判断,如果第一轮遍历没有相同的,那么str1的下标就要被str2的下标多了1,第二轮多了2,所以在进行判断的时候我们str的下标就是 ( i + j ), 这样每次循环str1的下标就会向前移动一位,if判断条件是两个字符不相等使用continue跳出内部循环,str1下标加一在重新判断,如果成功则输出 i 那就是它的初始下标。
class Solution {
public:
int strStr(string haystack, string needle)
{
int n = haystack.size(), m = needle.size();
for( int i = 0; i <= (n - m); i++)
{
bool flag = true;
for(int j = 0; j < m; j++)
{
if(haystack[i+j] != needle[j])
{
flag = false;
continue;
}
}
if(flag)
{
return i;
}
}
return -1;
}
};
2.KMP算法
KMP算法:一种改进的字符串匹配算法,它与BF算法不同的是,它的主串是不回退的,一直向前不回退。
2.1.next数组
KMP算法中的核心就是next数组。它用来储存子串某个位置匹配失败后回退的位置。
在看的时候一定要一步一步看,不能跳着看,前后是有关联的,如果了解next数组的特性那么可以跳着看
我们用next[i] = k来表示不同的下标对应的k值,以0下标开始,也就是这里的首元素到 i 下标找到匹配成功部分的两个相等的真子串,不包含本身。一般来说前两个k值的固定的 -1 与0 以第一组为例后看 a 前面 ab 没有 a 为0向后走。k是返回字串数组 i 的位置,i 即是子串数组下标也是next数组下标。
这里对应
a b a 找以a开头以b结尾的两个相同的串的个数--没有
-1 0 0 这一串是next数组
a b a b 找以a开头以a结尾的两个相同的串个数-- a与a一个
-1 0 0 1
a b a b c 找以a开头以b结尾的两个相同的串个数-- ab与ab两个
-1 0 0 1 2
a b a b c d 找以a开头以c结尾的两个相同的串个数-- 没有
-1 0 0 1 2 0
a b c a b c a b 找以a开头以a结尾的两个相同的串个数-- a b c a与a b c a一共四个
-1 0 0 0 1 2 3 4 可以进行字符串的重合,但是不能从首元素重合
这里我们发现一个特征,就是k的如果进行增加一定是 +1 的。next数组一定是以1 2 3 4这样的顺序增长的,不会是 1 3 5这样。而变小是不确定的。
至此,我们学会了,如何寻找k中对应的next值 ,那么如何用代码实现呢?k是next数组的值,k代表返回的真子串的下标i
我们假设 next[ i ] = k成立 即p[i] == p[k]所以i要回退到k下标 那么从 p[0]~p[k-1] 就是 a b c 而 p[x] ~p[i - 1] 也是 a, b, c那么 p[0]~p[k-1]==p[x] ~p[i - 1]即p[0]~p[2]与p[x]~p[i-1]的真子串是相同的,我们前提是知道 i,k 所以 k-1-0==i-1-x得出x==i-k得出x为5符合下图。然后我们将x代入得出 p[0]~p[k-1]==p[i-k]~p[i-1] 这个公式的前提是 next[i] = k; 如果 p[0]~p[k]==p[i-k]~p[i] 那么对比上个公式那么 next[i+1]=k+1。
但是如果 p[i] != p[k]这时候k=2 那么我们对k进行回退这时候k=0但还是 p[i] != p[k]继续回退这时候 k=0,并且p[i] == p[k]那么就满足上面的next[i + 1] = k+1;即j+1的位置k=1
综上所述得出一个结论:已知next[i] = k,如果p[i] == p[k],那么next[i+1] = k+1,如果p[i] != p[k],回退k,使其满足p[i] == p[k],得出next[i+1] = k+1。
上述条件的前提是知道next[i] = k, 但当我们进行代码实现的时候,不知道 i 位置中k的值,所以现在的 i 相当于原先将的 i+1,所以p[i-1] 与p[k]进行比较,所以当p[i-1] == p[k]的时候,next[i] = k+1。
这时候对于next数组的了解就很明确了。下面就是代码的实现,注释写的非常清楚
class Solution {
public:
int strStr(string haystack, string needle)
{
int n = haystack.size(), m = needle.size(); //主串和字串的长度
if(n == 0 || m > n) // 当主串或字串为0,或者字串大于主串返回-1
{
return -1;
}
else if(m == 0)
{
return 0;
}
int* next = new int[m]; // 给next数组开辟空间
set_next(needle, next, m);//进入函数得到next数组
int i = 0, j = 0;
while(i < n && j < m ) //遍历主串,使主串一种走,如果走到主串现在的位置到结尾位置的元素
//等于字串的位置还没有匹配。那么就再向后面走字串元素会比与主串相比较的元素对,那么就不用向后走了。
{
if((j == -1) || (haystack[i] == needle[j]))
{
i++;
j++;
}
else
{
j = next[j];
}
}
delete[] next; //扩容一定要释放内存,防止内存泄漏
if(j >= m)
{
return i - j;
}
else
{
return -1;
}
}
private:
void set_next(const string& needle, int * next, int m)//next数组
{
//如果字串有两个元素
next[0] = -1;//第一个next数组为-1
int k = -1; //起始元素
int i = 1; // 已经k元素的下一个元素坐标
if(m > 1)//字串有三个及以上元素
{
next[1] = 0;
k = 0;
i = 2;
}
while(i < m)
{
if(k == -1 || needle[i - 1] == needle[ k])//这里求得就是 i 位置下k的值
{
next [i] = k + 1;
i++;
k++;
}
else //回退
{
k = next[k];
}
}
}
};
总结
KMP和核心就是next数组,学会next数组基本上学会了。