这是考虑编程中递归性的正确方法吗? (例)

时间:2015-05-21 18:40:23

标签: algorithm function recursion

我一直在努力学习编程中的递归,我需要有人确认我是否理解它是什么。

我试图思考它的方式是通过对象之间的碰撞检测。

假设我们有一个功能。当确定发生碰撞时调用该函数,并使用对象列表调用该函数以确定哪个对象发生碰撞,以及碰撞的对象。它通过首先确认列表中的第一个对象是否与任何其他对象发生冲突来完成此操作。如果为true,则该函数返回列表中发生冲突的对象。如果为false,则该函数使用缩短的列表调用自身,该列表排除第一个对象,然后重复该过程以确定它是否是列表中碰撞的下一个对象。

这是一个有限递归函数,因为如果不满足所需条件,它会使用更短更短的列表调用自身,直到它推导出满足所需条件。这与潜在的无限递归函数形成对比,例如,它所调用的列表不会缩短,但列表的顺序是随机的。

所以......这是对的吗?或者这只是迭代的另一个例子?

谢谢!

编辑:我很幸运能够得到@rici,@ Evan和@Jack的三个非常好的答案。从技术和实践的角度来看,他们都从不同角度给我提供了宝贵的见解。谢谢!

3 个答案:

答案 0 :(得分:2)

从技术上讲,你对递归的工作方式有正确的看法。

实际上,您不希望对上面描述的实例使用递归。原因是每个递归调用都会增加堆栈(大小有限),并且递归调用在处理器上很昂贵,有足够的对象会在大型应用程序中遇到严重的瓶颈。

如果有足够的递归调用,就会导致堆栈溢出,这正是你得到的无限递归"。你永远不会想要无限量的东西;它违背了递归的基本原则。

递归适用于两个定义特征:

  1. 可以定义基本情况:根据您的需要最终可能达到0或1
  2. 可以定义一般情况:不断调用一般情况,减少问题集,直到达到基本情况。
  3. 一旦定义了两种情况,就可以定义递归解决方案。 递归的目的是解决一个非常大且难以解决的问题,并不断将其分解,直到它易于使用。

    一旦达到我们的基本案例 ,方法" recurse-out"。这意味着它们向后反弹,返回到调用它的函数中,从下面的函数中获取所有数据! 正是在这一点上,我们的运营才真正发生。

    一旦达到原始功能,我们就得到了最终结果。

    例如,让我们说你想要前3个整数的总和。第一个递归调用传递给数字3。

        public factorial(num) {
          //Base case
          if (num == 1) {
             return 1;
          }
          //General case
          return  num + factorial(num-1);
        }
    

    通过函数调用:

    factorial(3); //Initial function call
    
    //Becomes..
    
    factorial(1) + factorial(2) + factorial(3) = returned value
    

    这给了我们6的结果!

答案 1 :(得分:2)

任何迭代都可以递归表达。 (并且,使用辅助数据结构,反之亦然,但不是那么容易。)

我会说你是在思考迭代。那不是坏事;我不是说批评。简单地说,您的解释是“执行此操作,然后继续执行直到达到目的”。

递归是一种略有不同的思维方式。我有一些问题,如何解决它并不明显。但我观察到,如果我知道一个更简单的问题的答案,我可以很容易地解决手头的问题。而且,还有一些非常简单的问题我可以直接解决。

递归解决方案基于使用更简单(更小,更少,无论如何)的问题来解决手头的问题。如何找出一组对象中的哪些对象碰撞?

  1. 如果集合少于2个元素,则没有对。这是最简单的问题,它有一个明显的解决方案:空集。

  2. 否则,我选择一些对象。所有碰撞对都包含此对象,或者它们不包含此对象。所以这给了我两个子问题。

  3. 不涉及所选对象的一组碰撞显然与我开始时的问题相同,但设置较小。所以我用一个较小的问题替换了这部分问题。这是一次递归。

  4. 但我还需要所选对象与之碰撞的一组对象(可能是一个空集)。这是一个更简单的问题,因为现在已知每对中的一个元素。我也可以递归地解决这个问题:

    • 我需要包含对象X和一组对象的对。

      1. 如果集合为空,则没有对。简单。

      2. 否则,我从集合中选择一些元素。然后我找到X和集合其余部分之间的所有冲突(一个更简单但相同的问题)。

      3. 如果X和所选元素之间发生冲突,我会将其添加到刚刚找到的集合中。

      4. 然后我返回该集。

答案 2 :(得分:0)

在我看来,您的场景就像迭代编程一样,但您的功能只是将自己称为继续比较的一种方式。这只是重新设置你的功能,以便能够用较小的列表调用自己。

根据我的经验,递归函数更有可能扩展到多个线程' (可以这么说),用于处理信息的方式与公司的层次结构用于委派的方式相同;老板将合同交给管理人员,管理人员将工作分开并交给各自的员工,工作人员完成工作,并将其交还给经理,后者向老板汇报。

递归函数的最佳示例是遍历文件系统上所有文件的函数。 (我将在伪代码中执行此操作,因为它适用于所有语言)。

function find_all_files (directory_name)
{
    - Check the given directory name for sub-directories within it
    - for each sub-directory
        find_all_files(directory_name + subdirectory_name)

    - Check the given directory for files
    - Do your processing of the filename; it is located at directory_name + filename

}

通过使用目录路径作为参数调用它来使用该函数。它做的第一件事是,对于每个子目录,它生成一个到子目录的实际路径的值,并将其用作调用find_all_files()的值。只要给定目录中有子目录,它就会一直调用自己。

现在,当函数到达仅包含文件的目录时,允许它继续进行处理文件的部分。完成后,它退出,并返回到遍历目录的自己的前一个实例。

它继续处理目录和文件,直到它完成所有迭代并返回主程序流,在那里你首先调用了find_all_files的原始实例。

另外一个注意事项:有时候全局变量可以很方便地使用递归函数。如果你的功能只是搜索第一次出现的东西,你可以设置一个"退出"变量作为一个标志,以及#34;停止你现在正在做的事情!"。您只需在函数内部进行的任何迭代期间添加对标志状态的检查(如示例中所示,遍历所有子目录)。然后,当设置标志时,您的功能就会退出。由于该标志是全局的,因此该函数的所有代都将退出并返回主流。