如何解决递归和尾递归的实现逻辑
我已经编写了用于对数组元素求和的代码
递归
static int sum(int[] array)
{
if (array.Length == 1)
return array[0];
else
{
int[] newArr = new int[array.Length - 1];
for (int i = 1; i < array.Length; i++)
{
newArr[i - 1] = array[i];
}
return array[0] + sum(newArr);
}
}
和与
尾递归
static int sumTR(int[] array,int sum)
{
//base case
if (array.Length == 1)
return array[0] + sum;
else
{
//tail recursive case
int[] newArr = new int[array.Length - 1];
for (int i = 1; i < array.Length; i++)
newArr[i - 1] = array[i];
return sumTR(newArr,array[0] + sum);
}
}
据我所知,在尾递归中,基本方法不应该等待递归方法完成执行,也不应该依赖于它的输出。这种实现是实现这一目标的正确方法吗?
解决方法
据我所知,在尾递归中,基方法不应该等待递归方法完成执行,也不应该依赖其输出
这不太正确。尾递归主要使编译器能够应用 tail call optimization(如果支持),即将递归重写为常规循环。这具有减少堆栈中内存使用量的优点。它与“不等待”无关。
在第一个示例中,它必须为列表中的每个项目保留一个堆栈帧,如果您有一个很长的列表,则可能会耗尽堆栈内存并导致堆栈溢出。
在尾递归的情况下,当它到达尾调用时不再需要当前堆栈帧,因此每次调用都可以重新使用相同的堆栈帧,这应该导致代码排序等同于常规循环。
这个实现是实现这一目标的正确方法吗?
我觉得还不错。但这并不一定意味着会应用优化,它似乎取决于编译器版本,并且可能有其他要求。请参阅 Why doesn't .NET/C# optimize for tail-call recursion? 一般来说,我建议您依靠语言规范而不是编译器优化来确保程序的正确功能。
请注意,递归通常不是 C# 中的理想方法。对于简单的求和,使用常规循环更容易、更快、更易读。对于更复杂的情况,例如遍历树,递归可能是合适的,但在这种情况下,尾调用优化将无济于事。
,您可以使用 Span 防止复制数组。然后您可以 slice 递归到数组的末尾。
int sum(Span<int> span,int subtotal)
{
return span.Length > 0
? sum(span.Slice(1),subtotal + span[0])
: subtotal;
}
Span 是不久前添加的,我相信在 .NET Core 中,它带来了相当多的性能改进。它允许将更多代码从 C++ 核心移至 C#。 Here 是我读过的一篇关于该主题的文章。
,递归是一个调用自身的函数。尾递归是一个调用自身的函数,调用自身是它执行的最后一个操作。这很重要,因为支持尾递归的编译器可以在递归调用之前消除堆栈帧,甚至可以将其转换为循环。无论哪种情况,都可以防止由于重复调用而导致堆栈帧的累积,从而消除了堆栈溢出的可能性。
也就是说,您的递归 sum()
函数可以工作,但效率低下:它为每一步都创建新数组。有一种更简单的递归计算总和的方法:
static int sum(int[] array)
{
return sum(array,array.Length - 1);
}
static int sum(int[] array,int index)
{
return array[index] + (index == 0 ? 0 :
sum(array,index - 1));
}
第一个sum()
函数调用带有合适参数的辅助函数,辅助函数只需要调整提供的索引即可。为简洁起见,这里使用三元表达式完成此操作:它使函数本质上保持单行,并且清楚地说明递归调用不是返回前的最后一个操作(加法是)。
要将这个计算转换为尾递归计算,我们需要为中间结果添加一个参数:
static int sum(int[] array)
{
return sum(array,array.Length - 1,0);
}
static int sum(int[] array,int index,int res)
{
return index < 0 ? res :
sum(array,index - 1,res + array[index]);
}
这里的加法被移到递归调用之前,而且调用显然是最后一个操作,使得函数尾递归。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。