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

I don't think that teachers are telling students recursion is hard. I think that through observations, good teachers who have taught for many years have seen that many, many students will just never get recursion. And that's the basis of teachers saying that recursion is hard.


The idea behind recursion is I have X, if I know Y then this would be easy. But, picking the correct Y and finding a simple path to get Y is hard for many people.

Consider something as simple as factorials. 1! = 1 and N! = N * N-1! but try to code it as: N! = N! / 1! * 1 = N! / 2! * 2 = N! / 3! * 6. Then with a lot of time and effort they get something that works, but it's not intuitive.


Perhaps the other problem is that it's not obvious to students what the point of doing things recursively is. OK, it can shave a few characters off your factorization code. Or your Fibonacci code. That's neat, but why get excited?


I usually show them a recursive Towers of Hanoi solver. It's alarmingly simple (fewer than 10 LOC) and most of them can't even imagine how to write it iteratively.

I tell them that recursive code is rarely better than iterative code, but for certain types of problems, it's SO much easier to come up with a recursive solution that it's worth knowing how to do.


I am not sure if I understand recursion or not. My definition of recursion is a function that calls itself repeatedly until an exit condition occurs (the first chapters of little schemer define this as an empty list). If the exit condition never occurs, and the computer lacks infinite computing power, you will probably get a stack overflow. As far as I can tell, recursion and a for-loop accomplish the same thing.

Am I missing anything?

EDIT: I wrote this before reading the article


You aren't missing anything, but recursion can get more complicated. In the case of towers of Hanoi, or recursion on trees, the iterative version using a for-loop becomes substantially more complicated than the recursive version.

Also, in Scheme and other functional languages, function calls in the tail position are compiled or otherwise treated as jumps. So in Scheme this will actually produce an infinite loop with no stack overflow:

  (define (loop)
    (loop))
  (loop)


Thanks for your answer.

>Also, in Scheme and other functional languages, function calls in the tail position are compiled or otherwise treated as jumps.

So that is why people make such a big deal about tail-call optimization? Because it prevents stack overflow?


Yes.


I think you're forgetting that the function can have more than one recursive call site within its body. A for-loop is identical to a recursive function containing only one call site within its body.

By contrast, in order to calculate the number of nodes in a binary tree, you would do

    (nodes (tree)
        (if (tree? tree)
            (1+ (nodes (left-node tree))
                (nodes (right-node tree)))
             0))
which is not like a simple for loop at all.


I tell them that recursive code is rarely better than iterative code

As a general statement, this is wrong. Recursive code is almost always better than imperative code in languages designed to encourage recursion -- I'm thinking of functional languages here, of course. Please qualify your statements to your students, lest they get the idea that it is a problem with recursion as a principle rather than a problem with the language they're working in.


Solving a maze is a classic. The recursive solution is the obvious/intuitive one.


Again, mazes and Towers of Hanoi are neat, but still not exactly what you'd call problems with real-world applicability. There must be some out there -- some types of parsers, perhaps? Are there any matrix operations which are best done recursively?

I just think that recursion falls into the category of things that professors think are neat rather than the category of things that are useful to students. Some students will love that kind of thing, others will wonder what the hell the point is.


Any "divide and conquer" algorithm is likely to be recursive. Thus, for instance, the FFT, the Karatsuba multiplication algorithm for large integers, Strassen's matrix multiplication algorithm for matrices and the like are all recursive.

Moving on, dynamic programming is a very important technique. About half the time it is easier for me to figure out a dp solution by writing a recursive solution with memoization than it is to build the solution bottom up.

The single most studied problem in computer science is sorting, for the simple reason that a surprising fraction of computing time spent is spent doing sort operations. Virtually every efficient sort algorithm uses recursion somewhere.

The fact that a lot of students won't go on to use recursion doesn't mean that it isn't a very useful technique.


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.


Yes, exactly, recursive descent parsers are what they sound like. I'm not very up on other types of parsers, but this is the kind I learned about, and I would say they are very intuitive.

http://en.wikipedia.org/wiki/Recursive_descent_parser


Graph problems would be hard without recursion I think. I've been working on some problems that are naturally expressed as graphs, and recursion is the natural way to do some things; it's be quite hard to do some things I did iteratively, now that I think about it (that is, without 'emulating' recursion with an 'explicit', manually-maintained stack).


Yes. Though breadth-first approaches look much more imperative than depth-first traversals. That's because basically you exchange the stack for a queue. (Those algorithms can still look nice in, say, Haskell.)


If you're writing a 3d engine, you may encounter many tree-like structures and recursive operations on them: BSPs, quadtrees, octrees, triangle trees for variable LOD terrain...


One of the projects in my CS program using recursion was a 6-degrees of Kevin Bacon game. It was neat and real-world practical IMO.


I'd have said quicksort is an example of an algorithm where the easiest way to think of is recursively.


And while you're at it, quickselect, too.


This is the reason why I'm never get excited about recursion. I hardly ever find a real world application for it aside from a tree transversal.

Most examples are toys (fib, towers of hanoi, maze solving) or problems that are already solved, sorting, searching, etc.


Try writing a program that does complex things with trees (directory structures, for example) without using recursion.




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

Search: