【问题标题】:Seeking for best algorithm (asymptotically) and ignoring other details寻求最佳算法(渐近)并忽略其他细节
【发布时间】:2011-10-28 12:54:56
【问题描述】:

在某些情况下,对问题采用蛮力方法的复杂性在性能方面不够好。

让我们举个例子,例如θ(n^2)。

使用递归方法可以将其改进为 Theta(nlogn)。

显然,渐近地人们会选择使用递归方法,因为对于越来越大的输入 N,增长的顺序会更低,即更好的性能。

我的问题如下:

如果随着 N 越来越大,递归(即分而治之的方法)的性能会越来越好,那么当我们对 N 的 huge 输入进行递归时,忽略这一点不是不切实际吗?出栈?
由于大量输入,我们实际上从未得到结果。

那么,如果我们忽略这些细节,我们如何确保为特定问题选择最佳算法呢?

【问题讨论】:

标签: performance algorithm recursion computer-science theory


【解决方案1】:

可以在不使用硬件堆栈的情况下实现递归算法。如果堆栈溢出的可能性是一个严重的问题,您可以实现自己的堆栈或简单地限制递归的深度。

但是,如果算法递归地划分一组数据(大小为n),则递归数将与log n 成正比。这意味着要耗尽大小为s 的堆栈,您需要大小为2^s 的数据,随着s 的增长非常快。人们往往不会意识到指数函数增长的速度有多快。我的观点是,使用这种类型的算法(在每个递归步骤中拆分一组数据),输入数据不太可能大到需要如此多的递归以耗尽堆栈。

编辑:但是我不是专业的程序员,所以我缺乏实践经验。

【讨论】:

  • 例如:在 64 位架构上,可寻址空间将其限制为 s=64,因此除非您在每个步骤的堆栈中存储大量信息,否则不会耗尽堆栈。跨度>
  • 不确定你的意思是“不使用硬件堆栈”。递归函数,作为不断调用自己,使用堆栈来存储当前的函数帧。@yi_H:现在你正在考虑硬件.但是RAM模型忽略了这些事情......
  • @user384706 我的意思是您可以(在数学上)实现相同的算法,而无需使用以某种特定语言调用自身的函数。回复您对 yi 的评论:实际上,您根本无法拥有足够的内存来耗尽 64 位地址空间。除了实际考虑之外,我的观点是,对于任何潜在的未来架构(假设我们的内存是现在的 1000 倍),堆栈空间可能会随着可用内存线性增长,而 需要 堆栈空间(对于这个算法)以对数方式增长(慢得多)。
  • @user384706 对于您描述的算法类型,您在实践中永远不会用完堆栈空间。当然,对于其他类型的递归算法,考虑堆栈溢出的可能性很重要......
  • @Szabolcs:在我看来,你有一个 +1。但另一方面,除了硬件之外,还有你使用的语言。如果你使用的 Java 有一个浅堆栈,你会得到一个 stackoverflow除非你开始篡改 JVM 参数。
【解决方案2】:

这是我第一次在快速排序中看到的一个有用的技巧:

f(x)
{
  for (;;)
  {
    if (x is trivial)
    {
      return;
    }
    x1 = one half of problem x
    x2 = other half of problem x
    if (x1 is the little half)
    {
      x = x2;
      f(x1);
    }
    else
    {
      x = x1;
      f(x2);
    }
  }
}

关键在于,每个递归调用都针对问题的一小部分——通常小于原始问题的一半——而问题的较大一半是通过迭代完成的。如果问题的大小为 2^n,则堆栈的大小只需为 n,即使 - 由于拆分非常不均匀 - 您最终花费的时间接近 n^2 而不是 n log n。

就使用 n log n 时间但会加载堆栈的算法的“实际成本”而言,请注意,在 n log n 时间内运行的算法没有时间使用超过 n log n 的内存,所以堆栈的负载和负载实际上不可能是大量的内存,尽管它可能足以让例如Java 解释器会在您的递归失控的印象中使您的程序崩溃。

【讨论】:

  • mcdowella:我真的不明白你发布的伪代码。大部分的迭代在哪里?你对大小部分都进行递归。an algorithm that runs in time n log n does not have the time to use more than n log n memory。你在比较时间和空间.我不明白这个说法是如何有效的。我的意思是如果一个算法需要 1 个时间步,根据你的类比它使用多少内存?
  • 抱歉 - 我过度简化了类似快速排序的程序的结构。 petebecker.com/js/js200101.html 有一个更合理(如果更长)的解释 - 看看它在哪里说“为了解决这里的问题,我们也需要使消除尾递归的代码更智能。如果我们对两部分中较小的部分调用排序,并在较大的部分上循环,我们将显着减少堆栈使用量”
  • 如果你用时钟周期来衡量时间——或者更好的是,内存总线周期,那么一个时间单位最多可以保存一个内存写入。更理论上,如果您的程序花费时间 n log n 但使用空间 n^2,那么它每单位时间写入 n / log n 个内存单元,随着 n 越来越大,这没有意义。
猜你喜欢
  • 1970-01-01
  • 2021-11-07
  • 2010-12-11
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
相关资源
最近更新 更多