【问题标题】:Find number of occurrences of substring查找子字符串的出现次数
【发布时间】:2023-03-13 06:28:01
【问题描述】:

我有一个小问题。我正在解决一项编程任务,但遇到了问题。这很简单,但时间限制使它有点难。

查找子字符串的出现次数。您将获得 M - 长度 子串;要查找的子字符串,N - 基本字符串的长度;根据 字符串。
M N

输入

10
budsvabbud
79
uaahskuskamikrofonubudsvabbudnebudlabutkspkspkspmusimriesitbudsvabbudsvabbudnel

输出
3

我尝试使用内置函数find,但速度不够:

#include<iostream>
#include<string>

using namespace std;

int main()
{
    int n;
    int occurrences = 0;
    string::size_type start = 0;
    string base_string, to_find;
    cin >> n >> to_find >> n >> base_string;
    while ((start = base_string.find(to_find, start)) != string::npos) {
        ++occurrences;
        start++;; // see the note
    }
    cout << occurrences << endl;
}

所以我尝试编写自己的函数,但速度更慢:

#include<iostream>
#include<cstdio>
#include<string>
#include<queue>

using namespace std;

int main()
{
    int n, m;
    string to_find;
    queue<int> rada;  
    int occurrences = 0;
    cin >> m >> to_find >> n;
    for (int i = 0; i < n; i++)
    {
        char c;
        scanf(" %c", &c);
        int max = rada.size();
        for (int j = 0; j < max; j++)
        {
            int index = rada.front();
            rada.pop();
            if (c == to_find[index])  
            {
                if (++index == m) {
                    occurrences++;
                }
                else
                    rada.push(index);
            }
        }
        if (c == to_find[0])
        {
            if (1 == m)
                n++;
            else
                rada.push(1);
        }
    }
    cout << occurrences << endl;

}

我知道有些人在 0 毫秒内完成了这项工作,但我的第一个代码需要超过 2000 毫秒,而第二个代码则要多得多。你有什么想法如何解决这个问题吗? 谢谢。

编辑: 长度限制:

M

N

【问题讨论】:

  • 2000 毫秒!输入多长时间?
  • @HumamHelfawi 抱歉,我忘记写了。我将编辑我的问题。
  • 你启用优化了吗?你真的是说 2000 毫秒吗?我会惊讶于即使是调试构建也需要这么长时间。
  • 对于大文件中的快速搜索,像 Boyer-Moore 之类的东西可能要快一个数量级(或更多) - 但对于 79 个字符来说,它可能不值得。
  • 是的,我的意思是 2000 毫秒。但不是为了输入我举的例子。可能有长度为 200 000 的输入。

标签: c++ string performance find find-occurrences


【解决方案1】:

您提出的算法是 O(M*N),其中 N 是文本的长度,M 是搜索到的世界的长度。通常,这些库也实现了朴素算法。然而,Knuth、Morrison 和 Pratt 有一个算法,它在 O(M+N) 时间内完成。参见,例如,维基百科Knuth-Morrison-Pratt Algorithm。它有一些变体,可能更容易实现,例如Boyer-Moore-Horsepool

【讨论】:

  • 这个。如果您要搜索的字符串是 aaaa... 的 200k,而您要搜索的字符串是 aaaa...b 的 100k(99999 a's then 1 b),那么搜索的复杂性将是 200000 * 100000,即巨大的。这是因为,使用任何简单的实现,您将尝试在位置 1 匹配搜索字符串,然后匹配 99999 a,然后无法匹配 a b。然后您移动到位置 2 并重复失败。在搜索字符串的每个位置,您必须进行 99999 次匹配。 KMP 解决了这个问题,它的复杂度为 O(m+n) 而不是 O(m * n)。
【解决方案2】:

安全版

static size_t findOccurences(const char * const aInput, const char * const aDelim)
{
    if (aInput == 0x0 || aDelim == 0x0)
    {
        throw std::runtime_error("Argument(s) null");
    }

    const size_t inputLength = strlen(aInput);
    const size_t delimLength = strlen(aDelim);

    size_t result = 0;

    if (delimLength <= inputLength && delimLength > 0)
    {
        size_t delimIndex = 0;

        for (size_t inputIndex = 0; inputIndex < inputLength; inputIndex++)
        {
            if (aInput[inputIndex] != aDelim[delimIndex])
            {
                delimIndex = 0;
            }
            else
            {
                delimIndex++;

                if (delimIndex == delimLength)
                {
                    delimIndex = 0;
                    result++;
                }
            }
        }
    }

    return result;
}

不安全的版本

static size_t unsafeFindOccurences(const char * const aInput, const char * const aDelim)
{
    const size_t inputLength = strlen(aInput);
    const size_t delimLength = strlen(aDelim);

    size_t result = 0;
    size_t delimIndex = 0;

    for (size_t inputIndex = 0; inputIndex < inputLength; inputIndex++)
    {
        if (aInput[inputIndex] != aDelim[delimIndex])
        {
            delimIndex = 0;
        }
        else
        {
            delimIndex++;

            if (delimIndex == delimLength)
            {
                delimIndex = 0;
                result++;
            }
        }
    }

    return result;
}

结果安全

          x86        x64
Debug     5501ms     5813ms
Release   3889ms     3998ms

结果不安全

          x86        x64
Debug     5442ms     5564ms
Release   3074ms     3139ms

在 Windows 10 x64 Pro 下使用 Visual Studio 2015、Visual Studio 2015 (v140) 工具集编译。

使用this 输入。搜索“广告”和 1.000.000 次迭代。

【讨论】:

  • 拯救了我的一天!谢谢。
【解决方案3】:

我在调试模式下尝试了这段代码,没有进行任何优化,它花了 11 毫秒。 VS.NET 2013,英特尔酷睿 i7:

int main()
{
    int n;
    int occurrences = 0;
    string::size_type start = 0;
    string base_string, to_find;
    base_string.reserve(200000);
    to_find.reserve(100000);
    for (size_t i = 0; i < 100000; i++){
        base_string.push_back('a');
    }
    for (size_t i = 0; i < 100000; i++){
        base_string.push_back('b');
    }
    for (size_t i = 0; i < 100000; i++){
        to_find.push_back('b');
    }
    auto start_s = clock();
    while ((start = base_string.find(to_find, start)) != string::npos) {
        ++occurrences;
        start++;; // see the note
    }
    auto stop_s = clock();
    std::cout << (stop_s - start_s) / double(CLOCKS_PER_SEC) * 1000;
    cout << occurrences << endl;
    std::getchar();
}

编译器、配置、你的机器有问题,但是你的代码有问题。

【讨论】:

  • 这是来自网站的任务,我也将此代码发送到那里进行检查。我刚刚得到一个结果 - 超出时间限制 - 以及我的代码需要的时间:1.in OK 0 ms 2.in OK 0 ms 3.in OK 0 ms 4.in OK 1 ms 5.in OK 164 ms 6。在 OK 45 毫秒 7.in TLE 1811 毫秒
  • 尝试制作 base_string 200,000 'a' 和 to_find 字符串 99,999 'a' 和单个 'b'。然后重新测量...
  • 我做了这个.. 它从 01 到 14440
猜你喜欢
  • 2015-01-28
  • 1970-01-01
  • 2011-10-16
  • 2010-12-25
  • 2021-01-11
  • 2012-12-26
  • 1970-01-01
相关资源
最近更新 更多