【问题标题】:Why does this function print a Linked List backwards?为什么这个函数向后打印一个链表?
【发布时间】:2012-07-19 03:23:20
【问题描述】:

我正在研究唐尼的《如何像计算机科学家一样思考》,我对他的Linked List 的 print_backward() 函数有疑问。

首先,这是 Downey 在 Python 中实现的链表:

 class Node:
     #initialize with cargo (stores the value of the node)
     #and the link. These are set to None initially.
     def __init__(self, cargo = None, next = None):
         self.cargo = cargo
         self.next = next

     def __str__(self):
         return str(self.cargo)

我们为这个类赋予以下货物和链接值:

 #cargo
 node1 = Node('a')
 node2 = Node('b')
 node3 = Node('c')
 #link them 
 node1.next = node2
 node2.next = node3

为了打印链表,我们使用了另一个唐尼函数。

def printList(node):
     while node:
         print node,
         node = node.next

 >>>printList(node1)
 >>>a b c

一切都非常简单。但我不明白以下函数中的递归调用如何允许向后打印链表。

def print_backward(list):
     if list == None : return
     print_backward(list.next)
     print list,

>>>print_backward(node1)
>>>c b a

调用“list.next”作为 print_backward 的值不会简单地给你“b c”吗?

注意:下面的几个人指出这个函数设计得很糟糕,因为给定任何列表,我们不能证明它总是会达到基本情况。唐尼也在同一章后面指出了这个问题。

【问题讨论】:

  • 正如在其他答案中所指出的,这个函数说明了递归,但在其他方面有一些缺陷:不是尾递归(在长列表上会导致堆栈溢出错误),类 Node 应该继承自objectlist 不应用作变量名。

标签: python linked-list


【解决方案1】:

在正向打印版本中,它会在执行递归调用之前打印每个节点。在向后打印版本中,它会在执行递归调用之后打印每个节点

这不是巧合。

这两个函数都递归直到到达列表的末尾。不同之处在于打印是在此过程中进行还是在之后进行。

函数调用使用堆栈,这是一种后进先出数据结构,可记住在进行函数调用时计算机执行代码的位置。以一种顺序放入堆栈的内容以相反的顺序取出。因此,递归以与原始调用相反的顺序“展开”。打印发生在展开过程中,即在每个递归调用完成之后。

【讨论】:

  • 关于 LIFO 的要点正是我需要了解唐尼函数的工作原理。感谢您的帮助:-)
【解决方案2】:

有时我发现将递归视为仅构造要按特定顺序进行的调用列表更容易。随着函数的继续,它会建立一堆调用,直到最终到达基本情况。基本情况是不需要进一步分解程序的情况;在这个函数中,基本情况是没有要打印的内容,在这种情况下,我们就直接离开,而不对 return 做任何事情。

当我们展开函数调用的递归堆栈时,通常会在返回途中发生很酷的事情。目前,print_backward 已在列表的每个元素上调用,它现在将“展开”,首先完成最近的调用,最后完成较早的调用。这意味着当您在最后一个元素上调用它时创建的print_backward 的“实例”是第一个完成的,因此最后一个元素是第一个要打印的元素,然后是倒数第二个,倒数第三个等,直到原来的函数最终退出。

看看这个表示发生了什么:

print_backward(node1)            #first call to print_backward
    print_backward(node2)        #calls itself on next node
        print_backward(node3)    #calls itself on next node
            print_backward(None) #calls itself on None. We can now start unwinding as this is the base case:
        print Node3              #now the third invocation finishes...
    print Node2                  #and the second...
print Node1                      #and the first. 

虽然函数首先在较早的元素上调用,但实际打印该元素的部分在递归调用之后,因此在递归调用完成之前它不会真正执行。在这种情况下,这意味着 print list 部分将不会执行,直到所有后面的元素都首先打印(以相反的顺序),从而为您提供向后打印的列表元素。 :D

【讨论】:

    【解决方案3】:
    def print_backward(list):
         if list == None : return
         print_backward(list.next)
         print list,
    

    调用“list.next”作为 print_backward 的值不会简单地给您“b c”吗?

    没有;想象一下当 a->b->c 被传递给 print_backward 时会发生什么:

    "[b c]" 被传递给 print_backward,然后 "a" 被打印。 但是在打印“a”之前,“print_backward”会调用自身。所以:

    • [ a b c ] 不是 None,所以 b->c 被传递给 print_backward
      • [ b c ] 被传递给 print_backward
        • [c] 被传递给 print_backward
        • 没有传递给 print_backward
          • 返回
        • 然后打印“c”
      • 然后打印“b”
    • 然后打印“a”
    • 退出。

    【讨论】:

      【解决方案4】:

      如果列表不是无,它调用 print_backward,然后打印列表的第一个成员。扩展,这就是发生的事情。您可以看到,当调用开始返回时,会打印“c”,然后是“b”,然后是“a”。

      看起来在实际打印列表时,它会打印第一个节点

      print_backward(list='a','b','c')
        print_backward(list='b','c')
          print_backward(list='c')
            print_backward(list=None)
              list is None, so return
            print 'c'
          print 'b','c'
        print 'a','b','c'
      

      【讨论】:

        【解决方案5】:

        您的节点如下所示:

        node1 node2 node3
        'a' => 'b' => 'c' => None
        

        在第一次调用print_backward 时,变量list 的值是'a',随后对print_backward 的调用向下移动。请注意,在您击中警卫 (None) 之前,它们都不会打印任何内容,此时,由于接收节点 'c'print_backward 必须在接收节点的 print_backward 之前返回,所以事情会从后到前打印'b'可以打印(因为打印语句在函数调用之后)等等。

        虽然我知道这是其他人的代码,但这里有一些不好的做法——我现在最好在你学习的时候告诉你,而不是以后再告诉你。首先,不要使用list 作为变量名,因为它是python 中内置函数/类型的名称。其次,相等性测试if obj == None 最好由if obj is None 完成,最后,让您的类从object (class node(object):) 继承总是一个好主意,因为这使它成为一个新型类。

        【讨论】:

          【解决方案6】:

          没有。有两种递归:

          1. 尾递归:如果函数返回后除了返回它的值外无事可做。函数调用不堆叠。
          2. 首先找到基本情况的递归(在本例中为null,然后反向处理列表)。每个函数调用都被压入堆栈,以供以后处理。在您的示例中,该函数被堆叠为'a'->'b'->'c'->null,然后当堆栈被弹出时,作者通过向后打印表明:`if null return: print 'c' -> print 'b' -> print 'a'

          在你的例子中,作者只演示了一个不同的递归概念,并用它来向后打印列表。

          【讨论】:

            【解决方案7】:

            它使用递归。它一直“压缩”到最后,然后在每次调用返回时打印每个元素。由于第一个要打印的是最近调用的,因此它会向后打印列表。

            【讨论】:

              猜你喜欢
              • 2017-06-10
              • 1970-01-01
              • 2017-04-11
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-12-02
              • 2016-11-01
              • 1970-01-01
              相关资源
              最近更新 更多