【问题标题】:Time complexity in recursive function in which recursion reduces size递归函数的时间复杂度,其中递归减小了大小
【发布时间】:2016-01-22 20:49:48
【问题描述】:

我必须估计 Solve() 的时间复杂度:

// Those methods and list<Element> Elements belongs to Solver class

void Solver::Solve()
{
    while(List is not empty)
        Recursive();
}

void Solver::Recursive(some parameters)
{

    Element WhatCanISolve = WhatCanISolve(some parameters); //O(n) in List size. When called directly from Solve() - will always return valid element. When called by recursion - it may or may not return element
    if(WhatCanISolve == null)
        return;

    //We reduce GLOBAL problem size by one.
    List.remove(Element); //This is list, and Element is pointed by iterator, so O(1)

    //Some simple O(1) operations


    //Now we call recursive function twice.
    Recursive(some other parameters 1);
    Recursive(some other parameters 2);
}

//This function performs search with given parameters
Element Solver::WhatCanISolve(some parameters)
{
    //Iterates through whole List, so O(n) in List size
    //Returns first element matching parameters
    //Returns single Element or null
}

我的第一个想法是它应该在 O(n^2) 左右。

然后我想到了

T(n) = n + 2T(n-1)

其中(根据 wolframalpha)扩展为:

O(2^n)

但是我认为第二个想法是错误的,因为 n 在递归调用之间减少了。

我还对大型集进行了一些基准测试。结果如下:

N       t(N) in ms
10000   480
20000   1884
30000   4500
40000   8870
50000   15000
60000   27000
70000   44000
80000   81285
90000   128000
100000  204380
150000  754390

【问题讨论】:

  • 这是什么语言?当然不是 C++。
  • 很多时候,当你在给定时间内减少购买一定数量的成本时,你最终会得到某种形式的 log(n)
  • 知道WhatCanISolve 的工作原理很难估计。 IE。什么时候可以返回 null(w.r.t. 列表大小)。
  • @SergeyA 这是伪c++,我简化了很多,使其更简洁。
  • @MikhailMaltsev 此函数迭代元素 (std::list)。在每次迭代中,它比较 6 个双精度数。

标签: c++ algorithm recursion time-complexity


【解决方案1】:

您的算法仍然是 O(2n),即使它每次将问题大小减少一项。你的差分方程

T(n) = n + 2T(n-1)

不考虑在每个步骤中删除项目。但它只删除一项,所以方程应该是 T(n) = n + 2T(n-1) - 1。按照你的例子和

使用WolframAlpha to solve this 保存代数得到解 T(n) = (c1 + 4) 2n-1 - n - 2 仍然是 O(2n)。它删除了 一个 项,考虑到其他因素(尤其是递归),这并不是一个可观的数量。

想到的一个类似例子是 n*n 2D 矩阵。假设您仅将其用于三角矩阵。即使您为每一列删除一行来处理,遍历每个元素仍然具有 O(n2) 复杂度,这与使用所有元素时相同 (即方阵)。

为了进一步的证据,我展示了你自己收集的运行时间数据的图表:

【讨论】:

  • “你的差分方程没有考虑到每一步删除一个项目。”嗯?它确实通过使用 n-1 调用递归来解决它,并且完成的工作仍然是 theta(n) (这也是为什么在减去或不减去 1 或任何其他常数的情况下仍然得到相同结果的原因)。另一方面,该图看起来很像 n^2,这是您所期望的,因为第二次递归总是 O(1)。
  • 为什么第二次递归总是O(1)?我们真的不知道它会做什么,因为伪代码没有指定。
  • 列表不是作为参数传递的,而是类的一个字段。显然是一个错误,但从图表来看,它也存在于真实代码中。
  • 现在我想,我添加-1 对我来说没有意义。无论如何,T(n) = n + 2T(n-1) 仍然是 O(2^n)。我会改变答案
  • 是的,但是代码的编写方式意味着第二次递归永远不会起作用,所以它是 n+T(n-1).. 看图表 - 这不是指数的样子,这是经典的二次行为!
【解决方案2】:

大概时间是二次的。如果WhatCanISolve返回nullptr,如果列表为空,则所有调用

Recursive(some other parameters 2);

将在 O(1) 内完成,因为它们是使用空列表运行的。这意味着,正确的公式实际上是

T(n) = C*n + T(n-1)

这意味着,T(n)=O(n^2),这与我们在图中看到的非常吻合。

【讨论】:

  • 我们真的不知道some other parameters 是什么。更多地澄清代码正在做什么会有所帮助。我们不知道任何一个 Recursive() 调用是否一定总是返回基本情况(即在恒定时间内)。
  • 我们调用每个递归两次,这改变了公式。
  • @peku33 在第一次递归返回后,我们知道 WhatCanISolve 在第二次递归时也将返回 null,这使得它成为 O(1)。当然是代码中的一个错误(对共享列表的递归......至少可以说很奇怪,我想不出在这种形式中有意义的任何枪战),但就目前而言,这是正确的答案。跨度>
  • @Voo。为什么会返回null? WhatCanISolve 可以匹配单个参数集的许多元素。澄清。这是立方体包装问题。放置一个立方体后,剩下的空间被分成两部分,每部分递归填充
猜你喜欢
  • 1970-01-01
  • 2022-11-19
  • 2018-05-29
  • 2018-08-18
相关资源
最近更新 更多