【问题标题】:O(n) substring algorithmO(n) 子串算法
【发布时间】:2020-05-13 05:00:30
【问题描述】:

所以我一直在研究子字符串搜索算法,发现大多数算法(如 kmp 和 rabin-karp 算法)在进行一些字符串匹配之前需要额外的时间复杂度来进行预处理。这样做有什么好处吗?为什么他们不直接跳到字符串匹配,以使大 O 时间复杂度不会下降到 O(m+n)? 我尝试通过简单地跳过预处理时间来创建一个我认为是 O(n) 的子字符串算法(如果我错了,请纠正我)。我想知道为什么人们不这样做,请参考下面的C代码。

int search(char hay[], char needle[], int hayLen, int needleLen){
    int found;
    int i = 0;

    while (i < (hayLen - needleLen + 1)){
        if (hay[i] == needle[0]){
            found = 1;
            for (int j=0; j<needleLen; j++){
                if (hay[i] != needle[j]){
                    found = 0;
                    break;
                }
                i++;
            }
            if (found)
                return i - needleLen;
        }
        else
            i++;
    }
    return -1;
}

编辑:

删除了 strlen 函数以避免任何不必要的时间复杂度

【问题讨论】:

  • 如果你关心性能,你应该做的第一件事就是从你的循环中取出所有那些恒定的strlen() 调用,并在进入任何循环之前对它们进行一次评估。
  • 不确定 Rabin-Karp,但 KMP 是高效的,因为预处理允许它在输入中向前跳过并绕过许多比较。
  • 你在这里调用了很多 strlen,这对于应该是高性能的代码来说是相当惩罚的。这些值不会改变。在函数开始时将它们捕获到变量中。每个strlen() 调用本身都是O(n)
  • 当您对 'haystack' 中的每个字符重复迭代 'needle' 时,最终会花费 O(nm) 时间。想象一下如果 'needle' 是 'a...ab' 而 haystack 是 'a...a' 会发生什么(两个很长的字符串都有很多 a)。比 O(n+m) 差很多
  • 你不需要传入这些值,你可以随时计算它们。

标签: c algorithm substring knuth-morris-pratt rabin-karp


【解决方案1】:

嗯,你当前的代码是 O(n) 但是......

您的代码不起作用!

试试这个:

int main()
{
    char a[] = "aaaab";
    char b[] = "aaab";
    if (search(a, b, strlen(a), strlen(b)) != -1) 
        printf("OK\n"); 
    else 
        printf("FAIL\n");
    return 0;
}

显然b 可以在a 中找到,但您的代码显示它不存在。

问题是你总是增加i。通过这样做,您确实会得到 O(n),但它也会使代码失败。

【讨论】:

  • 谢谢!最后一个很好的解释为什么人们似乎使用 kmp 或 rabin-karp 进行子字符串搜索算法。我试图找出我的算法的缺陷,并想知道他们为什么不使用这种算法。
  • 这个main() 代码是O(a+b) 因为strlen(a), strlen(b) 都被调用了。 strlen(b) &lt; strlen(a) 是后来发现的,这里不是先决条件。代码可以使用strlen(a), strnlen(b, strlen(a)) 等来强制 O(a)
【解决方案2】:

老实说,这不是一个可怕的问题。我认为我们中的大多数人在尝试在发现 KMP 之前尝试创建字符串查找算法时都尝试过这样的解决方案。答案是这个贪心算法不起作用——它在i 中永远不会倒退。你可能会想“啊哈!这是针的开始!”继续前进,直到发现“呃-哦!这不是全部针!”。在这个算法中,我们只向前推进,继续寻找针的起点。但是,实际针的开头可能是您认为是中间字符,同时试图贪婪地匹配尽可能多的针。

例如,aabaaab。直到第三个a,你才意识到“呃,哦,这毕竟不是针”,然后一个彻底的 O(nm) 算法从第二个位置重新开始,但你的算法只是向前推进,并且永远不会意识到从第二个位置开始的aab。 KMP 通过注意中间针的哪些部分也可能是针的潜在起点来解决这个问题。

【讨论】:

  • Re "老实说不是一个可怕的问题。",好吧,除了 OP 忽略了 O(n+m) 在这种情况下仅意味着 O(n) (因为当提供长度时,m 总是小于 n),所以 O(n) 算法的复杂度不会更低。
  • @ikegami 您的评论没有错,但并不要求 m 小于 n。即使 O(n + n) 也是 O(n)
  • @4386427,确实,我的意思是小于或等于(前面已经说过了)。
  • @ikegami“因为当提供长度时,m 总是小于 n”——>这取决于长度是如何确定的。可以是你说的,也可以是 O(n+m)。
  • @chux-ReinstateMonica,我说它是 O(n) 当提供长度时(因为你可以添加if (needleLen &lt; hayLen) return -1;
【解决方案3】:

删除了 strlen 函数以避免任何不必要的时间复杂度

您删除了strlen 调用,但现在必须将字符串的长度传递给函数:

int search(char hay[], char needle[], int hayLen, int needleLen)

那么...随着needle 的大小增加,整个子字符串搜索的复杂性如何变化?毕竟不管是在函数内部还是函数外部计算长度,还是需要做的。 O(m+n) 表示复杂度取决于needlehaystack 的长度。

为了将这一点发挥到极致,您可以编写一个 O(1) search 函数,只需在haystack 中添加一个指示needle 位置的参数。

【讨论】:

    猜你喜欢
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-22
    • 1970-01-01
    • 1970-01-01
    • 2019-12-23
    • 1970-01-01
    相关资源
    最近更新 更多