Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

You can talk about "divide and conquer" but the most common problem where recursion really helps is looking at tree data structures. As then to consider looking for the total of some value stored in an XML tree.

In an iterative language it's a for loop and then calling the function on each of the children. In a Lisp it's calling it's self on the child and next node.

PS: Showing someone they can transform a recursion function into a while loop if they keep track of their own stack seems to help. Yea, you can write it that way, but recursion really is simpler. (This also helps explain what a stack really is.)



Yes, recursion is a natural way to walk through a recursive data structure, like a tree. However if you only know how to reach for recursion, you're completely hosed the second you need a breadth-first search instead. (I've watched candidates completely collapse when faced with that problem.)


A breadth-first search can be easily handled with recursion (in fact, I'd argue the specification is almost as simple):

  def bfs(search_node, nodes_to_visit):
      node = nodes_to_visit.pop()
      if node == search_node:
          return node
      else:
          nodes_to_visit.extend(node.neighbors)
          return bfs(search_node, nodes_to_visit)
 
  bfs(search_node, [root_node])


If it is so easy, why does your implementation crash if the element is not in the tree? :-P

After you fix that minor bug, you'll find that your code crashes for trees with over 1000 nodes. That is also easy to fix. But in any language without tail recursion, your implementation hits the stack hard. Which in many multi-threaded environments may not be not a wise thing to do.

All that said, I submit that you easily think of that version because you're familiar with how the recursive solution manages the stack. If you're familiar with that, then switching from dfs to bfs is just a question of replacing a stack with a queue. Compare:

  def dfs(search_node, root_node):
      nodes_to_visit = [root_node]
      while 0 < len(nodes_to_visit):
          node = nodes_to_visit.pop()
          if node == search_node:
              return node
          else:
              nodes_to_visit.extend(node.children)
      return None
  
  def bfs(search_node, root_node):
      nodes_to_visit = [root_node]
      while 0 < len(nodes_to_visit):
          node = nodes_to_visit.pop(0)
          if node == search_node:
              return node
          else:
              nodes_to_visit.extend(node.children)
      return None
Now contrast the two obvious recursive variations.

  def dfs_recursive(search_node, root_node):
      if search_node == root_node:
          return root_node
      for child_node in root_node.children:
          answer = dfs_recursive(search_node, child_node)
          if answer is not None:
              return answer
      return None

  def bfs_recursive(search_node, root_node):
      nodes_to_visit = []
      def _recurse():
          if 0 == len(nodes_to_visit):
              return None
          node = nodes_to_visit.pop()
          if node == search_node:
              return node
          else:
              nodes_to_visit.extend(node.children)
              return _recurse()
      
      return _recurse()


Well, I agree with you there. Any language that can't handle proper tail recursion makes it really difficult to safely implement a recursive solution. But I often find it easier to solve a problem with a recursive solution and perform an almost mechanical conversion into the corresponding iterative solution. (also I'm embarrassed that I forgot the edge case where the node is not in the tree)


We all make silly errors. Your bfs was a complicated dfs, and neither of us noticed. (In fact I copied your code and edited it, and had dfs and bfs reversed.)

Luckily I was able to edit my node to hide my variation of the error.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: