简体   繁体   中英

Why does this function print a Linked List backwards?

I'm working through Downey's How To Think Like a Computer Scientist and I have a question regarding his print_backward() function for Linked List .

First, here's Downey's implementation of a Linked List in 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)

We give this class the following cargo and link values:

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

To print the linked list, we use another of Downey's functions.

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

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

All very straightforward. But I don't understand how the recursive call in the following function allows one to print the linked list backwards.

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

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

Wouldn't calling "list.next" as the value of print_backward simply give you "bc"?

Note: Several people below have pointed out that this function is badly designed since, given any list, we cannot show that it will always reach the base case. Downey also points out this problem later in the same chapter.

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

Wouldn't calling "list.next" as the value of print_backward simply give you "bc"?

No; picture what happens when a->b->c gets passed to print_backward:

"[bc]" is passed to print_backward and then "a" is printed. But "print_backward", before "a" is printed, calls itself. So:

  • [ abc ] is not None, so b->c gets passed to print_backward
    • [ bc ] is passed to print_backward
      • [ c] is passed to print_backward
      • None is passed to print_backward
        • which returns
      • and then "c" is printed
    • and then "b" is printed
  • and then "a" is printed
  • quit.

In the forward-printing version, it prints each node before doing the recursive call. In the backward-printing version, it prints each node after doing the recursive call.

This is not coincidental.

Both of the functions recurse until the end of the list is reached. The difference is whether printing happens during this process or afterward.

Function calls use a stack, a last-in first-out data structure that remembers where the computer was executing code when the function call was made. What is put on in the stack in one order comes off in the opposite order. Thus, the recursion is "unwound" in the reverse order of the original calls. The printing occurs during the unwinding process, ie, after each recursive call has completed.

If list isn't None, it calls print_backward, then prints the first member of the list. Expanded, this is exssentially what happens. You can see that when calls start returning, 'c' is printed, then 'b', then 'a'.

It looks like when actually printing a list, it prints the first node

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'

Sometimes I find it easier to think of recursion as merely constructing a list of calls to be made in a certain order. As the function continues, it builds up a bunch of calls until it finally gets to the base case. The base case is the situation where no further breaking down the of program is necessary; in this function, the base case is when there is nothing to print, in which case we just leave without doing anything with return .

The cool stuff usually happens on the way back as we unwind the recursive stack of function calls. currently, print_backward has been called on each element of the list, and it will now 'unwind', finishing the most recent calls first and the earlier calls last. This means that the 'instance' of print_backward created when you call it on the last element is the first one to finish, and thus the last element is the first one to be printed, followed by the second to last, third to last, etc., until the original function finally exits.

Take a look at this representation of what happened:

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. 

While the function is called first on the earlier elements, the part that actually prints that element comes after the recursive call, so it won't actually execute until that recursive call finishes. In this case, that means that the print list part won't execute until all of the later elements have been printed first (in reverse order), thus giving you the list elements printed backwards. :D

It's using recursion. It "zips" all the way down until it gets to the end, then it prints every element as each call returns. Since the first one to get to print is the most recent called, it prints the list backwards.

No. There's two kinds of recursion:

  1. Tail recursion: if there is nothing to do after the function returns except return its value. Function calls are not stacked.
  2. Recursion that finds the base case first (in this case, null , then backwardly processes the list). Each function call is pushed into the stack, for later processing. In your exemple, the function is stacked as 'a'->'b'->'c'->null , then as the stack is popped, the author showed that by printing backwards: `if null return: print 'c' -> print 'b' -> print 'a'

In your case, the author only demonstrated a different concept of recursion, and used that to print the list backwards.

Your nodes look something like this:

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

At the first call to print_backward , the variable list has the value 'a' , subsequent calls to print_backward move one further down the line. Note that none of them print anything until you hit the guard ( None ) at which time, things get printed from the back to front as the print_backward that received node 'c' must return before the print_backward that received node 'b' can print (because the print statement is after the function call) and so on.

While I recognize that this is somebody else's code, there are a few things in here which are bad practice -- Best I tell you now while you're learning rather than later. First, don't use list as a variable name since it is the name of a builtin function/type in python. second the equality test if obj == None is better done by if obj is None , finally, it's always a good idea to have your classes inherit from object ( class node(object): ) as that makes it a new-style class.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM