规划问题的递归解决方案的最佳方法是什么?

时间:2011-09-06 02:06:19

标签: language-agnostic data-structures recursion

我正在学习递归。我已经使用递归解决了一些其他问题,例如创建二叉树,河内塔等等。所以,我理解递归是什么,但我发现自己很难规划和实现正确的递归解决方案。

是否有任何一般性提示可用于规划,思考或实施问题的递归解决方案?

4 个答案:

答案 0 :(得分:4)

递归就是在解决问题的进程中识别 “自相似性” 。一个典型的递归示例,计算正整数的阶乘表明这个过程非常好。

由于阶乘n!定义为n * (n-1) * (n-2) ... * 1,您应该能够看到

n! = n * (n-1)!

换句话说, n的阶乘是“n次因子(n-1)”

如果您能够理解该陈述,以及它如何表现出“自我相似”的行为,那么您就可以很好地解决递归问题了。 编程递归时的关键是识别何时停止,而不执行递归调用。在阶乘的情况下,当您尝试确定阶乘的数字为1时停止。结果简单地定义为1,因此您返回该值而不是返回递归函数调用的值。

因此,在考虑如何递归地解决问题时,我的建议是尝试在手头的问题中找出这种自相似性。如果您可以轻松识别这种相似性,那么问题可能会有一个高效优雅的递归解决方案。如果这种自相似性不明显,则可能更适合迭代方法。

答案 1 :(得分:2)

基本上,只考虑两件事:

  • 可以用相同(或类似)的问题来表达问题,但是使用“更简单”的参数吗?
  • 有一个明确的问题,那个更容易的问题变得微不足道了吗?

你会发现这些属性适用于所有经典的递归算法,如二进制搜索,树遍历,排序/合并,阶乘计算,最大公分母计算等等(a)

如果满足这两个条件,则问题可能适合递归解决方案。

我说“可能”,因为即使出现这些属性的问题也不总是适合递归,例如:

// Add two unsigned inetegers.
unsigned int add (unsigned int a, unsigned int b) {
    if (a == 0)
        return b;
    return add (a - 1, b + 1);
}

现在虽然这是一个有点有效的递归解决方案(虽然有点做作),但当你的初始a很大时,你几乎肯定会用完堆栈空间。换句话说,现实世界将影响数学思想的纯洁性。

(a)您可能想知道为什么上面的add和GCD或因子计算之间存在差异。答案通常在于每次递归调用都会缩短“搜索空间”(所有可能结果的列表)的速度。

例如,遍历平衡二叉树将消除每次调用大约一半的剩余搜索空间。 GCD计算执行模运算,也可以合理地快速缩小搜索空间。

然而,add函数根本不会很快减少搜索空间,这就是为什么它不适合递归。

阶乘法也不会迅速减少搜索空间,因为它会从每个递归调用的参数中减去一个(类似于add)。

然而人们仍然使用它,可能是因为在递归调用的数量产生差异之前,你将耗尽因子 long 的存储空间(64位无符号整数只能保持不变20!)。

答案 2 :(得分:1)

一旦确定问题可以递归解决,最重要的事情之一就是确定递归算法的停止条件。一个简单的例子是阶乘的计算:你知道你应该在你到达0或1时(无论你选择什么)停止,因此这应该是你输入你的函数之前检查的第一件事,如果你不允许递归继续我想最终得到一个堆栈溢出异常:

public static int factorial(int n)
{
    if (n == 1)//I'm done
        return 1;
    return n * factorial(n - 1); //continue with the recursion
}

这几乎是我的递归方法:什么是停止条件?输入递归函数后将其作为第一个语句。

答案 3 :(得分:0)

通常,您需要设计一些任务来设计递归算法。我将以河内塔问题为例,因为它非常符合法案。

首先,确保您可以根据递归定义查看问题本身。具体而言,您希望确定如何将整个问题描述为对类似的较小子问题以及固定数量的工作进行操作。

  

对于河内塔的问题,一个非常容易的情况是移动一个大小为N的塔基本上与N-1和单个盘的移动塔相同。但是,如果不知道解决方案,这并不是很明显,哪个磁盘应该是N + 1;无论是顶部还是底部。我们需要更多信息。

下一部分,实际上是上述问题的一个子集,是考虑终止条件;您必须知道何时停止递归。如果您错过了算法中的这一步,您将最终陷入无限循环或超越数据结构。

  

移动1号塔的塔与移动单个盘完全相同;没有理由回复。换句话说,移动一个零大小的塔就像什么都不做一样,你可以完全跳过它。

最后;您必须确定问题所定义的不变量,以指导您实际执行工作的方式。它基本上归结为找到你的算法必须做的事情,以便较小的子问题确实看起来像更大的问题,然后只在那些条件下递归。

  

河内塔的具体要求是不允许磁盘放在较小的磁盘上。换句话说,您不允许将放置在比该塔的底部磁盘小的磁盘上。关于这个问题的更多推理将使我们得出这样的结论:如果我们在中间的某个点拆分塔,并将拆分下方的盘重新排列成一些任意但有效的布置,那么拆分上方的塔可以在顶部任何这些重新排列的磁盘,因为每一个都必须大于拆分时的磁盘。无法在拆分上方重新安排磁盘;顶部磁盘顶部不允许任何内容。

     

总的来说,这意味着我们必须从自下而上开始工作;将塔分成底盘。这也意味着退化的情况,即n = 1,正在移动顶部磁盘。因此,递归算法是递归移动N-1个磁盘,将第n个磁盘移动到目标,然后将N-1个磁盘移动到目标位置。

如果这还不够指导,那么您可能想要提出更具体的问题