【问题标题】:Time complexity analysis of function with recursion inside loop循环内递归函数的时间复杂度分析
【发布时间】:2013-08-26 03:11:48
【问题描述】:

我正在尝试分析以下函数的时间复杂度。该函数用于检查一个字符串是否由其他字符串组成。

set<string> s; // s has been initialized and stores all the strings
bool fun(string word) {
    int len = word.size();

    // something else that can also return true or false with O(1) complexity

    for (int i=1; i<=len; ++i) {
       string prefix = word.substr(0,i);
       string suffix = word.substr(i);
       if (prefix in s && fun(suffix))
           return true;
       else
           return false;
    }
}

我认为时间复杂度是O(n),其中 n 是单词的长度(对吗?)。但是由于递归在循环内,我不知道如何证明它。

编辑:

此代码不是正确的 C++ 代码(例如,prefix in s)。我只是展示这个函数的思想,想知道如何分析它的时间复杂度

【问题讨论】:

  • 我怀疑是O(n)。外层循环执行n 次,每一次都再次调用该函数。我在想一些不好的事情,比如O(2^n)。你能解释一下由其他字符串组成的字符串是什么意思吗?
  • @Teepeemm 喜欢 word1 = abcdef,word2 = abc,word3 = def。 word1 由 word2 和 word3 组成
  • 这段代码看起来有问题。循环不应该从i=1 开始吗?实际上,除非s 包含空字符串,否则它总是会立即返回false,如果s 确实包含空字符串,它将无限递归。此外,除非s 被初始化,否则prefix in s 将始终评估为false。这也会使函数立即返回false
  • @TedHopp 是的,此代码不正确。我只是想展示这个函数的想法并决定它的复杂性

标签: algorithm time-complexity


【解决方案1】:

对此进行分析的方法是根据输入的长度和前缀在s 中的(未知)概率来开发递归关系。假设前缀出现在s 中的概率由前缀长度 L 的某个函数 pr(L) 给出。让复杂度(操作数)由 T(len) 给出。

如果 len == 0(word 是空字符串),则 T = 1。(该函数在循环后缺少最终的 return 语句,但我们假设实际代码只是一个想法的草图,而不是实际执行的内容)。

对于每个循环迭代,用 T(len; i) 表示循环体复杂度。如果前缀不在s 中,则主体具有恒定复杂度(T(len; i) = 1)。此事件的概率为 1 - pr(i)。

如果前缀在s中,则函数根据对fun(suffix)的递归调用返回truefalse,其复杂度为T(len - i)。该事件的概率为 pr(i)。

所以对于i的每个值,循环体复杂度为:

T(len; i) = 1 * (1 - pr(i)) + T(len - i) * pr(i)

最后(这取决于预期的逻辑,而不是发布的代码),我们有

T(len) = sum i=1...len(T(len; i))

为简单起见,让我们将 pr(i) 视为值为 0.5 的常数函数。那么 T(len) 的递归关系是(直到一个常数因子,这对于 O() 计算并不重要):

T(len) = sum i=1...len(1 + T(len - i)) = len + sum i=0...len-1(T(i))

如上所述,边界条件是 T(0) = 1。这可以通过标准递归函数方法解决。让我们看一下前几个术语:

len   T(len)
0     1
1     1 + 1 = 2
2     2 + 2 + 1 = 5
3     3 + (4 + 2 + 1) = 11
4     4 + (11 + 5 + 2 + 1) = 23
5     5 + (23 + 11 + 5 + 2 + 1) = 47

模式显然是 T(len) = 2 * T(len - 1) + 1。这对应于指数复杂度:

T(n) = O(2n)

当然,这个结果取决于我们对 pr(i) 所做的假设。 (例如,如果所有 i 的 pr(i) = 0,则 T(n) = O(1)。如果 pr(i) 具有最大前缀长度,那么也会出现非指数增长——pr(i) = 0 对于所有 i > M 对于某些 M。)pr(i) 独立于 i 的假设可能是不现实的,但这实际上取决于s 的填充方式。

【讨论】:

    【解决方案2】:

    假设您已经修复了其他人注意到的错误,那么 i 值是字符串被拆分的位置(每个 i 是最左边的拆分点,然后您递归到右边的所有内容i)。这意味着如果您要展开递归,您将查看最多 n-1 不同的分割点,并询问每个子字符串是否是有效单词。如果word 的开头没有您的集合中的很多元素,则一切正常,从那时起您可以跳过递归。但在最坏的情况下,prefix in s 始终为真,并且您尝试所有可能的n-1 分割点子集。这给出了2^{n-1} 不同的拆分集,乘以每个此类集的长度。

    【讨论】:

    • 你能解释为什么在最坏的情况下它是 2^{n-1} 不同的分裂吗?谢了!
    • 就目前而言,循环使用i=1 执行,然后返回。这意味着fun 在最坏的情况下是O(n)。假设循环应该与所有i 一起执行然后返回,可能更容易想到一个子集。那么这个子集对应于i=subset[0],其余的子集依赖于suffix上的递归。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-26
    • 2022-10-25
    • 2020-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多