串操作(BF暴力、KMP)

137 阅读3分钟

BF算法(暴力解法)

int BF(std::string& s, std::string& t)
{
    int i = 0, j = 0;
    int size1 = s.size();    // 比如主串长度为m
    int size2 = t.size();    // 比如子串长度为n
    while (i < size1 && j < size2)    // 时间复杂度O(m * n)
    {
        if (s[i] == t[j])    // 相等的情况
        {
            i++; j++;    // 两个字符串都向后比较
        }
        else    // 不相等的情况
        {
            i = i - j + 1;    // 主串递增一个字符
            j = 0;    // 子串从第一个字符开始
        }
    }
    if (j == size)
        return i - j;
    return 0;
}

KMP

KMP算法则是在BF算法基础上改进,先分析一下BF算法有哪些不足

image.png

这是BF算法中进行比较的过程。
当【情况①】 f和y不相等,j被重置为0,i设置为1.然后再依次进行比较。
其实仔细观察可以发现,"a b d e"上下都相等,然后这四个字符彼此又不同.所以,【情况②、③、④】根本不需要进行匹配比较,这几次比较完全就是浪费性能.应该直接从【情况⑤】进行比较。

解决上面这种情况就是当不相等时, i不要设置 i = i - j + 1,i保持原位置即可


再看下面这幅图

image.png

"a b b a",这四个字符中,前面和后面两个a相等,长度为2
那么上面这幅图,不相等,i和j应该如何设置?

image.png

因为字符f前面有'a',字符y前面也有'a',我们将j=1,从子串'b'开始和主串'f'进行比较.
设置j的下标其实和子串中相等前后缀字符串的长度有关

相等前后缀字符串

就上图来说,就是j下标之前的字符串. 找出前面和后面相等的字符串
image.png 相等前后缀字符串的长度为1,那么j回溯设置的下标就为1,也就将字符b和字符f进行比较
每次回溯j应该设置什么下标,这就需要有个地方存储,所以就需要KMP代码中的next数组

#include <memory>
#include <string>

using std::unique_ptr;
using std::string;

int* getNext(string str)
{
    int size = str.size();
    int* next = new int[size];
    int j = 0;	// 遍历子串
    int k = -1;	// 表示公共前后缀长度
    next[j] = k;

    while (j < size - 1)
    {
        if (k == -1 || str[k] == str[j])
        {
            j++;
            k++;
            next[j] = k;
        }
        else
            k = next[k];	// 回溯,继续找最长的公共前后缀
    }
    return next;
}

int KMP(std::string s, std::string t)
{
    int i = 0, j = 0;
    int size1 = s.size(), size2 = t.size();
    unique_ptr<int> next(getNext(t));
    while (i < size1 && j < size2)
    {
        if (j == -1 || s[i] == t[j])	// j == -1表示首字符没有匹配成功
        {
            i++; j++;
        }
        else
        {
            // KMP核心是不回退i,只回退j值
            j = next.get()[j];	// 如果首字母匹配失败,这里j == -1
        }
    }
    delete next;

    if (j == size2)
        return i - j;

    return -1;
}

上述代码的next数组有个问题,那就是当子串为 "abababc"
根据上面没有优化的代码, next数组存储的应该为: [-1, 0, 0, 1, 0, 1, 2]
image.png 当获取next[2]的时候,获取的值是0, 将j设置为0,然后子串下标0还是个字符'a'.
还有获取next[3]和next[5],获取的值是1. 子串下标1还是字符'b',所以上述代码需要加个判断优化一下

int* getNext(std::string& t)
{
    int size = t.size();
    int* next = new int[size];
    
    int j = 0;	// 用来遍历子串
    int k = -1;	// 公共前后缀的长度
    next[j] = k;

    while (j < size - 1)
    {
        if (k == -1 || t[j] == t[k])
        {
            k++; j++;
            if (t[k] == t[j])
                // KMP算法优化
                next[j] = next[k];
            else
                next[j] = k;
         }
         else
            k = next[k];	// 做k值回溯,继续找最长的公共前后缀
    }
    return next;
}