【问题标题】:Forming recurrence relations形成递归关系
【发布时间】:2016-11-12 16:27:45
【问题描述】:

我有一个关于形成递归关系和计算时间复杂度的问题。

如果我们有一个递归关系 T(n)=2T(n/2) + c 那么这意味着恒定的工作量 c 被分成 2 部分 T(n/2) + T(n/2)绘制递归树时。

现在考虑阶乘的递归关系,即 T(n)=n*T(n-1) + c 。如果我们遵循上述方法,那么我们应该将工作 c 分成 n 个实例,每个实例 T(n-1),然后评估时间复杂度。但是,如果以这种方式计算,那么答案将是 O(n^n),因为我们将有 O(n^n) 的递归调用,这是错误的。

所以我的问题是为什么我们不能使用与第一种情况相同的方法将元素分成子部分。

【问题讨论】:

  • "这意味着恒定的工作量 c 被分成 2 部分 T(n/2) + T(n/2)" - 这不是正确的图片。可以将其视为“首先递归访问子节点。然后访问当前节点并引发那里有价值的工作”。所以对于T(n)=n*T(n-1) + c,我们首先做n 的复杂工作T(n-1),然后我们做1 复杂的工作c...

标签: algorithm recursion time-complexity


【解决方案1】:

让递归关系为T(n) = a * T(n/b) + O(n)

这个递归意味着有一个递归函数:

  • 将原问题分解为a子问题
  • 如果当前问题大小为n,则每个子问题的大小将为n/b
  • 当子问题很简单(太容易解决)时,不需要递归,直接解决(这个过程需要 O(n) 时间)。

当我们说原始问题被划分为a子问题时,我们的意思是在函数体中有a递归调用。

例如,如果函数是:

int f(int n)
{
    if(n <= 1)
        return n;
    return f(n-1) + f(n-2);
}

我们说问题(大小为n)分为两个子问题,大小为n-1n-2。递归关系为T(n) = T(n-1) + T(n-2) + c。这是因为有 2 个递归调用,并且具有不同的参数。

但是,如果函数是这样的:

int f(int n)
{
    if(n <= 2)
        return n;
    return n * f(n-1); 
}

我们说问题(大小为n)仅分为1 个子问题,大小为n-1。这是因为只有 1 个递归调用

所以,递归关系将是T(n) = T(n-1) + c

如果我们将T(n-1)n 相乘,这看起来很正常,我们表示进行了n 递归调用。

请记住,我们形成递归关系的主要动机是对递归函数进行(渐近)复杂性分析。尽管 n 看起来不能从关系中丢弃,因为它取决于输入大小,但它的用途与在函数本身中的用途不同。

但是,如果您说的是函数返回的,则应该是f(n) = n * f(n-1)。在这里,我们乘以n,因为它是一个实际值,将用于计算。

现在,来到T(n) = T(n-1) + c 中的c;它只是建议当我们解决大小为n 的问题时,我们需要解决大小为n-1 的较小问题以及其他一些常量(常量时间)工作,例如比较、乘法和返回值也会被执行。

即使使用递归树方法,我们也永远无法将“恒定工作量c”分成T(n/2)T(n/2) 两部分。事实上,我们正在将问题分成两半。在递归树的每一级中,每次递归调用都需要相同的“c”工作量。

如果有像T(n) = 2T(n/2) + O(n)这样的循环关系,其中要完成的工作量取决于输入大小,那么每个级别要完成的工作量将在下一个级别减半,就像你一样描述。

但是,如果递归关系类似于T(n) = T(n-1) + O(n),我们不会在下一个递归级别将工作量分成两半。我们只会在每个连续级别将工作量减少一个(n 大小的问题在下一个级别变成 n-1)。

要检查工作量将如何随着递归而变化,请将替换方法应用于您的递归关系。

希望我已经回答了你的问题。

【讨论】:

    猜你喜欢
    • 2016-07-22
    • 2012-04-09
    • 1970-01-01
    • 2020-11-09
    • 2014-05-03
    • 2019-10-18
    • 2014-12-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多