递归函数如何在'for循环'中工作

时间:2017-08-15 08:19:39

标签: python loops recursion

我在'递归函数'中相当新。所以,我试图解决为什么我们使用递归函数以及递归函数如何工作,我想我对它有一个相当好的理解。

两天前,我试图解决最短路径问题。我有一个下面的图(它在python中):

 graph = {'a': ['b', 'c'],
             'b': ['c', 'd'],
             'c': ['d'],
             'd': ['c'],
             'e': ['f'],
             'f': ['c']}

我只是想找到一条路,而不是最短路径。所以,这是代码:

def find_path(graph,start,end,path=[]):
    path = path + [start]
    #Just a Test
    print(path)

    if start == end:
        return path

    if start not in graph:
        return None

    for node in graph[start]:
        if node not in path:
            new_path = find_path(graph,node,end,path)
        if new_path:
            #Just a test
            print(path)
            return new_path

print(find_path({'a':['b','c'],'b':['c','d'],'c':['d'],'d':['c'],'e':
['f'],'f':['c']},'a','d'))

我的起始顶点是'a',结束顶点是'd'。

在第四行中,我只是打印了“路径”以查看路径的位置。

在第17行,我还打印了“路径”,再次进行测试。这是结果:

['a']
['a', 'b']
['a', 'b', 'c']
['a', 'b', 'c', 'd']
['a', 'b', 'c']
['a', 'b']
['a']
['a', 'b', 'c', 'd']

结果的前四行是代码第4行的'print(path)'的结果。但是,第5行,第6行和第7行是代码第17行“print(path)”的结果。

我的问题是为什么路径列表每次都减少一个顶点?

我一直试图找到它的解决方案2天。我去过论坛,阅读有关递归和观看视频的文档。但是,没有运气。

如果有人能回答,我会很高兴。

3 个答案:

答案 0 :(得分:3)

这是因为递归产生了从“最里面”到“最外面”调用的结果。这是第一行17 print语句从最深的递归级别发生,其中路径具有最多的节点。在该级别返回之后,打印下一级“向上”(路径中的一个节点较少)。请注意,在递归调用print之后,您的find_path函数会出现

您可以按如下方式对其进行可视化:

find_path(..., path=['a'])  # Recursion level 1.
|
|   find_path(..., path=['a', 'b'])  # Recursion level 2.
|   |
|   |   find_path(..., path=['a', 'b', 'c'])  # Recursion level 3.
|   |   print(path)  # Prints ['a', 'b', 'c'].
|   |   return  # Return from level 3.
|   |
|   print(path)  # Prints ['a', 'b'].
|   return  # Return from level 2.
|
print(path)  # Prints ['a'].
return  # Return from level 1.

如果您希望单个(子)路径以“递增”顺序打印,那么您只需将print函数放在递归调用之前find_path

它是new_path变量,它保存递归找到的路径,而path只保存当前节点的路径。

顺便说一下,如果尚未输入前一个if new_path:分支,if子句可能会失败,因为new_path未定义。

答案 1 :(得分:1)

首先,我要解释回溯意味着什么。我也发布了这个答案here

递归意味着从同一个函数中调用函数。现在发生的事情是,当函数遇到对自身的调用时......想象一下,当函数再次遇到该函数时,打开一个新页面并将控件从旧页面传输到此新页面到函数的开头。新页面,旁边打开另一个页面,这样新页面就会在旧页面旁边弹出。请注意,所有局部变量仅在其各自页面的范围内。也就是说,如果要访问上一页中的值,可以将其传递给参数中的函数,或者将变量设置为全局。

返回的唯一方法是使用return语句。当函数遇到它时,控件从新页面返回到调用它的同一行上的旧页面,并开始执行该行下面的任何内容。这是回溯开始的地方。为了避免在填充数据时再次输入数据等问题,通常需要在每次调用函数后都输入一个return语句。

现在在你的代码中,

def find_path(graph,start,end,path=[]):
    path = path + [start]
    #Just a Test
    print(path)

    if start == end:
        return path

    if start not in graph:
        return None

    for node in graph[start]:
        if node not in path:
            new_path = find_path(graph,node,end,path)  <---- when function returns it will start executing from here again.
        if new_path:
            #Just a test
            print(path)
            return new_path

请注意,您的path变量不是全局变量。这是一个本地人。这意味着每次调用其重置时。为避免这种情况,您将在函数参数(最后一个)中再次传递路径值。

所以最后当函数在找到d之后返回时,它返回到之前路径变量只有a, b, c的状态。那是你打印出来的。

编辑: - 以防任何人反对,我对使用页面的递归的解释纯粹是非技术性的,如果你想知道它是如何真正发生的那么你必须阅读有关激活记录以及它如何推动所有状态到堆栈

答案 2 :(得分:1)

1)首先使用find_path作为起始节点调用a方法,该路径将路径设置为['a']并使用find_path调用b方法作为第17行打印路径之前的起始节点。

2)使用find_path作为起始节点调用b方法将路径设置为['a','b'],并使用find_path调用c方法作为在第17行打印路径之前启动节点。

3)使用find_path作为起始节点调用c方法将路径设置为['a','b','c'],并使用find_path调用d方法作为在第17行打印路径之前启动节点。

4)使用find_path作为起始节点的d方法调用将路径设置为['a','b','c','d']将其打印在第4行并返回。

5)现在它在方法find_path的执行中返回第14行,其中c作为起始节点(已将路径设置为['a','b','c'],如第3点所述)并且在第17行打印路径(这是结果的第5行)并返回。

6)这将在方法find_path的执行中返回第14行,其中b作为起始节点(已将路径设置为['a','b'],如第2点所述)并打印第17行的路径(结果的第6行)并返回。

7)这将在执行方法find_path时返回第14行,其中a作为起始节点(已将路径设置为['a'],如第1点所述)并打印第17行的路径(结果的第6行)并返回。

你可以把它想象成LIFO(后进先出)