C++ 子串匹配主串

暴力匹配

#include <iostream>
#include <string>
using namespace std;

int strTsr(string haystack, string needle)
{
    if (needle.empty())
        return 0;

    int n = haystack.size();
    int m = needle.size();

    for (int i = 0; i < n; ++i)
    {
        int pos = i;
        for (int j = 0; j < m; ++j)
        {
            if (needle[j] != haystack[pos++])
            {
                break;
            }
            else
            {
                if (j == (m - 1))
                {
                    return i;
                }
            }
        }
    }
    return -1;
}

int main()
{

    int asn = strStr("ABCDABCDE", "ABCDE");

    if (asn == 0)
    {
        cout << "匹配的子串为空!" << endl;
    }
    else if (asn == -1)
    {
        cout << "子串不匹配主串!" << endl;
    }
    else
    {
        cout << "子串匹配了主串,开始位置:" << asn << endl; 
    }
    

    return 0;
}

字符串的KMP算法

1. 原由

紧接上文,我们知道了暴力匹配的算法在时间运行上的缺陷,假设字符串T的长度为n,字符串P的长度为m,则整个算法的时间复杂度为O( n * m ),而对于一个复杂的现实情况而言 n >> m >> 2 (即n远远大于m,m远远大于常数),这样的计算计算机的负担很重。

请思考一个暴力匹配的情况:

给定一个主字符串

T = “AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB”(47位)

同时给定模式串 P = “AAAAAB”(6位)

试问搜索的情况,很显然,暴力搜索对于每一次搜索,都要搜索到最后一个字符才能进行下一轮的搜索,因此进行的计算近似可以理解为:O(47 * 6) ,对于这样很少的数据已经有很高的计算量了。

KMP算法一种改进的模式匹配算法,是D.E.Knuth、V.R.Pratt、J.H.Morris于1977年联合发表,KMP算法又称克努特-莫里斯-普拉特操作, KMP算法与前文的暴力匹配算法,核心的区别就是没有不匹配的回溯,而是根据整个字符串的情况进行一次位移,这样大大减少了回溯产生的缺陷,KMP算法的时间复杂度可以优化到 O( n + m)级别,是二次优化到线性的程度。

2.构造next表(以-1开头)

对于模式串P而言,我们需要知道模式串中P的每一位的前一位是否存在相等的完全相等的前后缀,并且求这个最大的完全相等的前后缀,如一个模式串”ABCABDE”对于第倒数第二位字符而言,其符合情况的前后缀就是”AB”,而最后一位则没有完全相等的前后缀。

PS:何为前后缀:如一个字符串”ABCD”,其前缀有可能为”A”“AB”“ABC”(即除去本身的全部字符),同理,则后缀可能为:”D””CD””BCD”

我们需要求的就是每一个字符其相对应的最大前后缀数,这样与模式串P一一对应的表称之为next表。

因此”ABCABDE”的next表为:-1 0 0 0 1 2 0 (字符用空格隔开)

A B C A B D E
-1 0 0 0 1 2 0

那么我们该如何实现代码呢?

对于每一个当前需要判断的字符而言,在构造next表时,应该向前进行比对,以上一个已经判断的情况为基础(初始值赋-1,部分教程中初始值赋0,两者没有实质区别),后缀如果+1位置的字符与前缀+1位置的字符相等,则next[i]就是next[i-1]+1,而如果不相等,则说明无法匹配,则next[i]=0。

3. KMP实现

与暴力匹配极其相似,利用while循环的条件控制, 进行匹配失败时,只需要将失败的模式串P的索引指向next表中对应的数值即可,其余匹配照旧线性执行即可。

KMP算法

#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;

class Solution 
{
public:
    int * next;
public:
    void buildNext(string P)
    {
        // 记录字符串的长度
        int m = P.size(), j = 0;
        // 给next数组分配空间
        next = new int[m];
        // 因为第一个字母没有前缀,所以next[0] = -1
        int t = next[0] = -1;
        
        while (j < m - 1)
        {
            // t < 0 也就是 t = -1, 模式串回溯到第一位,主串向前移动1步
            if (0 > t || P[j] == P[t])
            {
                next[++j] = ++t;    
            }
            else
            {
                t = next[t];    
            }
        }
    }
    
    int KMP(string T, string P)
    {
        // 更新next数组
        buildNext(P);
        // 指向主串
        int n = T.size(), i = 0;
        // 指向模式串
        int m = P.size(), j = 0;
        
        while (j < m && i < n)
        {
            // 如果 主串的第i个字符 和 模式串的第j个匹配,同时加加,匹配下一个
            // 如果 j < 0, 主串的i++ 和 模式串从0开始匹配
            if (j < 0 || T[i] == P[j])
            {
                i++;
                j++;
            }
            else
            {
                // 在next数组中找到j下一次开始的位置
                j = next[j];    
            }
        }
        
        if (j == m) // 匹配
        {
            return i - j;    
        }
        return -1; // 不匹配
    }
};

int strStr(string haystack, string needle)
{
    Solution s;
    int ans = s.KMP(haystack, needle);
    return ans;
}

int main()
{

    int ans = strStr("ABCDABCDEF", "ABCDE");
    
    cout << ans << endl;
    
    
    return 0;    

}
posted @ 2022-03-01 12:16  萨塔妮娅  阅读(213)  评论(0编辑  收藏  举报