如何以递归方式思考?

Posted

技术标签:

【中文标题】如何以递归方式思考?【英文标题】:How to think in recursive way? 【发布时间】:2013-07-10 18:45:10 【问题描述】:

为了理解贪婪方法和动态规划等高级算法概念,首先需要精通递归。

我对递归比较陌生。每当提出问题时,首先想到的就是使用迭代的解决方案。尽管我知道递归方法的含义以及它是如何工作的,但以递归的方式思考是非常困难的。

请回答以下问题以提供帮助:

1) 任何迭代方法都可以用递归代替吗?

例如,如何递归打印大小为n的数组中的元素?

for i 0 to n
 Print a[i]

2) 如何递归解决问题?步骤是什么?是否有任何提示可以确定问题可以递归解决?

例如:如果要求打印出一个字符串的所有子字符串

INPUT: CAT
OUTPUT: CAT,CA,A,AT,T

我可以快速想出一个迭代的方式。使用两个循环可以解决问题。

但是如何递归地解决它。如何确定一个问题可以递归地解决。

如果我的第一个问题的答案是肯定的,那么使用两次递归而不是迭代可以解决我的问题吗?

3) 谁能建议我一些材料/资源来彻底理解递归的概念?

【问题讨论】:

你有一袋信和一条街的房子。你是说“对于每一所房子,检查堆顶的信件,然后交付,然后继续下一个房子”还是你说,“将第一组信件交付到街道上的第一所房子,从街道上移走那所房子,并重复,直到街上没有房子”? 并不是每个问题在递归中都有意义——从数组中打印元素就是一个很好的例子,这更适合纯迭代。从 多维 数组中打印元素可能更适合递归方法 【参考方案1】:

有一种思考递归的方法,它可以像迭代一样简单。

在迭代中,我们有一个循环。认为它有 4 个部分:

    根据某些“控制”数据决定是继续还是停止,并作为逻辑条件进行评估。

    完成工作的主体。有时,主体与下一部分结合在一起。

    一种更改“控制”数据的方法。通常通过更换计数器。

    再次调用构造(在本例中为循环)的方法。在 c 风格的语言中,这是由 for、while 或 do 语法提供的。

