尾部递归函数调用允许编译器执行通常无法通过常规递归的特殊优化。在尾部递归功能中,递归调用是要执行的最后一件事。在这种情况下,编译器可以重新使用当前堆栈框架,而不是为每个调用分配堆栈框架,而是将尾部收回功能仅使用单个堆栈框架,而不是数百甚至数千个。
这种优化是可能的,因为编译器知道一旦尾递归调用,就不需要以前的变量副本,因为不再需要执行代码。例如,如果遵循递归调用的打印语句,则编译器将需要知道递归呼叫返回后要打印的变量的值,因此无法重复使用堆栈帧。
这是Wiki页面,如果您想了解有关此“节省空间”和堆栈重复使用的更多信息,以及示例: 尾声
编辑:我没有解释这是如何适用于QuickSort的吗?好吧,在这篇文章中,有些术语使一切都混淆了(其中有些是完全错误的)。给出的第一个功能(QuickSort)在左侧进行递归调用,右侧是递归调用,然后退出。请注意,右侧的递归电话是功能中发生的最后一件事。如果编译器支持尾部递归优化(上面解释),则只有左呼叫会创建新的堆栈框架;所有正确的调用只需重用当前帧即可。这可以保存 一些 堆叠框架,但仍然可能会遭受分区形成一系列调用的情况,在尾部递归优化无关紧要的情况下。另外,即使右侧呼叫使用相同的框架,左侧呼叫调用 内 右侧调用仍然使用堆栈。在最坏的情况下,堆栈深度为N。
所述的第二个版本是 不是 尾巴递归的QuickSort,而不是循环排序的QuickSort,并且使用循环进行了右排序。实际上,此QuickSort(如其他用户之前描述的那样)不能对其进行尾随递归优化,因为递归调用不是最后执行的事情。这是如何运作的?正确实施后,对QuickSort的第一个调用与原始算法中的左侧调用相同。但是,甚至没有调用右侧递归电话。这是如何运作的?好吧,循环会解决这个问题:它不是对“左右右”进行排序,而是用呼叫对左侧分组,然后通过仅连续排序 右侧的左侧. 。听起来确实很荒谬,但基本上只是整理了很多左边,以至于权利成为单一要素,而不需要整理。这有效地消除了正确的递归,从而使功能降低了递归(如果可以的话,伪递归)。但是,实际实现并非每次都选择左侧。它选择最小的一面。这个想法仍然相同;基本上,它仅在一侧而不是两者都进行递归调用。选择较短的一面将确保堆栈深度永远不会大于log2(n),这是合适的二进制树的深度。这是因为较短的一侧总是将最多是当前数组部分的大小。文章给出的实现并不能确保这一点,因为它可能会遭受“左为整棵树”的最坏情况。如果您愿意做更多的阅读,则本文实际上可以很好地解释它: 基于QuickSort的有效选择和部分分类