这里使用尾递归有什么好处?

2024-03-17

我一直在阅读描述如何通过使用尾递归版本来降低快速排序的空间复杂度的文章,但我无法理解这是怎么回事。以下是两个版本:

QUICKSORT(A, p, r)
       q = PARTITION(A, p, r)
       QUICKSORT(A, p, q-1)
       QUICKSORT(A, q+1, r)


TAIL-RECURSIVE-QUICKSORT(A, p, r)
   while p < r
      q = PARTITION(A, p, r)
      TAIL-RECURSIVE-QUICKSORT(A, p, q-1)
      p = q+1

(来源 -http://mypathtothe4.blogspot.com/2013/02/lesson-2-variations-on-quicksort-tail.html http://mypathtothe4.blogspot.com/2013/02/lesson-2-variations-on-quicksort-tail.html)

据我了解,这两者都会导致数组的左半部分和右半部分递归调用。在这两种情况下,一次只会处理一半,因此在任何时候只有一个递归调用会使用堆栈空间。我无法看到尾递归快速排序如何节省空间。

上面的伪代码摘自文章 -http://mypathtothe4.blogspot.com/2013/02/lesson-2-variations-on-quicksort-tail.html http://mypathtothe4.blogspot.com/2013/02/lesson-2-variations-on-quicksort-tail.html文章中的解释更让我困惑——

快速排序对给定的子数组进行分区并继续递归两次; 一个在左侧子数组,一个在右侧。这些中的每一个 递归调用将需要其自己的单独的堆栈空间流。 该空间用于存储数组的索引变量 某种程度的递归。如果我们从一开始就想象这种情况发生 到执行结束时,我们可以看到堆栈空间每次都会翻倍 层。

那么尾递归快速排序如何解决所有这些问题呢?

好吧,我们现在只递归两个子数组,而不是递归 一。这样就无需在每一层将堆栈空间加倍 的执行。我们通过使用 while 循环来解决这个问题 执行相同任务的迭代控制。而不是需要 堆栈来保存两个递归调用的变量集,我们只需 更改同一组变量并使用单个递归调用 新变量。

我不明白在常规快速排序的情况下,堆栈空间如何在每一层执行时加倍。

注意:- 本文中没有提及编译器优化。


尾递归函数调用允许编译器执行常规递归通常无法执行的特殊优化。在尾递归函数中,递归调用是最后执行的事情。在这种情况下,编译器不必为每个调用分配堆栈帧,而是可以重新编写代码以简单地重用当前堆栈帧,这意味着尾递归函数将仅使用单个堆栈帧,而不是数百甚至数千。

这种优化是可能的,因为编译器知道一旦进行尾递归调用,就不需要变量的先前副本,因为没有更多的代码要执行。例如,如果递归调用后有打印语句,则编译器需要知道递归调用返回后要打印的变量的值,因此堆栈帧无法重用。

如果您想了解有关“空间节省”和堆栈重用实际如何工作的更多信息,请参阅以下 wiki 页面以及示例:尾调用 http://en.wikipedia.org/wiki/Tail_call

编辑:我没有解释这如何应用于快速排序,是吗?嗯,那篇文章中抛出了一些术语,这让一切都变得混乱(其中一些完全是错误的)。给定的第一个函数 (QUICKSORT) 在左侧进行递归调用,在右侧进行递归调用,然后退出。请注意,右侧的递归调用是函数中发生的最后一件事。如果编译器支持尾递归优化(如上所述),则只有左侧调用创建新的堆栈帧;所有正确的调用都只是重用当前帧。这可以节省some堆栈帧,但仍然会遇到分区创建一系列调用的情况,其中尾递归优化无关紧要。另外,即使右侧调用使用相同的框架,左侧调用也会调用within右侧调用仍然使用堆栈。最坏情况下,堆栈深度为N。

描述的第二个版本是not尾递归快速排序,而是一种仅递归完成左侧排序,而使用循环完成右侧排序的快速排序。事实上,这个快速排序(如另一个用户之前所描述的)不能应用尾递归优化,因为递归调用不是最后执行的事情。这是如何运作的?当正确实现时,对快速排序的第一次调用与原始算法中的左侧调用相同。然而,甚至没有调用右侧递归调用。这是如何运作的?好吧,循环会处理这个问题:它不是“先左后右”排序,而是通过调用对左侧进行排序,然后通过不断地仅对右侧进行排序来对右侧进行排序。左边的右边的。这听起来确实很荒谬,但它基本上只是对如此多的左元素进行排序,使得右元素变成单个元素,不需要排序。这有效地消除了正确的递归,从而减少了函数的递归性(伪递归,如果你愿意的话)。然而,真正的实现并不是每次都只选择左侧;它选择最小的一侧。想法还是一样的;它基本上只在一侧而不是两侧进行递归调用。选择较短的一侧将确保堆栈深度永远不会大于 log2(N),这是正确二叉树的深度。这是因为较短的边始终最多是当前数组部分大小的一半。然而,本文给出的实现并不能确保这一点,因为它可能会遇到“左边是整棵树”的相同最坏情况。如果您愿意阅读更多内容,这篇文章实际上对此给出了很好的解释:基于快速排序的高效选择和部分排序 http://blogs.msdn.com/b/devdev/archive/2006/01/18/514482.aspx

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

这里使用尾递归有什么好处? 的相关文章

  • 查找一个二维矩阵是否是另一个二维矩阵的子集

    最近我参加了一个黑客马拉松 我了解到一个问题 试图在 2d 矩阵中找到网格形式的模式 模式可以是 U H 和 T 并由 3 3 矩阵表示 假设我想展示 H 和 U 1 0 1 1 0 1 1 1 1 gt H 1 0 1 gt U 1 0
  • 如何提高洪水填充例程的性能?

    我正在我的应用程序中实现四路洪水填充 伪代码如下 Flood fill node target color replacement color 1 If the color of node is not equal to target co
  • 基于 2 个输入的伪随机数生成器 [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我需要根据 2 个输入值 X 和 Y 生成一个伪随机数 给定相同的 X 和 Y 值 我需要得到相同的结果 结果应介于 0 和 1 之间 含
  • 寻找簇的中心

    我有以下问题 进行抽象以找出关键问题 我有 10 个点 每个点与其他点有一定距离 我想要 能够找到簇的中心 即与其他点的成对距离最小的点 令 p j p k 表示点 j 和 k 之间的成对距离p i 是簇的中心点 iff p i s t m
  • 求一根棒可以切割的最大片数

    这是完整的问题陈述 给定一根长度为n的绳子 你需要找到最大的绳子数你可以让每一段的长度都在集合 a b c 中给定三个值a b c 我知道可以通过动态规划来实现最优解 但是 我还没有学过这个主题 我需要递归地解决这个问题 对于递归 主要的事
  • 从对列表创建邻接列表类型结构

    在 C 中 我有 class Pair int val1 int val2 我有一个来自以下来源的配对列表 List
  • 为什么Python中pop()的大O与pop(0)不同[重复]

    这个问题在这里已经有答案了 他们不应该都是O 1 因为从 Python 列表中的任何位置弹出一个元素涉及销毁该列表并在新的内存位置创建一个元素 蟒蛇的list实现使用动态调整大小的 Carray在引擎盖下 删除元素usually要求您移动后
  • 整数除法性质

    下面的整数算术性质成立吗 m n l m n l 起初我以为我知道答案 不成立 但现在不确定 它适用于所有数字还是仅适用于某些条件 即n gt l 该问题涉及计算机算术 即q n m q m n 忽略溢出 Case1 assume m kn
  • 在哪里可以找到有关双三次插值和 Lanczos 重采样的好读物?

    我想用 C 实现上述两种图像重采样算法 双三次和 Lanczos 我知道现有的实现有几十种 但我仍然想制作自己的实现 我之所以这么做 部分原因是我想了解它们是如何工作的 部分原因是我想为它们提供一些主流实现中没有的功能 例如可配置的多 CP
  • 比较两棵树的伪代码

    这是我遇到过几次的问题 并且不确信我使用了最有效的逻辑 例如 假设我有两棵树 一棵是文件夹结构 另一棵是该文件夹结构的内存 模型 我希望比较两棵树 并生成一棵树中存在的节点列表 而不是另一棵树中存在的节点列表 反之亦然 是否有公认的算法来处
  • 了解 F# 尾递归

    最近在学习F 我尝试以不同的方式解决问题 像这样 0 1 2 3 4 5 6 7 8 gt 0 1 2 3 4 5 6 7 8 head recursive let rec toTriplet v1 list match list with
  • 对列表中的相邻元素进行分组

    假设我想编写一个函数来执行此操作 输入 1 1 3 3 4 2 2 5 6 6 输出 1 1 3 3 4 2 2 5 6 6 它将相同的相邻元素分组 这个方法的名称应该是什么 此操作有标准名称吗 In 1 1 3 3 4 2 2 5 6 6
  • 加权图的 BFS 算法 - 寻找最短距离

    我看过很多帖子 即 post1 https stackoverflow com questions 30409493 using bfs for weighted graphs post2 https cs stackexchange co
  • 为什么使用 no-op 来填补 paxos 事件之间的空白是合法的?

    我正在学习Paxos算法 http research microsoft com en us um people lamport pubs paxos simple pdf http research microsoft com en us
  • n的渐近增长选择下限(n/2)

    如何找到 n select Floor n 2 的渐近增长 我试过 使用扩展并得到它等于 n n 1 floor n 2 1 n floor n 2 知道我该如何从那里去吗 感谢任何帮助 更喜欢提示而不是答案 我同意上面的答案 但想提供更多
  • 插入排序 - 如何接受输入并打印排序后的数组

    我试图做一个插入排序程序 它接受任何数据类型 Int Double String 然后打印排序后的数组 我知道我的代码可以工作 但我无法找出真正的问题 import java util public class MyInsertionSor
  • 模式识别算法

    过去我必须开发一个充当规则评估器的程序 你有一个先行词和一些后续词 动作 所以如果先行词评估为真 则执行的动作 当时我用的是修改版RETE算法 http en wikipedia org wiki Rete algorithm RETE 有
  • 在 O(n) 时间内对列表中的数字方块进行排序?

    给定一个按排序顺序排列的整数列表 例如 9 2 0 2 3 我们必须对每个元素进行平方并按排序顺序返回结果 所以 输出将是 0 4 4 9 81 我可以找出两种方法 O NlogN 方法 我们将每个元素的平方插入哈希集中 然后将元素复制到列
  • 竞争性编码 - 以最低成本清除所有级别:未通过所有测试用例

    当我遇到这个问题时 我正在一个竞争性编码网站上解决问题 问题指出 游戏中有 N 个关卡和 M 种可用武器 等级编号从 0 到 N 1 武器编号从 0 到 M 1 您可以按任意顺序清除这些级别 在每个关卡中 需要这些 M 武器的某些子集才能通
  • 地形/山地算法未按预期工作

    我想使用一个非常基本的原理创建一个上面有山的地形 如以下高度图所示 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 2 1 0 0 0

随机推荐