在递归中,我们有一个函数(有时是几个)。它们具有相同的 4 个部分:

    根据某些“控制”数据决定是继续还是停止,并作为逻辑条件进行评估。控制数据通常作为参数传递给函数。

    完成工作的主体。有时,主体与下一部分结合在一起。

    一种更改“控制”数据的方法。通常通过更换计数器。

    再次调用构造(在本例中为函数)的一种方式 - 这意味着调用函数(并记住传递更改后的“控制”数据。

这两个结构具有相同的部分也就不足为奇了,因为它们是等价的。

【讨论】:

这是一种有趣的方式来思考这两个概念并将它们联系起来!感谢分享!【参考方案2】:

    是的,主要是。一般来说,递归是为程序员而不是计算机而完成的。有一些迭代方法在某些情况下可能比递归方法运行得更快,但迭代方法可能需要 300 行代码和递归 3 行。还有一些情况很容易弄清楚如何递归地编程,但是非常难以迭代编写,反之亦然。

    一般而言,递归解决方案需要考虑函数。如果我们使用像 C++ 这样的东西,我们可以使用处理字符串引用和事物的解决方案,慢慢调整作为参数传递的字符串。但是,“两次递归”结束附近的点被误导了。这里的原则是,我们可以使用一种递归方法,而不是两次迭代。

    http://introcs.cs.princeton.edu/java/23recursion/ 这个网站(在 google 搜索中排名靠前)教授了很多关于递归的数学理论,并包含一个常见问题解答,它可能会给你一个更令人满意的答案。

    李>

【讨论】:

感谢您的回答。您能解释一下如何递归打印数组中的所有元素吗? 基本上,我们设置一个带有输入参数的函数,然后我们在更少的输入上调用这个相同的函数,在 CAT 的情况下,我们在 CAT 上调用它,然后在 CA 上调用它,然后是 C,然后是 A,然后在 AT 上调用它,然后是 A,然后是 T。按此顺序调用它的原因是因为递归代码就是这样工作的。我们还将通过简单地检查输出数据结构来实现检查以防止 A 被打印两次。 @RahulKurup 我给出了答案。【参考方案3】:

让我们做一个简单的任务。打印从 1 到 10 的数字。我这里使用 Python2.7。

for i in range(1,11):
    print i

现在让我们尝试做同样的事情,使用递归。

>>> def print_me(n):
    if n > 0:
        print_me(n - 1)
        print n
    else:
        return

>>> print_me(10)
1
2
3
4
5
6
7
8
9
10

那我们怎么看呢?

Step1:想想我的界限。我需要两个。 1 和 10。接下来。 Step2:打印语句。这就是我们的动机。打印数字。和 我们希望它从 1 到 10。所以我需要先打印 1。

第 3 步:我们首先编写一个函数来完成打印 作为参数传递的数字。让我们想想,主要 任务。

def print_me(n): 打印n

第 4 步:如果 n

def print_me(n): 如果 n > 0: 打印 n 别的: 返回

第 5 步:现在我想将 1 到 10 的数字传递给这个函数,但是 我们不想要 1 到 10 的循环,然后将其传递给我们的函数。我们 希望它以递归方式完成。

什么是递归? 简单来说,就是递归过程或定义的重复应用。

所以为了让它递归,我们需要调用函数本身。我们希望通过我们的 1 到 10 的范围。

def print_me(n):
    if n > 0:
        print_me(n - 1)
        print n
    else:
        return

总结: 所有递归调用都必须遵守 3 个重要规则:

    递归算法,必须有一个基本情况。 一种递归算法,必须改变其状态并移向基数 案子。 递归算法必须以递归方式调用自身。

来源:交互式python

另一个用 Javascript 查找阶乘的程序:

function factorial(n)
    if (n == 1)
        
        return 1;
        
    else 
        return n * factorial(n-1);
        

【讨论】:

【参考方案4】:
@Test
public void testStrings() 
   TreeSet<String> finalTree = getSubStringsOf("STACK");
    for(String subString : finalTree)
        System.out.println(subString);
    


public TreeSet<String> getSubStringsOf(String stringIn) 
    TreeSet<String> stringOut = new TreeSet<String>();
    if (stringIn.length() == 1) 
        stringOut.add(stringIn);
        return stringOut;
     else 
        for (int i = 1; i < stringIn.length() ; i++) 
            String stringBefore = stringIn.substring(0, i);
            String stringAfter = stringIn.substring(i);
            stringOut.add(stringBefore);
            stringOut.add(stringAfter);
            stringOut.addAll(getSubStringsOf(stringBefore));
            stringOut.addAll(getSubStringsOf(stringAfter));
        
        return stringOut;
    

我不知道你是否需要解释。每次可能时,您都将字符串分成两部分。因此,Cat 被分成 CA,T 和 C,AT,您将它们添加到子字符串列表中,然后查找这些子字符串的每个子字符串。如果字符串是单个字符,则将单个字符添加到树中。

编辑:这是堆栈的输出:

A AC ACK C CK K S ST STA STAC T TA TAC TACK

再次编辑:如您所见,每次运行方法 subString 时,都会在其中使用两次,除非它是单个字符串。因此复杂度为 O(n²)。对于“STACK”,程序的长度为 0.200 毫秒,“STACKSTACKSTACK”(3 次堆栈)为 2 秒,“STACKSTACKSTACKSTACKSTACK”理论上是 2^10 倍,因此为 2000 秒。

【讨论】:

【参考方案5】:

这是我在 Python 中的简单测试:

def p(l, index):
    if index == len(l):
        return
    else:
        print l[index]
        index = index + 1
        p(l, index)

然后调用:

p("123456", 0)

【讨论】:

【参考方案6】:

这是在 c++ 中递归打印数组的代码

#include<iostream>
using namespace std;

void print(int arr[],int n)

if(n==0)     //base case. function call hits the base case when n==0

cout<<arr[0]<<" ";
return;

print(arr,n-1);   //function will be called recursively until it reaches the 
                    base case.
cout<<arr[n]<<" ";

              //Driver function
int main()

int arr[]=10,20,30,40,50; //array has been initialized with values 
                                               // 10,20,30,40,50 

int n=sizeof(arr)/sizeof(arr[0]) ; //n stores the size of array,i.e, n=5; 
print(arr,n-1);   //print function has been called with arr[] and (n-1) as 
                    parameters.
return 0;

我希望它在某种程度上有所帮助

【讨论】:

请在您的代码中添加一些解释,以便其他人可以从中学习 请不要鼓励使用#include&lt;bits/stdc++.h&gt;!请参阅:here。 谢谢,Adrian 让我知道,我主要是在做竞技编程时使用它,不过它在比赛中更节省时间和高效。【参考方案7】:

CAT问题可以这样解决:(Python代码)

s = "CAT"
op = []
def sub(s):
    # terminating condition.
    if (len(s) == 0) : return

    if s not in op : op.append(s)
    
    # slices from begning
    sub(s[1:])
    # slices from the end
    sub(s[:-1])

sub(s)
print(op)
Output : ['CAT', 'AT', 'T', 'A', 'CA', 'C']

【讨论】:

【参考方案8】:

我认为我们可以使用递归函数解决任何迭代问题,但它可能不是最有效的方法。这是一个在 Python3 中同时使用“for 循环”和“递归”打印列表中项目的简单示例:

a = [ 1,2,3,4,5] 
#iterative approach using a for loop
for i in nums:
   print(i)

#recursive approach
def printing_list(nums):
    #base case
    if len(nums)==0:
        return
    #recursive
    else:
        print(nums[0])
    return printing_list(nums[1:])

printing_list(nums)

要递归地思考,最好先编写 for 循环,然后尝试在此基础上编写递归解决方案。 记住所有递归方法都需要一个基本案例来停止递归

【讨论】:

以上是关于如何以递归方式思考?的主要内容,如果未能解决你的问题,请参考以下文章

用函数式的方式思考——递归

学会聪明人的思考方式-解读《聪明人是如何思考的》

递归的思考(看图思考2小时)

函数式编程思想:以函数的方式思考,第3部分

关于递归排序和快速排序的衍生思考

递归思想之---斐波拉契数列